Edgeweir
Deployment

deploy.sh reference

Reference for the repository-root deploy.sh: commands, unattended variables, generated files, backup layout, restore, and exit behavior.

Requirements

ItemRequirement
HostBaoTa Panel / aaPanel, or any Linux host with Docker
Shellbash
DockerDocker Engine and Compose v2 (docker compose); docker info must succeed (root or sudo)
ImagesPull access to ghcr.io/marvinli001/edgeweir and postgres:18.6-alpine; otherwise docker load beforehand and set EDGEWEIR_NO_PULL=1
Optional toolsss or netstat: port-in-use checks, skipped when missing; openssl: /dev/urandom is used when missing; curl: script download and the self-update fallback source
curl -fsSL -o deploy.sh https://raw.githubusercontent.com/marvinli001/edgeweir/master/deploy.sh
sudo bash deploy.sh install

Save the script to a file before running it: through a pipe (curl … | bash) or process substitution (bash <(curl …)) it exits with 1. After install, a copy lives at <install directory>/deploy.sh; run the other commands there as ./deploy.sh <command>. Full flow on BaoTa / aaPanel: baota.en.md.

Commands

CommandArgumentsEffect
install—Interactive install: choose the database mode, check the database, write .env, compose.yml, and the script copy, start and wait for health checks, print the setup token
update (alias upgrade)[tag] [--no-backup]Back up, then upgrade to the given tag; without a tag, the dated tag behind latest. See update
backup—Back up the database, .env (without the master key), and compose file to backups/<time>/, keeping the newest 5. See Backups
restore<backup> [--no-backup]Back up the current database, then replace it with the backup's edgeweir.dump; .env is left alone. See restore
config—Change the console URL and node channel URL, then recreate the containers; interactive only
start—Start the project and wait for health checks
stop—docker compose stop; containers are kept
restart—Start as start does, recreating the console container in any case: .env changes take effect
status—docker compose ps, plus the deployment directory, mode, and running version
logs[service…]Follow logs, starting with the last 200 lines; services are console and postgres (bundled), all when omitted
setup-token—Read the most recent setup token from the console logs
templatehost | bundledPrint a compose template; no Docker needed
self-update—Replace this script; sources in self-update
help (-h, --help)—Print usage; same without a command

Deployment directory

Commands other than install, template, and help look for the deployment directory in this order and use the first match:

  1. EDGEWEIR_DIR
  2. The script's directory
  3. The working directory
  4. /www/dk_project/edgeweir
  5. /opt/edgeweir
ItemRule
Deployment directoryContains .env and a compose file containing container_name: edgeweir-console: compose.yml, compose.yaml, docker-compose.yml, or docker-compose.yaml
Modehost when the compose file contains network_mode: host, otherwise bundled
Override filecompose.override.yml next to the compose file (docker-compose.override.yml for docker-compose.yml, and so on) is passed to Compose as well when it exists; local changes go there and survive template replacements by update. BaoTa / aaPanel run the project with docker compose -f <compose file> and skip the override file
Compose project nameTaken from the com.docker.compose.project label of the edgeweir-console container, so projects a panel created under another name work too
EnvironmentShell variables named in .env or the compose file are removed before Compose runs; .env decides. .env goes to Compose with --env-file, so its COMPOSE_PROFILES (for example for an analytics service added in the override file) applies to every command

install

Prompts

OrderPromptDefaultVariable
1Install directory/www/dk_project/edgeweir when /www/server/panel exists, otherwise /opt/edgeweirEDGEWEIR_DIR
2Database: 1 local or cloud PostgreSQL (host), 2 bundled PostgreSQL (bundled)1EDGEWEIR_DB
3host: 1 enter fields, 2 paste a connection string1DATABASE_URL
3aFields: address, port, database name, user name, password127.0.0.1, 5432, edgeweir, the database name, none (required, not echoed)—
3bFields with a non-loopback address: use TLS and verify the certificateYes: appends ?sslmode=verify-full—
4Console URL—EDGEWEIR_PUBLIC_URL
5Node channel URLhttps://<console host name>:<EDGEWEIR_NODE_API_PORT or 8443>EDGEWEIR_NODE_API_URL
6Web console port; only when the port is in useThe busy port plus 1EDGEWEIR_HTTP_PORT
7Start the install (after a summary of directory, database, URLs, and version)Yes—

