Skip to content

Production install (Ubuntu 24.04)

See also: Installation hub · Reverse proxy and TLS · Installer reference · Troubleshooting · Upgrading · Multiple instances

Overview

This is the main bare-metal guide: fifteen steps from a clean Ubuntu 24.04 LTS server to a running, TLS-protected, supervised Dédalo instance. Every command is meant to be run as root (or under sudo) unless it is explicitly prefixed with sudo -u dedalo.

The engine is a single long-lived Bun process. It listens on a unix socket; a reverse proxy owns TCP, TLS, the client static files and the media bytes. PostgreSQL is the system of record.

Other distributions

RHEL, Rocky, AlmaLinux and Fedora follow the same fifteen steps with different package names, a different PostgreSQL repository, SELinux and firewalld. The deltas are in RHEL-based systems — read this page first, then that one.

The layout this guide builds

/opt/dedalo/master_dedalo/     the repo (git clone) — the artifact
/opt/dedalo/private/           ../private — .env (0600), sessions, state, backups
/opt/dedalo/.bun/bin/bun       the pinned runtime
/srv/dedalo/media/             MEDIA_PATH — originals + derivatives + markers
user/group: dedalo

Two properties of this layout are load-bearing, and both are checked by the installer:

  • private/ is a sibling of the repo, one level above it. It is never inside the served tree, and it holds every secret. The installer creates it (0700) — so /opt/dedalo/ must be writable by the service user.
  • MEDIA_PATH is absolute and independent of the repo. It can live on its own volume; media is by far the largest thing you will store.

The base directory is a choice, not a requirement. /opt/dedalo suits a single instance; if you expect to host several domains, install under /home/ded_<site>/ from the start — everything below is identical, only the base path changes — and follow Multiple instances on one server.

Hosting several domains on one server

This guide builds one instance. To run several domains on the same box — each with its own database, media and login — repeat this install per domain and follow Multiple instances on one server, which covers what must be unique per instance and how to template systemd.

Before you start: choose your media access mode

Media is served by the web server, not by Dédalo — but the access rules are generated by Dédalo. Two decisions here shape the whole install, so make them now: where MEDIA_PATH lives (step 1 creates it, step 8 persists it) and which access mode the web server enforces.

DEDALO_MEDIA_ACCESS_MODE Who can read a media file
(unset) the publication default — logged-in users plus anonymous readers of published records (fail-closed since 2026-08-24; set false to deliberately serve an open tree)
private logged-in Dédalo users only
publication logged-in users plus anonymous readers of published records, in public quality folders only

Three operator facts you cannot guess and must not forget:

  • No Dédalo process is ever in the media byte path. Authorisation is a single stat() performed by the web server, which is why sendfile, HTTP Range and the H.264 ?start= clipping still work on multi-gigabyte files.
  • The gate fails closed as 404, never 403. The existence of unpublished media is not disclosed.
  • Master qualities can never be made public. The original / modified folders are filtered out of the public list no matter what you configure.

How each mode actually works, and how you wire it into nginx or Apache, is step 11 — and it is not optional: an unwired gate serves the whole media tree to the world.

Steps

1. Service user and directory tree

1.1 Create the GNU/Linux user for Dédalo

sudo adduser --system --group --home /opt/dedalo --shell /usr/sbin/nologin dedalo

--group also creates a group named dedalo. Both matter: the unit in step 10 names the user and the group, and the group is what the web server is later added to so it can reach the socket.

Upgrading a host that already runs Dédalo? Do not create a second user

Adopt the existing one — the home, the media tree and the backups are already its. Its group, however, must change: migrating a v6 install to v7 explains what changed and gives the four commands.

1.2 Assign correct permissions to the Dédalo home. The user must have read and execute permissions on the /opt/dedalo directory.

sudo chmod 0755 /opt/dedalo

The parent of the repo must be writable by the service user

The installer creates ../private/ itself. If /opt/dedalo/ is not writable by dedalo, the pre-flight check fails with Private config directory is not creatable/writable.

1.3 Create the media directory. Media directory will be used to storage images, av, pdf and other media files. Must be writable by the service user.

sudo mkdir -p /srv/dedalo/media

