K3s cluster on Proxmox with WireGuard mesh networking
Replaced Headscale (too buggy in 0.28.x — random node drops) with direct WireGuard hub-and-spoke + full mesh. 7 Proxmox VMs across 3 hosts form a K3s v1.34.6 cluster: 3 control-plane/etcd nodes, 4 workers. Running services: postgres, mariadb, ghost (x3), forgejo, authentik. All unpinned services use local-path StorageClass. Databases pinned to pve-worker and adder-worker with local PVs. Includes VM provisioning scripts (create-debian-template.sh, clone-vm.sh), K3s manifests for all services, and full deployment docs in k3s/README.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a9876bf5b5
commit
759ef949bc
26 changed files with 2231 additions and 0 deletions
142
K3s-SESSION-STATE.md
Normal file
142
K3s-SESSION-STATE.md
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
# K3s Session State
|
||||||
|
# Saved: 2026-04-06 (end of session 3)
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
New Proxmox-based K3s cluster in progress. VirtualBox cluster retired.
|
||||||
|
All 7 Proxmox VMs created and on WireGuard mesh. K3s not yet installed.
|
||||||
|
Old VirtualBox services (ghost, forgejo, postgres, mariadb) still running on old cluster until migration complete.
|
||||||
|
|
||||||
|
## Proxmox VMs
|
||||||
|
|
||||||
|
| Node | vmbr1 IP | WG IP | Proxmox Host | Role |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| pve-control | 10.10.10.151 | 10.0.0.6 | pve | k3s control plane |
|
||||||
|
| pve-worker | 10.10.10.126 | 10.0.0.7 | pve | k3s worker |
|
||||||
|
| adder-control | 10.10.10.185 | 10.0.0.8 | adder | k3s control plane |
|
||||||
|
| adder-worker | 10.10.10.83 | 10.0.0.9 | adder | k3s worker |
|
||||||
|
| game-control | 10.10.10.158 | 10.0.0.10 | game | k3s control plane |
|
||||||
|
| game-worker-hdd | 10.10.10.186 | 10.0.0.11 | game | k3s worker (local-lvm/HDD) |
|
||||||
|
| game-worker-ssd | 10.10.10.153 | 10.0.0.12 | game | k3s worker (game-ssd/NVMe) |
|
||||||
|
|
||||||
|
WG IPs 10.0.0.2–10.0.0.5 reserved (old VirtualBox nodes, do not reuse).
|
||||||
|
Hub: DO droplet at 138.197.87.251:51820, WG IP 10.0.0.1
|
||||||
|
|
||||||
|
## VM Specs
|
||||||
|
|
||||||
|
| Node | vCPUs | RAM | Disk | Storage |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| pve-control | 2 | 2GB | 20G | local-lvm |
|
||||||
|
| pve-worker | 6 | 8GB | 100G | local-lvm |
|
||||||
|
| adder-control | 2 | 2GB | 20G | local-lvm |
|
||||||
|
| adder-worker | 6 | 8GB | 100G | local-lvm |
|
||||||
|
| game-control | 2 | 2GB | 20G | local-lvm |
|
||||||
|
| game-worker-hdd | 6 | 8GB | 200G | local-lvm (HDD) |
|
||||||
|
| game-worker-ssd | 10 | 8GB | 200G | game-ssd (NVMe) |
|
||||||
|
|
||||||
|
## Network Architecture
|
||||||
|
|
||||||
|
- All VMs on vmbr1 (10.10.10.0/24), DHCP
|
||||||
|
- WireGuard mesh via DO hub — all nodes have static WG IPs (10.0.0.0/24)
|
||||||
|
- Full mesh: all nodes have each other as explicit WireGuard peers (not just hub-and-spoke)
|
||||||
|
- K3s will use --flannel-iface=wg0 so all cluster traffic runs over WireGuard
|
||||||
|
- Caddy at DO hub proxies external traffic to any node's WG IP + NodePort
|
||||||
|
- Tailscale/Headscale abandoned — too unreliable for cluster networking
|
||||||
|
|
||||||
|
## Proxmox Host Specs
|
||||||
|
|
||||||
|
- pve: workstation i9-13900KF, 96GB RAM
|
||||||
|
- adder: Proxmox node with RTX 2070, 4TB NVMe available
|
||||||
|
- game: Proxmox node with RTX 2070, 16GB RAM, 256GB NVMe (game-ssd) + 2TB HDD (local-lvm)
|
||||||
|
|
||||||
|
## VM Provisioning
|
||||||
|
|
||||||
|
### Template & Clone Scripts
|
||||||
|
Scripts at `~/private/Knowledge/repos/homelab/proxmox/scripts/`:
|
||||||
|
- `create-debian-template.sh <VMID> <NAME> [STORAGE] [BRIDGE]`
|
||||||
|
- Defaults: STORAGE=local-lvm, BRIDGE=vmbr1
|
||||||
|
- Bakes in: qemu-guest-agent, curl, wget, nano, rsync, htop, tmux, emacs-nox, nfs-common, tailscale
|
||||||
|
- Zeroes /etc/machine-id, removes /etc/ssh/ssh_host_* (Cloud-Init regenerates on first boot)
|
||||||
|
- Does NOT create .ssh or set keys — done post-boot via qm set
|
||||||
|
- `clone-vm.sh <TEMPLATE_VMID> <NEW_VMID> <NAME> [CORES] [MEMORY_MB] [DISK_SIZE] [STORAGE]`
|
||||||
|
- Defaults: 2 cores, 2048MB RAM, 20G disk, local-lvm storage
|
||||||
|
- Full clone, auto-starts the VM
|
||||||
|
|
||||||
|
### Post-Clone Formula (confirmed working)
|
||||||
|
1. Clone: `./clone-vm.sh <template> <vmid> <name> [cores] [mem] [disk] [storage]`
|
||||||
|
2. Get IP: `qm guest cmd <vmid> network-get-interfaces`
|
||||||
|
3. Set SSH key: `qm set <vmid> --sshkeys <pubkey-file>`
|
||||||
|
4. Reboot VM: `qm reboot <vmid>`
|
||||||
|
5. SSH in: `ssh samantha@<ip>`
|
||||||
|
6. Configure WireGuard on the VM
|
||||||
|
|
||||||
|
### VMID Convention
|
||||||
|
- pve: 100-199 (templates at 199)
|
||||||
|
- adder: 200-299 (templates at 299 — currently 200 exists, destroy after use)
|
||||||
|
- game: 300-399 (templates at 399 — currently 300 exists, destroy after use)
|
||||||
|
|
||||||
|
### Useful Proxmox CLI
|
||||||
|
- `qm guest cmd <VMID> network-get-interfaces` — get VM IP
|
||||||
|
- `qm set <VMID> --vga std --delete serial0` — fix serial console
|
||||||
|
- `qm destroy <VMID> --purge` — remove VM
|
||||||
|
- `qm list` — list all VMs
|
||||||
|
- `vgs` — check local-lvm free space
|
||||||
|
- `pvesh get /nodes/<nodename>/status` — CPU/memory usage
|
||||||
|
|
||||||
|
## Immediate Next Steps
|
||||||
|
1. Install K3s on pve-control first (--cluster-init)
|
||||||
|
2. Join adder-control and game-control as control plane peers
|
||||||
|
3. Join all 4 workers
|
||||||
|
4. Label workers and GPU nodes
|
||||||
|
5. Create namespaces: sjasoft, fulfillment, privacy-practice
|
||||||
|
6. Migrate services from old VirtualBox cluster
|
||||||
|
|
||||||
|
## K3s Install — see k3s/README.md for full commands
|
||||||
|
|
||||||
|
- Control plane uses --cluster-init on first node, --server on subsequent nodes
|
||||||
|
- All nodes use --flannel-iface=wg0 and --node-ip=<wg-ip>
|
||||||
|
- Traefik disabled on all nodes
|
||||||
|
- 3 control plane nodes for HA etcd (tolerates 1 failure)
|
||||||
|
|
||||||
|
## Running Services (old VirtualBox cluster — not yet migrated)
|
||||||
|
|
||||||
|
- postgres:16 — ClusterIP:5432
|
||||||
|
- mariadb:11 — ClusterIP:3306
|
||||||
|
- ghost1/2/3 — NodePorts 32368/32369/32370
|
||||||
|
- forgejo:9 — NodePort 32371, git.sjasoft.com
|
||||||
|
|
||||||
|
## NodePort Registry
|
||||||
|
|
||||||
|
| Port | Service | Namespace |
|
||||||
|
|---|---|---|
|
||||||
|
| 32368 | ghost1 | fulfillment |
|
||||||
|
| 32369 | ghost2 | fulfillment |
|
||||||
|
| 32370 | ghost3 | fulfillment |
|
||||||
|
| 32371 | forgejo | sjasoft |
|
||||||
|
|
||||||
|
## Manifests
|
||||||
|
|
||||||
|
All in Knowledge/repos/homelab/k3s/:
|
||||||
|
- k3s/postgres/postgres.yaml
|
||||||
|
- k3s/mariadb/mariadb.yaml
|
||||||
|
- k3s/ghost/ghost.yaml
|
||||||
|
- k3s/forgejo/forgejo.yaml
|
||||||
|
- k3s/README.md (authoritative WG mesh table + K3s install commands)
|
||||||
|
|
||||||
|
## Remaining Services to Port (from Proxmox Docker stack)
|
||||||
|
- authentik.yml — SSO (postgres)
|
||||||
|
- n8n.yml — automation (postgres)
|
||||||
|
- vaultwarden.yml — passwords
|
||||||
|
- nats.yml — messaging
|
||||||
|
- monerod.yml — monero node
|
||||||
|
- snikket.yml — XMPP
|
||||||
|
- synapse.yml — Matrix
|
||||||
|
|
||||||
|
## Known Issues / Notes
|
||||||
|
- Tailscale/Headscale abandoned — unreliable, randomly drops nodes, requires manual reconnect
|
||||||
|
- WireGuard full mesh is the correct approach for K3s cluster networking
|
||||||
|
- kubectl requires KUBECONFIG=~/.kube/config in ~/.bashrc on control nodes
|
||||||
|
- Cross-namespace secrets not supported — keep secrets in same namespace as consumer
|
||||||
|
- game node only has 16GB RAM — allocate worker VMs conservatively
|
||||||
|
- game-ssd is only 256GB NVMe — keep disk allocations conservative on game-worker-ssd
|
||||||
|
- Templates should be destroyed after all clones are complete on each node
|
||||||
248
k3s/README.md
Normal file
248
k3s/README.md
Normal file
|
|
@ -0,0 +1,248 @@
|
||||||
|
# K3s Cluster — Setup & Deployment Notes
|
||||||
|
|
||||||
|
This is the production cluster running on Proxmox VMs, connected via WireGuard hub-and-spoke.
|
||||||
|
The VirtualBox learning cluster this replaced is retired.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WireGuard Mesh — Node Assignments
|
||||||
|
|
||||||
|
Hub: DO droplet at 138.197.87.251:51820, WG IP 10.0.0.1/24
|
||||||
|
|
||||||
|
| Node | vmbr1 IP | WG IP | Proxmox Host |
|
||||||
|
|---|---|---|---|
|
||||||
|
| pve-control | 10.10.10.151 | 10.0.0.6 | pve |
|
||||||
|
| pve-worker | 10.10.10.126 | 10.0.0.7 | pve |
|
||||||
|
| adder-control | 10.10.10.185 | 10.0.0.8 | adder |
|
||||||
|
| adder-worker | 10.10.10.83 | 10.0.0.9 | adder |
|
||||||
|
| game-control | 10.10.10.158 | 10.0.0.10 | game |
|
||||||
|
| game-worker-hdd | 10.10.10.186 | 10.0.0.11 | game |
|
||||||
|
| game-worker-ssd | 10.10.10.153 | 10.0.0.12 | game |
|
||||||
|
|
||||||
|
IPs 10.0.0.2–10.0.0.5 are reserved (old VirtualBox K3s nodes, leave alone).
|
||||||
|
|
||||||
|
All VMs are Debian Trixie on vmbr1 (10.10.10.0/24). Inter-node traffic runs over WireGuard (10.0.0.0/24).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## K3s Install
|
||||||
|
|
||||||
|
### Prerequisites — each VM must be on the WireGuard mesh first
|
||||||
|
|
||||||
|
WireGuard is configured via wg0.conf on each node (hub-and-spoke through DO droplet).
|
||||||
|
Verify connectivity: `ping 10.0.0.1` from the node.
|
||||||
|
|
||||||
|
### First control plane node (cluster init)
|
||||||
|
```bash
|
||||||
|
curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--cluster-init --disable traefik \
|
||||||
|
--node-ip=<10.0.0.x> --flannel-iface=wg0" sh -
|
||||||
|
|
||||||
|
# Get token for other nodes to join
|
||||||
|
sudo cat /var/lib/rancher/k3s/server/node-token
|
||||||
|
```
|
||||||
|
|
||||||
|
### Second and third control plane nodes
|
||||||
|
```bash
|
||||||
|
curl -sfL https://get.k3s.io | K3S_URL=https://<control-1-mesh-ip>:6443 K3S_TOKEN=<token> \
|
||||||
|
INSTALL_K3S_EXEC="--server https://<control-1-mesh-ip>:6443 --disable traefik \
|
||||||
|
--node-ip=<this-node-mesh-ip> --flannel-iface=wg0" sh -
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: use `--server` not just `K3S_URL` — this is what makes it a control plane peer, not a worker.
|
||||||
|
etcd requires odd numbers — 3 control nodes tolerates 1 failure. Never stop at 2.
|
||||||
|
|
||||||
|
### Workers
|
||||||
|
```bash
|
||||||
|
curl -sfL https://get.k3s.io | K3S_URL=https://<any-control-mesh-ip>:6443 K3S_TOKEN=<token> \
|
||||||
|
INSTALL_K3S_EXEC="--node-ip=<this-node-mesh-ip> --flannel-iface=wg0" sh -
|
||||||
|
```
|
||||||
|
|
||||||
|
### kubeconfig for normal user (on any control node)
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.kube
|
||||||
|
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
|
||||||
|
sudo chown samantha:samantha ~/.kube/config
|
||||||
|
export KUBECONFIG=~/.kube/config # also add to ~/.bashrc
|
||||||
|
# Update server IP in config if needed:
|
||||||
|
sed -i 's/127.0.0.1/<control-1-mesh-ip>/' ~/.kube/config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Label workers
|
||||||
|
```bash
|
||||||
|
kubectl label node <name> node-role.kubernetes.io/worker=worker
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GPU Worker Nodes — adder and game
|
||||||
|
|
||||||
|
Both Proxmox hosts `adder` and `game` have RTX 2070 GPUs available for PCIe passthrough.
|
||||||
|
|
||||||
|
### Proxmox PCIe passthrough setup (on each Proxmox host)
|
||||||
|
```bash
|
||||||
|
# Enable IOMMU in /etc/default/grub:
|
||||||
|
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
|
||||||
|
# (use amd_iommu=on for AMD hosts)
|
||||||
|
update-grub
|
||||||
|
reboot
|
||||||
|
|
||||||
|
# Blacklist nvidia drivers on host so GPU is free for passthrough:
|
||||||
|
echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
|
||||||
|
echo "blacklist nvidia" >> /etc/modprobe.d/blacklist.conf
|
||||||
|
update-initramfs -u
|
||||||
|
reboot
|
||||||
|
```
|
||||||
|
|
||||||
|
In Proxmox UI: VM Hardware → Add → PCI Device → select the RTX 2070 → check "All Functions" and "Primary GPU" if it is the only GPU.
|
||||||
|
|
||||||
|
### Inside the GPU worker VM — install NVIDIA drivers
|
||||||
|
```bash
|
||||||
|
apt-get install -y linux-headers-$(uname -r)
|
||||||
|
# Add non-free repo if needed:
|
||||||
|
apt-get install -y nvidia-driver firmware-misc-nonfree
|
||||||
|
reboot
|
||||||
|
# Verify:
|
||||||
|
nvidia-smi
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install NVIDIA device plugin in K3s
|
||||||
|
```bash
|
||||||
|
kubectl create -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/v0.14.0/nvidia-device-plugin.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Label GPU nodes
|
||||||
|
```bash
|
||||||
|
kubectl label node k3s-adder nvidia.com/gpu=true
|
||||||
|
kubectl label node k3s-game nvidia.com/gpu=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify GPU is schedulable
|
||||||
|
```bash
|
||||||
|
kubectl get nodes -o json | jq '.items[].status.capacity'
|
||||||
|
# Should show nvidia.com/gpu: "1" on adder and game
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scheduling a workload to a GPU node
|
||||||
|
```yaml
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
nvidia.com/gpu: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Namespaces — one per venture
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl create namespace sjasoft
|
||||||
|
kubectl create namespace fulfillment
|
||||||
|
kubectl create namespace privacy-practice
|
||||||
|
```
|
||||||
|
|
||||||
|
Secrets are always created per namespace — never share secrets across namespaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Secrets
|
||||||
|
|
||||||
|
Never stored in files with real values. Always create directly on a control node.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pattern — adapt per service and namespace
|
||||||
|
kubectl create secret generic <name> \
|
||||||
|
--namespace <namespace> \
|
||||||
|
--from-literal=<key>='<value>'
|
||||||
|
|
||||||
|
# Generate passwords with:
|
||||||
|
openssl rand -base64 24
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## NodePort Registry
|
||||||
|
|
||||||
|
NodePorts must be unique across the entire cluster (range 30000-32767).
|
||||||
|
Any NodePort is reachable on any node's WireGuard IP — K3s routes internally.
|
||||||
|
Caddy on each venture ingress VPS proxies to any node's WG IP + NodePort.
|
||||||
|
|
||||||
|
| Port | Service | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| 32368 | ghost1 | blog.the-fulfillment.org |
|
||||||
|
| 32369 | ghost2 | blog.privacy-practice.com |
|
||||||
|
| 32370 | ghost3 | blog.sjasoft.com |
|
||||||
|
| 32371 | forgejo | git.sjasoft.com |
|
||||||
|
| 32372 | authentik (HTTP) | auth.sjasoft.com — use this behind Caddy |
|
||||||
|
| 32373 | authentik (HTTPS) | skip — Caddy handles TLS |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Caddy Pattern — venture ingress VPS
|
||||||
|
|
||||||
|
Each venture has its own ingress VPS with its own public IP. Caddy on each proxies
|
||||||
|
to a different node's mesh IP for the same cluster — ventures look unrelated from outside.
|
||||||
|
|
||||||
|
```
|
||||||
|
# Example — any node's WG IP works for any NodePort
|
||||||
|
blog.the-fulfillment.org {
|
||||||
|
reverse_proxy 10.0.0.6:32368
|
||||||
|
}
|
||||||
|
|
||||||
|
git.sjasoft.com {
|
||||||
|
reverse_proxy 10.0.0.8:32371
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.sjasoft.com {
|
||||||
|
reverse_proxy 10.0.0.10:32372
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pick any node's WG IP per service — they all work. Use different nodes per venture
|
||||||
|
so ventures look unrelated from outside. See the WireGuard mesh table above for IPs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Deployment Status (2026-04-07)
|
||||||
|
|
||||||
|
K3s v1.34.6 cluster fully operational. WireGuard full mesh (direct peer-to-peer over vmbr1,
|
||||||
|
hub for external traffic). Headscale removed — too buggy (0.28.x dropped nodes randomly).
|
||||||
|
|
||||||
|
### Cluster Nodes
|
||||||
|
|
||||||
|
| Node | Role | WG IP | Proxmox Host | Resources |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| pve-control | control-plane, etcd | 10.0.0.6 | pve | 2 CPU, 2GB RAM, 20GB |
|
||||||
|
| pve-worker | worker | 10.0.0.7 | pve | 8 CPU, 58GB RAM, 3.3TB |
|
||||||
|
| adder-control | control-plane, etcd | 10.0.0.8 | adder | 2 CPU, 2GB RAM, 20GB |
|
||||||
|
| adder-worker | worker | 10.0.0.9 | adder | 10 CPU, 58GB RAM, 1.7TB |
|
||||||
|
| game-control | control-plane, etcd | 10.0.0.10 | game | 2 CPU, 2GB RAM, 20GB |
|
||||||
|
| game-worker-hdd | worker | 10.0.0.11 | game | 4 CPU, 6GB RAM, 1.4TB HDD |
|
||||||
|
| game-worker-ssd | worker | 10.0.0.12 | game | 10 CPU, 8GB RAM, 200GB SSD |
|
||||||
|
|
||||||
|
### Running Services
|
||||||
|
|
||||||
|
| Service | Node | NodePort | Domain | Status |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| postgres:16 | pve-worker (pinned) | ClusterIP | — | running |
|
||||||
|
| mariadb:11 | adder-worker (pinned) | ClusterIP | — | running |
|
||||||
|
| ghost1 | unpinned | 32368 | blog.the-fulfillment.org | running |
|
||||||
|
| ghost2 | unpinned | 32369 | blog.privacy-practice.com | running |
|
||||||
|
| ghost3 | unpinned | 32370 | blog.sjasoft.com | running |
|
||||||
|
| forgejo:9 | unpinned | 32371 | git.sjasoft.com | running |
|
||||||
|
| authentik server | unpinned | 32372 | auth.sjasoft.com | running |
|
||||||
|
| authentik worker | unpinned | — | — | running |
|
||||||
|
|
||||||
|
### Remaining Services to Deploy
|
||||||
|
|
||||||
|
n8n, nats, vaultwarden, synapse, snikket, monerod
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
- Add VirtualBox workstation VMs as workers to this cluster
|
||||||
|
- Wire up remaining Ghost blogs in Caddy
|
||||||
|
- Deploy remaining services from k3s/ manifests
|
||||||
|
|
||||||
|
### Install Method
|
||||||
|
|
||||||
|
K3s was installed using `/etc/rancher/k3s/config.yaml` on each node (not INSTALL_K3S_EXEC env vars,
|
||||||
|
which get lost in nested SSH). Binary was downloaded once to pve and distributed via scp.
|
||||||
|
Use `INSTALL_K3S_SKIP_DOWNLOAD=true` when binary is pre-staged.
|
||||||
45
k3s/authentik/authentik-db-init.yaml
Normal file
45
k3s/authentik/authentik-db-init.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Authentik DB Init Job
|
||||||
|
# Creates authentik database and user in PostgreSQL.
|
||||||
|
# Run once before deploying Authentik.
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic authentik-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>' \
|
||||||
|
# --from-literal=secret-key='<random-50-chars>'
|
||||||
|
# kubectl apply -f authentik-db-init.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Watch completion:
|
||||||
|
# kubectl get jobs -n <ns> -w
|
||||||
|
# kubectl logs job/authentik-db-init -n <ns>
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: authentik-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: authentik-db-init
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secret
|
||||||
|
key: password
|
||||||
|
- name: AUTHENTIK_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-secret
|
||||||
|
key: db-password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
psql -h postgres -U postgres <<EOF
|
||||||
|
CREATE USER authentik_user WITH PASSWORD '${AUTHENTIK_DB_PASSWORD}';
|
||||||
|
CREATE DATABASE authentik_db OWNER authentik_user;
|
||||||
|
EOF
|
||||||
163
k3s/authentik/authentik.yaml
Normal file
163
k3s/authentik/authentik.yaml
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# Authentik — SSO / identity provider
|
||||||
|
# PostgreSQL backend via cluster DNS: postgres
|
||||||
|
# No Redis required as of 2026.2.x
|
||||||
|
# Unpinned — scheduler places freely, local-path PVCs
|
||||||
|
# NodePort 32372 (HTTP), 32373 (HTTPS)
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic authentik-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>' \
|
||||||
|
# --from-literal=secret-key='<random-50-chars>'
|
||||||
|
# kubectl apply -f authentik-db-init.yaml -n <ns>
|
||||||
|
# kubectl get jobs -n <ns> -w # wait for completion
|
||||||
|
# kubectl apply -f authentik.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Initial setup wizard: http://<any-node-mesh-ip>:32372/if/flow/initial-setup/
|
||||||
|
#
|
||||||
|
# Generate secret-key with: openssl rand -base64 36
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: authentik-media-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: authentik-certs-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: authentik-server
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-server
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: authentik-server
|
||||||
|
image: ghcr.io/goauthentik/server:2026.2.1
|
||||||
|
command: ["ak", "server"]
|
||||||
|
env:
|
||||||
|
- name: AUTHENTIK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-secret
|
||||||
|
key: secret-key
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||||
|
value: postgres
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||||
|
value: authentik_db
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__USER
|
||||||
|
value: authentik_user
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-secret
|
||||||
|
key: db-password
|
||||||
|
ports:
|
||||||
|
- containerPort: 9000
|
||||||
|
- containerPort: 9443
|
||||||
|
volumeMounts:
|
||||||
|
- name: media
|
||||||
|
mountPath: /media
|
||||||
|
volumes:
|
||||||
|
- name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: authentik-media-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: authentik-worker
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: authentik-worker
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: authentik-worker
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: authentik-worker
|
||||||
|
image: ghcr.io/goauthentik/server:2026.2.1
|
||||||
|
command: ["ak", "worker"]
|
||||||
|
env:
|
||||||
|
- name: AUTHENTIK_SECRET_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-secret
|
||||||
|
key: secret-key
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__HOST
|
||||||
|
value: postgres
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__NAME
|
||||||
|
value: authentik_db
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__USER
|
||||||
|
value: authentik_user
|
||||||
|
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: authentik-secret
|
||||||
|
key: db-password
|
||||||
|
volumeMounts:
|
||||||
|
- name: media
|
||||||
|
mountPath: /media
|
||||||
|
- name: certs
|
||||||
|
mountPath: /certs
|
||||||
|
volumes:
|
||||||
|
- name: media
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: authentik-media-pvc
|
||||||
|
- name: certs
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: authentik-certs-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: authentik
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: authentik-server
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 9000
|
||||||
|
targetPort: 9000
|
||||||
|
nodePort: 32372
|
||||||
|
- name: https
|
||||||
|
port: 9443
|
||||||
|
targetPort: 9443
|
||||||
|
nodePort: 32373
|
||||||
|
type: NodePort
|
||||||
43
k3s/forgejo/forgejo-db-init.yaml
Normal file
43
k3s/forgejo/forgejo-db-init.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Forgejo DB Init Job
|
||||||
|
# Creates forgejo database and user in PostgreSQL.
|
||||||
|
# Run once before deploying Forgejo.
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic forgejo-secret \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f forgejo-db-init.yaml
|
||||||
|
#
|
||||||
|
# Watch completion:
|
||||||
|
# kubectl get jobs -w
|
||||||
|
# kubectl logs job/forgejo-db-init
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: forgejo-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: forgejo-db-init
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secret
|
||||||
|
key: password
|
||||||
|
- name: FORGEJO_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: forgejo-secret
|
||||||
|
key: db-password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
psql -h postgres -U postgres <<EOF
|
||||||
|
CREATE USER forgejo_user WITH PASSWORD '${FORGEJO_DB_PASSWORD}';
|
||||||
|
CREATE DATABASE forgejo_db OWNER forgejo_user;
|
||||||
|
EOF
|
||||||
86
k3s/forgejo/forgejo.yaml
Normal file
86
k3s/forgejo/forgejo.yaml
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Forgejo — self-hosted Git forge
|
||||||
|
# PostgreSQL backend via cluster DNS: postgres
|
||||||
|
# Unpinned — scheduler places freely, local-path PVC for /data
|
||||||
|
# NodePort 32371
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic forgejo-secret \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f forgejo-db-init.yaml
|
||||||
|
# kubectl get jobs -w # wait for completion
|
||||||
|
# kubectl apply -f forgejo.yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: forgejo-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: forgejo
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: forgejo
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: forgejo
|
||||||
|
image: codeberg.org/forgejo/forgejo:9
|
||||||
|
env:
|
||||||
|
- name: USER_UID
|
||||||
|
value: "1000"
|
||||||
|
- name: USER_GID
|
||||||
|
value: "1000"
|
||||||
|
- name: FORGEJO__database__DB_TYPE
|
||||||
|
value: postgres
|
||||||
|
- name: FORGEJO__database__HOST
|
||||||
|
value: postgres:5432
|
||||||
|
- name: FORGEJO__database__NAME
|
||||||
|
value: forgejo_db
|
||||||
|
- name: FORGEJO__database__USER
|
||||||
|
value: forgejo_user
|
||||||
|
- name: FORGEJO__database__PASSWD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: forgejo-secret
|
||||||
|
key: db-password
|
||||||
|
- name: FORGEJO__server__HTTP_PORT
|
||||||
|
value: "3000"
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
volumeMounts:
|
||||||
|
- name: forgejo-data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: forgejo-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: forgejo-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: forgejo
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: forgejo
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
nodePort: 32371
|
||||||
|
type: NodePort
|
||||||
60
k3s/ghost/ghost-db-init.yaml
Normal file
60
k3s/ghost/ghost-db-init.yaml
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Ghost DB Init Job
|
||||||
|
# Creates Ghost databases and users in MariaDB.
|
||||||
|
# Run once before deploying Ghost instances.
|
||||||
|
# Runs in default namespace — can access all secrets directly.
|
||||||
|
#
|
||||||
|
# Apply with:
|
||||||
|
# kubectl apply -f ghost-db-init.yaml
|
||||||
|
#
|
||||||
|
# Watch completion:
|
||||||
|
# kubectl get jobs -w
|
||||||
|
# kubectl logs job/ghost-db-init
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: ghost-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: ghost-db-init
|
||||||
|
image: mariadb:11
|
||||||
|
env:
|
||||||
|
- name: MARIADB_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mariadb-secret
|
||||||
|
key: root-password
|
||||||
|
- name: GHOST1_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost1-db-password
|
||||||
|
- name: GHOST2_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost2-db-password
|
||||||
|
- name: GHOST3_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost3-db-password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
mariadb -h mariadb -u root -p"${MARIADB_ROOT_PASSWORD}" <<EOF
|
||||||
|
CREATE DATABASE IF NOT EXISTS ghost1_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS ghost2_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE DATABASE IF NOT EXISTS ghost3_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
CREATE USER IF NOT EXISTS 'ghost1_user'@'%' IDENTIFIED BY '${GHOST1_PASSWORD}';
|
||||||
|
CREATE USER IF NOT EXISTS 'ghost2_user'@'%' IDENTIFIED BY '${GHOST2_PASSWORD}';
|
||||||
|
CREATE USER IF NOT EXISTS 'ghost3_user'@'%' IDENTIFIED BY '${GHOST3_PASSWORD}';
|
||||||
|
GRANT ALL ON ghost1_db.* TO 'ghost1_user'@'%';
|
||||||
|
GRANT ALL ON ghost2_db.* TO 'ghost2_user'@'%';
|
||||||
|
GRANT ALL ON ghost3_db.* TO 'ghost3_user'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
26
k3s/ghost/ghost-secrets.yaml
Normal file
26
k3s/ghost/ghost-secrets.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Ghost DB passwords
|
||||||
|
# Replace CHANGEME values before applying.
|
||||||
|
# Generate with: openssl rand -base64 24
|
||||||
|
#
|
||||||
|
# Apply with:
|
||||||
|
# kubectl apply -f ghost-secrets.yaml
|
||||||
|
#
|
||||||
|
# Or create directly without a file:
|
||||||
|
# kubectl create secret generic ghost-secrets \
|
||||||
|
# --namespace ghost \
|
||||||
|
# --from-literal=ghost1-db-password='<password>' \
|
||||||
|
# --from-literal=ghost2-db-password='<password>' \
|
||||||
|
# --from-literal=ghost3-db-password='<password>'
|
||||||
|
#
|
||||||
|
# NOTE: Do not commit this file with real passwords to git.
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: ghost-secrets
|
||||||
|
namespace: ghost
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
ghost1-db-password: CHANGEME
|
||||||
|
ghost2-db-password: CHANGEME
|
||||||
|
ghost3-db-password: CHANGEME
|
||||||
243
k3s/ghost/ghost.yaml
Normal file
243
k3s/ghost/ghost.yaml
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
# Ghost blogs — three instances
|
||||||
|
# Unpinned — scheduler places them anywhere
|
||||||
|
# Storage via local-path (K3s default provisioner)
|
||||||
|
# MariaDB connection via cluster DNS: mariadb
|
||||||
|
# Runs in default namespace
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic ghost-secrets \
|
||||||
|
# --from-literal=ghost1-db-password='<password>' \
|
||||||
|
# --from-literal=ghost2-db-password='<password>' \
|
||||||
|
# --from-literal=ghost3-db-password='<password>'
|
||||||
|
# kubectl apply -f ghost-db-init.yaml
|
||||||
|
# kubectl get jobs -w # wait for completion
|
||||||
|
# kubectl apply -f ghost.yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
# Ghost 1 — blog.the-fulfillment.org — port 2368
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: ghost1-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ghost1
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: ghost1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: ghost1
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ghost1
|
||||||
|
image: ghost:5-alpine
|
||||||
|
env:
|
||||||
|
- name: database__client
|
||||||
|
value: mysql
|
||||||
|
- name: database__connection__host
|
||||||
|
value: mariadb
|
||||||
|
- name: database__connection__port
|
||||||
|
value: "3306"
|
||||||
|
- name: database__connection__user
|
||||||
|
value: ghost1_user
|
||||||
|
- name: database__connection__password
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost1-db-password
|
||||||
|
- name: database__connection__database
|
||||||
|
value: ghost1_db
|
||||||
|
- name: url
|
||||||
|
value: https://blog.the-fulfillment.org
|
||||||
|
ports:
|
||||||
|
- containerPort: 2368
|
||||||
|
volumeMounts:
|
||||||
|
- name: ghost1-storage
|
||||||
|
mountPath: /var/lib/ghost/content
|
||||||
|
volumes:
|
||||||
|
- name: ghost1-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ghost1-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ghost1
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: ghost1
|
||||||
|
ports:
|
||||||
|
- port: 2368
|
||||||
|
targetPort: 2368
|
||||||
|
nodePort: 32368
|
||||||
|
type: NodePort
|
||||||
|
|
||||||
|
---
|
||||||
|
# Ghost 2 — port 2369
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: ghost2-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ghost2
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: ghost2
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: ghost2
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ghost2
|
||||||
|
image: ghost:5-alpine
|
||||||
|
env:
|
||||||
|
- name: database__client
|
||||||
|
value: mysql
|
||||||
|
- name: database__connection__host
|
||||||
|
value: mariadb
|
||||||
|
- name: database__connection__port
|
||||||
|
value: "3306"
|
||||||
|
- name: database__connection__user
|
||||||
|
value: ghost2_user
|
||||||
|
- name: database__connection__password
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost2-db-password
|
||||||
|
- name: database__connection__database
|
||||||
|
value: ghost2_db
|
||||||
|
- name: url
|
||||||
|
value: https://blog.privacy-practice.com
|
||||||
|
- name: server__port
|
||||||
|
value: "2369"
|
||||||
|
ports:
|
||||||
|
- containerPort: 2369
|
||||||
|
volumeMounts:
|
||||||
|
- name: ghost2-storage
|
||||||
|
mountPath: /var/lib/ghost/content
|
||||||
|
volumes:
|
||||||
|
- name: ghost2-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ghost2-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ghost2
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: ghost2
|
||||||
|
ports:
|
||||||
|
- port: 2369
|
||||||
|
targetPort: 2369
|
||||||
|
nodePort: 32369
|
||||||
|
type: NodePort
|
||||||
|
|
||||||
|
---
|
||||||
|
# Ghost 3 — port 2370
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: ghost3-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: ghost3
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: ghost3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: ghost3
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: ghost3
|
||||||
|
image: ghost:5-alpine
|
||||||
|
env:
|
||||||
|
- name: database__client
|
||||||
|
value: mysql
|
||||||
|
- name: database__connection__host
|
||||||
|
value: mariadb
|
||||||
|
- name: database__connection__port
|
||||||
|
value: "3306"
|
||||||
|
- name: database__connection__user
|
||||||
|
value: ghost3_user
|
||||||
|
- name: database__connection__password
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: ghost-secrets
|
||||||
|
key: ghost3-db-password
|
||||||
|
- name: database__connection__database
|
||||||
|
value: ghost3_db
|
||||||
|
- name: url
|
||||||
|
value: https://blog.sjasoft.com
|
||||||
|
- name: server__port
|
||||||
|
value: "2370"
|
||||||
|
ports:
|
||||||
|
- containerPort: 2370
|
||||||
|
volumeMounts:
|
||||||
|
- name: ghost3-storage
|
||||||
|
mountPath: /var/lib/ghost/content
|
||||||
|
volumes:
|
||||||
|
- name: ghost3-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: ghost3-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: ghost3
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: ghost3
|
||||||
|
ports:
|
||||||
|
- port: 2370
|
||||||
|
targetPort: 2370
|
||||||
|
nodePort: 32370
|
||||||
|
type: NodePort
|
||||||
17
k3s/mariadb/mariadb-secret.yaml
Normal file
17
k3s/mariadb/mariadb-secret.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# MariaDB secret
|
||||||
|
# Replace CHANGEME with your actual password before applying.
|
||||||
|
# Generate a good one with: openssl rand -base64 24
|
||||||
|
#
|
||||||
|
# Apply with:
|
||||||
|
# kubectl apply -f mariadb-secret.yaml
|
||||||
|
#
|
||||||
|
# NOTE: Do not commit this file with a real password to git.
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: mariadb-secret
|
||||||
|
namespace: databases
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
root-password: CHANGEME
|
||||||
91
k3s/mariadb/mariadb.yaml
Normal file
91
k3s/mariadb/mariadb.yaml
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# MariaDB 11 — pinned to node: adder-worker
|
||||||
|
# Local PersistentVolume: 25GB on adder's disk
|
||||||
|
# Runs in default namespace
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic mariadb-secret --from-literal=root-password='<password>'
|
||||||
|
# kubectl apply -f mariadb.yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: mariadb-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 25Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
storageClassName: local-storage
|
||||||
|
local:
|
||||||
|
path: /var/lib/k3s-data/mariadb
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- adder-worker
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: mariadb-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 25Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: mariadb
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: mariadb
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: mariadb
|
||||||
|
spec:
|
||||||
|
nodeName: adder-worker
|
||||||
|
containers:
|
||||||
|
- name: mariadb
|
||||||
|
image: mariadb:11
|
||||||
|
env:
|
||||||
|
- name: MYSQL_ROOT_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: mariadb-secret
|
||||||
|
key: root-password
|
||||||
|
ports:
|
||||||
|
- containerPort: 3306
|
||||||
|
volumeMounts:
|
||||||
|
- name: mariadb-storage
|
||||||
|
mountPath: /var/lib/mysql
|
||||||
|
volumes:
|
||||||
|
- name: mariadb-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: mariadb-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: mariadb
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: mariadb
|
||||||
|
ports:
|
||||||
|
- port: 3306
|
||||||
|
targetPort: 3306
|
||||||
|
type: ClusterIP
|
||||||
82
k3s/monerod/monerod.yaml
Normal file
82
k3s/monerod/monerod.yaml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# Monerod — Monero full node (pruned)
|
||||||
|
# NAS-backed PVC for blockchain data — unpinned, free to migrate across nodes
|
||||||
|
# Ban list stored on NAS alongside blockchain data
|
||||||
|
# NodePorts: 32379 (P2P), 32380 (restricted RPC)
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# NAS share /volume1/k3s/monerod must exist on Synology
|
||||||
|
# Copy ban list to NAS: /volume1/k3s/monerod/ban_list.txt
|
||||||
|
# nas-pv.yaml must be applied first
|
||||||
|
# nfs-common installed on all worker VMs
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl apply -f ../storage/nas-pv.yaml # once, if not already applied
|
||||||
|
# kubectl apply -f monerod.yaml -n <ns>
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: monerod-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
storageClassName: nas-nfs
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 200Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: monerod
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: monerod
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: monerod
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: monerod
|
||||||
|
image: ghcr.io/sethforprivacy/simple-monerod:latest
|
||||||
|
args:
|
||||||
|
- --rpc-restricted-bind-ip=0.0.0.0
|
||||||
|
- --rpc-restricted-bind-port=18089
|
||||||
|
- --no-igd
|
||||||
|
- --enable-dns-blocklist
|
||||||
|
- --ban-list=/home/monero/.bitmonero/ban_list.txt
|
||||||
|
- --prune-blockchain
|
||||||
|
ports:
|
||||||
|
- containerPort: 18080
|
||||||
|
- containerPort: 18089
|
||||||
|
volumeMounts:
|
||||||
|
- name: monerod-data
|
||||||
|
mountPath: /home/monero/.bitmonero
|
||||||
|
volumes:
|
||||||
|
- name: monerod-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: monerod-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: monerod
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: monerod
|
||||||
|
ports:
|
||||||
|
- name: p2p
|
||||||
|
port: 18080
|
||||||
|
targetPort: 18080
|
||||||
|
nodePort: 32379
|
||||||
|
- name: rpc
|
||||||
|
port: 18089
|
||||||
|
targetPort: 18089
|
||||||
|
nodePort: 32380
|
||||||
|
type: NodePort
|
||||||
44
k3s/n8n/n8n-db-init.yaml
Normal file
44
k3s/n8n/n8n-db-init.yaml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
# n8n DB Init Job
|
||||||
|
# Creates n8n database and user in PostgreSQL.
|
||||||
|
# Run once before deploying n8n.
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic n8n-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f n8n-db-init.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Watch completion:
|
||||||
|
# kubectl get jobs -n <ns> -w
|
||||||
|
# kubectl logs job/n8n-db-init -n <ns>
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: n8n-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: n8n-db-init
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secret
|
||||||
|
key: password
|
||||||
|
- name: N8N_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: n8n-secret
|
||||||
|
key: db-password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
psql -h postgres -U postgres <<EOF
|
||||||
|
CREATE USER n8n_user WITH PASSWORD '${N8N_DB_PASSWORD}';
|
||||||
|
CREATE DATABASE n8n_db OWNER n8n_user;
|
||||||
|
EOF
|
||||||
83
k3s/n8n/n8n.yaml
Normal file
83
k3s/n8n/n8n.yaml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# n8n — workflow automation
|
||||||
|
# PostgreSQL backend via cluster DNS: postgres
|
||||||
|
# Unpinned — scheduler places freely, local-path PVC
|
||||||
|
# NodePort 32374
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic n8n-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f n8n-db-init.yaml -n <ns>
|
||||||
|
# kubectl get jobs -n <ns> -w # wait for completion
|
||||||
|
# kubectl apply -f n8n.yaml -n <ns>
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: n8n-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: n8n
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: n8n
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: n8n
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: n8n
|
||||||
|
image: n8nio/n8n:latest
|
||||||
|
env:
|
||||||
|
- name: DB_TYPE
|
||||||
|
value: postgresdb
|
||||||
|
- name: DB_POSTGRESDB_HOST
|
||||||
|
value: postgres
|
||||||
|
- name: DB_POSTGRESDB_PORT
|
||||||
|
value: "5432"
|
||||||
|
- name: DB_POSTGRESDB_DATABASE
|
||||||
|
value: n8n_db
|
||||||
|
- name: DB_POSTGRESDB_USER
|
||||||
|
value: n8n_user
|
||||||
|
- name: DB_POSTGRESDB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: n8n-secret
|
||||||
|
key: db-password
|
||||||
|
ports:
|
||||||
|
- containerPort: 5678
|
||||||
|
volumeMounts:
|
||||||
|
- name: n8n-data
|
||||||
|
mountPath: /home/node/.n8n
|
||||||
|
volumes:
|
||||||
|
- name: n8n-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: n8n-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: n8n
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: n8n
|
||||||
|
ports:
|
||||||
|
- port: 5678
|
||||||
|
targetPort: 5678
|
||||||
|
nodePort: 32374
|
||||||
|
type: NodePort
|
||||||
101
k3s/nats/nats.yaml
Normal file
101
k3s/nats/nats.yaml
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# NATS — JetStream-enabled message broker
|
||||||
|
# JetStream enabled with persistent storage via local-path PVC
|
||||||
|
# Unpinned — scheduler places freely
|
||||||
|
# NodePorts: 32376 (client), 32377 (websocket), 32378 (monitoring)
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl apply -f nats.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Internal cluster DNS: nats:4222
|
||||||
|
# WebSocket: nats:8080
|
||||||
|
# Monitoring: nats:8222
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: nats-config
|
||||||
|
data:
|
||||||
|
nats.conf: |
|
||||||
|
jetstream {
|
||||||
|
store_dir: /data
|
||||||
|
}
|
||||||
|
|
||||||
|
http_port: 8222
|
||||||
|
|
||||||
|
websocket {
|
||||||
|
port: 8080
|
||||||
|
no_tls: true
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: nats-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nats
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nats
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nats
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nats
|
||||||
|
image: nats:latest
|
||||||
|
command: ["-c", "/etc/nats/nats.conf"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 4222
|
||||||
|
- containerPort: 8080
|
||||||
|
- containerPort: 8222
|
||||||
|
volumeMounts:
|
||||||
|
- name: nats-config
|
||||||
|
mountPath: /etc/nats
|
||||||
|
- name: nats-data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: nats-config
|
||||||
|
configMap:
|
||||||
|
name: nats-config
|
||||||
|
- name: nats-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: nats-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nats
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: nats
|
||||||
|
ports:
|
||||||
|
- name: client
|
||||||
|
port: 4222
|
||||||
|
targetPort: 4222
|
||||||
|
nodePort: 32376
|
||||||
|
- name: websocket
|
||||||
|
port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
nodePort: 32377
|
||||||
|
- name: monitoring
|
||||||
|
port: 8222
|
||||||
|
targetPort: 8222
|
||||||
|
nodePort: 32378
|
||||||
|
type: NodePort
|
||||||
17
k3s/postgres/postgres-secret.yaml
Normal file
17
k3s/postgres/postgres-secret.yaml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# PostgreSQL secret
|
||||||
|
# Replace CHANGEME with your actual password before applying.
|
||||||
|
# Generate a good one with: openssl rand -base64 24
|
||||||
|
#
|
||||||
|
# Apply with:
|
||||||
|
# kubectl apply -f postgres-secret.yaml
|
||||||
|
#
|
||||||
|
# NOTE: Do not commit this file with a real password to git.
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: postgres-secret
|
||||||
|
namespace: databases
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
password: CHANGEME
|
||||||
93
k3s/postgres/postgres.yaml
Normal file
93
k3s/postgres/postgres.yaml
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# PostgreSQL 16 — pinned to node: pve-worker
|
||||||
|
# Local PersistentVolume: 50GB on pve's disk
|
||||||
|
# Runs in default namespace
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic postgres-secret --from-literal=password='<password>'
|
||||||
|
# kubectl apply -f postgres.yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: postgres-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 50Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
storageClassName: local-storage
|
||||||
|
local:
|
||||||
|
path: /var/lib/k3s-data/postgres
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- pve-worker
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: postgres-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-storage
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: postgres
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: postgres
|
||||||
|
spec:
|
||||||
|
nodeName: pve-worker
|
||||||
|
containers:
|
||||||
|
- name: postgres
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
- name: POSTGRES_USER
|
||||||
|
value: postgres
|
||||||
|
- name: POSTGRES_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secret
|
||||||
|
key: password
|
||||||
|
ports:
|
||||||
|
- containerPort: 5432
|
||||||
|
volumeMounts:
|
||||||
|
- name: postgres-storage
|
||||||
|
mountPath: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- name: postgres-storage
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: postgres-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: postgres
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: postgres
|
||||||
|
ports:
|
||||||
|
- port: 5432
|
||||||
|
targetPort: 5432
|
||||||
|
type: ClusterIP
|
||||||
121
k3s/snikket/snikket.yaml
Normal file
121
k3s/snikket/snikket.yaml
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Snikket — XMPP server (Prosody-based)
|
||||||
|
# Unpinned — scheduler places freely, local-path PVC
|
||||||
|
# TLS terminated externally by Caddy at venture ingress VPS
|
||||||
|
# NodePorts: 32381 (web/admin), 32382 (XMPP client), 32383 (XMPP federation), 32384 (file transfer proxy)
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl apply -f snikket.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Caddy must proxy port 80 traffic (invites/admin portal) via NodePort 32381
|
||||||
|
# XMPP client connections on 32382 must be reachable directly (not HTTP — raw TCP)
|
||||||
|
# XMPP federation on 32383 must be reachable directly (raw TCP)
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: snikket-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: snikket-web
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: snikket-web
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: snikket-web
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: snikket-web
|
||||||
|
image: snikket/snikket-server:latest
|
||||||
|
command: ["web"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
volumeMounts:
|
||||||
|
- name: snikket-data
|
||||||
|
mountPath: /snikket
|
||||||
|
volumes:
|
||||||
|
- name: snikket-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: snikket-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: snikket-server
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: snikket-server
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: snikket-server
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: snikket-server
|
||||||
|
image: snikket/snikket-server:latest
|
||||||
|
command: ["server"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 5222
|
||||||
|
- containerPort: 5269
|
||||||
|
- containerPort: 5000
|
||||||
|
volumeMounts:
|
||||||
|
- name: snikket-data
|
||||||
|
mountPath: /snikket
|
||||||
|
volumes:
|
||||||
|
- name: snikket-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: snikket-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: snikket-web
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: snikket-web
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 32381
|
||||||
|
type: NodePort
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: snikket
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: snikket-server
|
||||||
|
ports:
|
||||||
|
- name: xmpp-client
|
||||||
|
port: 5222
|
||||||
|
targetPort: 5222
|
||||||
|
nodePort: 32382
|
||||||
|
- name: xmpp-federation
|
||||||
|
port: 5269
|
||||||
|
targetPort: 5269
|
||||||
|
nodePort: 32383
|
||||||
|
- name: file-transfer
|
||||||
|
port: 5000
|
||||||
|
targetPort: 5000
|
||||||
|
nodePort: 32384
|
||||||
|
type: NodePort
|
||||||
47
k3s/storage/nas-pv.yaml
Normal file
47
k3s/storage/nas-pv.yaml
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# NAS PersistentVolume — Synology 425+ at 192.168.40.96
|
||||||
|
# NFS share mounted cluster-wide — any pod can claim storage from it via PVC
|
||||||
|
# ReadWriteMany — multiple pods on different nodes can mount simultaneously
|
||||||
|
#
|
||||||
|
# Prerequisites on every K3s worker VM:
|
||||||
|
# apt install nfs-common
|
||||||
|
#
|
||||||
|
# Deploy (once, cluster-scoped — no namespace):
|
||||||
|
# kubectl apply -f nas-pv.yaml
|
||||||
|
#
|
||||||
|
# Then any service can claim NAS storage with a PVC like:
|
||||||
|
# storageClassName: nas-nfs
|
||||||
|
# accessModes: [ReadWriteMany]
|
||||||
|
#
|
||||||
|
# Replace /volume1/k3s with your actual NAS share path.
|
||||||
|
# Create subdirectories on the NAS per service to keep data organised:
|
||||||
|
# /volume1/k3s/monerod
|
||||||
|
# /volume1/k3s/vaultwarden
|
||||||
|
# etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: nas-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 40Ti
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
storageClassName: nas-nfs
|
||||||
|
mountOptions:
|
||||||
|
- hard
|
||||||
|
- nfsvers=4.1
|
||||||
|
nfs:
|
||||||
|
server: 192.168.40.96
|
||||||
|
path: /volume1/k3s
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: nas-nfs
|
||||||
|
provisioner: kubernetes.io/no-provisioner
|
||||||
|
volumeBindingMode: Immediate
|
||||||
|
reclaimPolicy: Retain
|
||||||
50
k3s/synapse/synapse-db-init.yaml
Normal file
50
k3s/synapse/synapse-db-init.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Synapse DB Init Job
|
||||||
|
# Creates synapse database and user in PostgreSQL.
|
||||||
|
# Synapse requires UTF-8 encoding and C locale — standard CREATE DATABASE won't work.
|
||||||
|
# Run once before deploying Synapse.
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic synapse-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f synapse-db-init.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Watch completion:
|
||||||
|
# kubectl get jobs -n <ns> -w
|
||||||
|
# kubectl logs job/synapse-db-init -n <ns>
|
||||||
|
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: synapse-db-init
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
containers:
|
||||||
|
- name: synapse-db-init
|
||||||
|
image: postgres:16
|
||||||
|
env:
|
||||||
|
- name: PGPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: postgres-secret
|
||||||
|
key: password
|
||||||
|
- name: SYNAPSE_DB_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: synapse-secret
|
||||||
|
key: db-password
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
psql -h postgres -U postgres <<EOF
|
||||||
|
CREATE USER synapse_user WITH PASSWORD '${SYNAPSE_DB_PASSWORD}';
|
||||||
|
CREATE DATABASE synapse_db
|
||||||
|
ENCODING 'UTF8'
|
||||||
|
LC_COLLATE='C'
|
||||||
|
LC_CTYPE='C'
|
||||||
|
template=template0
|
||||||
|
OWNER synapse_user;
|
||||||
|
EOF
|
||||||
91
k3s/synapse/synapse.yaml
Normal file
91
k3s/synapse/synapse.yaml
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Synapse — Matrix homeserver
|
||||||
|
# PostgreSQL backend via cluster DNS: postgres
|
||||||
|
# Unpinned — scheduler places freely, local-path PVC
|
||||||
|
# NodePort 32385
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic synapse-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=db-password='<password>'
|
||||||
|
# kubectl apply -f synapse-db-init.yaml -n <ns>
|
||||||
|
# kubectl get jobs -n <ns> -w # wait for completion
|
||||||
|
# kubectl apply -f synapse.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# First boot generates /data/homeserver.yaml automatically.
|
||||||
|
# After first boot, exec into the pod and update homeserver.yaml
|
||||||
|
# to add the PostgreSQL database config (replaces default SQLite):
|
||||||
|
#
|
||||||
|
# database:
|
||||||
|
# name: psycopg2
|
||||||
|
# args:
|
||||||
|
# user: synapse_user
|
||||||
|
# password: <from synapse-secret>
|
||||||
|
# database: synapse_db
|
||||||
|
# host: postgres
|
||||||
|
# cp_min: 5
|
||||||
|
# cp_max: 10
|
||||||
|
#
|
||||||
|
# Then restart the deployment:
|
||||||
|
# kubectl rollout restart deployment/synapse -n <ns>
|
||||||
|
#
|
||||||
|
# Set SYNAPSE_SERVER_NAME to the actual Matrix domain before deploying.
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: synapse-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: synapse
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: synapse
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: synapse
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: synapse
|
||||||
|
image: matrixdotorg/synapse:latest
|
||||||
|
env:
|
||||||
|
- name: SYNAPSE_SERVER_NAME
|
||||||
|
value: "matrix.sjasoft.com"
|
||||||
|
- name: SYNAPSE_REPORT_STATS
|
||||||
|
value: "no"
|
||||||
|
ports:
|
||||||
|
- containerPort: 8008
|
||||||
|
volumeMounts:
|
||||||
|
- name: synapse-data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: synapse-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: synapse-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: synapse
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: synapse
|
||||||
|
ports:
|
||||||
|
- port: 8008
|
||||||
|
targetPort: 8008
|
||||||
|
nodePort: 32385
|
||||||
|
type: NodePort
|
||||||
83
k3s/vaultwarden/vaultwarden.yaml
Normal file
83
k3s/vaultwarden/vaultwarden.yaml
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# Vaultwarden — self-hosted Bitwarden-compatible password manager
|
||||||
|
# SQLite backend — data persisted in local-path PVC
|
||||||
|
# Unpinned — scheduler places freely
|
||||||
|
# NodePort 32375
|
||||||
|
# Signups disabled — use admin panel to invite users
|
||||||
|
#
|
||||||
|
# Deploy:
|
||||||
|
# kubectl create secret generic vaultwarden-secret \
|
||||||
|
# --namespace <ns> \
|
||||||
|
# --from-literal=admin-token='<token>'
|
||||||
|
# kubectl apply -f vaultwarden.yaml -n <ns>
|
||||||
|
#
|
||||||
|
# Generate admin token with: openssl rand -base64 48
|
||||||
|
# Admin panel: http://<any-node-mesh-ip>:32375/admin
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: vaultwarden-pvc
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: local-path
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: vaultwarden
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: vaultwarden
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: vaultwarden
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: vaultwarden
|
||||||
|
image: vaultwarden/server:latest
|
||||||
|
env:
|
||||||
|
- name: SIGNUPS_ALLOWED
|
||||||
|
value: "false"
|
||||||
|
- name: INVITATIONS_ALLOWED
|
||||||
|
value: "true"
|
||||||
|
- name: SHOW_PASSWORD_HINT
|
||||||
|
value: "false"
|
||||||
|
- name: ROCKET_PORT
|
||||||
|
value: "8222"
|
||||||
|
- name: ADMIN_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: vaultwarden-secret
|
||||||
|
key: admin-token
|
||||||
|
ports:
|
||||||
|
- containerPort: 8222
|
||||||
|
volumeMounts:
|
||||||
|
- name: vaultwarden-data
|
||||||
|
mountPath: /data
|
||||||
|
volumes:
|
||||||
|
- name: vaultwarden-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: vaultwarden-pvc
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: vaultwarden
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: vaultwarden
|
||||||
|
ports:
|
||||||
|
- port: 8222
|
||||||
|
targetPort: 8222
|
||||||
|
nodePort: 32375
|
||||||
|
type: NodePort
|
||||||
47
proxmox/adder/clone-vm.sh
Normal file
47
proxmox/adder/clone-vm.sh
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# clone-vm.sh
|
||||||
|
# Clones a Proxmox VM template into a new full VM.
|
||||||
|
# Run this on the same node where the template lives (local storage).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./clone-vm.sh <TEMPLATE_VMID> <NEW_VMID> <NAME> [CORES] [MEMORY_MB] [DISK_SIZE]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./clone-vm.sh 100 101 k3s-control-1
|
||||||
|
# ./clone-vm.sh 100 202 k3s-worker-1 6 8192 100G
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# CORES = 2
|
||||||
|
# MEMORY_MB = 2048
|
||||||
|
# DISK_SIZE = 20G
|
||||||
|
#
|
||||||
|
# Note: Template must exist on the local node (local-lvm).
|
||||||
|
# Run create-debian-template.sh on each node first.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TEMPLATE_VMID="${1:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <NAME> [CORES] [MEMORY_MB] [DISK_SIZE]}"
|
||||||
|
NEW_VMID="${2:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <NAME> [CORES] [MEMORY_MB] [DISK_SIZE]}"
|
||||||
|
NAME="${3:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <NAME> [CORES] [MEMORY_MB] [DISK_SIZE]}"
|
||||||
|
CORES="${4:-2}"
|
||||||
|
MEMORY="${5:-2048}"
|
||||||
|
DISK_SIZE="${6:-20G}"
|
||||||
|
|
||||||
|
echo "==> Cloning template ${TEMPLATE_VMID} to ${NEW_VMID} (${NAME})..."
|
||||||
|
qm clone "${TEMPLATE_VMID}" "${NEW_VMID}" --name "${NAME}" --full
|
||||||
|
|
||||||
|
echo "==> Setting resources: ${CORES} cores, ${MEMORY}MB RAM..."
|
||||||
|
qm set "${NEW_VMID}" --cores "${CORES}" --memory "${MEMORY}"
|
||||||
|
|
||||||
|
echo "==> Resizing disk to ${DISK_SIZE}..."
|
||||||
|
qm resize "${NEW_VMID}" scsi0 "${DISK_SIZE}"
|
||||||
|
|
||||||
|
echo "==> Setting hostname via Cloud-Init..."
|
||||||
|
qm set "${NEW_VMID}" --ciuser samantha --ipconfig0 ip=dhcp
|
||||||
|
|
||||||
|
echo "==> Starting VM..."
|
||||||
|
qm start "${NEW_VMID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. VM ${NEW_VMID} (${NAME}) started."
|
||||||
|
echo "Find its IP: qm guest cmd ${NEW_VMID} network-get-interfaces"
|
||||||
80
proxmox/adder/create-debian-template.sh
Normal file
80
proxmox/adder/create-debian-template.sh
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# create-debian-template.sh
|
||||||
|
# Creates a Debian Trixie Cloud-Init VM template on Proxmox.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./create-debian-template.sh <VMID> <n> [STORAGE] [BRIDGE]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./create-debian-template.sh 9000 debian-trixie-template
|
||||||
|
# ./create-debian-template.sh 9000 debian-trixie-template local-lvm vmbr1
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# STORAGE = local-lvm
|
||||||
|
# BRIDGE = vmbr1
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VMID="${1:?Usage: $0 <VMID> <n> [STORAGE] [BRIDGE]}"
|
||||||
|
NAME="${2:?Usage: $0 <VMID> <n> [STORAGE] [BRIDGE]}"
|
||||||
|
STORAGE="${3:-local-lvm}"
|
||||||
|
BRIDGE="${4:-vmbr1}"
|
||||||
|
|
||||||
|
IMAGE="debian-13-genericcloud-amd64.qcow2"
|
||||||
|
IMAGE_URL="https://cloud.debian.org/images/cloud/trixie/latest/${IMAGE}"
|
||||||
|
TMPDIR="/tmp"
|
||||||
|
|
||||||
|
echo "==> Downloading Debian Trixie cloud image..."
|
||||||
|
wget -q --show-progress -O "${TMPDIR}/${IMAGE}" "${IMAGE_URL}"
|
||||||
|
|
||||||
|
echo "==> Installing libguestfs-tools if needed..."
|
||||||
|
apt-get install -y libguestfs-tools > /dev/null
|
||||||
|
|
||||||
|
echo "==> Customizing image (installing base tools + Tailscale repo)..."
|
||||||
|
virt-customize -a "${TMPDIR}/${IMAGE}" \
|
||||||
|
--install qemu-guest-agent,curl,wget,nano,rsync,htop,tmux,emacs-nox,nfs-common \
|
||||||
|
--run-command 'curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg -o /usr/share/keyrings/tailscale-archive-keyring.gpg' \
|
||||||
|
--run-command 'echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/debian trixie main" > /etc/apt/sources.list.d/tailscale.list' \
|
||||||
|
--run-command 'apt-get update -qq' \
|
||||||
|
--install tailscale \
|
||||||
|
--run-command 'mkdir -p /home/samantha/.ssh && echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJq4fOOrPQKYAq5olwKWAXKGO5zv/rujveyTORxMrDp root@pve" > /home/samantha/.ssh/authorized_keys && chmod 700 /home/samantha/.ssh && chmod 600 /home/samantha/.ssh/authorized_keys' \
|
||||||
|
--run-command 'chown -R samantha:samantha /home/samantha' \
|
||||||
|
--run-command 'truncate -s 0 /etc/machine-id' \
|
||||||
|
--run-command 'rm -f /etc/ssh/ssh_host_*' \
|
||||||
|
--quiet
|
||||||
|
|
||||||
|
echo "==> Creating VM ${VMID} (${NAME})..."
|
||||||
|
qm create "${VMID}" \
|
||||||
|
--name "${NAME}" \
|
||||||
|
--memory 2048 \
|
||||||
|
--cores 2 \
|
||||||
|
--net0 "virtio,bridge=${BRIDGE}" \
|
||||||
|
--ostype l26
|
||||||
|
|
||||||
|
echo "==> Importing disk to ${STORAGE}..."
|
||||||
|
qm importdisk "${VMID}" "${TMPDIR}/${IMAGE}" "${STORAGE}"
|
||||||
|
|
||||||
|
echo "==> Configuring VM..."
|
||||||
|
qm set "${VMID}" --scsihw virtio-scsi-pci --scsi0 "${STORAGE}:vm-${VMID}-disk-0"
|
||||||
|
qm set "${VMID}" --boot c --bootdisk scsi0
|
||||||
|
qm set "${VMID}" --ide2 "${STORAGE}:cloudinit"
|
||||||
|
qm set "${VMID}" --vga std
|
||||||
|
qm set "${VMID}" --agent enabled=1
|
||||||
|
PUBKEY_FILE=$(mktemp)
|
||||||
|
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJq4fOOrPQKYAq5olwKWAXKGO5zv/rujveyTORxMrDp root@pve" > "${PUBKEY_FILE}"
|
||||||
|
qm set "${VMID}" --ciuser samantha --cipassword "changeme" --sshkeys "${PUBKEY_FILE}"
|
||||||
|
rm -f "${PUBKEY_FILE}"
|
||||||
|
qm set "${VMID}" --ipconfig0 ip=dhcp
|
||||||
|
|
||||||
|
echo "==> Converting to template..."
|
||||||
|
qm template "${VMID}"
|
||||||
|
|
||||||
|
echo "==> Cleaning up..."
|
||||||
|
rm -f "${TMPDIR}/${IMAGE}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. Template ${VMID} (${NAME}) created."
|
||||||
|
echo "Clone with: ./clone-vm.sh ${VMID} <NEW_VMID> <NEW_NAME> [CORES] [MEMORY_MB] [DISK_SIZE]"
|
||||||
|
echo ""
|
||||||
|
echo "After cloning, join the Headscale mesh:"
|
||||||
|
echo " tailscale up --login-server <headscale_server> --authkey mkey:..."
|
||||||
50
proxmox/scripts/clone-vm.sh
Normal file
50
proxmox/scripts/clone-vm.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# clone-vm.sh
|
||||||
|
# Clones a Proxmox VM template into a new full VM.
|
||||||
|
# Run this on the same node where the template lives (local storage).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./clone-vm.sh <TEMPLATE_VMID> <NEW_VMID> <n> [CORES] [MEMORY_MB] [DISK_SIZE] [STORAGE]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./clone-vm.sh 100 101 k3s-control-1
|
||||||
|
# ./clone-vm.sh 100 202 k3s-worker-1 6 8192 100G
|
||||||
|
# ./clone-vm.sh 300 303 game-worker-ssd 10 61440 200G game-ssd
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# CORES = 2
|
||||||
|
# MEMORY_MB = 2048
|
||||||
|
# DISK_SIZE = 20G
|
||||||
|
# STORAGE = local-lvm
|
||||||
|
#
|
||||||
|
# Note: Template must exist on the local node.
|
||||||
|
# Run create-debian-template.sh on each node first.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TEMPLATE_VMID="${1:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <n> [CORES] [MEMORY_MB] [DISK_SIZE] [STORAGE]}"
|
||||||
|
NEW_VMID="${2:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <n> [CORES] [MEMORY_MB] [DISK_SIZE] [STORAGE]}"
|
||||||
|
NAME="${3:?Usage: $0 <TEMPLATE_VMID> <NEW_VMID> <n> [CORES] [MEMORY_MB] [DISK_SIZE] [STORAGE]}"
|
||||||
|
CORES="${4:-2}"
|
||||||
|
MEMORY="${5:-2048}"
|
||||||
|
DISK_SIZE="${6:-20G}"
|
||||||
|
STORAGE="${7:-local-lvm}"
|
||||||
|
|
||||||
|
echo "==> Cloning template ${TEMPLATE_VMID} to ${NEW_VMID} (${NAME})..."
|
||||||
|
qm clone "${TEMPLATE_VMID}" "${NEW_VMID}" --name "${NAME}" --full
|
||||||
|
|
||||||
|
echo "==> Setting resources: ${CORES} cores, ${MEMORY}MB RAM..."
|
||||||
|
qm set "${NEW_VMID}" --cores "${CORES}" --memory "${MEMORY}"
|
||||||
|
|
||||||
|
echo "==> Resizing disk to ${DISK_SIZE} on ${STORAGE}..."
|
||||||
|
qm resize "${NEW_VMID}" scsi0 "${DISK_SIZE}"
|
||||||
|
|
||||||
|
echo "==> Setting hostname via Cloud-Init..."
|
||||||
|
qm set "${NEW_VMID}" --ciuser samantha --ipconfig0 ip=dhcp
|
||||||
|
|
||||||
|
echo "==> Starting VM..."
|
||||||
|
qm start "${NEW_VMID}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. VM ${NEW_VMID} (${NAME}) started."
|
||||||
|
echo "Find its IP: qm guest cmd ${NEW_VMID} network-get-interfaces"
|
||||||
78
proxmox/scripts/create-debian-template.sh
Normal file
78
proxmox/scripts/create-debian-template.sh
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# create-debian-template.sh
|
||||||
|
# Creates a Debian Trixie Cloud-Init VM template on Proxmox.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./create-debian-template.sh <VMID> <n> [STORAGE] [BRIDGE]
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# ./create-debian-template.sh 9000 debian-trixie-template
|
||||||
|
# ./create-debian-template.sh 9000 debian-trixie-template local-lvm vmbr1
|
||||||
|
#
|
||||||
|
# Defaults:
|
||||||
|
# STORAGE = local-lvm
|
||||||
|
# BRIDGE = vmbr1
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VMID="${1:?Usage: $0 <VMID> <n> [STORAGE] [BRIDGE]}"
|
||||||
|
NAME="${2:?Usage: $0 <VMID> <n> [STORAGE] [BRIDGE]}"
|
||||||
|
STORAGE="${3:-local-lvm}"
|
||||||
|
BRIDGE="${4:-vmbr1}"
|
||||||
|
|
||||||
|
IMAGE="debian-13-genericcloud-amd64.qcow2"
|
||||||
|
IMAGE_URL="https://cloud.debian.org/images/cloud/trixie/latest/${IMAGE}"
|
||||||
|
TMPDIR="/tmp"
|
||||||
|
|
||||||
|
echo "==> Downloading Debian Trixie cloud image..."
|
||||||
|
wget -q --show-progress -O "${TMPDIR}/${IMAGE}" "${IMAGE_URL}"
|
||||||
|
|
||||||
|
echo "==> Installing libguestfs-tools if needed..."
|
||||||
|
apt-get install -y libguestfs-tools > /dev/null
|
||||||
|
|
||||||
|
echo "==> Customizing image (installing base tools + Tailscale repo)..."
|
||||||
|
virt-customize -a "${TMPDIR}/${IMAGE}" \
|
||||||
|
--install qemu-guest-agent,curl,wget,nano,rsync,htop,tmux,emacs-nox,nfs-common \
|
||||||
|
--run-command 'curl -fsSL https://pkgs.tailscale.com/stable/debian/trixie.noarmor.gpg -o /usr/share/keyrings/tailscale-archive-keyring.gpg' \
|
||||||
|
--run-command 'echo "deb [signed-by=/usr/share/keyrings/tailscale-archive-keyring.gpg] https://pkgs.tailscale.com/stable/debian trixie main" > /etc/apt/sources.list.d/tailscale.list' \
|
||||||
|
--run-command 'apt-get update -qq' \
|
||||||
|
--install tailscale \
|
||||||
|
--run-command 'truncate -s 0 /etc/machine-id' \
|
||||||
|
--run-command 'rm -f /etc/ssh/ssh_host_*' \
|
||||||
|
--quiet
|
||||||
|
|
||||||
|
echo "==> Creating VM ${VMID} (${NAME})..."
|
||||||
|
qm create "${VMID}" \
|
||||||
|
--name "${NAME}" \
|
||||||
|
--memory 2048 \
|
||||||
|
--cores 2 \
|
||||||
|
--net0 "virtio,bridge=${BRIDGE}" \
|
||||||
|
--ostype l26
|
||||||
|
|
||||||
|
echo "==> Importing disk to ${STORAGE}..."
|
||||||
|
qm importdisk "${VMID}" "${TMPDIR}/${IMAGE}" "${STORAGE}"
|
||||||
|
|
||||||
|
echo "==> Configuring VM..."
|
||||||
|
qm set "${VMID}" --scsihw virtio-scsi-pci --scsi0 "${STORAGE}:vm-${VMID}-disk-0"
|
||||||
|
qm set "${VMID}" --boot c --bootdisk scsi0
|
||||||
|
qm set "${VMID}" --ide2 "${STORAGE}:cloudinit"
|
||||||
|
qm set "${VMID}" --vga std
|
||||||
|
qm set "${VMID}" --agent enabled=1
|
||||||
|
PUBKEY_FILE=$(mktemp)
|
||||||
|
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJq4fOOrPQKYAq5olwKWAXKGO5zv/rujveyTORxMrDp root@pve" > "${PUBKEY_FILE}"
|
||||||
|
qm set "${VMID}" --ciuser samantha --cipassword "changeme" --sshkeys "${PUBKEY_FILE}"
|
||||||
|
rm -f "${PUBKEY_FILE}"
|
||||||
|
qm set "${VMID}" --ipconfig0 ip=dhcp
|
||||||
|
|
||||||
|
echo "==> Converting to template..."
|
||||||
|
qm template "${VMID}"
|
||||||
|
|
||||||
|
echo "==> Cleaning up..."
|
||||||
|
rm -f "${TMPDIR}/${IMAGE}"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done. Template ${VMID} (${NAME}) created."
|
||||||
|
echo "Clone with: ./clone-vm.sh ${VMID} <NEW_VMID> <NEW_NAME> [CORES] [MEMORY_MB] [DISK_SIZE]"
|
||||||
|
echo ""
|
||||||
|
echo "After cloning, join the Headscale mesh:"
|
||||||
|
echo " tailscale up --login-server <headscale_server> --authkey mkey:..."
|
||||||
Loading…
Reference in a new issue