Variables supply the defaults; in unattended mode they are the answers. A set EDGEWEIR_DB or DATABASE_URL skips the matching prompt. Loopback means 127.*, localhost, or ::1. The prompts are in Chinese.

Validation

InputRule
Install directoryAbsolute path; missing, empty, or holding only deploy.sh; no container named edgeweir-console on the host
Existing databundled: aborts when the Docker volume edgeweir_postgres-data (the database of an earlier install) exists; host: see Database check
Console URLhttp(s)://host[:port] with a port of 1–65535, without a path; a trailing / is removed; a warning when it is not https://
Node channel URLhttps://host[:port] without a path; its port is the public node channel port, 443 when omitted
Connection stringpostgres:// or postgresql://; with user name and database name; a single host; no whitespace, quotes, backticks, \, $, or # (URL-encode special characters in the password, e.g. $ as %24)
PortsThe web and node channel ports are numbers and differ; the install aborts when the node channel port is in use

Result

  1. Resolve and pull the image version, see Version resolution.
  2. Create the install directory (700), write .env (600), compose.yml (600), .compose.cksum, and deploy.sh (700).
  3. Pull the compose images, start, and wait for health checks.
  4. Print the running version, next steps, and the setup token.

Unattended install

When EDGEWEIR_YES is non-empty, or /dev/tty cannot be opened, the script reads no input: each prompt takes its default or the variable below.

VariableValuesDefaultEffect
EDGEWEIR_YESAny non-empty valueEmptyEnables unattended mode; an unattended restore requires it
EDGEWEIR_DBhost | bundledbundledDatabase mode; any other value aborts
DATABASE_URLpostgres://user:password@host:port/dbname[?sslmode=verify-full]—Required in host mode
EDGEWEIR_PUBLIC_URLhttps://host[:port]—Required
EDGEWEIR_NODE_API_URLhttps://host[:port]https://<console host name>:<EDGEWEIR_NODE_API_PORT>Node channel URL
EDGEWEIR_NODE_API_PORTPort8443Only for the node channel URL default; the port written to .env comes from the node channel URL
EDGEWEIR_HTTP_PORTPort3000Web console port; the part after the last : is used; aborts when in use
EDGEWEIR_VERSIONTaglatestImage version to pin
EDGEWEIR_DIRAbsolute pathSee PromptsInstall directory; other commands look here first
EDGEWEIR_NO_PULLAny non-empty valueEmptyinstall and update pull no images and use local ones only
EDGEWEIR_BACKUP_KEEPNon-negative integer5Backups backup and update keep; 0 keeps all
EDGEWEIR_SCRIPT_URLURLhttps://raw.githubusercontent.com/marvinli001/edgeweir/master/deploy.shFallback source of self-update
ConfirmationUnattended answer
PostgreSQL major version below 18, continueNo: abort
Database check failed or the database is not empty, enter againNot asked: abort
Start the installYes
update rollback confirmationNo: abort
update replaces an edited compose fileNo: the existing file is kept (an unedited one is replaced without asking)
update replaces this script with the image's copyYes
restore confirmationYes when EDGEWEIR_YES is set; without a terminal and without it: abort
configNot supported: abort
EDGEWEIR_YES=1 EDGEWEIR_DB=bundled \
EDGEWEIR_PUBLIC_URL=https://cdn-admin.example.com \
bash deploy.sh install

EDGEWEIR_NO_PULL=1 needs the target tag (or latest) of the console image and postgres:18.6-alpine (host mode checks and backups, the bundled database) on the host.

Database check

In host mode the database is checked before any file is written. The check runs psql from postgres:18.6-alpine (pinned by digest, as in compose.baota.yml) on the host network; the password travels in the environment; the connect timeout is 8 seconds.

