Ubuntu 24.04 ships PEP 668 (/usr/lib/python3.12/EXTERNALLY-MANAGED), so `pip install --user` is refused and users had no supported way to install Python CLI tools. Add pipx to the base toolset. Also put ~/.local/bin on PATH via /etc/profile.d/pipx.sh rather than relying on Ubuntu's stock ~/.profile, which only adds it when the directory already exists at login -- it does not until the first pipx install -- and which non-bash logins skip entirely. Without this the first install looks like it worked and then the command is not found. Covers applications only; importable libraries still need a user venv. |
||
|---|---|---|
| cmd | ||
| docs | ||
| host | ||
| images | ||
| internal | ||
| pkg/shells | ||
| .gitignore | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
shells-config
Public free-shell program for IRCNow / sh.ircforever.org.
Each user picks a name and gets <name>.sh.ircforever.org, an Incus container
on the shells VPS (89.167.23.102, Hetzner Helsinki), and a dedicated IPv6
/128 from 2a01:4f9:c014:663f::/64 routed directly into their container.
Architecture
main VPS (control plane) ──HMAC POST──▶ shellsd on shells VPS
│
▼
incus exec / useradd /
Traefik fragment write /
DNS provider call
pkg/shells/— library.Backendinterface + Incus implementation. IPv6 allocator, JSON state, Traefik file fragments, Cloudflare DNS client.cmd/shellsd/— webhook receiver daemon. systemd unit; listens on127.0.0.1:8090, fronted by Traefik onapi.sh.ircforever.org.cmd/shellctl/— break-glass CLI for the shells host.internal/webhook/— HMAC verify + HTTP handlers.host/— config that lives on the shells VPS:bootstrap.sh— idempotent host bootstrap (incus + UFW + sysctl + sshd)incus-init.yaml— preseed forincus admin initsshd-shells.conf—Match Group shellsblock for sshdshells-enter+shells-enter-root— login-shell wrapper into the user's container (viasudo+ the sudoers rule below)sudoers-shells—%shells ALL=(root) NOPASSWD: …shells-enter-rootshellsd.service— systemd unittelegraf.conf— baked into the base image; exposes in-guest metrics on :9273observability/— host-sidevmagent(scrapes each container's telegraf and remote-writes to the central VictoriaMetrics) +shells-targets.{py,service,timer}that regenerate the scrape list fromincus list
images/build-shells-base.sh— bake the Ubuntu 24.04 base image (incl. Telegraf)
Webhook API
All requests POST JSON to https://api.sh.ircforever.org/v1/… signed with
X-Signature: sha256=<hex(hmac_sha256(secret, body))> + X-Timestamp
(5-minute skew window).
| Endpoint | Body | Effect |
|---|---|---|
POST /v1/provision |
{username, pubkey} |
create container, host account, Traefik fragment, DNS records |
POST /v1/deprovision |
{username} |
reverse |
POST /v1/block |
{username} |
stop the container (login refused, data kept) |
POST /v1/unblock |
{username} |
start a blocked container |
GET /v1/list |
– | list provisioned shells |
GET /healthz |
– | 200 if incus reachable |
GET /metrics |
– | Prometheus exposition |
Metrics
Each container runs Telegraf (baked into shells-base) exposing in-guest metrics
(cpu/mem/disk/net/netstat/system/…) on :9273 — no credentials live inside the
container (users control their own shell), so it only exposes metrics; nothing
pushes with a secret from inside. Telegraf is un-killable by the user because
shells are unprivileged (no sudo). A host-side vmagent (host/observability/)
scrapes every container over the incus bridge and remote-writes to the central
VictoriaMetrics on the main VPS; shells-targets.timer keeps the scrape list in
sync with incus list. Grafana renders the per-shell Proxmox LXC Container
Data community dashboard (ID 23742), retargeted to these metrics.
container telegraf :9273 ──scrape──▶ vmagent (host) ──remote_write──▶ VictoriaMetrics (VPS) ──▶ Grafana
Per-user surface
| Path | What lands there |
|---|---|
ssh <user>@89.167.23.102 (IPv4) |
host sshd → shells-enter → incus exec shells-<user> |
ssh <user>@<user>.sh.ircforever.org (IPv6) |
direct to container's own sshd (when DNS AAAA is set) |
https://<user>.sh.ircforever.org |
Traefik (host) → container's :80 |
Bootstrap a fresh shells host
# On the host, as root:
git clone … /usr/local/share/shells-config
cd /usr/local/share/shells-config
sudo host/bootstrap.sh
sudo images/build-shells-base.sh
# Install the daemon binary + secret:
sudo install -m 755 shellsd /usr/local/bin/shellsd
SECRET=$(openssl rand -hex 32)
sudo sed -i "s|^SHELLSD_HMAC_SECRET=.*|SHELLSD_HMAC_SECRET=$SECRET|" /etc/shellsd/env
sudo systemctl enable --now shellsd
Plan
See the parent plan file at
/Users/james/.claude/plans/polymorphic-gliding-rossum.md.