20 KiB
Installing and updating RustProxy 0.29.1
The scripts prepare a Linux/systemd installation. This source archive is not a verified production build: Rust/Cargo, real CA operations, live systemd startup and rollback against a real Rust database have not been exercised in the packaging environment. Perform the acceptance steps below on an isolated server first.
Official installation repositories
Use the project-maintained package repositories as the normal installation/update path:
- DEB/RPM:
https://rustproxy.linuxiarz.pl/ - OPNsense plugin:
https://rustproxy-opn.linuxiarz.pl/
Package-build metadata is intentionally maintained outside this source tree. The Debian source bootstrap below remains supported for development, custom builds and environments where the packaged path is not suitable.
Optional: Debian 13+ one-line source install/update
# auto: first install or managed update
wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash
# force the update path
wget -qO- https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/install-debian13.sh | bash -s -- update
The bootstrap installs build dependencies, installs Rust with rustup for an unprivileged build user,
clones https://git.linuxiarz.pl/gru/RustProxy and builds with cargo build --release --locked.
Its default auto mode calls install.sh only when no managed installation exists. When
/opt/rustproxy/install.json exists it calls update.sh, which uses the saved installation record,
backs up private state, switches the release, verifies health/version and rolls code/data/unit back
on failure. First install keeps administration on 127.0.0.1:9090 over HTTP, matching the normal
install.sh loopback-safe default. Updates preserve the saved admin/listener settings. For initial
remote access use an SSH tunnel, then enable Administration HTTPS before persisting a non-loopback bind.
Plain remote HTTP requires both an explicit non-loopback RUSTPROXY_ADMIN_LISTEN and
RUSTPROXY_ALLOW_REMOTE_ADMIN=1, and should be limited to a trusted private network.
Equivalent prerequisite commands are:
apt install -y build-essential gcc git ca-certificates python3 python3-venv python3-pip pkg-config wget sudo certbot python3-certbot-dns-rfc2136 python3-certbot-dns-cloudflare python3-certbot-dns-ovh
wget -qO- --https-only --secure-protocol=TLSv1_2 https://sh.rustup.rs | sh
Build as a normal user
Requirements: stable Rust (minimum declared 1.88), Cargo, a C compiler/linker,
Python 3.10+, Bash. Linux/systemd deployment also requires systemctl, runuser
and a usable non-login service account (created by the installer when missing).
The static TOML checker additionally needs Python 3.11+. No script downloads an
executable or installs a compiler as root.
cd RustProxy
bash dev.sh doctor
bash dev.sh check
bash dev.sh test
bash dev.sh release
The first build resolves and creates Cargo.lock; retain the real lockfile and
binary hash for repeatable subsequent builds. There is no fabricated lockfile or
precompiled binary in the archive. Set --binary explicitly when using a custom
CARGO_TARGET_DIR. Review and trust the source and built binary before installation.
First installation
install.sh also downloads the direct user-country-ipv4.csv and user-country-ipv6.csv
assets from the sapics/ip-location-db latest release over HTTPS, validates their CSV
structure and writes them privately to DATA_DIR/geoip. Package/plugin installs may omit
that packaging-time download: after all listeners bind successfully, RustProxy starts an
enabled-by-default GeoIP worker, performs an immediate background refresh and then checks
every 24 hours by default. Country allow-only policies continue to fail closed until a valid
database is available. Settings can use separate IPv4/IPv6 CSV files or one native MaxMind
MMDB file; direct source URLs, optional per-file SHA-256 URLs, maximum file size and interval
are configurable under Settings -> GeoIP updates.
bash install.sh --plan --binary target/release/rust-proxy
sudo bash install.sh --binary target/release/rust-proxy
Defaults: HTTP 0.0.0.0:80, HTTPS 0.0.0.0:443, administration
127.0.0.1:9090, code /opt/rustproxy, all private state /var/lib/rustproxy,
service account rustproxy. The initial page is a small informational response; no demonstration
backend or sample application is started. The first database has no configured
vhosts. Existing, nonempty data is refused rather than reset.
The first install also writes /etc/profile.d/rustproxy-path.sh, which prepends the
managed PREFIX/current directory (normally /opt/rustproxy/current) to PATH for
new login shells. Open a new login shell, or source that profile explicitly, before
expecting rust-proxy to resolve without its absolute path.
HTTPS is enabled by default and starts with a self-signed certificate, not a publicly trusted certificate. For an explicit HTTP-only installation use:
sudo bash install.sh --binary target/release/rust-proxy --no-https
Assign the intended certificate in the panel before exposing applications. Existing HTTP/3 defaults enable QUIC on TLS listeners: permit the corresponding UDP port when desired, or explicitly disable H3 in Listeners. Review firewall rules yourself; the installer does not change them. Public frontend TLS and administration TLS are independent settings.
Generate the one-time setup capability directly from the installed release CLI:
sudo -u rustproxy /opt/rustproxy/current/rust-proxy \
--data-dir /var/lib/rustproxy admin setup-token
This works before the first server start. After an administrator exists, setup tokens remain invalid. You can alternatively initialize the account interactively:
sudo -u rustproxy /opt/rustproxy/current/rust-proxy \
--data-dir /var/lib/rustproxy admin init
For direct remote administration, first sign in on the default loopback listener and
open Settings -> Administration. Enable HTTPS, choose the application-managed
certificate or a certificate from the inventory, set the bind address (for example
0.0.0.0:9090) and optionally add DNS names accepted by Host/Origin validation.
Save and restart RustProxy. The persisted remote bind is accepted only while
administration TLS is enabled. Keep the listener behind a firewall/private network;
the certificate must be trusted by the browser if you want a clean HTTPS connection.
An SSH tunnel remains available without exposing the listener directly:
ssh -L 9090:127.0.0.1:9090 operator@your-server
Open http://127.0.0.1:9090 on that workstation while the administration listener
is still using its default HTTP mode. No default administrator/password is added.
Plain-HTTP remote administration is possible with the explicit allow flag:
sudo bash install.sh --binary target/release/rust-proxy \
--admin 0.0.0.0:9090 --allow-remote-admin
That plain-HTTP path has no transport confidentiality and should be limited to a trusted private network. Prefer the built-in administration HTTPS mode for a direct remote listener.
Other installation switches: --prefix, --data-dir, --user, --http, --https,
--no-https, --admin, --allow-remote-admin, --sha256 (expected binary digest), --no-start. Paths must be dedicated,
absolute, separate, non-nested and without spaces or symlink components.
--plan reads the supplied binary but changes nothing; it does not prove port
availability, compiler success or systemd readiness.
Startup-owned listeners
When HTTP, HTTPS or administration listen addresses are supplied explicitly by systemd, CLI
arguments or the corresponding RUST_PROXY_*_LISTEN environment variables, RustProxy treats
those exact binds as process-owned. Settings shows them as locked and configuration updates
cannot remove, disable or rewrite them. Listeners not supplied at process start remain normal
editable desired-state entries.
ACME prerequisites
Automatic renewal runs inside RustProxy, not a separately installed cron timer. RustProxy
prefers a private Python environment at <data-dir>/acme-python. When that environment is
missing or incomplete, the service creates it automatically and installs the pinned Certbot
core plus the Cloudflare, OVH and RFC2136 plugins with pip. The application never executes
sudo and never modifies distribution-owned Python site-packages.
The host should provide Python 3 with either the standard venv module or pip; RustProxy can
bootstrap virtualenv into its own data directory when a distribution splits venv into a
separate package. OpenSSL remains a local certificate-inspection fallback. If the private
environment cannot be created, RustProxy uses a runnable distribution certbot from PATH
when one is available.
If Python is absent (a possible layout on appliances such as OPNsense), or Python is present but no local environment can be created and no system Certbot exists, the Certificates panel shows ACME as unavailable. PEM upload and references to existing certificate files remain available.
On the standard service installation the managed environment therefore lives at
/var/lib/rustproxy/acme-python. Do not run a second Certbot renewal timer over RustProxy's
certs/acme profiles. See renewal.
A source-tree preflight can still inspect a distribution-provided fallback environment with:
sudo -u rustproxy env PATH=/usr/local/bin:/usr/bin:/bin \
python3 scripts/runtime/acme_provider.py --doctor
The provider script is embedded in the RustProxy binary; this command is diagnostic only.
Using a separate system Certbot
If another service already owns a certificate through the system Certbot, keep that lineage outside RustProxy's managed ACME profiles and create/use a manual RustProxy certificate id.
The preferred 0.28.2 workflow is to select Existing server files / external paths and then External files (absolute paths) in the Certificates panel. For example:
Certificate: /etc/letsencrypt/live/example.com/fullchain.pem
Private key: /etc/letsencrypt/live/example.com/privkey.pem
RustProxy does not copy these files into its data directory. On every targeted reload it resolves
Certbot's live/ symlinks again, validates the new certificate/key pair and atomically publishes a
new public TLS snapshot. A renewal wrapper can therefore call:
curl --fail --show-error -X POST \
-H 'Authorization: Bearer TOKEN' \
-H 'X-RustProxy-Certificate-Key: certbot-main' \
-H 'Content-Type: application/json' \
--data '{}' \
https://proxy-admin.example/api/certificates/shared-cert/reload
The rustproxy service identity must be able to traverse the parent directories and read both
files. RustProxy 0.28.2 supports POSIX ACLs and does not require one exact chmod value. External
certificate/key files must not be group/world-writable, and an external private key must not be
world-readable. Named ACL read access for rustproxy is accepted; the operating system still
performs the final access check when RustProxy opens the file. Every parent directory in the
configured and resolved path must be owned by root or the RustProxy service user and must not be
group/world-writable (root-owned sticky ancestors such as /tmp are the narrow exception).
These external files are not included in a RustProxy backup.
The packaged systemd service keeps /etc/letsencrypt and /etc/ssl read-only. ReadOnlyPaths=
does not grant Unix permissions, so use narrow ACLs instead of making private keys globally
readable. For example:
# Traversal only for the protected roots.
setfacl -m u:rustproxy:--x /etc/ssl/
setfacl -m u:rustproxy:--x /etc/letsencrypt/ /etc/letsencrypt/live/ /etc/letsencrypt/archive/
# Traversal only for this Certbot lineage.
setfacl -m u:rustproxy:--x /etc/letsencrypt/live/example.com/ /etc/letsencrypt/archive/example.com/
# Read only the certificate/key material RustProxy actually needs.
setfacl -m u:rustproxy:r-- /etc/letsencrypt/archive/example.com/fullchain*.pem \
/etc/letsencrypt/archive/example.com/privkey*.pem
# Optional default ACL for renewers that preserve inherited ACL masks.
setfacl -m d:u:rustproxy:r-- /etc/letsencrypt/archive/example.com/
# Robust renewal-hook form: re-grant read access to the current generation before API reload.
setfacl -m u:rustproxy:r-- \
"$(readlink -f /etc/letsencrypt/live/example.com/fullchain.pem)" \
"$(readlink -f /etc/letsencrypt/live/example.com/privkey.pem)"
Because /etc/letsencrypt/live/.../*.pem are symlinks into archive/, access must cover the
resolved archive files. Some renewal tools recreate private keys as 0600 and may mask an
inherited default ACL; therefore the renewal wrapper should reapply the named file ACL before
calling the RustProxy reload API. Never grant w to rustproxy on the Certbot tree. Verify a deployment
with sudo -u rustproxy test -r /etc/letsencrypt/live/example.com/privkey.pem && echo OK.
If you do not want the RustProxy service to read /etc/letsencrypt, keep the previous copy/deploy
workflow instead. A Certbot deploy hook can publish every successful renewal into RustProxy's
managed certificate directory:
/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy --id shared-cert \
--cert /etc/letsencrypt/live/example/fullchain.pem \
--key /etc/letsencrypt/live/example/privkey.pem
The deploy command reads the Certbot files locally and sends them to the authenticated loopback
POST /api/certificates/deploy endpoint. The running RustProxy service writes a private managed
generation and hot-publishes it for new public TLS/H2/H3 handshakes. Do not use deploy for a
certificate ID configured with external absolute paths; renew those files in place and call the
targeted reload endpoint instead. Administration TLS is startup-fixed and may still require a
service restart.
Optional environment overrides go in a root-owned /etc/rustproxy/environment
(mode 0600); create the directory when needed. Restart after changing service
environment values. Provider secrets remain in private profile files, not this
file or the exported configuration. A systemd environment file is not a shell script.
Update an installation created by install.sh
Build and test the new release as a normal user first, then:
bash update.sh --plan --binary target/release/rust-proxy
sudo bash update.sh --binary target/release/rust-proxy
Before stopping the service, the updater downloads the current direct user-country
IPv4/IPv6 CSV assets, validates their structure and compares their local SHA-256 with the
installed files. Unchanged files are left untouched; changed or missing assets are staged
and published only after the private-data backup exists. This intentionally avoids requiring
a separately moving publisher-checksum release. The manual updater and the in-process GeoIP
worker use the same .geoip-update.lock, so they cannot publish concurrently. Use
--skip-geoip only when intentionally accepting a stale/missing country database (for
example during an upstream outage).
Use the original --prefix for a non-default installation. Other startup/account/
data settings are read from root-owned install.json; update does not silently
replace them with command-line defaults. Change intentional listener settings in
RustProxy, or maintain the unit separately when needed.
Update mode checks that /etc/profile.d/rustproxy-path.sh still matches the saved
installation prefix, but deliberately does not rewrite it. A missing or changed file
produces an explicit warning before service downtime; repair the profile separately
if direct rust-proxy shell invocation is required.
The updater verifies the supplied binary hash and runs only its --version as the
service account. Immediately before stopping a RustProxy 0.14.0+ service it creates a
private one-shot managed-update marker. During graceful shutdown the process forces hot
objects into the enabled cold tier even when ordinary cold_on_shutdown=false; the marker
is removed before backup. The managed unit allows up to 120 seconds for controlled stop.
If cold storage is disabled, RAM-only cache cannot survive the process exit. The updater
then stages an immutable release, stops the service and backs up the entire data directory. This includes SQLite and sidecars, certificates, account
keys, provider credentials, Certbot renewal configs and internal relative symlinks.
Code changes use an atomic current symlink switch. It then starts the new process
and requires systemd activity, minimal local /healthz readiness, and the actual
MainPID executable matching the staged, locally version-checked binary. The local
health probe follows the persisted administration bind and HTTP/HTTPS mode (including
the configured Host allowlist), rather than assuming the install-time plain-HTTP
address. Version is no longer exposed by the anonymous health endpoint.
The managed systemd unit is regenerated from the saved installation record and
daemon-reload is run before startup. The previous unit is included in the backup
and restored on rollback. Keep custom service policy outside this managed unit or
reapply it after an update.
A failed start/health check restores the previous release, private data and
managed systemd unit. The reported error now includes the original update failure;
recent service status/journal output is emitted before rollback when available.
Failed-generation data is retained alongside the original data directory; the
backup is retained under /opt/rustproxy/backups. A failed rollback health check is
reported explicitly. Protect these directories: they contain private keys and
credentials. They are never automatically pruned. Plan enough free space for the
full backup, restored data, retained failed state and old/new executable releases.
During development, if an older saved vhost/settings document is intentionally incompatible with the current format, use:
sudo bash update.sh --reset-config --binary target/release/rust-proxy
This happens only after the backup is created. It deletes the saved desired
configuration row so the new process creates current listener defaults; administrator
account data, certificates, ACME/provider files and other private state are retained.
Do not use this flag when the old vhost/settings configuration must be preserved.
After a failed update, do not run install.sh: rollback leaves the previous
installation installed. Fix the reported cause and rerun update.sh (optionally
with --reset-config for this development-only incompatible-config case).
An update interrupts traffic; zero-downtime deployment is not implemented. --no-start
leaves the service stopped and cannot certify a healthy installation. Backup failure
leaves the old code/data in place and attempts to restart a previously running service.
Keep an independent off-host backup; local rollback is not a disaster-recovery plan.
Existing source-tree installations from earlier archives
Do not point a first installer at your old live data. To keep the current source- tree deployment, stop RustProxy and use the retained source updater:
bash /path/to/new/RustProxy/update.sh --source-tree /path/to/old/RustProxy --stopped
This mode keeps the old upgrade.sh contract and does not install a systemd service
or build a binary. Back up all private data and ACME profiles separately before
starting it. See migration. To adopt the new service layout, make an
explicit offline migration with ownership, absolute Certbot paths and port settings
reviewed; do not merely move a Certbot account tree to another location.
Server acceptance
Check actual non-root HTTP/TLS/QUIC binds, certificate assignment, a real renewal
validation (Dry run), backend failures, live traffic, Basic Auth over TLS, ACLs,
WebSocket enable/disable and location isolation. Verify the backup can be restored
on another isolated instance. Run systemctl status rustproxy and
journalctl -u rustproxy after installation/update. Only then decide on rollout.