CheckOn failure
ConnectionPrints the error and the hint below; interactive mode offers to enter the details again
The user has CREATE on the database and on schema public (migrations and the job queue)Suggests ALTER DATABASE <db> OWNER TO <user>; and ALTER SCHEMA public OWNER TO <user>;
Server major version 18 or laterWarns and asks whether to continue, default no
No drizzle.__drizzle_migrations in the database (no console has run on it)Note: the encrypted data in it opens only with the original master key; to keep using it, put the original .env back into the deployment directory and run ./deploy.sh start, otherwise use an empty database. Interactively asks whether to enter the database again
Error containsHint
refusedNo PostgreSQL listens on that address and port
passwordWrong user name or password
pg_hbapg_hba.conf does not admit this host
does not existThe database or user does not exist
timeout, timed outFirewall, security group, or cloud database allow list
certificate, SSLThe certificate is not from a public CA or does not match the host name

The check and backups connect according to sslmode in DATABASE_URL:

sslmodeConnection
None, disable, allowUnencrypted
no-verifyEncrypted, certificate not verified
Any other valueCertificate and host name verified; CA from the host's /etc/ssl/certs/ca-certificates.crt, /etc/pki/tls/certs/ca-bundle.crt, or /etc/ssl/cert.pem, the system default when none exists

update

./deploy.sh update [tag] [--no-backup] runs these steps:

  1. Resolve the target version, see Version resolution. When the target equals EDGEWEIR_VERSION in .env and the container already runs it, print 已经是 <version>。 (already at) and exit 0.

  2. A target other than latest that is older than the current version is a rollback: warn and ask whether to continue, default no. The dates in the tags are compared first; two tags of the same day compare the commit times of the two images (label org.opencontainers.image.created). When the order cannot be told (a tag is not <YYYYMMDD>-<commit>, or one image of the same day is not local), only a note is printed.

  3. Back up to backups/<time>-before-<target version>/; --no-backup skips this. A failed backup aborts with the deployment unchanged.

  4. When the compose file differs from the built-in template:

    • Same as the script last wrote it (per .compose.cksum): replaced with the new template.
    • Edited since: the difference is shown and the script asks whether to replace it, default no.
    • No .compose.cksum (older installs): the difference is shown and the script asks, default yes interactively, kept in unattended mode.

    The old file is in the step 3 backup; local changes belong in the override file; ./deploy.sh template <mode> prints the template.

  5. Write EDGEWEIR_VERSION and recreate the containers with the start flow. Database migrations run when the console starts.

  6. Print the rollback command ./deploy.sh update <previous version> (valid only when no migration was added between the two versions).

  7. When /app/deploy.sh in the new image differs from this script, ask whether to replace this script, default yes.

Version policy and rollback constraints: upgrade.en.md.

Version resolution

TargetBehavior
latestPull ghcr.io/marvinli001/edgeweir:latest, read the image label org.opencontainers.image.version, pull that dated tag, and pin it; when the label is empty or dev, warn and pin latest
<tag>Pull that tag and pin it as given
EDGEWEIR_NO_PULL setNo pull; the image must be local. When the dated tag behind latest is missing locally, it is tagged from the local latest

install resolves EDGEWEIR_VERSION (default latest) with the same rules.

config

Interactive only; in unattended mode or without a terminal it aborts; edit .env directly and run ./deploy.sh start instead.

  1. Ask for the console URL and node channel URL; Enter keeps the current value. Validation as in install.
  2. When the port in the node channel URL changes: an EDGEWEIR_NODE_API_PORT that is unset or equal to the old URL's port follows it, with a warning to open the new port and re-enroll nodes with the new URL; a value set apart (e.g. 127.0.0.1:18443 behind an nginx stream) is kept, with a note to adjust it yourself.
  3. When the node channel host name changes, warn: enrolled nodes re-enroll, or add the old host name to EDGEWEIR_NODE_API_HOSTNAMES.
  4. After confirmation, write EDGEWEIR_PUBLIC_URL, EDGEWEIR_NODE_API_URL, and EDGEWEIR_NODE_API_PORT when step 2 changes it, then recreate the containers with the start flow.

