Skip to content

Hive Server Deployment ​

Deploy the Cortexa Hive relay server as a Linux daemon with systemd, behind nginx or Caddy for TLS termination and WebSocket proxying.

Building the Binary ​

Native Build ​

On the target machine (or a machine with the same architecture):

bash
cargo build -p cortexa-hive --release

The binary lands at target/release/cortexa-hive.

Cross-Compilation ​

For Raspberry Pi (ARM64) from a Linux or macOS build machine:

bash
rustup target add aarch64-unknown-linux-gnu
cargo build -p cortexa-hive --release --target aarch64-unknown-linux-gnu

The binary lands at target/aarch64-unknown-linux-gnu/release/cortexa-hive.

Install ​

Copy the binary to the server:

bash
sudo cp target/release/cortexa-hive /usr/local/bin/
sudo chmod +x /usr/local/bin/cortexa-hive

First Run ​

Start the server manually to generate the initial configuration:

bash
./cortexa-hive --port 7474 --data-dir /path/to/hive-data

On first run, the server:

  1. Creates the data directory if it does not exist.
  2. Generates hive.toml with a random admin token.
  3. Creates hive.db (SQLite database).
  4. Prints the admin token to stdout.

Save the admin token. You will need it to create invite codes and manage publications.

CLI Flags ​

FlagDefaultPurpose
--port7474Listen port
--bind0.0.0.0Bind address
--data-dir./hive-dataData directory (DB, config, published HTML)
--config<data-dir>/hive.tomlConfig file path override

Verify ​

bash
curl http://localhost:7474/health
# {"ok":true,"version":"0.13.1","name":"Cortexa Hive"}

Data Directory Layout ​

hive-data/
  hive.toml           # Server config (port, bind, admin token, site title)
  hive.db             # SQLite: peers, invites, shared items, publications
  pub/                # Generated static HTML files
    index.html        # Blog index page
    feed.xml          # RSS/Atom feed
    my-first-post.html
    another-thought.html

systemd Service ​

These instructions work on Raspberry Pi OS, Ubuntu, Debian, Fedora, and any systemd-based distro.

1. Create a dedicated user ​

bash
sudo useradd --system --no-create-home --shell /usr/sbin/nologin cortexa-hive
sudo mkdir -p /var/lib/cortexa-hive
sudo chown cortexa-hive:cortexa-hive /var/lib/cortexa-hive

2. Create the service unit ​

bash
sudo tee /etc/systemd/system/cortexa-hive.service > /dev/null << 'EOF'
[Unit]
Description=Cortexa Hive relay server
After=network.target
Wants=network-online.target

[Service]
Type=simple
User=cortexa-hive
Group=cortexa-hive
ExecStart=/usr/local/bin/cortexa-hive --port 7474 --data-dir /var/lib/cortexa-hive
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/cortexa-hive
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

The hardening directives:

DirectiveEffect
NoNewPrivileges=truePrevents the process from gaining additional privileges
ProtectSystem=strictMounts /usr, /boot, /efi read-only
ProtectHome=trueMakes /home, /root, /run/user inaccessible
ReadWritePaths=/var/lib/cortexa-hiveAllows writes only to the data directory
PrivateTmp=trueGives the service its own /tmp

3. Enable and start ​

bash
sudo systemctl daemon-reload
sudo systemctl enable cortexa-hive
sudo systemctl start cortexa-hive

4. Retrieve the admin token ​

The token is generated on first start and saved in hive.toml:

bash
sudo grep admin_token /var/lib/cortexa-hive/hive.toml

Or check the journal for the first-run log line:

bash
journalctl -u cortexa-hive --no-pager | grep "admin token"

5. Verify ​

bash
# Service status
sudo systemctl status cortexa-hive

# Health check
curl http://localhost:7474/health

# Follow logs
journalctl -u cortexa-hive -f

Reverse Proxy with nginx ​

For HTTPS access from outside your LAN, put the Hive behind nginx. WebSocket support is required since clients connect via GET /ws.

Install ​

bash
sudo apt install nginx certbot python3-certbot-nginx

Configuration ​

Create /etc/nginx/sites-available/cortexa-hive:

nginx
server {
    listen 80;
    server_name hive.example.com;

    location / {
        proxy_pass http://127.0.0.1:7474;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 86400s;
    }
}

Key settings:

DirectivePurpose
proxy_http_version 1.1Required for WebSocket upgrade
proxy_set_header UpgradePasses the WebSocket upgrade header
proxy_set_header Connection "upgrade"Completes the WebSocket handshake
proxy_read_timeout 86400sKeeps WebSocket connections alive for 24 hours

Desktop clients older than v0.11.0 put their hive token in the WebSocket URL (/ws?token=…), and nginx's default access log records full URLs. The relay still accepts that for those clients (logging a deprecation warning), but you can keep the token out of the log by logging only the path. Add the log_format at http level (e.g. /etc/nginx/conf.d/cortexa-log.conf) and the access_log line inside location /:

nginx
log_format cortexa_noquery '$remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" '
                           '$status $body_bytes_sent "$http_referer" "$http_user_agent"';
