Skip to content

feat(dist): Proxmox LXC template with self-managing first server #17

@pachev

Description

@pachev

Summary

Publish Mast as a Proxmox LXC template so a user can launch it on their homelab with a one-liner. The LXC bootstraps itself as Mast's first managed server + release on first boot, so a fresh install already has something live on the dashboard.

Why this shape

  • Proxmox is the primary deployment target for the maintainer's homelab.
  • The community-scripts ecosystem (post-tteck) is where Proxmox homelabbers actually look for new apps; standing alongside that catalog is the path of least friction.
  • Mast manages Linux boxes running Elixir releases. The LXC is one. Self-managing it on first boot doubles as a smoke test and a built-in demo.

Build approach

Tool: `distrobuilder` (LXC's own image builder, YAML def).

  • Produces a `rootfs.tar.zst` from a Debian base + provisioning steps.
  • Packer's Proxmox plugin targets VMs, not LXC. Skip it.
  • Alternative for later: `nixos-generators -f proxmox-lxc` if/when the Nix track matures (see companion issue).

Provisioning steps inside the image build:

  1. `apt install postgresql postgresql-contrib openssl ca-certificates curl`
  2. Drop the Mast release tarball at `/opt/mast/`
  3. Write systemd units (see below)
  4. Create `mast` system user, narrow sudoers entry for `/usr/bin/apt` only
  5. Disable Postgres listener on TCP, keep unix socket only

Distribution

Two paths, both worth doing:

  1. Prebuilt rootfs on GitHub Releases: `mast--lxc.tar.zst`. User runs `pveam` import or drops it in `/var/lib/vz/template/cache/` and `pct create`s from it.
  2. community-scripts/ProxmoxVE PR (later): `ct/mast.sh` + `install/mast-install.sh`. Their convention runs on a fresh Debian CT and apt-installs Mast. Higher reach, more support surface — do after Inline 'paste new key' form in Add System modal #1 is stable.

Self-managing first server

The defining feature. A `mast-bootstrap.service` (oneshot, `Wants=mast.service`, `After=mast.service postgresql.service`) runs once on first boot:

  1. Generates an Ed25519 keypair at `/var/lib/mast/self.key` (mode 0600, owner `mast`).
  2. Appends the pubkey to the `mast` user's `~/.ssh/authorized_keys`.
  3. Runs `/opt/mast/bin/mast eval 'Mast.Bootstrap.self_enroll!()'` which:
    • Inserts a server row: `host=127.0.0.1`, `user=mast`, key encrypted via existing `Mast.Vault`, `tag=self`.
    • Inserts a release row pointing at the local systemd unit.
    • Writes audit log entry `server.self_enrolled`.
  4. Disables itself (`systemctl disable mast-bootstrap.service`).

Gating:

  • Env var `MAST_SELF_MANAGE` (default `true` in the LXC template, `false` everywhere else).
  • Idempotent: `Mast.Bootstrap.self_enroll!` no-ops if a `tag=self` server already exists.
  • UI badge: self-managed server is marked and cannot be deleted from the UI (would orphan the dashboard).

Secret bootstrap

Generated at install time, not baked into the image:

```
/opt/mast/.env (mode 0600, owner mast)
SECRET_KEY_BASE=<openssl rand -base64 48>
DATABASE_URL=postgresql:///mast?host=/var/run/postgresql
PHX_HOST=<hostname -f>
MAST_SELF_MANAGE=true
```

Systemd: `EnvironmentFile=/opt/mast/.env`.

Admin user: created on first web visit (existing `setup_live` flow if present, or first registration becomes admin).

Systemd units to ship

  • `mast.service` — `ExecStart=/opt/mast/bin/mast start`, `User=mast`, `After=postgresql.service network-online.target`.
  • `mast-bootstrap.service` — described above.
  • (existing) `postgresql.service` from apt.

Scope

In:

  • distrobuilder config (`mast-lxc.yaml`).
  • Build script: `scripts/build-lxc.sh` that produces `rootfs.tar.zst` and ships SHA256.
  • `Mast.Bootstrap` module with `self_enroll!/0`, idempotent.
  • `mast-bootstrap.service` + `mast.service` unit files in `rel/lxc/`.
  • README section: "Install on Proxmox" with the `pveam`/`pct create` commands.
  • GitHub Actions workflow that builds the rootfs on tag push.

Out:

  • community-scripts PR (separate follow-up).
  • Multi-arch (x86_64 only for v1; aarch64 once anyone asks).
  • LXC -> VM migration tooling.

Open questions

  • Postgres bundled vs Postgres-on-another-CT: bundled for v1 (one-click). Note the override path in docs.
  • Mast user privileges: `sudo -n apt` only, no general sudo. Confirm the patch worker still works with this narrowing (it should — patches.ex already prefixes `sudo -n`).
  • Self-enrolled server's SSH key: stored encrypted via `Mast.Vault` like any other, but the plaintext is on the same box. Document this in the LXC README so operators understand the trust model (compromising the CT compromises that key — but that key only grants access to the CT itself, so the blast radius is the CT, not the fleet).
  • LXC template versioning: tie to app version (`mast-0.6.0-lxc.tar.zst`) or rolling (`mast-lxc-latest.tar.zst`)? Probably both — latest + versioned.

Related

  • Companion issue: Nix flake distribution.
  • ADR 0005 (no host agent) — unchanged. The self-managed first server uses the existing SSH path, not an agent.
  • ADR 0006 (SSH key storage) — applies to the self-enrolled key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    distributionPackaging and install paths (LXC, Nix, Docker, etc.)enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions