rosepetal-meta-cli
Interactive installer that turns a vanilla Ubuntu machine into a Rosepetal Controller. A single binary, a single command, zero state files: each run inspects the host's real state and decides what to do.
Install
The CLI lives in a public GCS bucket. To install the latest prod binary on a fresh controller:
curl -fsSL https://storage.googleapis.com/rosepetal-cli-dist/install.sh | sudo bashThis downloads rosepetal-cli to /usr/local/bin/ and makes it executable. Re-run the same command any time to update to the latest prod release.
Then start the install flow:
sudo rosepetal-cliFlow at a glance
vanilla Ubuntu
│
▼
sudo rosepetal-cli [Install controller]
│
┌───────────────┴──────────────┐
▼ ▼
PHASE 1: apt installs PHASE 2: configure + redeem + install
- baseline tools - prompt token + redeem (gets compose YAML,
- OpenSSH Tailscale authkey, AR pull token)
- NVIDIA driver - create user 'rosepetal'
- Docker + nvidia-docker2 - delete every other UID≥1000 login user
- Tailscale binary - set hostname to device_name
- disable unattended-upgrades / apport
(no token needed yet) - install rosepetal-network systemd unit
- tailscaled syslog override + restart
if NVIDIA module not loaded - tailscale up --auth-key=...
→ ask reboot, exit - write /opt/rosepetal/{compose.yml,.env,proxy.sh}
- docker login + docker compose up -d + logout
- pre-pull auxiliary node-red images
- run /opt/rosepetal/proxy.sh (funnels)
→ install complete2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
The PHASE selection is automatic. The CLI runs command -v docker, dpkg-query, /proc/modules etc. to find out what state the host is in, and dispatches accordingly. No state.json, no marker files, no saved tokens. Re-running after a successful install with a fresh token will swap the controller's identity (overwrites /opt/rosepetal/*, re-joins the tailnet, re-runs compose).
Requirements
- Ubuntu (jammy/22.04 or noble/24.04 — anything Docker's official apt repos support, paired with NVIDIA's libnvidia-container repo).
- Root via
sudo. - Outbound internet to: apt repos (Ubuntu, Docker, NVIDIA), Tailscale's install script, the meta-api Cloud Run URL, and the Artifact Registry host that meta-api gives back in the redeem response (
europe-southwest1-docker.pkg.dev). - An enrollment token (
RPC1-XXXX-YYYY) minted viaPOST /devices/enrollon the meta-api. Required for Phase 2 only; Phase 1 doesn't need one.
Usage
sudo rosepetal-cliThe CLI shows a single-choice menu. Pick "Install controller" and follow the prompts. Phase 1 walks unattended through the apt installs; Phase 2 asks for the enrollment token after a host check.
If Phase 1 just finished and the NVIDIA kernel module isn't loaded yet, the CLI offers to reboot. After reboot, re-run the CLI — it detects the loaded module and skips straight to Phase 2.
Installing over SSH is safe — even through the controller's own Tailscale address. Once the token is redeemed, the disruptive tail of the install (wipe of previous state, Tailscale join, docker pulls, finalizing reboot) runs as a transient systemd unit (rosepetal-install) that keeps going if the session drops; the CLI just tails its log (/var/log/rosepetal-install.log). If you get disconnected — expected when the install resets the Tailscale identity you're connected through — reconnect (LAN IP, or ssh rosepetal@<device-name> once the new identity is up) and re-run sudo rosepetal-cli to re-attach to the running install.
Flags
There are none today. Future flags (overriding the API URL, dry-run, etc.) will live behind subcommands once we have more than one user action.
Defaults
| API URL | https://meta-api-7qq323bz2a-uc.a.run.app (override via ROSEPETAL_API_URL env var) |
| install dir | /opt/rosepetal/ |
| user | rosepetal (sudo + docker groups) + password |
| Tailscale device tags | tag:prod, tag:maintenance |
| Tailscale operator | rosepetal |
| NVIDIA driver | nvidia-driver-580-open |
Hardcoded credentials are inherited from the manual install script (rosepetal-dep-controller/postinstall-firstboot.sh) for parity. Known security gap — see v2 TODO.
Build
GOOS=linux GOARCH=amd64 go build -o rosepetal-cli .The compose template / network script / proxy.sh are embedded via //go:embed into internal/install/assets.go, so the final binary has no runtime file dependencies beyond apt, docker, tailscale, and the system utilities Phase 1 installs.
For local builds during development:
go build . # native architectureLayout
rosepetal-meta-cli/
├── main.go # entry, menu, dispatch
├── go.mod / go.sum
├── README.md
└── internal/install/
├── orchestrator.go # phase detection + dispatch
├── detect.go # state checks (command/dpkg/modules)
├── prompts.go # menu + token + y/n
├── api.go # /devices/enroll/redeem HTTP client
├── exec.go # shell helpers (run/capture/atomic write)
├── assets.go # //go:embed declarations
├── phase1.go # apt installs (Docker, NVIDIA, Tailscale, ...)
├── phase2.go # orchestrator for the Rosepetal install
├── phase2_system.go # user create + cleanup, hostname, hardening, network svc
├── phase2_tailscale.go # tailscaled override + tailscale up + status parse
├── phase2_stack.go # /opt/rosepetal/ + docker login + compose up + proxy
├── phase2_detach.go # SSH-disconnect-proof background tail (systemd-run)
├── phase2_wipe.go # detect + wipe previous controller state
└── assets/
├── proxy.sh # Tailscale funnels (verbatim from controller bundle)
├── rosepetal-network.sh # NIC tuning + sysctl + cpufreq
└── rosepetal-network.service2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Known v1 limitations
- NVIDIA driver install requires reboot — the CLI does NOT auto-reboot; it prompts the operator. Re-running after reboot is automatic.
- No
update/uninstall/statussubcommands. Re-running install with a new token is the supported "update" path (overwrites/opt/rosepetal/*and re-applies compose). - proxy.sh failures are surfaced as warnings, not errors — the stack is considered "up" even if HTTPS funnels didn't fully apply.