1.4 Give access to the Dédalo user to write in the media directory.

sudo chown dedalo:dedalo /srv/dedalo/media

2. Base OS packages

2.1 Update the OS.

sudo apt update
sudo apt upgrade

2.2 Install base OS packages

sudo apt install -y git unzip gzip file ca-certificates curl

git and unzip are not only for you: the in-app code-update subsystem shells out to them. file is the fallback MIME sniffer for ambiguous uploads.

3. Media toolchain

3.1 Install OS libraries dependecies

sudo apt install -y ffmpeg imagemagick poppler-utils ocrmypdf librsvg2-bin
Tool Used for
ffmpeg / ffprobe audiovisual transcoding, posterframes, probing
qt-faststart moves the MP4 index to the front so video starts before it finishes downloading
ImageMagick image derivatives, thumbnails, colour-space conversion
pdftotext, pdftohtml, pdfinfo (poppler) PDF text extraction and page rendering
ocrmypdf optional automatic OCR of uploaded PDFs
rsvg-convert (librsvg) thumbnails of SVG records — the ONLY vector rasterizer Dédalo uses

Why SVG thumbnails do not go through ImageMagick

ImageMagick can render SVG, but only through the coder (MVG) that Dédalo's hardened ImageMagick policy disables — that coder is a long-standing remote-code-execution vector, and the policy is what keeps a hostile upload from reaching it. Vector rendering is therefore delegated to rsvg-convert, which parses SVG and nothing else. Without librsvg installed, SVG records still upload, store, display and download normally; only their thumbnail cannot be built, and the media-versions panel reports why.

ImageMagick 6 is supported

Ubuntu 24.04 ships ImageMagick 6, which provides convert and identify but no magick binary. This is fine: the image engine probes for magick first and falls back to convert/identify when it is absent (resolveMagick() in src/core/media/engine/imagemagick.ts). Nothing to configure.

Binaries are resolved from a platform base directory (/usr/bin on Linux) and each one can be overridden individually. Check qt-faststart in particular — it is not always in the same package:

3.2 Check the installation of libraries

command -v ffmpeg ffprobe qt-faststart convert identify pdftotext ocrmypdf rsvg-convert

If qt-faststart is missing or lives elsewhere, set its absolute path in the .env you write in step 9:

DEDALO_AV_FASTSTART_PATH=/usr/local/bin/qt-faststart
Extra OCR languages

ocrmypdf needs a Tesseract language pack per language you want to recognise (apt install tesseract-ocr-spa tesseract-ocr-cat …). Without the pack, OCR silently falls back to English.

4. PostgreSQL 18

4.1 Install the server and the client tools from the PostgreSQL project's own repository (PGDG), using a modern signed-by keyring:

sudo apt install -y curl ca-certificates
install -d /usr/share/postgresql-common/pgdg
curl -fsSL -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \
  https://www.postgresql.org/media/keys/ACCC4CF8.asc
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
  > /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install -y postgresql-18 postgresql-client-18

psql is a hard requirement, and it must not be older than the server

The installer, the database restore, the hierarchy import and the backup widget all shell out to psql / pg_dump. A missing client fails the pre-flight check outright; an older client refuses to connect to a newer server, which surfaces as a confusing mid-install failure.

The resolver (src/core/install/pg_bin.ts) looks at DEDALO_PG_BIN_PATH first, then a small set of version-suffixed locations, then $PATH. On a box that carries several major versions, pin the right one explicitly:

DEDALO_PG_BIN_PATH=/usr/lib/postgresql/18/bin

4.2 Verify both halves agree:

psql --version          # must be >= the server
sudo -u postgres psql -c 'SELECT version();'

5. The pinned Bun runtime

The runtime version is pinned in .bun-version and in package.json (engines.bun). Read the pin from the repo you are about to clone — do not copy a version number out of a document:

# at the time of writing: 1.4.0
BUN_VERSION=1.4.0
curl -fsSL https://bun.sh/install | BUN_INSTALL=/opt/dedalo/.bun bash -s "bun-v${BUN_VERSION}"
chown -R dedalo:dedalo /opt/dedalo/.bun
/opt/dedalo/.bun/bin/bun --version

