English | Русский
A fork of p1neappleXpress/OpenFlux. This repo holds
install.sh: a script that asks a handful of questions and rolls out
openflux-server's controlplane — Postgres, the
systemd service, Nginx, and an HTTPS certificate (Let's Encrypt) with auto-renewal — on a fresh
Debian/Ubuntu or AlmaLinux/RHEL-family VPS.
On a fresh VPS (Debian/Ubuntu or AlmaLinux), as root:
curl -fsSL https://raw.githubusercontent.com/wlruscfd/openflux-deploy/main/easy-install.sh | sudo bashIt detects the server's IP, picks free ports, creates swap when memory is short, generates the
tokens, installs everything (Postgres, Nginx, controlplane, an exit node on the same server) and at
the end verifies the services are actually running, then prints the panel address, the admin
token and a "what next" checklist. The full installation log is kept in
/var/log/openflux-install.log (mode 600). If it stops partway, running it again is safe - no data
is lost.
Want something different? Override variables: DOMAIN=panel.example.com LE_EMAIL=you@example.com TLS_MODE=domain (a domain instead of an IP), NODE_NAME=..., WEB_PANEL=y, OPENFLUX_LANG=en
(English messages). The interactive version is below.
curl -fsSL https://raw.githubusercontent.com/wlruscfd/openflux-deploy/main/uninstall.sh | sudo bashRemoves everything install.sh set up: the services, /opt/openflux, /etc/openflux, the Postgres
database and role, the Nginx config, the Let's Encrypt certificate, the system user and the node's
iptables rules. You have to type DELETE first (or run with CONFIRM=yes), and a database dump plus
the env files are saved to /root/openflux-uninstall-<timestamp>/ (disable with NO_BACKUP=y).
Postgres, Nginx, snapd, git and other packages are left alone - other services may rely on them; Go in
/usr/local/go is removed only with REMOVE_GO=y.
Run it directly on the target VPS, as root:
curl -fsSL https://raw.githubusercontent.com/wlruscfd/openflux-deploy/main/install.sh | sudo bash(Not orchestrated remotely over SSH from your own machine - simpler and more robust to just run it where it's actually provisioning things.)
It will ask for:
- Which
openflux-serverrepo/branch to deploy (defaults to thewlruscfdone, override for your own fork). - TLS mode:
domain(standard Let's Encrypt via certbot's Nginx plugin - you'll need a domain already pointed at this server's IP, plus an email for the Let's Encrypt account) orip(no domain needed; attempts Let's Encrypt's short-lived IP-address certificate, falling back to a self-signed certificate - with a clear warning - if that doesn't succeed, so the panel is still reachable over HTTPS either way). - An admin token (or press Enter to generate one) - this is what you'll paste into the admin panel
at
https://<your-domain-or-ip>/admin/afterwards. - For
domain/ipmode: which HTTPS port to use (default443) - only worth changing if something else on the server already owns it. - For
domain/ipmode: whether to reserve port 80 for another service on this machine (default no - normal Let's Encrypt issuance/renewal, which needs port 80 reachable). Say "y" only if something else on this server needs port 80 for itself; this skips Let's Encrypt entirely and uses a self-signed certificate on the HTTPS port only, with nginx never listening on 80 at all. - Whether to install the full SvelteKit web panel (requires Bun; default yes): it serves the
dashboard at
/admin/behind Nginx next to controlplane's/v1/*. Say "n" and you get the original, self-contained panel that's embedded inside the controlplane binary itself (still served at/admin/). Saying "y" here never breaks an install — if Bun can't be installed or the panel fails to build, the script degrades to the embedded panel and says so. - Whether to register a first exit node right away.
If that node runs here (RUN_NODE_HERE, raw mode), its outbound-ports-per-key setting defaults to
96 and is set via the NODE_PORT_RANGE_SIZE env var - not an interactive prompt, and remembered
across redeploys the same way CONTROLPLANE_PORT is. Lower it to fit more keys on this one node;
raise it if a single key's traffic needs more than ~96 simultaneous outbound connections at once
(observed in practice: Telegram opens quite a few while loading media in an active chat).
At the end it prints the panel URL, the admin token (save it - it's stored hashed and can't be recovered from the server afterwards), and, if you registered one, the first exit node's token and the exact flags to hand to whoever runs that exit node:
./universal-bypass-tool --exit-node --managed \
--control-url "https://<your-domain-or-ip>" \
--node-token "<node token>"Re-running the script later redeploys a newer branch/tag of openflux-server in place.
Re-running install.sh on a server it's already set up (upgrading to a newer openflux-server, or
just via the app's "Deploy again") keeps the existing database, admin token, and token pepper -
every key, node, and ingest token you've already issued keeps working. Before touching anything, it
also dumps the database to /opt/openflux/backups/<timestamp>/openflux.sql (plus a copy of the env
file) as a just-in-case safety net. A first-time install has nothing to preserve or back up yet, so
none of this applies to it.
Every prompt is skipped if its variable is already set in the environment (REPO_URL, GIT_REF,
TLS_MODE, DOMAIN, LE_EMAIL, SERVER_IP, HTTPS_PORT, RESERVE_PORT_80, ADMIN_TOKEN,
DB_PASSWORD, REGISTER_NODE, NODE_NAME, NODE_MAX_KEYS, WEB_PANEL - the exact names used
inside the script), so it can be driven without a human at the keyboard. RESERVE_PORT_80 (default
n, domain/ip modes only) - set to y to skip Let's Encrypt entirely and use a self-signed
certificate with no port-80 listener at all, so port 80 stays free for another service on the same
machine:
REPO_URL=https://github.com/wlruscfd/openflux-server.git GIT_REF=main \
TLS_MODE=domain DOMAIN=panel.example.com LE_EMAIL=you@example.com \
ADMIN_TOKEN="$(openssl rand -hex 32)" DB_PASSWORD="$(openssl rand -hex 24)" \
REGISTER_NODE=y NODE_NAME=node-1 NODE_MAX_KEYS=500 WEB_PANEL=y \
bash install.shThis is exactly what the openflux-app Android app's
Deploy tab does over SSH, so you never see a prompt when deploying from the app. Non-interactive
callers that don't pre-set WEB_PANEL get the embedded panel - the script does not prompt where
stdin isn't interactive.
The exit node reports what its transport is doing in the journal, no --debug needed:
journalctl -u openflux-nodeagent -fworker started/transport connected- the node reached the provider's document; if traffic is still one-way, look at the exit side (firewall,iptables, provider blocking raw packets).the provider asked for a captcha; waiting for a cookie jar- the node's IP is being challenged. Push cookies from the app (profile editor -> "Send cookies"), then look forapplying the uploaded cookie jar. If the captcha line keeps coming back after that, the provider is rejecting the jar for this server's IP - typical for datacenter ranges the provider has flagged; try a different VPS/IP or another transport (mailru,mts).attempt N failed (...)- the reason code and cause of each failed attempt (once a minute per reason).- a line a minute: how many keys run and which are not connected to their provider yet.
openfluxsystem user,/opt/openflux/{bin,server,web},/etc/openflux/controlplane.env(mode600, holds the DB URL / token pepper / admin token).- A local Postgres role + database.
openflux-controlplane.service(systemd unit, embedded in install.sh), enabled and started.- With the web panel: Bun at
/opt/openflux/bun, the built SvelteKit app at/opt/openflux/web, andopenflux-web.service(bun server.json127.0.0.1:3000,CONTROLPLANE_UPSTREAMholding the controlplane address it forwards/v1/*to). - Nginx:
/admin/→ the web panel (or controlplane's embedded panel, when Bun was skipped),/v1/+/healthz→ controlplane on127.0.0.1:8080(override with theCONTROLPLANE_PORTenv var if something else on the server already holds 8080 - a redeploy remembers whatever port was used last time), all over TLS per the mode above. Inhttpmode without a web panel the split doesn't apply and controlplane answers directly.
Let's Encrypt's short-lived certificates for bare IP addresses are newer and less battle-tested
than the domain path, and need a recent certbot (--ip-address needs 5.3+, webroot support for it
needs 5.4+) - the certbot package in Debian/Ubuntu's apt or AlmaLinux's EPEL is normally far older
than that and doesn't support IP certificates at all, so this script installs certbot via snap specifically to get a current
enough one. That said, this is still a newer Let's Encrypt capability with its own moving parts;
if issuance still fails for you, the script notices and falls back to a self-signed certificate
rather than leaving the install half-finished. If you can get a domain pointed at the server
instead, that path is the well-trodden one.