nginx
access_log /var/log/nginx/access.log cortexa_noquery;

Current clients authenticate the socket with signed headers and never put a credential in a URL. Older logs may already contain tokens; if so, rotate them away, and re-invite any peer whose token may have been exposed.

Enable and get TLS certificate ​

bash
sudo ln -s /etc/nginx/sites-available/cortexa-hive /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d hive.example.com

Certbot automatically:

  • Obtains a Let's Encrypt TLS certificate.
  • Configures nginx to serve HTTPS on port 443.
  • Sets up automatic certificate renewal via a systemd timer.

Verify TLS ​

bash
curl https://hive.example.com/health

Reverse Proxy with Caddy ​

Caddy handles TLS automatically via Let's Encrypt with zero additional configuration.

Install ​

bash
# Debian/Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

Configuration ​

Edit /etc/caddy/Caddyfile:

hive.example.com {
    reverse_proxy localhost:7474
}

That is the entire configuration. Caddy:

  • Obtains and renews Let's Encrypt certificates automatically.
  • Enables WebSocket proxying by default.
  • Redirects HTTP to HTTPS.

Restart ​

bash
sudo systemctl reload caddy

Let's Encrypt TLS ​

Both nginx (via certbot) and Caddy (built-in) use Let's Encrypt for free, automated TLS certificates. Requirements:

  • A domain name pointing to your server's public IP (A record or AAAA for IPv6).
  • Ports 80 and 443 open in your firewall.
  • For certbot: the certbot package and the nginx plugin (python3-certbot-nginx).
  • For Caddy: nothing extra -- it handles ACME challenges automatically.

Certificates renew automatically. Certbot installs a systemd timer; Caddy handles renewal internally.

Client Configuration ​

After setting up the reverse proxy, desktop clients connect using the HTTPS URL:

  1. Open Settings > Hive in the Cortexa desktop app.
  2. Enter https://hive.example.com as the server URL.
  3. Enter your display name and an invite code.
  4. Click "Register & Connect".

The app stores the token and auto-connects on future launches.

Admin Dashboard ​

GET /admin behaves differently depending on whether this relay is paired with a Team server:

  • Unified (server_admin_team_id is set in hive.toml — see Hive Team Server Deployment): /admin 308-redirects to /team/, which is where the full Hive console now lives (a React SPA embedded in cortexa-hive-team, built from hive-console/). Sign in there with your Team account; owners of the designated team get the server-management panels (overview, signal feed, peers, projects/grants, invites, shares, publications, deployments, site) on top of the usual team/space/member/invite/audit views. The query string (e.g. a return_to) survives the redirect.
  • Relay-only (no Team server, server_admin_team_id unset): /admin serves a small, trimmed fallback page — Peers, Invitations and Site only. It carries a banner pointing you at the Team server for the full console, monitoring and alerts.

Relay-only fallback: signing in ​

On first visit the fallback page prompts you to create a login. Paste the server's admin_token (from hive.toml) once to authorize, then choose a username and password — stored as an Argon2 hash in hive.toml. After that you sign in with just username/password (the raw admin_token still works as a recovery credential — e.g. curl -H "Authorization: Bearer $(grep admin_token hive.toml | cut -d'"' -f2)" https://hive.example.com/admin/peers). Every dashboard request carries the session as an Authorization: Bearer header; its script is a separate file (/admin/fallback.js) served under a strict script-src 'self' CSP — no inline script, no inline event handlers.

What the relay-only fallback does ​

  • Peers — the registered peer roster with live online status and role; change a peer's role, or revoke/restore its signed-challenge key.
  • Invitations — create, copy, and revoke invite codes; see usage.
  • Site — pick a theme for the published /pub site and toggle public access.

