All-in-one installer for the ework self-hosted AI development stack:
| Component | npm package | Role |
|---|---|---|
| ework-web | ework-web |
Multi-project issue tracker (web UI + Gitea-compat REST) |
| ework-daemon | ework-daemon |
Issue-driven AI bridge (spawns opencode to resolve issues) |
| opencode-ework | opencode-ework |
OpenCode plugin (gives agents issue/reply/floor tools) |
ework-aio wires the three together on a single host: it installs systemd units, generates tokens, bootstraps the bot user, and registers the plugin in ~/.config/opencode/opencode.json.
# Recommended: user-level install (no sudo needed, no systemd needed)
npm install -g ework-aio --prefix ~/.local && ework-aio installThat's the whole install. It runs in PID-file mode (services via nohup, no systemctl calls, no linger prompt) and prints your login URL, operator login, and token when done.
If you want systemd to supervise the services (auto-restart on crash, start on boot, journal logging), add the systemd subcommand:
ework-aio install systemd # writes units, daemon-reload, enable, restartThis is opt-in. Without systemd, install never touches systemctl, works on hosts without systemd (containers, macOS, WSL without systemd), and doesn't prompt for linger.
PATH heads-up:
--prefix ~/.localputs bin shims at~/.local/bin/. Most distros already have~/.local/binon PATH (viasystemd-user-sessionsor~/.profile). Ifcommand -v ework-aiofails after install, add this to your~/.bashrc/~/.zshrc:export PATH="$HOME/.local/bin:$PATH"
The install command checks for these and aborts with a hint if any are missing:
| Tool | Min ver | When required | Install from |
|---|---|---|---|
bun |
1.1.0 | always | https://bun.sh |
opencode |
1.14 | always | https://opencode.ai |
npm |
any | always | ships with bun or node |
systemctl |
any | only install systemd (not default) |
systemd-based Linux |
Everything else (HTTP requests, AES/HMAC, JSON parsing, env-file editing) is done in-process via Bun — no curl/openssl/jq/awk needed.
# PID-file mode (default): no systemctl, works anywhere
ework-aio install
# systemd mode: writes units + enables + starts via systemd
ework-aio install systemd
# (system-level variant: services run as root, units in /etc/systemd/system)
sudo npm install -g ework-aio && sudo ework-aio install systemd --system
# Run without installing globally (downloads + runs once)
npx ework-aio install
# Route through an HTTP proxy if npm registry is slow on your machine
HTTPS_PROXY=http://127.0.0.1:7890 npm install -g ework-aio --prefix ~/.local && ework-aio installUser-level (default in this README) keeps everything under your home directory:
- bins:
~/.local/bin/ - npm package files:
~/.local/lib/node/ - data:
~/.local/share/ework-aio/ - PID files + logs:
~/.local/share/ework-aio/run/{web,daemon}.{pid,log} - systemd units (only with
install systemd):~/.config/systemd/user/
sudo is never required; uninstall is rm -rf ~/.local/share/ework-aio && npm uninstall -g ework-aio --prefix ~/.local.
npm install -g ework-aio only lays down files: the bin launcher and the bash installer. It does not run the installer. The second ework-aio install step is what actually:
- writes
.envfiles with random tokens, - creates systemd units (user-level by default, system-level if you sudo),
- starts services,
- bootstraps the bot user,
- edits
~/.config/opencode/opencode.json.
Keeping these in a separate, explicitly-invoked step is intentional:
- Privilege boundary.
sudo npm install -gruns as root; the install step runs as you. Folding them together would force system-level systemd units and root-owned files in$HOME. - No surprise side-effects.
npm install -g fooshould lay down files and stop. Creating services, generating tokens, editing your opencode config — that's invasive and belongs in a step you opted into. - npm
--ignore-scripts. Many users / CI disable lifecycle scripts. Auto-installing viapostinstallwould silently no-op for them. npm uninstallreversibility. Files outside npm's tracking (systemd units,.env, DBs, bot PAT) can't be cleaned by npm. Keeping them in a separate command meansnpm uninstall -g ework-aiodoes what people expect (removes files) andework-aio uninstalldoes the rest.
bin/ework-aio with no args defaults to install, so npm install -g ework-aio && ework-aio (no install) also works.
- Verifies prerequisites (bun, npm, opencode; systemctl only if
install systemd). - Installs the 3 npm packages globally (if not already present).
- Generates
.envwith random tokens at~/.local/share/ework-aio/{ework-web,ework-daemon}/.env(preserved across re-runs). - Writes systemd units (
ework-web.service,ework-daemon.service) — user-level by default, system-level when run as root. - Starts ework-web and waits for it to listen.
- Bootstraps the bot user (
ework-daemonby default) viaework-web's/admin/users/create, then mints a PAT via/me/tokens/create. Saved to~/.local/share/ework-aio/.bot-token(reused on re-runs). - Writes ework-daemon
.envwith the bot PAT. - Starts ework-daemon.
- Merges
opencode-eworkinto~/.config/opencode/opencode.json'spluginarray (idempotent; backs up the original).
ework-aio install [options] Install or upgrade the stack
ework-aio uninstall Stop services and remove units (data preserved)
ework-aio status Show service status (works in PID-file and systemd mode)
ework-aio logs [web|daemon] Tail logs
ework-aio env Print key paths (no secrets)
ework-aio config <subcommand> Read / change runtime .env keys
config list List settable keys + current values
config get <KEY> Print one key's current value
config set <KEY> <VALUE> Set a key, then restart affected service
(use --no-restart to defer)
config restart <web|daemon|both> Restart one or both services
# PID-file mode (no systemd required — see below)
ework-aio start [web|daemon|both] Start services in background (detached)
ework-aio stop [web|daemon|both] Stop services (SIGTERM, 5s, SIGKILL)
ework-aio restart [web|daemon|both] Stop + start
ework-aio ps Show PID-file mode status
| Flag | Default | Description |
|---|---|---|
--user |
default when EUID≠0 | Use systemctl --user (only with install systemd subcommand) |
--system |
default when EUID=0 | Use systemctl system-level (only with install systemd, sudo) |
--data-dir <path> |
~/.local/share/ework-aio |
Override data root |
--port <n> |
3002 |
ework-web port |
--daemon-port <n> |
3101 |
ework-daemon port |
--bot-name <login> |
ework-daemon |
Bot username in ework-web |
--no-start |
(off) | Install but don't start services |
--yes / -y |
(off) | Skip prompts (use generated defaults) |
--as-user <login> |
(off) | sudo only: drop privileges — re-exec install as <login> |
~/.local/share/ework-aio/
├── ework-web/
│ ├── .env # ework-web config (tokens, ports, paths)
│ ├── ework.db # SQLite database (issues, comments, users, ...)
│ └── attachments/ # Filesystem attachments
├── ework-daemon/
│ ├── .env # ework-daemon config (bot creds, opencode paths)
│ └── ework-daemon.db # Daemon state (processes, runs, ...)
├── opencode-workdir/ # Where opencode checks out repos
└── .bot-token # Bot PAT (chmod 600)
~/.config/systemd/user/
├── ework-web.service
└── ework-daemon.service
~/.config/opencode/opencode.json # plugin: ["opencode-ework", ...]
Re-running ework-aio install is safe:
.envfiles are preserved (usermto regenerate).- Bot user creation returns 400/409 if it already exists; PAT is reused.
- Systemd units are overwritten (config drift auto-corrected).
- Plugin merge skips if
opencode-eworkalready inpluginarray.
For changing settings after install, prefer config over editing .env by hand — it writes the right file, handles cross-service dependencies, and restarts the affected service for you.
ework-aio config list # see all settable keys + current values
ework-aio config get WORK_PORT # read one
ework-aio config set WORK_PORT 8080 # set + auto-restart ework-web
ework-aio config set WORK_PORT 8080 --no-restart # stage the change, apply later
ework-aio config restart both # explicit restart| Key | Service | What it controls |
|---|---|---|
WORK_PORT |
web | ework-web listen port (default 3002) |
WORK_HOST |
web | bind address (default 127.0.0.1; use 0.0.0.0 for LAN) |
WORK_OPERATOR_LOGIN |
web | login auto-promoted to admin |
WORK_OPENCODE_BIN |
web | opencode binary path used by ework-web |
WORK_TRANSLATE_URL |
web | OpenAI-compat /v1/chat/completions endpoint for translate |
WORK_TRANSLATE_MODEL |
web | translate model name |
WORK_TTS_SPEED |
web | TTS playback rate (default 1.0) |
WORK_FILE_ROOTS |
web | comma-separated file-viewer roots |
WORK_COMMENT_SORT |
web | desc or asc |
DAEMON_PORT |
daemon | ework-daemon listen port (default 3101) |
DAEMON_HOST |
daemon | bind address |
OPENCODE_BINARY |
daemon | opencode binary path |
OPENCODE_BASE_WORKDIR |
daemon | opencode working directory base |
COMPLETION_CHECK_API_KEY |
daemon | completion-check API key |
COMPLETION_CHECK_BASE_URL |
daemon | completion-check API base URL |
COMPLETION_CHECK_MODEL |
daemon | completion-check model name |
Changing WORK_PORT or DAEMON_PORT also rewrites the URL the other service uses to call it (so the daemon still finds web, and web still finds the daemon), and restarts both:
config set WORK_PORT 8080→ updatesWORK_PORTin web env +GITEA_URLin daemon env, restarts bothconfig set DAEMON_PORT 3102→ updatesDAEMON_PORTin daemon env +WORK_DAEMON_WEBHOOK_URLin web env, restarts both
Secrets and the web↔daemon contract aren't exposed via config — rerun ework-aio install to regenerate them (delete the relevant .env first if you want fresh tokens):
- Random secrets:
WORK_TOKEN,WORK_COOKIE_SECRET,WORK_DAEMON_WEBHOOK_SECRET,BOT_TOKEN - DB / attachment paths:
WORK_DB_PATH,WORK_ATTACHMENT_ROOT,DAEMON_DB_PATH - Web↔daemon contract:
GITEA_URL,GITEA_TOKEN,WORK_DAEMON_BOT_LOGIN,WORK_DAEMON_WEBHOOK_URL,BOT_USERNAME
To regenerate all of them: rm -rf ~/.local/share/ework-aio && ework-aio install.
ework-aio uninstall # stops services, removes units
rm -rf ~/.local/share/ework-aio # also delete data
npm uninstall -g ework-aio ework-web ework-daemon opencode-ework
# Remove the plugin entry from ~/.config/opencode/opencode.json manuallyinstall / status / config / uninstall go through systemctl, which assumes systemd. If you're on a system without systemd — macOS, WSL1, Alpine without systemd, Docker containers, dev laptops — use the parallel PID-file mode instead:
| Mode | Start | Stop | Status | Restart |
|---|---|---|---|---|
| systemd | ework-aio install |
ework-aio uninstall |
ework-aio status |
ework-aio config restart both |
| PID-file | ework-aio start |
ework-aio stop |
ework-aio ps |
ework-aio restart |
PID-file mode writes per-service PID + log under ~/.local/share/ework-aio/run/:
~/.local/share/ework-aio/run/
├── web.pid # ework-web PID
├── web.log # nohup stdout+stderr
├── daemon.pid
└── daemon.log
# One-time setup: scaffold .env + data dir (no systemd required if you skip the unit install)
ework-aio install --no-start
# Then control with PID-file mode:
ework-aio start # start web + daemon in background (detached)
ework-aio start web # just web
ework-aio stop # SIGTERM, 5s grace, SIGKILL if needed
ework-aio restart web # stop + start
ework-aio ps # show PID + log path for each serviceOverride the data dir with ework-aio start --data-dir /path (or pass --data-dir to install).
Notes:
install(with--no-start) is still required once to scaffold.envand the data directory. The systemd units it writes are inert when nothing manages them.- PID-file mode does NOT auto-restart on crash. Consider a process supervisor (
pm2,supervisord,runit,launchd) if you need that. - Both modes can coexist (systemd-managed services ignore PID files, and vice versa) but you should pick one to avoid port conflicts.
- Docker AIO: the
ework-webrepo shipsdocker/build.sh+docker/run.shfor a single-container deployment. Useful when you don't want to manage host systemd. - Manual: install the 3 npm packages yourself and wire services by hand using the systemd unit templates in each package.
MIT