Never run bun upgrade on a production box

The engine is coupled to version-specific runtime behaviour — JSONB parameter inference in Bun.sql above all. A silent drift there is a data-corruption class, not a performance regression. The server echoes its runtime at boot and warns loudly when it does not match the pin.

Consequences you must honour:

  • the systemd unit's ExecStart points at the absolute pinned path (/opt/dedalo/.bun/bin/bun), never at a floating bun on $PATH;
  • upgrading the runtime is a deliberate act: change the pin, run the test suite, deploy. See Upgrading.

The rationale is written up in engineering/PRODUCTION.md §1.

6. Get the code

You can obtain the code from offical repositories:

  • https://gitlab.com/renderpci/dedalo.git
  • https://github.com/renderpci/dedalo.git

Use one of them to clone the repository:

sudo -u dedalo git clone <your-dedalo-remote> /opt/dedalo/master_dedalo
cd /opt/dedalo/master_dedalo
sudo -u dedalo /opt/dedalo/.bun/bin/bun install --frozen-lockfile --production

--frozen-lockfile refuses to silently resolve a different dependency tree than the one that was tested. --production skips the dev dependencies (the test harness and the linters).

The cd is part of the command

sudo -u dedalo … does not move to that user's home — it inherits the directory you are standing in. Run the install from anywhere else and Bun reports could not find a package.json file to install from, having never looked at the clone. Combine them when in doubt:

sudo -u dedalo bash -c 'cd /opt/dedalo/master_dedalo && /opt/dedalo/.bun/bin/bun install --frozen-lockfile --production'

A clone is self-contained — there is no sync step and no fetch step

The browser libraries the client loads (Leaflet, three.js, D3, CKEditor,the PDF viewer…) ship with the repo: they come either from bun install (node_modules/) or from the committed vendor/ tree, and they are served through an explicit allowlist (src/core/client_libs/registry.ts). Nothing is downloaded at install time, nothing is copied into place, nothing can be half-built.

Only the dev-only libraries (the browser test harness) are dev dependencies, and they are gated behind DEDALO_DEV_MODE — so --production is safe.

7. Create the database and role — empty

The installer restores into a database; it does not create one, and it refuses a non-empty one (it probes for matrix_users and stops if the table already exists — an existing install is never clobbered).

Database name

Change the user, password and database name as needed.

sudo -u postgres psql <<'SQL'
CREATE USER dedalo_user PASSWORD 'a-long-random-password';
CREATE DATABASE dedalo_main WITH ENCODING='UTF8' OWNER=dedalo_user;
COMMENT ON DATABASE dedalo_main IS 'Dédalo: cultural heritage and memory management';
SQL

The role must be able to CREATE EXTENSION

The seed creates btree_gin, pg_trgm and unaccent in the target database. Making the role the database owner, as above, is the simplest way to grant that. If your policy forbids it, have a superuser create the three extensions in the empty database beforehand.

No ~/.pgpass needed

Dédalo threads PGPASSWORD into every psql / pg_dump subprocess and passes -h/-p explicitly, so local and remote databases work the same way. A ~/.pgpass file is still honoured by libpq if you prefer it (leave the password empty in the configuration and rely on peer/trust auth).

8. Run the installer

The installer has two front ends driving one engine: a headless CLI and a browser wizard. On a server, use the CLI — it needs no restart, no exposed pre-auth surface, and it ends by verifying a real login. The wizard route is written out in 8.4, along with the three things a server has to do differently for it.

The root password comes from the environment (DEDALO_INSTALL_ROOT_PASSWORD), never a flag — a --root-password argv is visible in ps and lands in your shell history. Read it (and the database password) with read -rs, so neither is ever typed literally. --media-path, --socket and --media-access-mode are persisted to .env, so the serving config is set here — there is nothing to append later.

8.1 Prepare passwords.

# Passwords are read silently — they never enter shell history or `ps`:
read -rsp 'Database password: '  DB_PASSWORD;                  echo
read -rsp 'New root password:  '  DEDALO_INSTALL_ROOT_PASSWORD; echo
export DB_PASSWORD DEDALO_INSTALL_ROOT_PASSWORD