The signal feed, project spaces/grants, shared-item search, publications browsing and Foundry deployment management live in the full Hive console — pair this relay with a Team server (set the same server_admin_team_id UUID in both hive.toml here and the Team server's hive-team.toml) to get them.

Public-access kill switch ​

Turning public access off makes the public blog at /pub return 403 to the world — while peer sync and the admin dashboard (unified or relay-only fallback) keep working. Logged-in admins can still read published posts (including their images and audio), since the admin session is honored on /pub too — from the unified console's Publications tab, or directly via /pub/<slug>.html from the relay-only fallback (which has no Publications tab of its own).

Roles & access control ​

Hive enforces visibility server-side via project spaces and per-peer roles. The role ladder is viewer < contributor < admin:

RoleCan do
viewerReceive shares for scopes/projects it's granted on. Cannot share (read-only).
contributorEverything a viewer can, plus share into the projects (or whole hive) where it holds contributor+.
adminFull access — share into any project space and manage the relay.

A share is either everyone-scope (whole hive) or project-scope (only peers granted on that project's key — which is the project slug). For a team hive, create scoped invites: pick the project(s) and a role, and the invited peer becomes a viewer hive-wide but holds that role on just those projects. That is how a teammate connects "limited to the projects you invite them to" without seeing the rest of the hive. For a personal hive (syncing your own machines), an unscoped invite grants a hive-wide role and full everyone-scope sync.

To grant an existing peer access to a project, open the unified console's Projects → (project) → grants, add the peer's client id and choose a role; revoke the same way. (Project spaces aren't in the relay-only fallback — pair this relay with a Team server to manage them from a browser, or use the peer's own POST /project call.) For the user-facing side of this, see the Hive Sharing & Teams guide.

Updating ​

To deploy a new version of the Hive server:

bash
# Build or copy the new binary
sudo cp cortexa-hive /usr/local/bin/
sudo systemctl restart cortexa-hive

On SIGTERM (what systemctl stop/restart sends) or Ctrl-C, the relay stops accepting connections, lets in-flight requests finish, closes every WebSocket with code 1001 (moving any shares still queued for a peer into its outbox, so they arrive on reconnect), and checkpoints the SQLite WAL into hive.db. The drain is capped at about 10 seconds, well inside systemd's default 90-second TimeoutStopSec; after that the process exits anyway and logs what it abandoned. Clients reconnect on their own.

The SQLite database and hive.toml config are preserved across restarts. Published files (index.html, feed.xml, post HTML) use atomic writes (tmp+rename) to prevent partial reads during updates.

Schema migration ​

Schema migration runs automatically on restart and is additive and fast — it only issues metadata-only ALTER TABLE ADD COLUMN / CREATE TABLE IF NOT EXISTS statements (instant regardless of DB size) and never rewrites existing rows. The one large index (shared_items(project_key)) builds in the background after the port binds, so startup is never blocked. Existing peers are grandfathered to the admin role, and nothing already shared is retroactively hidden. No manual SQL is needed.

Do not rotate admin_token after upgrading

Shared-item content and uploaded files are encrypted at rest with a key derived from admin_token. If you change admin_token after the project-spaces upgrade, all previously-encrypted content and files become permanently unreadable. Keep the existing token.

Verify after update ​

bash
sudo systemctl status cortexa-hive
curl https://hive.example.com/health
journalctl -u cortexa-hive -n 20

Configuration Reference ​

The hive.toml file supports these fields:

toml
port = 7474
bind = "0.0.0.0"
site_title = "My Thoughts"
admin_token = "64-char-hex-string"

# Optional — pairs this relay with a Team server. Set to the SAME UUID as
# the Team server's `server_admin_team_id` in `hive-team.toml` to put /admin
# in unified mode (redirects to /team/, the full console). Leave unset for a
# standalone relay (the trimmed Peers/Invitations/Site fallback at /admin).
# server_admin_team_id = "11111111-1111-4111-8111-111111111111"
FieldDefaultDescription
port7474Listen port (overridden by --port CLI flag)
bind"0.0.0.0"Bind address (overridden by --bind CLI flag)
site_title""Title shown on the blog index page (falls back to "Cortexa Hive")
admin_token(auto-generated)Admin bearer token for invite/publish management
server_admin_team_idunsetTeam UUID whose owners get the full Hive console at /team/; unified mode makes /admin redirect there. Must match the Team server's server_admin_team_id exactly.

Security Checklist ​

  • [ ] Run behind a reverse proxy with TLS -- never expose the raw HTTP port to the internet.
  • [ ] Use strong, unique invite codes and set max_uses. Invites expire after 7 days by default and admin-role invites after 24 hours; revoke older never-expiring codes you no longer need.
  • [ ] Keep the admin token secret. It is written to hive.toml on first run. Restrict file permissions:
    bash
    sudo chmod 600 /var/lib/cortexa-hive/hive.toml
    sudo chown cortexa-hive:cortexa-hive /var/lib/cortexa-hive/hive.toml
  • [ ] The systemd service runs as a dedicated user with strict filesystem access.
  • [ ] Enable firewall rules to allow only ports 80, 443, and SSH (or your custom SSH port).
  • [ ] Shared-item content, preview, summary, and meta are AES-256-GCM encrypted at rest in hive.db (key derived from admin_token, the same trust root as file blobs). This protects against disk or backup theft. It is at-rest, not end-to-end: the live relay decrypts content to fan it out to peers and to render publications, so the running server still sees plaintext. End-to-end encryption is not implemented.
  • [ ] Do not rotate admin_token after upgrading — the content- and file-encryption keys derive from it, so changing it makes existing encrypted content and files unreadable.

Monitoring ​

Logs ​

bash
# Follow live logs
journalctl -u cortexa-hive -f

# Last 100 lines
journalctl -u cortexa-hive -n 100

# Logs since boot
journalctl -u cortexa-hive -b

Health Check ​

Set up a periodic health check with a cron job or monitoring tool:

bash
curl -sf https://hive.example.com/health | jq .ok

Returns true when the server is operational.

Disk Usage ​

The SQLite database grows with shared items. Check periodically:

bash
du -sh /var/lib/cortexa-hive/
ls -lh /var/lib/cortexa-hive/hive.db

Published HTML files are small (a few KB each) and stored in /var/lib/cortexa-hive/pub/.

Built with Rust, Tauri, and a lot of captured thoughts.