Installation¶
The quick install is a single command - that's the path most people want. The other tabs cover building from source and a local dev checkout.
Requirements
- Linux - for the user-level systemd services (the app itself runs anywhere Django does).
- PostgreSQL 15+ (17 or 18 recommended) and Redis.
- Everything else - Python 3.13, Node, all dependencies - is bundled with the quick installer, so a fresh box needs nothing else.
Install¶
Every release ships a self-contained bundle - Python, Node, all dependencies and the prebuilt frontend baked in. On a fresh Ubuntu/Debian box, one line does everything:
The bootstrap script resolves the latest release automatically (so
there's no version to fill in), downloads the offline bundle, verifies its
published SHA-256, unpacks it, and runs the bundled installer - everything
after -- is passed straight through to it. Pin a specific version with
DANBYTE_VERSION:
curl -fsSL https://danbyte.net/install.sh | DANBYTE_VERSION=0.9.10 bash -s -- --host danbyte.example.com
It creates the danbyte service user, installs to /opt/danbyte, generates
all secrets, sets up the database, starts the services, writes logs to
/var/log/danbyte, and puts nginx + TLS in front.
When it finishes it prints the admin password - open
https://danbyte.example.com/, sign in as admin, and change it under
User → Preferences.
Manual bundle download (airgapped / offline)
Prefer to fetch the bundle yourself - e.g. to carry it onto an airgapped
host? Download the release asset, verify it, and run its bundled
installer. Replace <version> with the latest release number:
base=https://github.com/danbyte-net/danbyte/releases/latest/download
curl -fsSLO $base/danbyte-<version>-linux-x86_64.tar.gz
curl -fsSLO $base/danbyte-<version>-linux-x86_64.tar.gz.sha256
sha256sum -c danbyte-<version>-linux-x86_64.tar.gz.sha256
sudo tar xzf danbyte-<version>-linux-x86_64.tar.gz # as root, so root owns what it runs
cd danbyte-<version>-linux-x86_64
sudo ./install.sh --host danbyte.example.com
The bundle needs no PyPI / npm / python.org access. Only the OS
packages it builds on - postgresql, redis-server, nginx - come from
your distro; on an airgapped host, point apt at a local mirror or
pre-install them.
All installer options - flags, environment variables & what it does
The one-liner and the manual bundle run the same installer
(install.sh inside the bundle). Everything after -- in the one-liner
is passed straight to it:
curl -fsSL https://danbyte.net/install.sh | bash -s -- --host db.example.com --no-nginx -y
# │ │ └──────────── passed to install.sh ─────────────┘
# │ └─ end of bash options; the rest are the script's args
# └─ run the piped script from stdin
Installer flags (install.sh)
| Flag | What it does | Default |
|---|---|---|
--host <name> |
Hostname/IP the server answers on. Sets ALLOWED_HOSTS (<host>,127.0.0.1,localhost), the nginx server_name, the TLS cert name, and the admin email (admin@<host>). |
- (set it) |
--host=<name> |
Same, = form. |
- |
--service-home <path> |
Install location (app lands in <path>/danbyte). On a re-run it auto-detects the existing install, so you rarely set this. |
/opt/danbyte |
--no-nginx |
Don't install/configure nginx or TLS - for running your own reverse proxy. Also sets DANBYTE_HTTPS=False so Secure cookies/HSTS don't break login without a TLS terminator. |
nginx on |
--force |
On a re-run: upgrade over a git checkout, or past an upgrade lock nothing holds any more. Never a downgrade. | off |
--skip-backup |
On a re-run: no pre-upgrade backup. | off |
--host-only |
On a host that runs Danbyte: only the root steps an upgrade ends with (logrotate, nginx + TLS, the certificate unit), from the bundle of the release that runs, with no upgrade stage. It changes no code, so a git checkout takes it too. | off |
--adopt |
On a re-run or with --host-only: replace a Danbyte nginx site edited by hand with the new render, keeping a backup. Without it the new render lands beside the site as danbyte.conf.new. |
off |
--unattended, -y |
Skip interactive confirmation prompts (scripted / CI installs). | prompts on |
Environment variables (set before the command; alternative to flags)
| Variable | Same as / purpose | Default |
|---|---|---|
DANBYTE_HOST |
--host |
- |
SERVICE_USER |
OS account the app runs as | danbyte |
SERVICE_HOME |
--service-home |
/opt/danbyte |
DANBYTE_LOG_DIR |
log directory | /var/log/danbyte |
DANBYTE_VERSION |
bootstrap only - pin a specific release instead of the latest (put DANBYTE_VERSION=0.9.10 right before bash in the one-liner) |
latest |
What the installer does, in order:
- Installs OS services from your distro -
postgresql,redis-server, and (unless--no-nginx)nginx. On a host that just booted and is still installing its own updates, it waits up to 15 minutes for the package lock instead of failing. - Creates the dedicated
danbyteservice user (rootless), home = the install path. - Deploys the app to
<service-home>/danbyteand builds the venv from the bundle's vendored CPython 3.13 + wheelhouse (no internet needed). - Generates secrets with Python's CSPRNG and writes a
chmod 600, service-user-owned.env-DJANGO_SECRET_KEY+MONITORING_SECRET_KEY(~400-bit), a 24-char DB password, and a 20-char admin password. - Creates the PostgreSQL role + database, runs migrations, and bootstraps the
adminsuperuser. - Installs the systemd units (web, workers, websocket, docs, timers), writes logs to
/var/log/danbyte, and - unless--no-nginx- puts nginx + TLS in front, with the root unit that applies a certificate the app drops (Settings → Updates → Site certificate). That unit runs a root-owned copy of its script from/usr/local/libexec/danbyte/, never the one in the app directory, and answers in/var/lib/danbyte-tls/. - Prints the generated admin password at the end.
Run again on a box that already has Danbyte, the installer upgrades
it with the release's own upgrade stage instead of steps 3-6 - see
Upgrading → Offline bundle. Everything it runs as root
comes from the unpacked bundle, never from the app directory the
service account owns, and what it puts in that directory - the code,
.env - the service account writes; unpack the bundle as root.
PostgreSQL and Redis are native, not containers
A production install uses the distro's postgresql and
redis-server and creates the role and database with psql. The
docker-compose.yml in the repo and the danbyte-infra unit that
runs it are development only - they are never installed on a
production host. If you already run PostgreSQL, the installer uses
it: point DB_HOST / DB_PORT / DB_NAME / DB_USER /
DB_PASSWORD in .env at your server and re-run
manage.py migrate.
Installs made before this was fixed may have a stray
danbyte-infra unit and an unused Postgres container. Remove them
with:
- Prints the generated admin password at the end.
Where things land
| Path | Contents |
|---|---|
<service-home>/danbyte |
the application + .env (600, owned by danbyte) |
/var/log/danbyte |
service logs (make logs / make logs-file) |
systemd --user/system units |
danbyte-web, danbyte-workers, danbyte-ws, timers |
Re-running = upgrade. Running the installer again (same or newer
bundle) finds the existing install by the service user's home, reuses
the existing .env (secrets and DB password are preserved), and just
redeploys + migrates + restarts. It never regenerates secrets on an
upgrade. (The in-app updater under Settings → Deployment does the same
thing from the UI.)
After first login: change the admin password (User → Preferences) and
remove DJANGO_SUPERUSER_PASSWORD from .env. The long machine keys
(DJANGO_SECRET_KEY, MONITORING_SECRET_KEY, DB_PASSWORD) stay in
.env permanently - don't change MONITORING_SECRET_KEY once credentials
are stored or existing ciphertext becomes unreadable.
A start-to-finish install on a fresh Ubuntu/Debian box.
1 · Packages + service user. Danbyte runs rootless under a dedicated
danbyte account whose home is /opt/danbyte:
sudo apt update && sudo apt install -y postgresql redis-server git curl
curl -LsSf https://astral.sh/uv/install.sh | sh # Python 3.13 manager
sudo adduser --disabled-password --gecos "Danbyte" --home /opt/danbyte danbyte
sudo loginctl enable-linger danbyte # start at boot, no login
sudo install -d -o danbyte -g danbyte -m 755 /var/log/danbyte
2 · Secrets - generated in your browser.
The blocks below are pre-filled with secrets generated on this page
Every secret in the steps below (DJANGO_SECRET_KEY,
MONITORING_SECRET_KEY, the database password, the admin password) is
randomly generated in your browser when this page loads - so no two
installs share a "default". The same value is reused consistently across
steps (the DB password in the SQL matches the one in your .env), so you
can copy each block as-is. Hit ↻ Regenerate for a fresh set, and
store them somewhere safe - you can rotate any of them later (see
Rotating secrets).
3 · Database - create the role + database (the password is your generated one):
sudo -u postgres psql <<SQL
CREATE ROLE danbyte LOGIN PASSWORD 'GENDBPASSWORD';
CREATE DATABASE danbyte OWNER danbyte;
SQL
4 · Code + venv. Switch to the service user
(sudo machinectl shell danbyte@) and clone into its home:
git clone …/danbyte ~/danbyte && cd ~/danbyte
uv python install 3.13 && uv venv --python 3.13 .venv
VIRTUAL_ENV=$PWD/.venv uv pip install -r requirements.txt
5 · .env. Write ~/danbyte/.env - the secret key, monitoring key, DB
password, and admin password below are pre-filled with your generated
values. Set ALLOWED_HOSTS to your server's hostname or IP. DEBUG=False in
production; MONITORING_SECRET_KEY encrypts stored SNMP/SSH/SMTP/LDAP
credentials (required when DEBUG=False); the DJANGO_SUPERUSER_* line
seeds the admin on first bootstrap.
DJANGO_SECRET_KEY=GENDJANGOKEY
DEBUG=False
ALLOWED_HOSTS=your-server.example.com
DB_NAME=danbyte
DB_USER=danbyte
DB_PASSWORD=GENDBPASSWORD
DB_HOST=127.0.0.1
DB_PORT=5432
REDIS_URL=redis://localhost:6379/0
DANBYTE_LOG_DIR=/var/log/danbyte
MONITORING_SECRET_KEY=GENMONITORINGKEY
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@your-org.example
DJANGO_SUPERUSER_PASSWORD=GENADMINPASSWORD
Keep it private: chmod 600 ~/danbyte/.env.
6 · Migrate, build, and start. bootstrap seeds the default
organization, tenant, and status catalog, and creates your admin user:
.venv/bin/python manage.py migrate
.venv/bin/python manage.py bootstrap
make frontend-install frontend-build collectstatic
make install-services install-prod-services
systemctl --user enable --now danbyte-web danbyte-ws danbyte-frontend-prod danbyte-workers danbyte-fastlane
7 · nginx + TLS. These steps run as root, so not from
~danbyte/danbyte: the service account owns it, and a file changed there
would run as root. From your own login, with a copy of the same code that
root owns:
sudo apt install -y nginx
sudo git clone …/danbyte /root/danbyte-src # what ~danbyte/danbyte runs
sudo make -C /root/danbyte-src host-sync APP=/opt/danbyte/danbyte HOST=danbyte.example.com
That is what the bundle installer does as root: the nginx site with a
self-signed certificate, logrotate for /var/log/danbyte, and the root
unit that applies a certificate dropped from the app, which runs a
root-owned copy of its script in /usr/local/libexec/danbyte. After an
upgrade, run it again from a copy of the new release (see
After an upgrade).
Open https://danbyte.example.com/ and sign in as admin. The
certificate is self-signed; for a public host get a real one from
Settings → Updates → Site certificate - Let's Encrypt is one click,
or upload a pair (see the site's own
certificate).
The services (and why there are a few)
Danbyte runs as a handful of small user-level systemd units:
| Unit | Serves |
|---|---|
danbyte-web |
HTTP - the Django app (gunicorn) on 127.0.0.1:8000 |
danbyte-ws |
WebSockets / presence (daphne) on 127.0.0.1:8002 |
danbyte-frontend-prod |
the built SSR frontend (node) on 127.0.0.1:3000 |
danbyte-workers |
background jobs - scans, deploys (RQ) |
danbyte-fastlane |
sub-minute checks, from an in-memory schedule |
danbyte-* timers |
drift dispatch, cleanup, materialise, … |
gunicorn serves all plain HTTP; daphne serves only /ws/. Keeping the
ASGI server off the HTTP path is deliberate - putting it in front of
everything wedges ordinary requests.
Shortcut: make service-user
With the repo checked out, make service-user does all of step 1 (creates
the account + /opt/danbyte home, enables linger, adds your login to the
group, creates /var/log/danbyte). Relocate with
make service-user SERVICE_HOME=/var/lib/danbyte.
git clone …/danbyte && cd danbyte
uv python install 3.13 && uv venv --python 3.13 .venv
VIRTUAL_ENV=$PWD/.venv uv pip install -r requirements.txt
# Postgres role + db (dev creds are fine locally)
sudo -u postgres psql <<SQL
CREATE ROLE danbyte LOGIN PASSWORD 'danbyte';
CREATE DATABASE danbyte OWNER danbyte;
SQL
export DB_HOST=127.0.0.1 DB_USER=danbyte DB_PASSWORD=danbyte DB_NAME=danbyte
.venv/bin/python manage.py migrate
.venv/bin/python manage.py bootstrap
make install-services backend-up
Open http://localhost:8000/prefixes/.
Advanced¶
Reference material - you don't need any of this for a first install.
Logs¶
With DANBYTE_LOG_DIR set (the quick installer points it at /var/log/danbyte),
the app writes log files there in addition to the systemd journal:
| Where | What |
|---|---|
/var/log/danbyte/danbyte.log |
Application log - Django, workers, monitoring/LDAP |
/var/log/danbyte/gunicorn-{access,error}.log |
The web server's request + error logs |
journalctl --user -fu danbyte-web |
Per-service process output, still in the journal |
make logs follows the journal; make logs-file tails the files. Leave
DANBYTE_LOG_DIR unset in dev to keep logs on the console only.
Rotation. Every web and background process writes danbyte.log, so the
processes do not rotate it themselves: the installer puts
/etc/logrotate.d/danbyte in place (from deploy/logrotate/danbyte), which
rotates every *.log there at 10 MB, keeps five, and uses copytruncate so
no process needs a signal. Without that file the app falls back to rotating
danbyte.log itself - which loses lines between processes - and gunicorn
keeps its logs in the journal, because an access log nothing rotates only
grows.
Web workers¶
gunicorn starts twice the CPU count plus one sync workers, capped at 8.
Each holds a database connection, and RQ workers and daphne hold theirs, so
past that a large host ran out of Postgres's default 100 connections. Set
WEB_CONCURRENCY in .env to go higher, and raise max_connections in
Postgres with it. GUNICORN_TIMEOUT (default 60 s) is how long one request
may run before its worker is replaced.
A stalled or restarting Redis no longer takes the web tier down: the one cache read every request makes falls back to the database, and Redis calls give up after two seconds.
Rotating secrets¶
Any of these can change later without a reinstall:
- Admin password - User → Preferences (or
manage.py changepassword). DJANGO_SECRET_KEY- edit.env,make restart(signs everyone out).- DB password -
ALTER ROLE danbyte PASSWORD '…';, update.env,make restart.
Security checklist¶
-
DEBUG=FalseandALLOWED_HOSTSset to your real host(s). - nginx + TLS in front; Danbyte not exposed directly on
:8000. - Admin password changed after first login;
DJANGO_SUPERUSER_PASSWORDremoved from.env. - Runs as the dedicated non-login
danbyteuser, with linger enabled. -
.envischmod 600, owned bydanbyte, never committed. - Postgres / Redis bound to localhost (or firewalled).
Environment variables¶
| Variable | Default | Purpose |
|---|---|---|
DB_HOST / DB_PORT |
127.0.0.1 / 5432 |
PostgreSQL host / port |
DB_NAME / DB_USER / DB_PASSWORD |
danbyte |
Database name / role / password |
ALLOWED_HOSTS |
localhost,127.0.0.1 |
Comma-separated; set on a server |
DJANGO_SECRET_KEY |
dev-key-change-in-prod |
Required when DEBUG=False |
DEBUG |
True |
Disable in prod |
CORS_ALLOWED_ORIGINS |
http://localhost:3000 |
Frontend origin(s) |
DANBYTE_LOG_DIR |
- (console only) | Writable dir for file logs, e.g. /var/log/danbyte |
DJANGO_SUPERUSER_USERNAME / _EMAIL / _PASSWORD |
- | bootstrap creates this admin when set |
See danbyte/settings.py for the full list.
Verifying¶
make status # all services
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/prefixes/ # expect 200
Next: Upgrading when a new release lands.