8.2 Run the installer. Change the parameters before run it, according to your needs. Do not start bun by hand in this step as the installer will suggests.

cd /opt/dedalo/master_dedalo
sudo -u dedalo --preserve-env=DB_PASSWORD,DEDALO_INSTALL_ROOT_PASSWORD \
  /opt/dedalo/.bun/bin/bun run scripts/install.ts \
    --db-name dedalo_main \
    --db-user dedalo_user \
    --db-password "$DB_PASSWORD" \
    --db-host localhost \
    --db-port 5432 \
    --media-path /srv/dedalo/media \
    --media-access-mode publication \
    --socket /run/dedalo/dedalo_ts.sock \
    --entity institution \
    --entity-label 'My Institution' \
    --locale es-ES \
    --timezone Europe/Madrid \
    --langs lg-eng,lg-spa \
    --app-lang lg-eng \
    --data-lang lg-eng \
    --hierarchies es,lg

8.3 Clean up

unset DB_PASSWORD DEDALO_INSTALL_ROOT_PASSWORD

What happens to the two passwords

The root password flows only through the environment — --preserve-env hands it to the install process, so it is never in argv or history. The database password is a flag (--db-password), so for the few seconds the installer runs it is visible in ps; that is unavoidable, but sourcing it from $DB_PASSWORD keeps it out of your history.

--media-path/--socket replace the old append step

--media-path both write-probes the media root during the install AND persists MEDIA_PATH to .env. --socket persists SERVER_UNIX_SOCKET as /run/dedalo/dedalo_ts.sock — the path systemd and the proxy use; the built-in default /tmp/dedalo_ts.sock would not match and is the classic cause of a 502. So the serving keys are configured here, not appended in step 9.

Expected output:

Dédalo TS install — entity 'mib', db 'dedalo_main'

→ pre-flight checks
→ database connection
→ write ../private/.env
  generated DEDALO_SALT_STRING = ****
→ directories
→ restore database from seed
→ set root password
→ import hierarchies: es, lg
→ register tools
→ seal install
→ verify root login

✔ install complete — root login verified. Start the server with `bun run start`.

The installer suggests bun run start, but on this server you run the engine under systemd (step 10) — do not start it by hand. Step 9 is optional; you can go straight to step 10.

The full flag list, what each step does, and what the seed contains are in the installer reference.

8.4 Alternative: the browser wizard.

The wizard drives the same engine, and on this server it is the longer path — it needs a supervisor before the install, a way to reach HTTP before the proxy exists, and a manual fix-up afterwards. Use it only if you want the diagnostics panel; otherwise stay with 8.2 and skip to step 9. Its screens are documented in the installer reference; what follows is what a server changes.

Three facts set the procedure:

  1. Save config exits the process. Configuration is read once, at boot, so the wizard writes .env and then quits (exit 75) for a supervisor to restart it. Here that supervisor is the systemd unit — so step 10 moves ahead of the install.
  2. Without .env the engine listens on /tmp/dedalo_ts.sock, the built-in default — not the /run/dedalo/dedalo_ts.sock the unit and the proxy expect. So the wizard is not reachable through the proxy anyway; browse it over an SSH tunnel instead, which also keeps the pre-auth surface off the network entirely.
  3. The wizard has no field for the serving keys. MEDIA_PATH, SERVER_UNIX_SOCKET and DEDALO_MEDIA_ACCESS_MODE are CLI-only flags, so a wizard .env simply omits all three. Their defaults are wrong for this layout — media would resolve to /opt/dedalo/master_dedalo/media and the access gate would be off — so you append them by hand at the end.

8.4.1 Install the systemd unit now (all of step 10, brought forward), then add an install-time drop-in:

sudo systemctl edit dedalo-ts
[Service]
Environment=SERVER_TCP_PORT=3600
Environment=DEDALO_INSTALL_ALLOWED_IPS=loopback

That TCP listener binds every interface

