# luki-net / proxmox-scripts Community-script-style installers for LXC services in the luki-net Proxmox VE homelab. Each script creates an unprivileged Debian 12 LXC and installs one specific app — sensible defaults, interactive prompts, env-overridable for non-interactive runs. Inspired by [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE), but minimal, self-hosted and tailored to this stack. ## Script catalog | App | What it provisions | Status | |-----|--------------------|--------| | [devpi](ct/devpi.sh) | Private PyPI cache/mirror — saves time on CUDA/torch rebuilds | ✅ stable | | [webapp](ct/webapp.sh) | Next.js site with deploy-as-code via a repo-scoped Gitea Actions runner (host mode, no inbound port) | ✅ stable | | [nexus](ct/nexus.sh) | App LXC for [nexus](https://gitea.luki-net.org/l.kirchner/nexus-hub) (Family Knowledge Hub): host-mode runner (label `nexus`, CI + deploy), service skeleton, `/opt/nexus` layout. Runtime is provisioned/extended via [`install/nexus-runtime.sh`](install/nexus-runtime.sh) (idempotent, re-runnable) | ✅ in production | | [nexus-db](ct/nexus-db.sh) | PostgreSQL 16 + pgvector for nexus — least-privilege role, pg_hba allowlist (only the nexus LXC), DSN handed over via credentials file | ✅ in production | | [authentik](ct/authentik.sh) | Central homelab IdP (official Docker Compose via LXC nesting) — headless bootstrap admin **and** API token for agent-driven blueprint configuration, blueprints mount, permanent auth domain (WebAuthn RP-ID) | ✅ in production | | [runner](ct/runner.sh) | General **instance-wide** Gitea Actions runner (label `homelab`) — Docker for throwaway CI test containers, deliberately **no** sudoers/deploy rights | ✅ in production | Run any one-liner on a Proxmox VE host as root: ```bash bash -c "$(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/ct/.sh)" ``` ## Usage Interactive prompts cover CTID, hostname, disk, RAM, CPU, bridge/VLAN, storage, IP/gateway/DNS — plus app-specific values. Defaults are sane. Non-interactive override via env vars: ```bash CTID=200 HOSTNAME=devpi DISK_SIZE=30 RAM=4096 CORES=4 IPCFG=dhcp \ bash -c "$(curl -fsSL https://gitea.luki-net.org/luki-net/proxmox-scripts/raw/branch/main/ct/devpi.sh)" ``` All defaults (`DEFAULT_HOSTNAME`, `DEFAULT_DISK`, …) and app config values are settable per call via env vars. Every input is validated (digits-only for CTID/disk/cores/RAM/VLAN, IP/CIDR/gateway format, hostname/storage charsets). Invalid interactive input re-prompts — including pasted values with invisible control/non-ASCII bytes, which are rejected rather than silently stripped. Env-provided values are validated too and abort the run when malformed (no re-prompt loop in non-interactive use). ## The pattern Two files per app, both sourcing the shared libs via `curl`: - **`ct/.sh`** runs on the PVE host: prompts → unprivileged LXC → pushes a config env file into the container → bootstraps the installer. Apps that need Docker (authentik, runner) enable `nesting+keyctl` automatically. The standard prompts include an **SSH root login choice** (`SSH_ROOT_LOGIN`, default yes for homelab convenience; `no` keeps the Debian key-only default) — applied inside the container as an sshd drop-in by `configure_ssh_root_login`. - **`install/-install.sh`** runs inside the LXC: packages, unprivileged app user, secrets generated on-host (never printed), systemd units, a `/root/.credentials` notes file — then shreds the bootstrap env. Idempotent where it matters: re-runs skip what exists. `setup_base_apt` also fixes the bare-template **locale situation**: `C.UTF-8` is exported up front (glibc built-in, covers the first apt run without perl warnings), then `en_US.UTF-8` is generated and set as the system default. Shared libs: [`lib/build.func`](lib/build.func) (host-side: prompts, LXC create, bootstrap) and [`lib/install.func`](lib/install.func) (in-container: apt, users, systemd, http-wait). ## Security conventions - Unprivileged LXCs only; app processes run as dedicated system users. - Least privilege everywhere: narrow sudoers (exact-match commands — sudoers compares **verbatim incl. arguments**), DB allowlists, LAN-only binds for inference ports. - Secrets are generated on the target host and live in `0600` files — never in the repo, the wiki or chat logs. - **Runner separation:** the nexus runner is repo-scoped and lives on the production LXC *because* it holds deploy rights; the general `runner` LXC is instance-wide *because* it holds none. Don't mix these scopes. ## Contributing **All changes go through a pull request with cross-review** (Claude Code ↔ Codex, or a human) — no direct pushes to `main`. This rule exists because of two real incidents: a pasted VLAN tag carrying an invisible non-UTF-8 byte broke `pct create` mid-run, and the "missing `source build.func`" bug shipped twice — caught in review on the nexus-db PR, but reaching production via an un-reviewed authentik commit (see wiki → Lessons). CI (`.gitea/workflows/ci.yml`, instance-wide `homelab` runner from [ct/runner.sh](ct/runner.sh)) enforces on every PR: `bash -n` over all scripts, the "every `ct/*.sh` sources `build.func`" check ([tests/check_ct_source.sh](tests/check_ct_source.sh)) and the validation unit tests ([tests/test_validation.sh](tests/test_validation.sh)); shellcheck runs when available on the runner. Build new app prompts on `prompt_validated`/`require_valid` from `lib/build.func` instead of bare `read`. How to add a script: [docs/adding-a-script.md](docs/adding-a-script.md). ## Repo layout ``` . ├── ct/ # Host-side scripts, one per app ├── install/ # In-container installers (+ nexus-runtime.sh re-provisioner) ├── lib/ │ ├── build.func # Shared host-side helpers (prompts, LXC create, bootstrap) │ └── install.func # Shared in-container helpers (apt, systemd, users, http-wait) ├── tests/ │ ├── test_validation.sh # Unit tests for the input validation helpers │ └── check_ct_source.sh # Every ct/*.sh must source build.func ├── docs/ │ └── adding-a-script.md ├── README.md └── LICENSE ``` ## Related - [nexus-hub](https://gitea.luki-net.org/l.kirchner/nexus-hub) — Family Knowledge Hub (main consumer of nexus/nexus-db/authentik/runner) - [Wiki](https://gitea.luki-net.org/luki-net/proxmox-scripts/wiki) — per-app runbook pointers, conventions, lessons learned ## License MIT