config does not change EDGEWEIR_HTTP_PORT and does not check whether the new port is in use. A URL saved in the console under System settings → Node channel wins over EDGEWEIR_NODE_API_URL: once one is saved, the node channel URL config writes does not reach install commands; to change only the node channel URL, change it in System settings, which needs no restart; see Node channel URL and certificate.

Start, stop, and trusted proxy sync

CommandBehavior
startbundled: start postgres first and sync EDGEWEIR_TRUSTED_PROXIES. Then docker compose up -d --wait --remove-orphans; when the services do not become healthy, print the last 40 console log lines and abort
restartAs start, with docker compose up -d --wait --remove-orphans --force-recreate console as the last step: the console container is always recreated (docker compose restart would not apply .env changes), and postgres, which it depends on, is recreated when its configuration changed
stopdocker compose stop

install, update, and config use the start flow.

ItemRule
Sync conditionbundled mode, the postgres container is running, and EDGEWEIR_TRUSTED_PROXIES is empty or a single IPv4 address other than the current gateway
Value writtenThe IPv4 gateway of the network the postgres container is on
Left unchangedLists or CIDR ranges; host mode (template default 127.0.0.1,::1)

Meaning of trusted proxies: networking.en.md.

After a host mode start, the script checks where the node channel listens, in this order, and stops at the first warning:

ConditionWarning
The override file sets NODE_API_HOSTRestarting or updating the project in the panel skips the override file and loses the setting; set EDGEWEIR_NODE_API_HOST in .env instead
.env sets EDGEWEIR_NODE_API_HOST and the compose file does not read itThe compose file is an older template; change its NODE_API_HOST line as in ./deploy.sh template host
EDGEWEIR_NODE_API_HOST (default 0.0.0.0) is not a loopback address, and the node channel port listens on loopback only (skipped without ss)The image does not support NODE_API_HOST; ./deploy.sh update

self-update

OrderSourceUsed when
1/app/deploy.sh in the image ghcr.io/marvinli001/edgeweir:<EDGEWEIR_VERSION from .env>Readable, non-empty, passes bash -n
2EDGEWEIR_SCRIPT_URLDownloads and passes bash -n; otherwise aborts without replacing

Identical content is left alone. A replacement is written to a new file (700) and renamed over this script; the running process keeps reading the old file.

Generated files

PathModeContentWritten by
<dir>/700Deployment directoryinstall
<dir>/.env600See the next tableinstall; update, config, and the trusted proxy sync change keys in it
<dir>/compose.yml600Template of the chosen mode, byte for byte compose.baota-host.yml or compose.baota.ymlinstall; replaced by update, see update
<dir>/.compose.cksumPer umaskcksum of the compose file the script last wrote, to tell template updates from local changesinstall, update
<dir>/compose.override.yml—Local changes; never written by the script—
<dir>/deploy.sh700Copy of the scriptinstall; replaced by update and self-update
<dir>/backups/700Backupsbackup, update
.env keyModeValue
EDGEWEIR_MASTER_KEYAllopenssl rand -base64 32; without openssl, the base64 of 32 random bytes
DATABASE_URLhostThe pasted connection string, or one built from the fields: each part URL-encoded, IPv6 addresses in brackets
POSTGRES_PASSWORDbundledopenssl rand -hex 24
EDGEWEIR_HTTP_PORTAllWeb console port
EDGEWEIR_NODE_API_PORTAllPort in the node channel URL
EDGEWEIR_PUBLIC_URLAllConsole URL
EDGEWEIR_NODE_API_URLAllNode channel URL
EDGEWEIR_VERSIONAllPinned image tag
EDGEWEIR_TRUSTED_PROXIESbundledDocker gateway address; written at the first start

Values are unquoted. Changing a key keeps the other lines and mode 600. Other variables: Environment variables.

Backups

