Skip to content

Agent (gisila-agent)

gisila-agent is a single compiled Dart binary at /usr/local/bin/gisila-agent. It’s the only part of the panel that changes the host. The worker (root) calls it for each step of a job. The API server calls it without privileges only to read logs (logs), resource usage (stat) and its version.

You normally never run it yourself. This page helps you read build logs and worker output, and debug a node.

  • Every subcommand is idempotent: re-running it converges on the same state.
  • Arguments are validated before anything runs. --user must look like app_<id>, --work-dir must be under the apps root, and git URLs, refs, versions, hostnames, runtimes and source types are checked against strict patterns. Commands reject |, &, ;, $, <, >, backticks and control characters. Env var names must be identifiers.
  • Each run ends with one JSON status line on stdout: {"ok": true, "command": "…", …} or {"ok": false, "command": "…", "error": "…"}. A few subcommands print their own JSON result instead (runtime, app-health, firewall, stat, version).
  • The agent is started with an allowlisted environment, so the panel’s secrets in /etc/gisila/.env never reach builds or app processes.
Subcommand Key options Does
provision --app-id --user --work-dir [--port] Creates the Linux user and the work-dir layout (current, releases, shared, tmp, logs, .env).
build --app-id --user --work-dir --runtime --source-type [--git-url --git-branch --source-subdir --deploy-key-path --artifact-path --build-command --deploy-mode --<runtime>-version --env-json --no-cache --release-id --keep-release-ids] Fetches the source (git clone, unzip or binary install) and runs the runtime plugin’s build as the app’s user. Publishes releases/<id> and prunes releases outside --keep-release-ids.
apply-unit --app-id --user --work-dir [--port] --runtime [--start-command --source-subdir --memory-mb --cpu-quota --tasks-max --env-json --expose-mode], plus Python, Gunicorn and Celery options Writes the env file, AppArmor profile and systemd unit(s), resolves the start command to an absolute path, and reloads systemd.
apply-vhost --app-id --port [--hostname …] [--runtime --static-root --static-spa --media-root --protected-media --max-upload-mb] Renders the Nginx vhost, runs nginx -t and reloads.
activate-release --work-dir --user --runtime --release-id Rollback: points the app back at a kept release and restores its executable, toolchain link and virtualenv. No fetch or build.
restart, start, stop --user [--runtime] systemctl on the app’s unit(s).
app-health --user --runtime [--port --health-path --expose-mode --timeout-sec (45) --settle-sec (5)] Post-restart readiness check. Prints {"healthy", "detail", "units", "logTail"}.
expose-port, unexpose-port --port ufw allow / ufw delete allow <port>/tcp for tcp apps. Never closes an SSH port.
issue-cert --hostname certbot --nginx -d <hostname>, then reloads Nginx.
uninstall --user [--app-id --runtime --work-dir --hostname] Stops and disables the units, removes the unit, profile and vhost.
Subcommand Does
logs --user [--work-dir --lines --runtime --follow] Reads the app’s journal (needs only the systemd-journal group).
stat --user [--runtime] | --unit UNIT Resource usage as JSON. For Celery, sums every unit of the app.
exec --user --work-dir --command [--runtime --timeout] Runs a console or cron command as the app’s user, with its env and toolchain.
runtime install|remove|status --key RT [--version V] Manages runtime toolchains. status reports the versions actually on disk. Versioned runtimes require --version.
service install|configure|start|stop|uninstall --type T [--config JSON] Managed services (Redis, Memcached, Mailpit, PgBouncer, pgAdmin, mongo-express, …).
postgres <action> … Install, start, stop and uninstall clusters, create and drop databases, alter roles, configure settings, back up and restore.
mongo <action> … The same for MongoDB.
storage <action> … Install and manage Garage, create buckets and scoped keys.
mail setup, mail sync --domains JSON --accounts JSON Installs Postfix, Dovecot and OpenDKIM, then syncs domains, DKIM keys and mailboxes.
firewall status | apply --change-id N --op add|delete|enable|disable [--rule JSON --keep-ip IP --protect-port N --confirm-within S] | confirm --change-id N | rollback --change-id N ufw changes with a snapshot and an independent rollback timer. Refuses changes that would cut off SSH, 80, 443 or the requester.
version Prints {"version", "commit", "builtAt"}.

Each runtime is a plugin registered in the agent. It owns its toolchain install and removal and its build step:

Key Mode Versioned Toolchain location
dart build_execute yes per-version SDK under /opt
go build_execute yes per-version prefix
rust build_execute yes rustup
node build_execute yes fnm
bun build_execute yes per-version prefix
python direct_run yes pyenv
celery build_execute uses the app’s Python
zig build_execute no
binary direct_run no none
static static_publish no none

The commands the worker ran appear in each deployment’s build log. To reproduce one by hand, run it as root with the same arguments:

Terminal window
sudo gisila-agent app-health --user app_ab12cd --runtime dart --port 4001 --health-path /healthz
sudo gisila-agent logs --user app_ab12cd --lines 100
gisila-agent version

Running provision, build or apply-unit by hand changes the app outside the panel’s records. Prefer redeploying from the panel.