Command line
deploy.sh, the node installer install.sh, Docker Compose operations, the container entrypoint, account recovery, and development commands.
deploy.sh
Installer and operations script for 宝塔 / aaPanel Compose deployments. Usage: ./deploy.sh <command>.
| Command | Effect |
|---|---|
install | Interactive install: choose the database mode, write .env, start |
update [tag] | Back up, then upgrade to the latest version or the given tag; --no-backup skips the backup. Alias upgrade |
backup | Back up the database, .env (without the master key), and the Compose file to backups/, keeping the newest 5 |
restore <backup> | Back up the current database, then replace it with the backup's edgeweir.dump; .env stays, --no-backup skips the backup |
config | Change the console URL and the node channel URL |
start, stop | Start (applying .env changes), stop |
restart | Recreate the console container and start, applying .env changes |
status | Container status and the running version |
logs [service] | Follow logs; service is console or postgres |
setup-token | Print the first-run setup token |
template <host|bundled> | Print a Compose template for pasting into the panel |
self-update | Replace the script with the deploy.sh shipped in the current image, or the GitHub version when the image has none |
help | Print usage |
Arguments, unattended variables, files written, backup layout, and exit behavior: deploy.sh reference.
Node installer
The console serves install.sh at /install.sh; it installs and enrolls edgeweir-node on a node. The install command generated by the console already carries --server and --ca-sha256; see Adding nodes.
export EDGEWEIR_TOKEN='<one-time token>'
curl -fsSL https://<console>/install.sh | sudo --preserve-env=EDGEWEIR_TOKEN bash -s -- \
--server https://<console>:8443 --ca-sha256 <CA fingerprint>Options
<console> is EDGEWEIR_PUBLIC_URL, written into the script when the console serves it.
| Option | Argument | Default | Effect |
|---|---|---|---|
--server | URL | Required | Node channel URL; must be https://. |
--ca-sha256 | HEX | Required | SHA-256 fingerprint of the console's node CA, 64 lowercase hex characters; pinned during enrollment. |
--token-file | PATH | None | Read the enrollment token from a file (whitespace removed); takes precedence over EDGEWEIR_TOKEN. |
--version | VER | latest | edgeweir-node version to install; a semantic version, optional v prefix. latest is resolved from the mirror's latest file, then the latest GitHub release. Without it (and without --force), an enrolled host downloads and installs nothing; the service is only started and checked. |
--format | auto|deb|rpm|tar | auto | Package format. auto: deb with dpkg and apt-get; rpm with rpm and dnf or yum; otherwise tar. |
--mirror | URL | <console>/downloads/edgeweir-node | edgeweir-node release mirror, with files at URL/latest and URL/v<version>/<file>; cosign comes from the sibling cosign/v<version>/. Each file is tried from the mirror first, then from GitHub. |
--mirror-only | None | Off | Never fall back to GitHub. |
--no-modsecurity | None | Off | Do not install edgeweir-openresty-modsecurity: the node does not support OWASP CRS. |
--no-start | None | Off | Install and enroll only: do not require systemd, do not enable or start the service, no health check. |
--force | None | Off | Enroll an enrolled host again (needs a new token): stops edgeweir-node, replaces its identity, and starts it again. |
--allow-unsigned | None | Off | Skip the cosign signature check; development only. SHA-256 is still verified. |
-h, --help | None | None | Print usage. |
| Environment variable | Effect |
|---|---|
EDGEWEIR_TOKEN | One-time enrollment token, format ewt_…. The script removes it from its environment after reading, so other child processes do not inherit it; it reaches edgeweir-node enroll through the environment. |
--token and --token=… are rejected: command-line arguments are visible in the process list.
Verification
| Object | Check |
|---|---|
checksums.txt | Keyless cosign signature (checksums.txt.sigstore.json). Certificate identity: https://github.com/marvinli001/edgeweir-node/.github/workflows/release.yml@refs/tags/v<version>; OIDC issuer: https://token.actions.githubusercontent.com |
| Package | SHA-256 against the signed checksums.txt |
| cosign | Without cosign on the machine, v3.1.3 is downloaded and checked against the SHA-256 pinned in the script |
Nothing downloaded runs before these checks pass. System requirements, the full flow, and installed files: Adding nodes.
Exit codes
| Code | Meaning |
|---|---|
0 | Installed and enrolled, and edgeweir-node healthcheck passed (not checked with --no-start) |
1 | A check or step failed, including a health check that did not pass within 90 seconds; stderr shows [edgeweir] error: and the reason |
2 | --server or --ca-sha256 missing, or -h/--help given |
The script neither upgrades nor uninstalls. Node upgrades: Node upgrades.
Docker Compose
Run in the directory of compose.yml; add -f <file> for other Compose files.
| Operation | Command |
|---|---|
Start, or apply .env changes | docker compose up -d |
| Build from source and start | docker compose up -d --build |
| Stop | docker compose stop |
| Remove containers and networks, keep volumes | docker compose down |
Restart (does not apply .env changes) | docker compose restart console |
| Status | docker compose ps |
| Follow logs | docker compose logs -f console |
| Show the setup token log line | docker compose logs console | grep setupToken |
| Health check (host) | curl -s http://127.0.0.1:3000/healthz |
| Health check (in the container) | docker compose exec console edgeweir-healthcheck |
| Recover the account (reset the password, turn two-factor off) | docker compose exec console node dist/server/recover.js --reset-password --disable-two-factor, see Account recovery |
Pull the image set by EDGEWEIR_VERSION and recreate | docker compose pull && docker compose up -d |
| Start ClickHouse | COMPOSE_PROFILES=analytics in .env, then docker compose up -d (later commands include it) |
docker compose down -v removes named volumes such as postgres-data, that is, all data.
Print only the setup token:
docker compose logs --no-color --no-log-prefix console \
| sed -n 's/.*"setupToken":"\([^"]*\)".*/\1/p' | tail -n 1/healthz returns {"status":"ok","version":"<version>"}. When EDGEWEIR_HTTP_PORT is not 3000, use that port. Upgrade and rollback steps: Versions, upgrades, and rollback.
Container
| Item | Value |
|---|---|
| Image | ghcr.io/marvinli001/edgeweir:<tag>, linux/amd64 and linux/arm64 |
| Entrypoint | /sbin/tini -- |
| Command | node --enable-source-maps dist/server/main.js, working directory /app |
| User | node |
| Ports | 3000 (web, API), 8443 (node channel) |
| Health check | edgeweir-healthcheck; interval 10 s, timeout 3 s, start period 30 s, 5 retries |
| Bundled files | /usr/local/bin/edgeweir-certd, /usr/local/bin/edgeweir-healthcheck, /app/deploy.sh |
| Stop signals | SIGTERM, SIGINT: close the HTTP and node channel listeners and end the nodes' watch streams, give requests in flight up to 3 seconds and pg-boss up to 5 seconds at the same time, exit with 0; after 8 seconds exit with 1 |
edgeweir-healthcheck: with ROLE=worker it returns 0; other roles request http://127.0.0.1:${PORT}/healthz with a 2-second timeout and return non-zero on failure.
Environment defaults of the image: Environment variables.
ROLE
Values: all (default), app, and worker. Components, listeners, and scaling per role: Deployment overview.
Account recovery
dist/server/recover.js resets the password of the console's only account and turns two-factor authentication off, for when nobody can sign in. It reads the same environment variables as the console (DATABASE_URL, EDGEWEIR_MASTER_KEY, and so on) and changes the database directly, without the web UI or HTTP. Steps: Account recovery.
docker compose exec console node dist/server/recover.js --reset-password --disable-two-factor| Deployment | Command |
|---|---|
| Docker Compose | docker compose exec console node dist/server/recover.js <options> |
| Docker Compose, console container not running | docker compose run --rm console node dist/server/recover.js <options> |
宝塔 / aaPanel (deploy.sh) | docker exec -it edgeweir-console node dist/server/recover.js <options> |
Source (pnpm dev) | pnpm --filter @edgeweir/console recover <options>, see Commands |
Options
| Option | Effect |
|---|---|
--reset-password | Set a new password, 12–128 characters |
--disable-two-factor | Turn two-factor authentication off; delete the TOTP secret and backup codes |
-h, --help | Print usage |
At least one of --reset-password and --disable-two-factor is required.
New password
| Standard input | How it is read |
|---|---|
Terminal (allocated by default by docker compose exec and docker exec -it) | Prompts New password: and Repeat new password: without echo; nothing changes when the two entries differ. Ctrl-C cancels |
| Pipe or file | The first line, without its \n or \r\n ending |
docker compose exec -T console node dist/server/recover.js --reset-password < new-password.txt-T allocates no terminal; use it only with a pipe or file, because a password typed at the terminal is then shown on screen. The password is never taken from a command-line argument or an environment variable: arguments are visible in the process list and the shell history.
Result
These changes commit in one transaction with the audit entry; if any step fails, nothing changes:
| Change | Description |
|---|---|
| Password | Hashed by better-auth (scrypt) and stored on the account's password credential |
| Two-factor authentication | Off; the TOTP secret and backup codes are deleted |
| Sessions | All sessions of the account are deleted, as are sign-ins waiting for their two-factor code and trusted-device records |
| Audit log | account.recover, actor System (name recover); metadata passwordReset, twoFactorDisabled, sessionsRevoked |
Name, email, passkeys, and AccessKeys do not change. Standard output shows the account's name and email and what changed, never the password, its hash, or a session token:
Account: Ops <admin@example.com>
Password reset.
Two-factor authentication turned off.
Signed out 2 sessions.Exit codes
| Exit code | Meaning |
|---|---|
0 | Done, or -h / --help |
1 | Nothing changed: no account (setup not completed), password length out of range, entries differ, cancelled, invalid configuration, or the database connection or write failed; stderr shows error: and the reason |
2 | Unknown option, extra argument, or no action given; stderr shows the usage |
Development
Requirements: Node.js 24.11.0 or later, pnpm 12.6.0, Docker; helpers/certd and pnpm e2e also need Go 1.27.1. buf is installed with the dev dependencies.
Run locally
-
Install dependencies.
pnpm install -
Start the development database (
compose.dev.yml; port:DEV_POSTGRES_PORT).docker compose -f compose.dev.yml up -d -
Create
.envand setEDGEWEIR_MASTER_KEY(openssl rand -base64 32). The template'sDATABASE_URLmatches the development database.cp .env.example .env -
Start the console.
pnpm dev -
Verify.
curl -s http://localhost:3000/healthzExpected output:
{"status":"ok","version":"dev"}.
Certificate issuance needs the certificate helper: cd helpers/certd && go build -o bin/edgeweir-certd ., then set its absolute path in EDGEWEIR_CERTD_BIN.
Commands
Run from the repository root.
| Command | Effect |
|---|---|
pnpm dev | One process runs the API, the node channel, and Vite HMR; loads the repository .env when present; web listens on PORT |
pnpm build | Turborepo build of all packages; the console goes to apps/console/dist |
pnpm typecheck | Type checking |
pnpm test | Vitest; the database is PGlite, no Docker needed |
pnpm lint | biome check . and buf lint proto |
pnpm format | biome check --write . |
pnpm proto:lint | buf lint proto |
pnpm proto:gen | Generate packages/proto/src/gen from proto/ |
pnpm db:generate | Generate an SQL migration in packages/db/migrations from packages/db/src/schema (drizzle-kit) |
pnpm e2e | Run scripts/e2e.sh |
pnpm --filter @edgeweir/console start | Run the built dist/server/main.js |
pnpm --filter @edgeweir/console recover <options> | Run the account recovery command from source; reads .env in the repository root when present. No -- before the options |
pnpm --filter @edgeweir/console test:e2e | Playwright tests |
End-to-end tests
scripts/e2e.sh runs against compose.e2e.yml. It needs curl, jq, docker, and node; the installer step also needs goreleaser v2, syft, Go 1.27.1, and the sibling edgeweir-node checkout, and the signed upgrade step needs cosign v3.1.3; the run needs access to deb.debian.org and openresty.org.
docker compose -f compose.e2e.yml up -d --build
pnpm e2e| Argument | Effect |
|---|---|
--up | Run docker compose -f compose.e2e.yml up -d --build first |
--down | Run docker compose -f compose.e2e.yml --profile '*' down -v at the end, removing the profile services (ClickHouse, upgrade test node, regional probes) and their volumes as well |
--skip-ui | Skip the Playwright browser tests |
Arguments follow the script: bash scripts/e2e.sh --up --down.
| Variable | Default | Effect |
|---|---|---|
COMPOSE_PROJECT_NAME | edgeweir-e2e | Compose project name |
E2E_CONSOLE_PORT | 13000 | Console host port |
E2E_NODE_PORT | 18080 | Node HTTP host port |
E2E_NODE_TLS_PORT | 18443 | Node HTTPS host port (TCP and UDP) |
E2E_TAG | e2e | Tag of the built images |
E2E_SUBNET | 172.28.213.0/24 | Default network subnet; the script adds it to the origin allow list |
E2E_ISOLATED_SUBNET | 172.28.214.0/24 | Isolated network subnet; stays outside the allow list |
E2E_INSTALL_IMAGE | debian:bookworm-slim@sha256:… | Clean system image for the install.sh test |
E2E_ANALYTICS | lite | The console's EDGEWEIR_ANALYTICS |
E2E_CLICKHOUSE_PORT | 19123 | ClickHouse HTTP port, bound to 127.0.0.1 |
E2E_ACME_PORT | 14000 | Pebble ACME port, bound to 127.0.0.1 |
E2E_ACME_MGMT_PORT | 15000 | Pebble management port, bound to 127.0.0.1 |
E2E_MOCK_PORT | 19090 | Mock services port |
EDGEWEIR_NODE_CONTEXT | ../edgeweir-node | edgeweir-node source directory |
For a second environment on the same machine, change COMPOSE_PROJECT_NAME, the ports, E2E_TAG, E2E_SUBNET, and E2E_ISOLATED_SUBNET.