<dir>/backups/
└── 20260929-153000-before-20260930-b2c3d4e/
    ├── edgeweir.dump
    ├── env
    ├── compose.yml
    └── compose.override.yml   # when present
FileContent
edgeweir.dumppg_dump --format=custom. host: dumped per DATABASE_URL with postgres:18.6-alpine on the host network; without DATABASE_URL in .env, the file DATABASE_URL_FILE names (set and mounted in the override file) is read in a one-off console container; bundled: dumped inside the postgres container
envCopy of .env with the EDGEWEIR_MASTER_KEY, EDGEWEIR_MASTER_KEY_PREVIOUS, and BETTER_AUTH_SECRET lines turned into comments, without their values
compose.ymlCopy of the compose file under its original name, and of the override file when present
ItemRule
Directory nameYYYYMMDD-HHMMSS; backups made by update get the suffix -before-<target version>
ModesDirectory 700, files 600
PreconditionIn bundled mode the postgres container is running
FailureAborts when pg_dump fails
RetentionAfter a successful backup only the newest EDGEWEIR_BACKUP_KEEP are kept (default 5, 0 keeps all), older ones removed by directory name; other directories in backups/ are left alone
Master keyNot in the backup: it is only in .env (or the file named by EDGEWEIR_MASTER_KEY_FILE); keep it offline separately, restoring the database needs it
ScopeNo ClickHouse data

Restore with restore; manual restores and restore acceptance: backup.en.md.

restore

./deploy.sh restore <backup> [--no-backup] replaces the deployment's database with a backup. <backup> is a backup directory (its edgeweir.dump is used) or a dump file, looked up as given, then relative to the deployment directory, then relative to backups/, e.g. ./deploy.sh restore 20261001-080000.

  1. Check the backup: bundled starts postgres first; pg_restore --list must read it, and it must hold table data and drizzle.__drizzle_migrations (a console database), or the command aborts.
  2. host: the DATABASE_URL user must own the database (or be a superuser) and have CREATEDB, or the command aborts; restore into a new database by hand instead.
  3. Confirm, default no; unattended: see confirmations.
  4. Back up the current database to backups/<time>-before-restore/ without removing older backups; --no-backup skips this. A failed backup aborts with the deployment unchanged.
  5. Stop console.
  6. Drop and recreate the database: DROP DATABASE … WITH (FORCE) and CREATE DATABASE. bundled runs them in the postgres container as edgeweir; host runs them with postgres:18.6-alpine against the postgres maintenance database, and the new database belongs to the DATABASE_URL user.
  7. Import with pg_restore --exit-on-error --single-transaction --no-owner --no-privileges.
  8. Start and wait for the health checks as start does.
ItemRule
.envNot changed; the backup's env is not used. The master key must be the one in use when the backup was made, or the console refuses to start
Console versionNot older than at backup time: migrations only move forward
Failed importThe database is empty and the console stays stopped; the restore command for the pre-restore backup is printed
NodesReconnect after the restore; publish one configuration change so they resync, see Resynchronize nodes

Exit and abort behavior

CaseBehavior
SuccessExit code 0
AbortPrints ✗ <reason>, exit code 1
Unknown commandPrints usage, exit code 1
Failing underlying commandExits at once (set -Eeuo pipefail) with that command's exit code
End of input (EOF)Aborts: 「输入已结束,安装取消。」 or 「输入已结束,已取消。」 (input ended)
Declined confirmationAborts: 「已取消。」 (cancelled)
install before confirmationNothing is written to the install directory
install fails to startFiles are kept and 「启动失败。修正 .env 后运行 ./deploy.sh start 重试。」 (startup failed; fix .env and run start) is printed; the directory now counts as a deployment and a second install is refused
update fails to start.env already points at the target version; recover with the printed rollback command or the backup
restore import failsThe database is empty and the console stays stopped; restore the pre-restore backup with the printed command
setup-token finds no tokenAborts: setup is complete, or the container has not started

template and setup-token write their result to standard output; progress, prompts, and errors go to standard error, in color when standard error is a terminal.

Edit on GitHub

On this page