The engine's SERVER_TCP_PORT listener is plain HTTP on 0.0.0.0, and until the wizard is finished it serves a pre-auth install surface. Port 3600 must be closed at the firewall before the unit starts — sudo ufw status — and the drop-in is removed the moment the install is over (8.4.4). DEDALO_INSTALL_ALLOWED_IPS=loopback is the second lock: over the tunnel the caller is the loopback address, so it matches here (behind a proxy it would not). It is also what the engine applies when the key is unset — the default admits the local machine and nobody else — so the line above is written out to say so, not to change it. The firewall stays the first lock regardless: a request that reaches this listener with no X-Forwarded-For is seen as local whatever machine it came from.

8.4.2 Start the engine and confirm it is in install mode:

sudo systemctl daemon-reload && sudo systemctl enable --now dedalo-ts
journalctl -u dedalo-ts -n 20 --no-pager | grep 'INSTALL MODE'
[boot] INSTALL MODE — no database configured yet (../private/.env absent).
Serving the install wizard at /dedalo/core/page/.

No such line means .env already exists and this is a normal boot — the wizard will not appear.

8.4.3 Tunnel from your workstation and run the wizard:

ssh -N -L 3600:127.0.0.1:3600 you@your-server        # leave it running

Open http://localhost:3600/dedalo/core/page/. The database step takes the values from step 7 — host localhost, port 5432, and the name, role and password you created there. At Save config the engine exits and systemd restarts it within RestartSec=3; leave the tab open, the Verify button retries, and a reload resumes the wizard rather than dropping to a login form. Work through to Finish, which is refused unless a root user with a password exists.

8.4.4 Append the three serving keys the wizard could not write, drop the install-time overrides, and restart:

sudo -u dedalo tee -a /opt/dedalo/private/.env >/dev/null <<'ENV'
MEDIA_PATH=/srv/dedalo/media
SERVER_UNIX_SOCKET=/run/dedalo/dedalo_ts.sock
DEDALO_MEDIA_ACCESS_MODE=publication
ENV
sudo rm -rf /etc/systemd/system/dedalo-ts.service.d
sudo systemctl daemon-reload && sudo systemctl restart dedalo-ts

Then close the tunnel, skip step 10 (the unit is already installed and running) and continue at step 11. Skipping 8.4.4 is the classic wizard aftermath: every request 502s because the proxy looks for the socket in /run/dedalo/ while the engine put it in /tmp/.

9. Configure the instance (optional)

