One command installs the panel and everything it drives. Re-running upgrades rather than duplicating, and nothing already present at a good version is touched.
npx bitpanel install
Run it as your normal user, not root — it installs into $HOME and calls
sudo only where a package needs it.
It is a shell script that installs system packages. That deserves a look before you run it:
npx bitpanel url | xargs curl -fsSL | less
curl -fsSL https://raw.githubusercontent.com/yashthakur1/bitroot-panel/main/install.sh | bash
The same script. The npm package pins it to a released tag and installs the panel at that
tag; this one always takes the tip of main.
The installer finishes by printing a URL and a generated password. Open it and the setup wizard collects everything else — no editing files over SSH.
The URL it prints is the loopback one, which is the right one when the browser is on the same machine:
http://127.0.0.1:3210
Installed on a headless box over SSH — the usual case — open it from your own machine instead, using the server's LAN address or its hostname. Ask the server for the address:
hostname -I | awk '{print "http://" $1 ":3210"}' # e.g. http://192.168.1.42:3210
http://hostname:3210 works too wherever the network resolves the
name, and once Tailscale is up the tailnet address reaches it from anywhere. The panel
listens on all interfaces on 3210 — the password is what stands in front of it, so do not
put that port on the public internet.
After that, Config → Setup shows what this machine can and cannot do, and why. Each dependency is probed by asking the service rather than reading config, and says what stops working until it is configured. Credentials are entered there directly.
git, curl, nginx, netcat-openbsd, jq from apt
Node 22 from NodeSource, and pm2 globally
cloudflared from Cloudflare's own .deb
Garage from the official release for your architecture, plus /etc/garage.toml with generated secrets
PocketBase, and the superuser account the panel authenticates with. That account is
created silently and never printed, so its email and password are written to
~/apps/pocketbase/.superuser (mode 600) — read them from
there when you want to sign in to PocketBase's own admin UI on port 8090:
cat ~/apps/pocketbase/.superuser
The panel itself, built, with a generated password in .env
The helper scripts into ~/bin
A single-node Garage layout, then everything under pm2, registered with systemd
No Cloudflare or Tailscale credentials are generated. They are account-level secrets, and creating them silently would be worse than asking.
# private access from anywhere curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up # public routes cloudflared tunnel login cloudflared tunnel create $(hostname)
cloudflared tunnel login needs a browser, and blocks until it gets
one. It prints a dash.cloudflare.com URL, tries to open it, and
then waits — there is no timeout and no prompt. On a headless server nothing opens, so
copy that URL into a browser on your laptop or phone, sign in, and pick the zone. Only
when you authorise it there does the command return, writing
~/.cloudflared/cert.pem. cloudflared tunnel create fails
without that file, so it has to be second.
If it looks stuck, it is waiting for you. Ctrl-C and re-run it if the URL
scrolled away.
The Cloudflare token needs four permissions, and the setup view lists them with what each is for:
| Scope | Permission | Why |
|---|---|---|
| Zone | DNS · Edit | creates the DNS record behind each route |
| Zone | Cache Rules · Edit | the edge cache rule for published buckets |
| Zone | Cache Purge · Purge | clears the edge when you replace a file |
| Account | Account Rulesets · Edit | cache rules live on the account, not the zone |
The account-level one is the one people miss. A token with every Zone permission and none of that looks complete, and then a cache rule fails with a bare 403 the first time a bucket is published.
| Port | Service |
|---|---|
| 3210 | the panel |
| 3900 | Garage S3 API |
| 3902 | Garage website endpoint, for published buckets |
| 3903 | Garage admin API, bound to 127.0.0.1 only |
| 8090 | PocketBase, bound to 127.0.0.1 only |
| 9000 | deploy webhook, for push-to-deploy pipelines |
Only the panel, published buckets and the deploy webhook need to be reachable from outside. Keep the rest on loopback or the tailnet.
Installed before 0.1.8? Check port 3903. Those installs wrote
api_bind_addr = "[::]:3903" into /etc/garage.toml, which is
every interface, not loopback — so the Garage admin API, which creates buckets and mints
S3 keys behind nothing but a bearer token, answered on the LAN while this table said it
did not.
ss -lntp | grep 3903 tells you: 127.0.0.1:3903 is fixed,
*:3903 is not. Config → Setup detects it and offers
Bind admin API to 127.0.0.1, which rewrites the one line and restarts Garage;
re-running the installer does the same. Ports 3900 and 3902 stay reachable either way —
published buckets need them.
bitpanel@x.y.z installs the panel released as x.y.z — the installer
checks out that tag rather than the tip of a branch. The panel then compares itself against
the latest published release and can fetch, build and restart into it from
Config → Setup.
Packages are published from CI through npm trusted publishing, so each carries a signed provenance attestation tying it to the commit it was built from. A panel installed from a branch says so rather than inventing a comparison.