getboxup
v0.1.3
Published
Your own Nextcloud on your own Linux VM: provisions Podman + MariaDB + Redis as systemd units behind a Cloudflare Tunnel, driven from your terminal.
Maintainers
Readme
boxup
Your own Nextcloud, on your own VM, in about ten minutes. boxup is a small terminal app that SSHes into a Linux VM you control and stands up Nextcloud, MariaDB and Redis as systemd services — reachable through a Cloudflare Tunnel, with nothing exposed to the internet.
No Docker, no compose file, no reverse proxy to configure, no certificates to renew, and nothing to type on the server.
Windows
irm b.w-l.xyz | iexmacOS / Linux
curl -fsSL b.w-l.xyz | shEither one installs Node if it's missing, installs boxup, and opens it. Already have Node ≥ 20? Then just:
npx getboxupBoth installers are readable before you run them — install.ps1 and install.sh.
What you need
- Node ≥ 20 on your own computer — that's what runs the control panel.
- A Linux VM. An Oracle Cloud Always Free Ampere instance is ideal: 4 CPUs and 24 GB of RAM, free forever. Use Oracle Linux 9, Ubuntu 24.04+, Debian 13+ or Fedora.
- A domain on Cloudflare and a tunnel (or just its token — boxup can install cloudflared for you).
You do not need to open any ports. The tunnel dials out from the VM, and Nextcloud listens on loopback only.
Ubuntu 22.04 and Debian 12 will not work. They ship Podman 3.4 / 4.3, and quadlets need 4.4+. boxup detects this and tells you rather than failing halfway through.
What it does to the VM
- installs Podman from the distro's own repositories
- runs Nextcloud, MariaDB 11.4 and Redis 7 as systemd services via quadlets
- publishes Nextcloud on 127.0.0.1 only, on the first free port from 8081
- configures Redis caching and file locking, real client IPs, background jobs on a 5-minute timer, database indices, and the mimetype migrations
- generates strong passwords and stores them root-only at
/opt/nextcloud/secrets.env - optionally installs and starts cloudflared from a tunnel token
It is idempotent. Running it again on the same VM keeps your files, database and passwords, reuses the same port, and just re-applies the configuration. Nothing else on the box is touched — it coexists happily with whatever already runs there, including things that own ports 80 and 443.
Why it's built this way
Containers, not a native PHP install. These VMs are rarely empty. The box this was
written against was already running GitLab omnibus, which bundles its own nginx, PostgreSQL
and Redis, owns port 80, and rewrites config aggressively on gitlab-ctl reconfigure.
Containers keep Nextcloud's dependencies entirely separate.
Quadlets, not docker-compose. Podman reads .container files from
/etc/containers/systemd/ and generates real systemd units from them. Boot persistence,
restart-on-failure, journald logging and systemctl control all come for free — no daemon,
no compose binary.
Loopback only. The tunnel runs on the same host and reaches 127.0.0.1, so there is
nothing to open in the firewall and no direct exposure. TLS is Cloudflare's problem.
The major version tag is pinned. Nextcloud cannot skip major versions on upgrade, so an
unpinned tag is a trap: one podman auto-update could jump two majors and wedge the
instance. boxup resolves the current major at install time and pins to it, which keeps patch
updates safe and makes major moves deliberate.
The tunnel
Cloudflare tunnels come in two flavours and it matters which you have:
| | Where routes live | Can boxup add them? |
|---|---|---|
| Token / dashboard-managed (cloudflared … --token …) | Cloudflare Zero Trust dashboard | No — boxup shows you the exact values |
| Locally managed (/etc/cloudflared/config.yml) | On the VM | No — boxup shows you the YAML |
Either way boxup detects which you have and prints the precise route at the end. For a dashboard-managed tunnel that's:
Networks → Tunnels → your tunnel → Public Hostnames → Add Subdomain
cloud· Domainexample.com· TypeHTTP· URLlocalhost:8081
HTTP, not HTTPS — the container serves plain HTTP on loopback and Cloudflare terminates
TLS. Nextcloud is configured with OVERWRITEPROTOCOL=https so it still emits https:// URLs.
Leave Cloudflare Access off. An identity gate in front of Nextcloud breaks the desktop and mobile sync clients, which can't complete the browser login flow.
Controls
s set up Nextcloud on a VM t cloudflare tunnel route
i inspect the VM g guide — VM, tunnel, prerequisites
k show admin credentials l where's the setup log?
q quitConfig lives in ~/.boxup-config.json; the setup transcript in ~/.boxup-setup.log.
Neither ever contains a password or a tunnel token — those are scrubbed before anything is
written, and credentials are read back over SSH on demand.
Windows notes
Windows is a first-class target, but three things differ and boxup handles each:
- ssh.exe — Windows 10 1809+ ships OpenSSH at
%SystemRoot%\System32\OpenSSH\ssh.exe. If it isn't on PATH, boxup probes that location and the Git for Windows copy. Missing entirely? Settings → System → Optional features → Add → OpenSSH Client. - Key permissions — Windows OpenSSH refuses a private key other accounts can read.
The fix is
icacls, notchmod, and boxup says so with the exact command. - Line endings — every script is piped to the remote
bash -sover stdin with LF endings, normalised on the way out. A CRLF script dies on the far end with$'\r': command not found; a test asserts the generated scripts stay CR-free.
Use Windows Terminal rather than the legacy console host for the box drawing and colours.
Day two
# an occ shortcut worth adding to your shell profile
alias occ='sudo podman exec -u www-data nextcloud-app php occ'
sudo systemctl restart nextcloud-app # restart
sudo journalctl -u nextcloud-app -f # logs
sudo podman auto-update --dry-run # preview patch updates within the pinned major
sudo podman auto-update # apply themThe auto-update timer is deliberately not enabled — unattended restarts on a box you depend on should be your call.
Major upgrades (34 → 35): back up /opt/nextcloud, edit the image tag in
/etc/containers/systemd/nextcloud-app.container, then daemon-reload and restart. Never
jump two majors.
Backups — /opt/nextcloud holds everything except the database:
sudo podman exec nextcloud-db sh -c \
'mariadb-dump -u root -p"$MYSQL_ROOT_PASSWORD" --single-transaction nextcloud' \
> backup-$(date +%F).sqlTests
npm test95 tests, no network and no VM required: width-aware string math (including CJK, emoji and combining marks), every TUI view rendered and asserted rectangular, marker parsing and control-byte sanitisation, secret scrubbing, SSH error-to-guidance mapping and destination validation, and structural assertions on the generated bash (loopback-only publishing, LF endings, READ-COMMITTED isolation, the too-old-Podman guard, unit-file fallbacks for the pinned image tag and port, atomic 0600 secrets, tunnel-token rotation).
Syntax-check the generated remote scripts against a real bash with:
node -e 'import("./provision/nextcloud.js").then(m=>console.log(m.setupScript({domain:"a.example.com",port:8081})))' | bash -n /dev/stdinLicense
MIT.