The installer wrote ../private/.env — the database, entity, languages, the generated secret, and (from step 8's flags) MEDIA_PATH, SERVER_UNIX_SOCKET and DEDALO_MEDIA_ACCESS_MODE. The instance is fully configured to boot. Everything below is optional: production tuning, and a hardening checklist that only restates the safe defaults. Change nothing and the install is still correct — skip to step 10.

.env is append-only, documented keys only

Add keys; never rewrite the file by hand. A re-run of the installer preserves every key it does not manage, but a key you delete is gone. Each key is documented in ../private/sample.env and the configuration reference.

Production tuning — worth setting on a busy instance; each has a working default, so include only the lines you actually want to change:

sudo -u dedalo tee -a /opt/dedalo/private/.env >/dev/null <<'ENV'

# --- Serving ---
SERVER_IDLE_TIMEOUT_S=255        # runtime max; the proxy read timeout must be >= this
TRUSTED_PROXY_HOPS=1             # count of proxies that append X-Forwarded-For

# --- Database pool (per process) ---
DB_POOL_MAX=10                   # budget the SUM across processes vs max_connections
DB_STATEMENT_TIMEOUT_MS=60000    # 0 (off) by default — cap one runaway query

# --- Observability ---
DEDALO_ACCESS_LOG=true
DEDALO_SLOW_REQUEST_MS=5000

# --- Ontology ---
ACTIVE_ONTOLOGY_TLDS=dd,rsc,ontology,ontologytype,hierarchy,lg,utoponymy,nexus,oh,ich   # core set + this install's domain TLDs
ENV

Hardening checklist — these are already the defaults. You do not need to write them; they are listed so the security posture is explicit, and you verify them again at step 15:

DEDALO_DEV_MODE=false            # dev mode exposes the browser test harness
DEDALO_DEBUG_API_ERRORS=false    # otherwise exception text reaches the client
MEDIA_DEV_ROUTE_ENABLED          # leave UNSET — setting it true bypasses the media ACL
SESSION_COOKIE_SECURE=true       # requires TLS (the default)

DB_POOL_MAX deserves a second look on a multi-service box: the server, each diffusion runner and the RAG drain all draw from PostgreSQL's max_connections.

10. Run the engine under systemd

The engine is a long-lived process; systemd supervises it, creates its socket directory, and restarts it on failure. Do this before the reverse proxy — nginx has nothing to connect to until the engine is running and its socket exists.

Reference units ship under deploy/:

Unit What it does
dedalo-ts.service the server; creates /run/dedalo (RuntimeDirectory), Restart=always, journald capture, SIGTERM drain
dedalo-ts-watchdog.service + .timer every 30 s, curl --fail on /health over the socket; restarts the server on failure
dedalo-ts-restart.service the restart helper the watchdog fires
dedalo-backup.service + .timer the nightly backup set

1 — Copy the units:

cp /opt/dedalo/master_dedalo/deploy/dedalo-ts*.service \
   /opt/dedalo/master_dedalo/deploy/dedalo-ts*.timer \
   /opt/dedalo/master_dedalo/deploy/dedalo-backup.* /etc/systemd/system/

2 — Substitute the placeholders. The units ship with ALL-CAPS placeholders you must replace before starting anything:

Placeholder This guide's value
DEDALO_USER (User=) dedalo
Group=a separate value; must name a group that already exists (getent group dedalo) dedalo
WorkingDirectory /opt/dedalo/master_dedalo
ExecStart bun path /opt/dedalo/.bun/bin/bun
backup EnvironmentFile /opt/dedalo/private/.env
backup paths /opt/dedalo/private/backups/…, /srv/dedalo/media

Edit each file in place, or use systemctl edit --full <unit> after copying.

systemctl edit --full dedalo-ts.service
systemctl edit --full dedalo-ts-restart.service
systemctl edit --full dedalo-ts-watchdog.service
systemctl edit --full dedalo-ts-watchdog.timer
systemctl edit --full dedalo-backup.service
systemctl edit --full dedalo-backup.timer

After editing, verify the placeholders are gone:

systemctl cat dedalo-ts | grep -n DEDALO_USER   # must print NOTHING

Replace every DEDALO_USER — an unsubstituted placeholder fails with status=217/USER

User=DEDALO_USER names a user that does not exist, so systemd kills the process before Bun runs — the socket never appears and the proxy 502s with nothing wrong in the app. After editing.

The socket directory and permissions are handled by the shipped dedalo-ts.service already: RuntimeDirectory=dedalo creates /run/dedalo (owned by the service user) on every start, and UMask=0007 makes the socket group-writable. Nothing to add.

3 — Reload and start:

systemctl daemon-reload
systemctl enable --now dedalo-ts.service
systemctl enable --now dedalo-ts-watchdog.timer
systemctl enable --now dedalo-backup.timer

4 — Verify the engine is up and the socket exists:

systemctl status dedalo-ts --no-pager      # active (running), NOT activating (auto-restart)
ls -l /run/dedalo/dedalo_ts.sock           # srwxrwx--- dedalo dedalo
curl --fail --unix-socket /run/dedalo/dedalo_ts.sock http://localhost/health
# {"result":"ok","entity":"mib","db":"ok","request_id":"…"}

If status shows activating (auto-restart), read journalctl -u dedalo-ts -n 50 — the two usual causes are an unsubstituted DEDALO_USER (above) or a database the .env cannot reach.

The socket path and RuntimeDirectory must agree

SERVER_UNIX_SOCKET (persisted in step 8 as /run/dedalo/dedalo_ts.sock) must sit under the directory RuntimeDirectory=dedalo creates (/run/dedalo). /run is a tmpfs wiped on every reboot; RuntimeDirectory recreates it on each start, which a hand-made mkdir would not survive.

Why Restart=always and the watchdog are both needed

The server reads its configuration once, at boot — so a config change needs a restart, and any crash (OOM, native fault, unhandled error) must bring the process back; Restart=always does that. The watchdog catches a different class: systemd's native WatchdogSec needs sd_notify, which the runtime does not speak, so the 30-second curl /health timer stands in — and it sees process alive, service dead (database down, pool wedged) that Restart=always cannot.

11. Reverse proxy, TLS, and media access

The engine is running on its socket; now put a web server in front. Production serving is unix-socket-only — the proxy owns TCP and TLS, serves the client static files and the media bytes, and forwards the API and dynamic routes to the socket. It is also what enforces the media access mode you chose at the top of this guide.

Reverse proxy and TLS walks the full nginx and Apache setup: installing the web server, certbot, the generated media rule files, and the timeouts that matter. Come back here when the proxy answers on 443.

How the two access modes work

Rule A — the back-office cookie. When a user logs in, the engine sets a fixed-name cookie, dedalo_media_auth, whose value rotates daily and is also written as a zero-byte marker under <media>/.publication/auth/. The web server authorises a request by checking that the file named by the cookie exists. Today's and yesterday's values are both valid, so nobody is logged out of media at midnight.

Rule B — the publication markers (publication mode only). An anonymous visitor may read a file only when (a) it sits in a public quality folder and (b) the record is published. The record identity is parsed from the file name (…{component_tipo}_{section_tipo}_{section_id}[_lg-xxx].{ext}), and the web server checks for a marker at <media>/.publication/pub/{section_tipo}_{section_id}, maintained by the diffusion engine.

Let the proxy reach the socket

Connecting to a unix socket needs write permission on it. UMask=0007 (shipped in the unit) makes the socket srwxrwx---; add the web-server user to the dedalo group so it lands in the group bucket, then restart (not reload) the proxy — supplementary group membership is only read when a process starts:

usermod -aG dedalo www-data     # nginx on RHEL: usermod -aG dedalo nginx
systemctl restart nginx

Verify:

ls -l /run/dedalo/dedalo_ts.sock

Expected output:

→ `srwxrwx--- dedalo dedalo`, and a request through the proxy no longer returns `502`.

Optional media keys

# Which quality folders rule B may serve. Unset = derived from this install's
# quality catalogue (delivery qualities + thumbs + posterframes + subtitles).
DEDALO_MEDIA_PUBLIC_QUALITIES=["image/thumb","image/1.5MB","av/404","av/posterframe","av/subtitles","pdf/web"]

# Raw extra Apache directives appended to the generated rules (JSON array).
MEDIA_HTACCESS_ADDONS=[]

The whole subsystem is defined in engineering/MEDIA_PROTECTION.md; the administrator view is media protection.

12. First login and post-install

  1. Open https://your-domain/dedalo/core/page/ and log in as root with the password you set in step 8.
  2. Go to the Development Area and confirm the tools are registered (the installer does this unless you passed --skip-tools).
  3. Create an admin user, log out, log back in as that admin. Keep root for emergencies.
  4. Create your users and projects — see users and permissions.

A fresh install ships demo data

The default install path seeds the canonical test3 playground section — a small set of sample records used by the test suite and by the component documentation. It is harmless, but it is not yours. Delete the test3 section's records from the section list once you no longer need them, or hide the section from the menu with DEDALO_ENTITY_MENU_SKIP_TIPOS.

Importing a hierarchy is not the same as activating a thesaurus

--hierarchies imports the term and model records and realigns the counters. Making a hierarchy a browsable thesaurus tree (registering it in the hierarchy master and provisioning its virtual sections) is a separate post-install step you perform from the thesaurus tools — see installing new hierarchies. The core install is complete without it, and selecting no hierarchy at all is perfectly valid: the seed already carries the core ontology.

13. Backups

The backup set is four stores. The matrix database alone is not a backup:

  1. The matrix PostgreSQL database — the schema and every record.
  2. The RAG vector database, if you enabled RAG — a separate database, a separate dump.
  3. The media originals (MEDIA_PATH) — the original quality is the source of truth every derivative is rebuilt from. Derivatives need no backup.
  4. ../private/ — the .env secrets, the session store, ts_state.json.

deploy/dedalo-backup.service + .timer is the reference nightly job covering all four. The canonical rules — retention, what is derived data and therefore not worth dumping, and the restore drill — are in engineering/PRODUCTION.md §6 and in backup. Do not duplicate them into your own runbook; link to them.

A backup that has never been restored is a hypothesis

Restore-test into a scratch database at least quarterly.

14. Optional subsystems

All four are off by default. Turn on only what you need.

Diffusion (publication)

Publishes records to a MariaDB target database for a public website.

DEDALO_DIFFUSION_NATIVE=true
DEDALO_DIFFUSION_DB_HOST=localhost
DEDALO_DIFFUSION_DB_PORT=3306
DEDALO_DIFFUSION_DB_USER=dedalo_pub
DEDALO_DIFFUSION_DB_PASSWORD=…
DEDALO_DIFFUSION_DB_NAME=web_dedalo

You create the target database — the engine never does

A missing or ungranted target database is a loud configuration error (MissingTargetDatabaseError), not something the engine papers over. Create it and grant the role before enabling diffusion:

CREATE DATABASE web_dedalo CHARACTER SET utf8mb4;
GRANT ALL ON web_dedalo.* TO 'dedalo_pub'@'localhost';

The installer can write these keys for you (--diffusion --mysql-*), but it still does not create the database. Details: the diffusion engine.

DEDALO_RAG_ENABLED=true
DEDALO_RAG_DB_NAME=dedalo_rag

No code creates the RAG schema

The pgvector database, the vector extension and the base rag_embeddings table are not created by the engine. The DDL exists in exactly one place — the RAG cookbook — and you apply it by hand. (Once the base schema exists, the per-model partitions and the index queue table are created automatically.) Skip this and RAG fails at the first write.

AI assistant

The in-app assistant is disabled unless a model is configured. See assistant install.

H.264 real-time clipping

Serving audiovisual fragments by time range needs a web-server module. See H.264 streaming module.

15. Verify, then harden

Walk the whole path once, in order. Each line is a real failure mode if it does not pass.

  • [ ] curl --fail --unix-socket /run/dedalo/dedalo_ts.sock http://localhost/health200 with "db":"ok".
  • [ ] curl --fail https://your-domain/health → the same 200, through the proxy. A 404/403 here with the socket probe green means the vhost is missing its /health rule — see Reverse proxy and TLS.
  • [ ] https://your-domain/dedalo/core/page/ serves the login form over TLS.
  • [ ] Log in as the admin user. The menu renders.
  • [ ] Create a record in a section, save it, reload — the value persists.
  • [ ] Upload an image. The derivative and the thumbnail appear (this proves ImageMagick resolved and the media root is writable).
  • [ ] Search for the record you created.
  • [ ] GET /api/v1/counters as a global admin returns request, pool, queue and memory counters. (Anyone else gets a 404 — that is correct.)
  • [ ] systemctl reboot, then repeat the health check. The service, the socket and the proxy must all come back on their own.

Hardening recap — verify each, because each is a real hole:

Setting Production value Why
SERVER_TCP_PORT unset the TCP listener is a development convenience; production is socket-only
DEDALO_DEV_MODE false dev mode exposes the browser test harness and developer payloads
DEDALO_DEBUG_API_ERRORS false otherwise exception text is echoed to the client
MEDIA_DEV_ROUTE_ENABLED unset unset is already safe: the engine media fallback answers only on the TCP dev listener (unset in production) and only while protection is unconfigured. Setting it to true FORCES it on for every listener — the socket included — serving media with no per-record ACL and bypassing the generated rules entirely
DEDALO_INSTALL_ALLOWED_IPS unset (= the local machine only), or the exact address you install from the install surface is pre-auth until the instance is sealed; the default is fail-closed, and any is the only spelling that opens it
SESSION_COOKIE_SECURE true (the default) requires TLS — the browser drops a Secure cookie over plain HTTP
../private/.env 0600, owned by dedalo it holds every secret
../private/ 0700 it holds the session store and the media-auth store

After a successful install the wizard is gone

install_finish seals the instance (install_status: sealed in ts_state.json). From then on the whole install surface answers 404 — including on a restart, and including from an allowed IP. There is no way to re-open it accidentally.