8 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
9 changed files with 3070 additions and 23 deletions
+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 }}
+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"
]
}