13 Commits

Author SHA1 Message Date
kasun 7cdc35d696 added nfs and cert management
Deploy k8s Infra / Pulumi Deploy (pull_request) Has been skipped
Deploy Proxmox Infra / Pulumi Preview (pull_request) Successful in 1m21s
Deploy Proxmox Infra / Pulumi Deploy (pull_request) Has been skipped
Deploy k8s Infra / Pulumi Preview (pull_request) Successful in 1m7s
2026-06-01 16:18:35 +02:00
kasun 66cba5a075 Merge pull request 'Enhancement/improve k8s bootstrap deployment' (#4) from enhancement/improve-k8s-bootstrap-deployment into main
Deploy k8s Bootstrap / Pulumi Preview (push) Has been skipped
Deploy k8s Bootstrap / Bootstrap k3s Cluster (push) Successful in 4m24s
Deploy Proxmox Infra / Pulumi Preview (push) Has been skipped
Deploy Proxmox Infra / Pulumi Deploy (push) Successful in 16m19s
Reviewed-on: #4
2026-06-01 03:34:23 +02:00
kasun d4a3c38847 changed pulumi.dev.yaml name
Deploy k8s Bootstrap / Pulumi Preview (pull_request) Successful in 43s
Deploy k8s Bootstrap / Bootstrap k3s Cluster (pull_request) Has been skipped
Deploy Proxmox Infra / Pulumi Preview (pull_request) Successful in 1m9s
Deploy Proxmox Infra / Pulumi Deploy (pull_request) Has been skipped
2026-06-01 03:32:14 +02:00
kasun 8eb59643cf format
Deploy k8s Bootstrap / Pulumi Preview (pull_request) Successful in 37s
Deploy k8s Bootstrap / Bootstrap k3s Cluster (pull_request) Has been skipped
2026-06-01 03:15:15 +02:00
kasun e6d2b6154a fix: added instance ips from stack and fixed type issues 2026-06-01 03:14:55 +02:00
kasun c8e688b9ff fix: resolved sonarqube warning 2026-06-01 02:20:06 +02:00
kasun 4a96cb9d07 fix: bumbed compilerOptions target 2026-06-01 02:19:43 +02:00
kasun 3d38d60aa5 Merge pull request 'Feature/add pfsense api' (#3) from feature/add-pfsense-api into main
Deploy Proxmox Infra / Pulumi Preview (push) Has been skipped
Deploy Proxmox Infra / Pulumi Deploy (push) Successful in 16m14s
Reviewed-on: #3
2026-06-01 00:59:32 +02:00
kasun 5305061e7b added missing pulumi cli installation
Deploy Proxmox Infra / Pulumi Preview (pull_request) Successful in 1m5s
Deploy Proxmox Infra / Pulumi Deploy (pull_request) Has been skipped
2026-06-01 00:56:11 +02:00
kasun 136e6c9eec added pfsense provider for automating static ip setup
Deploy Proxmox Infra / Pulumi Preview (pull_request) Failing after 12s
Deploy Proxmox Infra / Pulumi Deploy (pull_request) Has been skipped
2026-06-01 00:51:22 +02:00
kasun e09ec50687 changed pulumi dev secret name 2026-06-01 00:47:00 +02:00
kasun 7815e1e4f2 Delete .env.local
accidentially commited. don't worry token is not valid anymore
2026-05-31 18:50:31 +02:00
kasun 6a70000c62 Merge pull request 'removed netcat dependency with /dev/tcp' (#2) from bug/fix-missing-dependencies-k8s-bootstrap into main
Deploy k8s Bootstrap / Pulumi Preview (push) Has been skipped
Deploy k8s Bootstrap / Bootstrap k3s Cluster (push) Successful in 48s
Deploy Proxmox Infra / Pulumi Preview (push) Has been skipped
Deploy Proxmox Infra / Pulumi Deploy (push) Successful in 16m7s
Reviewed-on: #2
2026-05-31 18:33:17 +02:00
17 changed files with 3225 additions and 49 deletions
-3
View File
@@ -1,3 +0,0 @@
GITEA_API_URL=https://gitea.kasuns.website/kasun/homelab-infrastructure-as-code.git
GITEA_TOKEN=ba3fd0f4851aa627e2088da1f94a596646ba2de7
+8 -8
View File
@@ -6,14 +6,14 @@ on:
branches:
- main
paths:
- 'k8s-bootstrap/**'
- '.gitea/workflows/deploy-k8s-bootstrap.yaml'
- "k8s-bootstrap/**"
- ".gitea/workflows/deploy-k8s-bootstrap.yaml"
pull_request:
branches:
- main
paths:
- 'k8s-bootstrap/**'
- '.gitea/workflows/deploy-k8s-bootstrap.yaml'
- "k8s-bootstrap/**"
- ".gitea/workflows/deploy-k8s-bootstrap.yaml"
jobs:
preview:
@@ -27,10 +27,10 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
node-version: "24"
- name: Restore Stack Config
run: echo "${{ secrets.K8S_BOOTSTRAP_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
run: echo "${{ secrets.K8S_BOOTSTRAP_PULUMI_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
@@ -57,10 +57,10 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
node-version: "24"
- name: Restore Stack Config
run: echo "${{ secrets.K8S_BOOTSTRAP_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
run: echo "${{ secrets.K8S_BOOTSTRAP_PULUMI_DEV_YAML }}" | base64 -d > k8s-bootstrap/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
+93
View File
@@ -0,0 +1,93 @@
name: Deploy k8s Infra
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'k8s-infra/**'
- '.gitea/workflows/**'
pull_request:
branches:
- main
paths:
- 'k8s-infra/**'
- '.gitea/workflows/**'
jobs:
preview:
name: Pulumi Preview
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.K8S_INFRA_PULUMI_DEV_YAML }}" | base64 -d > k8s-infra/Pulumi.dev.yaml
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install Dependencies
run: npm ci
working-directory: k8s-infra
- name: Preview
uses: pulumi/actions@v5
with:
command: preview
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
deploy:
name: Pulumi Deploy
runs-on: ubuntu-latest
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.K8S_INFRA_PULUMI_DEV_YAML }}" | base64 -d > k8s-infra/Pulumi.dev.yaml
- name: Install Helm
uses: azure/setup-helm@v4
- name: Install Dependencies
run: npm ci
working-directory: k8s-infra
- name: Refresh State
uses: pulumi/actions@v5
with:
command: refresh
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
- name: Deploy
uses: pulumi/actions@v5
with:
command: up
stack-name: dev
work-dir: k8s-infra
cloud-url: ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.PULUMI_CONFIG_PASSPHRASE }}
+18 -4
View File
@@ -30,10 +30,17 @@ jobs:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.PULUMI_DEV_YAML }}" | base64 -d > proxmox-infra/Pulumi.dev.yaml
run: echo "${{ secrets.PROXMOX_INFRA_PULUMI_DEV_YAML }}" | base64 -d > proxmox-infra/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
run: npm ci
working-directory: proxmox-infra
- name: Install Pulumi CLI
run: curl -fsSL https://get.pulumi.com | sh && echo "$HOME/.pulumi/bin" >> $GITHUB_PATH
- name: Generate Local pfSense SDK
run: pulumi package add terraform-provider marshallford/pfsense
working-directory: proxmox-infra
- name: Preview
@@ -60,10 +67,17 @@ jobs:
node-version: '24'
- name: Restore Stack Config
run: echo "${{ secrets.PULUMI_DEV_YAML }}" | base64 -d > proxmox-infra/Pulumi.dev.yaml
run: echo "${{ secrets.PROXMOX_INFRA_PULUMI_DEV_YAML }}" | base64 -d > proxmox-infra/Pulumi.dev.yaml
- name: Install Dependencies
run: npm install
run: npm ci
working-directory: proxmox-infra
- name: Install Pulumi CLI
run: curl -fsSL https://get.pulumi.com | sh && echo "$HOME/.pulumi/bin" >> $GITHUB_PATH
- name: Generate Local pfSense SDK
run: pulumi package add terraform-provider marshallford/pfsense
working-directory: proxmox-infra
- name: Refresh State
+3 -1
View File
@@ -3,4 +3,6 @@
.vscode
node_modules/
bin/
Pulumi.dev.yaml
Pulumi.dev.yaml
sdks/
.env
+26 -5
View File
@@ -19,7 +19,9 @@ This repo is intentionally abstract: credentials are never hardcoded, making it
├── proxmox-infra/ # Pulumi TypeScript stack — VMs & LXC on Proxmox
│ ├── index.ts # All Pulumi resources
│ ├── Pulumi.yaml # Stack project definition
── Pulumi.dev.yaml # Encrypted stack config (gitignored)
── Pulumi.dev.yaml # Encrypted stack config (gitignored)
│ └── sdks/
│ └── pfsense/ # Locally bundled @pulumi/pfsense SDK
├── .gitea/
│ └── workflows/
│ └── deploy-proxmox-infra.yaml # Gitea Actions CI/CD pipeline
@@ -37,12 +39,16 @@ Provisions a 5-node k3s cluster spread across two Proxmox hosts (`pve` and `pve-
| k3s-master-3 | master | pve-bckp |
| k3s-worker-2 | worker | pve-bckp |
Each node is a full clone of an Ubuntu Noble (24.04) cloud-image template, with cloud-init injecting hostname, user credentials, and SSH key at boot.
Each node is a full clone of an Ubuntu Noble (24.04) cloud-image template, with cloud-init injecting hostname, user credentials, and SSH keys at boot. Each VM's MAC address is registered as a DHCPv4 static mapping in pfSense so that nodes always receive their designated IPs.
An ED25519 SSH key pair is generated once and stored in Pulumi state. The public key is injected into every VM at boot; the private key is exported as a stack output so `k8s-bootstrap` can consume it via StackReference without any manual key distribution.
**Tech stack:**
- [Pulumi](https://www.pulumi.com/) with TypeScript
- [`@muhlba91/pulumi-proxmoxve`](https://github.com/muhlba91/pulumi-provider-proxmoxve) v8.x community provider
- [`@pulumi/pfsense`](https://github.com/marshallford/terraform-provider-pfsense) — locally bundled SDK bridged from the Terraform pfSense provider; installed automatically via `npm install`
- [`@pulumi/tls`](https://www.pulumi.com/registry/packages/tls/) — SSH key pair generation
- Self-hosted Pulumi state backend (PostgreSQL)
- Gitea Actions for CI/CD
@@ -51,6 +57,7 @@ Each node is a full clone of an Ubuntu Noble (24.04) cloud-image template, with
- [Pulumi CLI](https://www.pulumi.com/docs/install/) installed
- Node.js 18+ and npm
- Access to a Proxmox node with an API token
- pfSense instance with API credentials (used for DHCPv4 static mappings)
- A self-hosted Pulumi state backend (PostgreSQL connection string)
- Gitea instance for CI/CD (optional for local use)
@@ -64,20 +71,34 @@ cd proxmox-infra
npm install
```
> **pfSense SDK** — The `@pulumi/pfsense` SDK is bundled locally under `sdks/pfsense/` and referenced as a `file:` dependency in `package.json`. Running `npm install` compiles it automatically via its postinstall hook. No separate installation or build step is required.
### 2. Configure credentials
All secrets are stored as encrypted Pulumi config values — never in plain environment variables or committed files.
```bash
# Set Proxmox API credentials
# Proxmox API credentials
pulumi config set --secret pve1Endpoint https://<proxmox-host-1>:8006
pulumi config set --secret pve1ApiToken <user>@pam!<token-id>=<uuid>
pulumi config set --secret pve2Endpoint https://<proxmox-host-2>:8006
pulumi config set --secret pve2ApiToken <user>@pam!<token-id>=<uuid>
# Set VM credentials
# VM credentials
pulumi config set --secret k3sVmPassword <vm-password>
pulumi config set --secret sshPvePublicKey "ssh-ed25519 AAAA..."
# pfSense credentials (used for DHCPv4 static mappings)
pulumi config set --secret pfSenseUrl https://<pfsense-host>
pulumi config set --secret pfSenseUser <admin-username>
pulumi config set --secret pfSensePassword <admin-password>
# Static IP addresses assigned to each k3s node
pulumi config set --secret master1Ip <ip-for-k3s-master-1>
pulumi config set --secret master2Ip <ip-for-k3s-master-2>
pulumi config set --secret worker1Ip <ip-for-k3s-worker-1>
pulumi config set --secret master3Ip <ip-for-k3s-master-3>
pulumi config set --secret worker2Ip <ip-for-k3s-worker-2>
```
Pulumi encrypts these values into `Pulumi.dev.yaml` using your `PULUMI_CONFIG_PASSPHRASE`.
@@ -140,7 +161,7 @@ base64 -w 0 proxmox-infra/Pulumi.dev.yaml
- LXC container management
- Docker / Compose stack provisioning
- Network and firewall rules
- Firewall rules (pfSense)
- Automated k3s bootstrapping (kubeconfig export)
- Additional worker nodes and storage volumes
- Migrate secrets management to [OpenBao](https://openbao.org/) — replace `PULUMI_CONFIG_PASSPHRASE` and manual `Pulumi.dev.yaml` encoding with a self-hosted vault
+16 -14
View File
@@ -23,11 +23,11 @@ const pve2ApiToken = infraRef.requireOutput(
) as pulumi.Output<string>;
// Node IPs — static DHCP leases set in the router
const master1Ip = config.require("master1Ip");
const master2Ip = config.require("master2Ip");
const master3Ip = config.require("master3Ip");
const worker1Ip = config.require("worker1Ip");
const worker2Ip = config.require("worker2Ip");
const master1Ip = infraRef.requireOutput("master1Ip");
const master2Ip = infraRef.requireOutput("master2Ip");
const master3Ip = infraRef.requireOutput("master3Ip");
const worker1Ip = infraRef.requireOutput("worker1Ip");
const worker2Ip = infraRef.requireOutput("worker2Ip");
// Pre-shared k3s cluster token
const k3sToken = config.requireSecret("k3sToken");
@@ -47,7 +47,9 @@ const worker1VmId = vmIdsOutput.apply((ids) => String(ids.worker1));
const worker2VmId = vmIdsOutput.apply((ids) => String(ids.worker2));
// SSH connection helper
function conn(ip: string): command.types.input.remote.ConnectionArgs {
function conn(
ip: pulumi.Input<string>,
): command.types.input.remote.ConnectionArgs {
return { host: ip, user: "ubuntu", privateKey: ciRunnerPrivateKey };
}
@@ -97,7 +99,7 @@ const allStarts = [
const waitMaster1Ssh = new command.local.Command(
"wait-ssh-master-1",
{
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [master1VmId],
interpreter: ["/bin/bash", "-c"],
},
@@ -131,7 +133,7 @@ const waitK3sMaster1Ready = new command.remote.Command(
const waitMaster2Ssh = new command.local.Command(
"wait-ssh-master-2",
{
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [master2VmId],
interpreter: ["/bin/bash", "-c"],
},
@@ -141,7 +143,7 @@ const waitMaster2Ssh = new command.local.Command(
const waitMaster3Ssh = new command.local.Command(
"wait-ssh-master-3",
{
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master3Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${master3Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [master3VmId],
interpreter: ["/bin/bash", "-c"],
},
@@ -175,7 +177,7 @@ const joinMaster3 = new command.remote.Command(
const waitWorker1Ssh = new command.local.Command(
"wait-ssh-worker-1",
{
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker1Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [worker1VmId],
interpreter: ["/bin/bash", "-c"],
},
@@ -185,7 +187,7 @@ const waitWorker1Ssh = new command.local.Command(
const waitWorker2Ssh = new command.local.Command(
"wait-ssh-worker-2",
{
create: `for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
create: pulumi.interpolate`for i in $(seq 1 60); do (timeout 5 bash -c "echo > /dev/tcp/${worker2Ip}/22") 2>/dev/null && exit 0; sleep 5; done; exit 1`,
triggers: [worker2VmId],
interpreter: ["/bin/bash", "-c"],
},
@@ -227,7 +229,7 @@ const getKubeconfig = new command.remote.Command(
);
export const kubeconfig = pulumi.secret(
getKubeconfig.stdout.apply((kc) =>
kc.replace(/127\.0\.0\.1/g, master1Ip).trim(),
),
pulumi
.all([getKubeconfig.stdout, master1Ip])
.apply(([kc, ip]) => kc.replaceAll("127.0.0.1", ip as string).trim()),
);
+1 -1
View File
@@ -2,7 +2,7 @@
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2020",
"target": "es2024",
"module": "nodenext",
"moduleResolution": "nodenext",
"sourceMap": true,
+6
View File
@@ -0,0 +1,6 @@
name: k8s-infra
description: Cluster-level infrastructure for the k3s homelab cluster
runtime:
name: nodejs
options:
packagemanager: npm
+117
View File
@@ -0,0 +1,117 @@
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
const config = new pulumi.Config();
//fetch credentials from k8s-bootstrap
const infraRef = new pulumi.StackReference(
`${pulumi.getOrganization()}/k8s-bootstrap/dev`,
);
const kubeconfig = infraRef.requireOutput("kubeconfig");
const truenasHost = config.requireSecret("truenasHost");
const truenasNfsPath = config.requireSecret("truenasNfsPath");
const cloudflareToken = config.requireSecret("cloudflareApiToken");
const letsencryptEmail = config.requireSecret("letsencryptEmail");
const k8sProvider = new k8s.Provider("k3s", { kubeconfig });
const opts = (extras?: pulumi.ResourceOptions): pulumi.ResourceOptions => ({
provider: k8sProvider,
...extras,
});
// ── 1. NFS CSI Driver ────────────────────────────────────────────────────────
const nfsCsiDriver = new k8s.helm.v3.Release(
"nfs-csi-driver",
{
name: "csi-driver-nfs",
chart: "csi-driver-nfs",
repositoryOpts: {
repo: "https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts",
},
namespace: "kube-system",
version: "4.12.0",
values: {
kubeletDir: "/var/lib/kubelet",
},
},
opts(),
);
new k8s.storage.v1.StorageClass(
"truenas-nfs",
{
metadata: { name: "truenas-nfs" },
provisioner: "nfs.csi.k8s.io",
parameters: {
server: truenasHost,
share: truenasNfsPath,
mountPermissions: "0",
},
reclaimPolicy: "Retain",
volumeBindingMode: "Immediate",
allowVolumeExpansion: true,
mountOptions: ["nfsvers=4.1"],
},
opts({ dependsOn: [nfsCsiDriver] }),
);
// ── 2. cert-manager ──────────────────────────────────────────────────────────
const certManager = new k8s.helm.v3.Release(
"cert-manager",
{
name: "cert-manager",
chart: "cert-manager",
repositoryOpts: { repo: "https://charts.jetstack.io" },
namespace: "cert-manager",
createNamespace: true,
values: {
installCRDs: true,
},
},
opts(),
);
// ── 3. Cloudflare token secret + ClusterIssuer ───────────────────────────────
const cfSecret = new k8s.core.v1.Secret(
"cloudflare-token",
{
metadata: { name: "cloudflare-api-token", namespace: "cert-manager" },
stringData: { "api-token": cloudflareToken },
},
opts({ dependsOn: [certManager] }),
);
new k8s.apiextensions.CustomResource(
"letsencrypt-prod",
{
apiVersion: "cert-manager.io/v1",
kind: "ClusterIssuer",
metadata: { name: "letsencrypt-prod" },
spec: {
acme: {
server: "https://acme-v02.api.letsencrypt.org/directory",
email: letsencryptEmail,
privateKeySecretRef: { name: "letsencrypt-prod-account-key" },
solvers: [
{
dns01: {
cloudflare: {
apiTokenSecretRef: {
name: cfSecret.metadata.name,
key: "api-token",
},
},
},
},
],
},
},
},
opts({ dependsOn: [certManager, cfSecret] }),
);
export const storageClass = "truenas-nfs";
+2799
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
{
"name": "k8s-infra",
"main": "index.ts",
"devDependencies": {
"@types/node": "^18",
"typescript": "^5.0.0"
},
"dependencies": {
"@pulumi/kubernetes": "^4.0.0",
"@pulumi/pulumi": "^3.113.0"
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2024",
"module": "nodenext",
"moduleResolution": "nodenext",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
+6
View File
@@ -8,3 +8,9 @@ config:
pulumi:tags:
value:
pulumi:template: typescript
packages:
pfsense:
source: terraform-provider
version: 1.1.3
parameters:
- marshallford/pfsense
+54 -13
View File
@@ -1,18 +1,29 @@
import * as pulumi from "@pulumi/pulumi";
import * as proxmox from "@muhlba91/pulumi-proxmoxve";
import * as tls from "@pulumi/tls";
import * as pfsense from "@pulumi/pfsense";
const config = new pulumi.Config();
// ---------------------------------------------------------------------------
// Providers — one per standalone Proxmox machine
// ---------------------------------------------------------------------------
const pve1Endpoint = config.requireSecret("pve1Endpoint");
const pve1ApiToken = config.requireSecret("pve1ApiToken");
const pve2Endpoint = config.requireSecret("pve2Endpoint");
const pve2ApiToken = config.requireSecret("pve2ApiToken");
const pfSenseUrl = config.requireSecret("pfSenseUrl");
const pfSenseUser = config.requireSecret("pfSenseUser");
const pfSensePassword = config.requireSecret("pfSensePassword");
const master1Ip = config.requireSecret("master1Ip");
const master2Ip = config.requireSecret("master2Ip");
const worker1Ip = config.requireSecret("worker1Ip");
const master3Ip = config.requireSecret("master3Ip");
const worker2Ip = config.requireSecret("worker2Ip");
// ---------------------------------------------------------------------------
// Providers — one per standalone Proxmox machine
// ---------------------------------------------------------------------------
const pveProvider = new proxmox.Provider("pve", {
endpoint: pve1Endpoint,
apiToken: pve1ApiToken,
@@ -25,6 +36,17 @@ const pveBckpProvider = new proxmox.Provider("pve-bckp", {
insecure: true,
});
// ---------------------------------------------------------------------------
// Providers — PfSense
// ---------------------------------------------------------------------------
const pfSenseProvider = new pfsense.Provider("pfsense", {
url: pfSenseUrl,
username: pfSenseUser,
password: pfSensePassword,
tlsSkipVerify: true,
});
// ---------------------------------------------------------------------------
// CI runner SSH keypair — generated once, stored in Pulumi state backend.
// Public key goes into every VM; private key is exported for k8s-bootstrap.
@@ -150,6 +172,7 @@ interface NodeConfig {
provider: proxmox.Provider;
template: proxmox.VmLegacy;
diskDatastore: string;
ip: pulumi.Output<string>;
}
const nodeConfigs: NodeConfig[] = [
@@ -160,6 +183,7 @@ const nodeConfigs: NodeConfig[] = [
provider: pveProvider,
template: pveTemplate,
diskDatastore: "local-lvm",
ip: master1Ip,
},
{
name: "k3s-master-2",
@@ -168,6 +192,7 @@ const nodeConfigs: NodeConfig[] = [
provider: pveProvider,
template: pveTemplate,
diskDatastore: "local-lvm",
ip: master2Ip,
},
{
name: "k3s-worker-1",
@@ -176,6 +201,7 @@ const nodeConfigs: NodeConfig[] = [
provider: pveProvider,
template: pveTemplate,
diskDatastore: "local-lvm",
ip: worker1Ip,
},
{
name: "k3s-master-3",
@@ -184,6 +210,7 @@ const nodeConfigs: NodeConfig[] = [
provider: pveBckpProvider,
template: pveBckpTemplate,
diskDatastore: "local",
ip: master3Ip,
},
{
name: "k3s-worker-2",
@@ -192,6 +219,7 @@ const nodeConfigs: NodeConfig[] = [
provider: pveBckpProvider,
template: pveBckpTemplate,
diskDatastore: "local",
ip: worker2Ip,
},
];
@@ -235,9 +263,9 @@ const k3sVms = nodeConfigs.map(
username: "ubuntu",
password: k3sVmPassword,
keys: [
sshPvePublicKey.apply((k) => k.trim()),
ciRunnerKey.publicKeyOpenssh.apply((k) => k.trim()),
],
sshPvePublicKey.apply((k) => k.trim()),
ciRunnerKey.publicKeyOpenssh.apply((k) => k.trim()),
],
},
},
networkDevices: [{ bridge: "vmbr0", model: "virtio" }],
@@ -256,12 +284,22 @@ const k3sVms = nodeConfigs.map(
),
);
export const clusterInfo = k3sVms.map((vm, index) => ({
nodeName: vm.nodeName,
vmId: vm.vmId,
name: nodeConfigs[index].name,
role: nodeConfigs[index].role,
}));
k3sVms.forEach((vmResource, i) => {
const assignedMac = vmResource.networkDevices.apply(
(nic) => nic[0].macAddress,
);
return new pfsense.Dhcpv4Staticmapping(
`${nodeConfigs[i].name}-dhcp`,
{
interface: "lan",
macAddress: assignedMac,
ipAddress: nodeConfigs[i].ip,
hostname: nodeConfigs[i].name,
},
{ dependsOn: vmResource, provider: pfSenseProvider },
);
});
// Individual vmId exports — used by k8s-bootstrap to start VMs.
// Order matches nodeConfigs: master-1, master-2, worker-1, master-3, worker-2.
@@ -278,3 +316,6 @@ export const ciRunnerPrivateKey = pulumi.secret(ciRunnerKey.privateKeyOpenssh);
// Proxmox API credentials — consumed by k8s-bootstrap via StackReference.
export { pve1Endpoint, pve1ApiToken, pve2Endpoint, pve2ApiToken };
//k3s instance ips consumed by k8s-bootstrap.
export { master1Ip, master2Ip, worker1Ip, master3Ip, worker2Ip };
+43
View File
@@ -7,6 +7,7 @@
"name": "proxmox-infra",
"dependencies": {
"@muhlba91/pulumi-proxmoxve": "^8.2.1",
"@pulumi/pfsense": "file:sdks/pfsense",
"@pulumi/pulumi": "^3.113.0",
"@pulumi/tls": "^5.5.0"
},
@@ -703,6 +704,10 @@
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
"license": "BSD-3-Clause"
},
"node_modules/@pulumi/pfsense": {
"resolved": "sdks/pfsense",
"link": true
},
"node_modules/@pulumi/pulumi": {
"version": "3.243.0",
"resolved": "https://registry.npmjs.org/@pulumi/pulumi/-/pulumi-3.243.0.tgz",
@@ -2674,6 +2679,44 @@
"engines": {
"node": ">=12"
}
},
"sdks/pfsense": {
"name": "@pulumi/pfsense",
"version": "0.22.0",
"hasInstallScript": true,
"dependencies": {
"@pulumi/pulumi": "^3.238.0",
"@types/node": "^20",
"typescript": "^4.7.0"
}
},
"sdks/pfsense/node_modules/@types/node": {
"version": "20.19.41",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
"integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"sdks/pfsense/node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"sdks/pfsense/node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
}
}
}
+5
View File
@@ -7,7 +7,12 @@
},
"dependencies": {
"@muhlba91/pulumi-proxmoxve": "^8.2.1",
"@pulumi/pfsense": "file:sdks/pfsense",
"@pulumi/pulumi": "^3.113.0",
"@pulumi/tls": "^5.5.0"
},
"imports": {
"#pfsense": "./sdks/pfsense/index.js",
"#pfsense/*": "./sdks/pfsense/*"
}
}