ework-aio 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -12
  2. package/bin/install.sh +190 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -13,11 +13,21 @@ All-in-one installer for the **ework** self-hosted AI development stack:
13
13
  ## Quick start
14
14
 
15
15
  ```bash
16
- # Recommended: user-level install (no sudo needed)
16
+ # Recommended: user-level install (no sudo needed, no systemd needed)
17
17
  npm install -g ework-aio --prefix ~/.local && ework-aio install
18
18
  ```
19
19
 
20
- That's the whole install. When it finishes it prints your login URL, operator login, and token.
20
+ That'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.
21
+
22
+ ### When to use `install systemd`
23
+
24
+ If you want systemd to supervise the services (auto-restart on crash, start on boot, journal logging), add the `systemd` subcommand:
25
+
26
+ ```bash
27
+ ework-aio install systemd # writes units, daemon-reload, enable, restart
28
+ ```
29
+
30
+ This 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.
21
31
 
22
32
  > **PATH heads-up:** `--prefix ~/.local` puts bin shims at `~/.local/bin/`.
23
33
  > Most distros already have `~/.local/bin` on PATH (via `systemd-user-sessions`
@@ -32,19 +42,24 @@ That's the whole install. When it finishes it prints your login URL, operator lo
32
42
 
33
43
  The install command checks for these and aborts with a hint if any are missing:
34
44
 
35
- | Tool | Min ver | Install from |
36
- | ----------- | ------- | ------------------------------------ |
37
- | `bun` | 1.1.0 | https://bun.sh |
38
- | `opencode` | 1.14 | https://opencode.ai |
39
- | `npm` | any | ships with bun or node |
40
- | `systemctl` | any | systemd-based Linux |
41
- | `openssl`/`curl`/`jq`/`awk` | any | your distro package manager |
45
+ | Tool | Min ver | When required | Install from |
46
+ | ----------- | ------- | ----------------------------------------- | ------------------------------------ |
47
+ | `bun` | 1.1.0 | always | https://bun.sh |
48
+ | `opencode` | 1.14 | always | https://opencode.ai |
49
+ | `npm` | any | always | ships with bun or node |
50
+ | `systemctl` | any | only `install systemd` (not default) | systemd-based Linux |
51
+ | `openssl`/`curl`/`jq`/`awk` | any | always | your distro package manager |
42
52
 
43
53
  ### Install variants
44
54
 
45
55
  ```bash
46
- # System-level install (needs sudo; services run as root, units in /etc/systemd/system)
47
- sudo npm install -g ework-aio && sudo ework-aio install --system
56
+ # PID-file mode (default): no systemctl, works anywhere
57
+ ework-aio install
58
+
59
+ # systemd mode: writes units + enables + starts via systemd
60
+ ework-aio install systemd
61
+ # (system-level variant: services run as root, units in /etc/systemd/system)
62
+ sudo npm install -g ework-aio && sudo ework-aio install systemd --system
48
63
 
49
64
  # Run without installing globally (downloads + runs once)
50
65
  npx ework-aio install
@@ -58,7 +73,8 @@ User-level (default in this README) keeps everything under your home directory:
58
73
  - bins: `~/.local/bin/`
59
74
  - npm package files: `~/.local/lib/node/`
60
75
  - data: `~/.local/share/ework-aio/`
61
- - systemd units: `~/.config/systemd/user/`
76
+ - PID files + logs: `~/.local/share/ework-aio/run/{web,daemon}.{pid,log}`
77
+ - systemd units (only with `install systemd`): `~/.config/systemd/user/`
62
78
 
63
79
  `sudo` is never required; uninstall is `rm -rf ~/.local/share/ework-aio && npm uninstall -g ework-aio --prefix ~/.local`.
64
80
 
package/bin/install.sh CHANGED
@@ -17,7 +17,7 @@ set -euo pipefail
17
17
  # scope + polkit redirect, missing systemd PID 1, journalctl-as-root) all
18
18
  # manifest as silent exit at a systemctl call — without this trace, the user
19
19
  # just sees the prompt return and assumes install succeeded.
20
- trap 'rc=$?; if [[ $rc -ne 0 ]]; then echo "${c_red}✗${c_reset} install.sh exited with code $rc at line $LINENO (most recent command: ${BASH_COMMAND})" >&2; echo " If systemd is unavailable on this host, retry with: ework-aio install --no-start" >&2; echo " Then start services with: ework-aio start" >&2; fi' ERR
20
+ trap 'rc=$?; if [[ $rc -ne 0 ]]; then echo "${c_red}✗${c_reset} install.sh exited with code $rc at line $LINENO (most recent command: ${BASH_COMMAND})" >&2; echo " If running in PID-file mode (default), this is a real bug please report." >&2; echo " If running with '\''systemd'\'' subcommand and systemd is unavailable, retry without it." >&2; fi' ERR
21
21
 
22
22
  # ─── Pretty output ──────────────────────────────────────────────────────────
23
23
  c_reset=$'\033[0m'; c_bold=$'\033[1m'; c_dim=$'\033[2m'
@@ -42,13 +42,22 @@ AS_USER=""
42
42
  ALLOW_ROOT=0
43
43
  CFG_ARGS=()
44
44
  MODE_ARGS=()
45
+ # USE_SYSTEMD=1 only when user runs `install systemd`. Default install is
46
+ # pure PID-file mode (no systemctl calls, no unit files, no linger prompt).
47
+ # This is the inverse of v0.1.x behavior where install always tried systemd
48
+ # first. PID-file mode is simpler, works on hosts without systemd, and
49
+ # matches the dominant failure mode we've seen in the wild.
50
+ USE_SYSTEMD=0
45
51
 
46
52
  usage() {
47
53
  cat <<'EOF'
48
54
  ework-aio <command> [options]
49
55
 
50
56
  Commands:
51
- install [options] Install or upgrade the ework stack (default)
57
+ install [systemd] [options] Install or upgrade the ework stack (default)
58
+ Add 'systemd' to also write+enable systemd
59
+ units. Without 'systemd', runs in pure
60
+ PID-file mode (no systemctl calls).
52
61
  uninstall Stop services and remove units (data preserved)
53
62
  status Show service status
54
63
  logs [web|daemon] Tail logs
@@ -61,13 +70,15 @@ Commands:
61
70
  config restart <web|daemon> Restart one or both services
62
71
 
63
72
  Install options:
64
- --user Use user-level systemd units (default if non-root)
65
- --system Use system-level systemd units (default if root)
73
+ systemd Also install systemd units + enable them.
74
+ Without this flag, install is PID-file only.
75
+ --user (with systemd) user-level units (default)
76
+ --system (with systemd) system-level units (needs sudo)
66
77
  --data-dir <path> Override data directory (default: ~/.local/share/ework-aio)
67
78
  --port <n> ework-web port (default: 3002)
68
79
  --daemon-port <n> ework-daemon port (default: 3101)
69
80
  --bot-name <login> Bot username (default: ework-daemon)
70
- --no-start Install units but don't start services
81
+ --no-start Install but don't start services
71
82
  --yes Skip all prompts (use generated defaults)
72
83
  --as-user <login> (sudo only) drop priv: re-exec install as <login>
73
84
  after enabling linger. Data + opencode + npm
@@ -196,6 +207,7 @@ fi
196
207
  # know about installed units). Auto-export the runtime dir + bus socket if we
197
208
  # can find them. No-op for --system scope.
198
209
  ensure_user_session() {
210
+ [[ "$USE_SYSTEMD" == "1" ]] || return 0
199
211
  [[ "$SCOPENAME" == "--user" ]] || return 0
200
212
  local uid
201
213
  uid="$(id -u)"
@@ -227,17 +239,33 @@ ensure_user_session() {
227
239
  }
228
240
  ensure_user_session || true # warn but don't hard-fail — let later steps report specifics
229
241
 
242
+ # ─── Post-parse: detect `install systemd` sub-variant ──────────────────────
243
+ # `install systemd` (or `systemd install`, or `install ... systemd`) opts in
244
+ # to writing+enabling systemd units. Without this token, install is pure
245
+ # PID-file mode (no systemctl calls, no unit files, no linger prompt).
246
+ if [[ "$MODE" == "install" ]]; then
247
+ new_mode_args=()
248
+ for a in "${MODE_ARGS[@]:-}"; do
249
+ [[ -z "$a" ]] && continue
250
+ if [[ "$a" == "systemd" ]]; then
251
+ USE_SYSTEMD=1
252
+ else
253
+ new_mode_args+=("$a")
254
+ fi
255
+ done
256
+ MODE_ARGS=("${new_mode_args[@]:-}")
257
+ fi
258
+
230
259
  # ─── Pre-flight: command dependencies ───────────────────────────────────────
231
260
  need_cmd() {
232
261
  command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1. $2"
233
262
  }
234
263
  need_cmd awk "Should be present on any Linux."
235
264
 
236
- # install needs systemctl (to write unit files + reload). Other modes
237
- # (status/logs/env/config/uninstall) can fall back to PID-file mode via
238
- # the 'ework-aio' dispatcher if systemd is unreachable on this host.
265
+ # systemctl only required when user opts into systemd variant. Default
266
+ # install path is PID-file only and works on hosts without systemd.
239
267
  if [[ "$MODE" == "install" ]]; then
240
- need_cmd systemctl "Install requires systemd. For hosts without systemd, run 'npm install -g ework-aio' and use 'ework-aio start' directly (PID-file mode)."
268
+ [[ "$USE_SYSTEMD" == "1" ]] && need_cmd systemctl "Install with 'systemd' subcommand requires systemctl. Drop 'systemd' to install in PID-file mode."
241
269
  need_cmd bun "Install from https://bun.sh"
242
270
  need_cmd npm "Install Node.js or Bun (ships npm)"
243
271
  need_cmd opencode "Install from https://opencode.ai"
@@ -367,6 +395,11 @@ write_web_env() {
367
395
  mkdir -p "$(dirname "$WEB_ENV")"
368
396
  if [[ -f "$WEB_ENV" ]]; then
369
397
  log "Preserving existing $WEB_ENV"
398
+ # Older installs (pre-0.1.16) may have a .env that predates some keys
399
+ # ework-web's Zod schema now requires (WORK_COOKIE_SECRET in particular).
400
+ # Fill any missing required keys with fresh defaults so the preserved
401
+ # .env doesn't crash the next start. User-set keys are never overwritten.
402
+ ensure_web_env_keys
370
403
  return
371
404
  fi
372
405
  local tok secret webhook
@@ -392,6 +425,39 @@ EOF
392
425
  ok "Wrote $WEB_ENV"
393
426
  }
394
427
 
428
+ # ensure_env_key: append KEY=value to $file iff KEY is not already present.
429
+ # Used to forward-fill schema-required keys on preserved .env files without
430
+ # clobbering any user-set values.
431
+ ensure_env_key() {
432
+ local file="$1" key="$2" val="$3"
433
+ if ! grep -qE "^${key}=" "$file" 2>/dev/null; then
434
+ printf '%s=%s\n' "$key" "$val" >> "$file"
435
+ log " + added missing key: $key"
436
+ fi
437
+ }
438
+
439
+ ensure_web_env_keys() {
440
+ # Only fills keys that are missing; never overwrites existing values.
441
+ # Tokens / secrets get fresh random values (safe because if they were
442
+ # missing, web couldn't have started, so nothing depends on them yet).
443
+ local tok secret webhook
444
+ tok=$(gen_token 20)
445
+ secret=$(gen_token 24)
446
+ webhook=$(gen_token 20)
447
+ ensure_env_key "$WEB_ENV" WORK_PORT "$WORK_PORT"
448
+ ensure_env_key "$WEB_ENV" WORK_HOST "127.0.0.1"
449
+ ensure_env_key "$WEB_ENV" WORK_TOKEN "$tok"
450
+ ensure_env_key "$WEB_ENV" WORK_COOKIE_SECRET "$secret"
451
+ ensure_env_key "$WEB_ENV" WORK_OPERATOR_LOGIN "$USER"
452
+ ensure_env_key "$WEB_ENV" WORK_WRITES_ENABLED "true"
453
+ ensure_env_key "$WEB_ENV" WORK_DB_PATH "$WEB_DATA_DIR/ework.db"
454
+ ensure_env_key "$WEB_ENV" WORK_ATTACHMENT_ROOT "$WEB_DATA_DIR/attachments"
455
+ ensure_env_key "$WEB_ENV" WORK_FILE_ROOTS "/tmp,$DATA_DIR"
456
+ ensure_env_key "$WEB_ENV" WORK_DAEMON_BOT_LOGIN "$BOT_NAME"
457
+ ensure_env_key "$WEB_ENV" WORK_DAEMON_WEBHOOK_URL "http://127.0.0.1:$DAEMON_PORT"
458
+ ensure_env_key "$WEB_ENV" WORK_DAEMON_WEBHOOK_SECRET "$webhook"
459
+ }
460
+
395
461
  write_daemon_env() {
396
462
  mkdir -p "$(dirname "$DAEMON_ENV")"
397
463
  local bot_token="$1"
@@ -399,6 +465,22 @@ write_daemon_env() {
399
465
  # Reuse webhook secret from web env so daemon's signature matches web's verify
400
466
  webhook=$(awk -F= '/^WORK_DAEMON_WEBHOOK_SECRET=/{print $2}' "$WEB_ENV" 2>/dev/null || true)
401
467
  [[ -n "$webhook" ]] || webhook=$(gen_token 20)
468
+ if [[ -f "$DAEMON_ENV" ]]; then
469
+ log "Preserving existing $DAEMON_ENV"
470
+ # Same forward-fill pattern as web: required keys get filled if missing.
471
+ ensure_env_key "$DAEMON_ENV" DAEMON_ENV "production"
472
+ ensure_env_key "$DAEMON_ENV" DAEMON_PORT "$DAEMON_PORT"
473
+ ensure_env_key "$DAEMON_ENV" DAEMON_HOST "127.0.0.1"
474
+ ensure_env_key "$DAEMON_ENV" DAEMON_DB_PATH "$DAEMON_DATA_DIR/ework-daemon.db"
475
+ ensure_env_key "$DAEMON_ENV" GITEA_URL "http://127.0.0.1:$WORK_PORT"
476
+ ensure_env_key "$DAEMON_ENV" GITEA_TOKEN "$bot_token"
477
+ ensure_env_key "$DAEMON_ENV" GITEA_WEBHOOK_SECRET "$webhook"
478
+ ensure_env_key "$DAEMON_ENV" BOT_USERNAME "$BOT_NAME"
479
+ ensure_env_key "$DAEMON_ENV" BOT_TOKEN "$bot_token"
480
+ ensure_env_key "$DAEMON_ENV" OPENCODE_BINARY "$(command -v opencode)"
481
+ ensure_env_key "$DAEMON_ENV" OPENCODE_BASE_WORKDIR "$DATA_DIR/opencode-workdir"
482
+ return
483
+ fi
402
484
  cat > "$DAEMON_ENV" <<EOF
403
485
  # Generated by ework-aio at $(date -u +%Y-%m-%dT%H:%M:%SZ)
404
486
  DAEMON_ENV=production
@@ -809,14 +891,14 @@ esac
809
891
  # ─── Install mode ───────────────────────────────────────────────────────────
810
892
  hr
811
893
  log "ework-aio install"
812
- log " scope : $SCOPENAME"
894
+ log " mode : $([[ "$USE_SYSTEMD" == "1" ]] && echo "systemd" || echo "PID-file (no systemd)")"
813
895
  log " data dir : $DATA_DIR"
814
896
  log " web bin : $EWORK_WEB_BIN"
815
897
  log " daemon bin : $EWORK_DAEMON_BIN"
816
898
  log " opencode : $(command -v opencode) ($($(command -v opencode) --version 2>&1 | head -1))"
817
899
  hr
818
900
 
819
- if [[ "$SCOPENAME" == "--user" ]] && ! loginctl show-user "$USER" 2>/dev/null | grep -q '^Linger=yes'; then
901
+ if [[ "$USE_SYSTEMD" == "1" && "$SCOPENAME" == "--user" ]] && ! loginctl show-user "$USER" 2>/dev/null | grep -q '^Linger=yes'; then
820
902
  warn "User-level systemd requires lingering to keep services alive after logout."
821
903
  if [[ "$ASSUME_YES" == "1" ]]; then
822
904
  warn "Run this manually: sudo loginctl enable-linger $USER"
@@ -832,56 +914,57 @@ mkdir -p "$WEB_DATA_DIR" "$DAEMON_DATA_DIR" "$DATA_DIR/opencode-workdir"
832
914
 
833
915
  write_web_env
834
916
 
835
- # ─── Write ework-web systemd unit ──────────────────────────────────────────
836
- WEB_UNIT="$UNIT_DIR/ework-web.service"
837
- write_unit_file \
838
- "ework-web" \
839
- "ework — multi-project issue tracker" \
840
- "$EWORK_WEB_BIN" \
841
- "$WEB_DATA_DIR" \
842
- "$WEB_ENV" \
843
- "$WEB_UNIT"
844
- ok "Wrote $WEB_UNIT"
845
-
846
- # ─── Reload + start ework-web ──────────────────────────────────────────────
847
- # systemd calls here are best-effort. If systemd isn't functional on this host
848
- # (containers without systemd PID 1, polkit redirects under sudo, missing
849
- # /etc/systemd/system) we still want install to complete so the user can run
850
- # `ework-aio start` (PID-file mode) against the scaffolded .env.
851
- SYSTEMD_OK=1
852
- if ! ctl daemon-reload; then
853
- warn "systemctl daemon-reload failed — systemd may be unavailable on this host."
854
- SYSTEMD_OK=0
855
- fi
856
-
857
- if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
858
- log "Starting ework-web..."
859
- ctl enable ework-web.service || { warn "systemctl enable failed"; SYSTEMD_OK=0; }
860
- if [[ "$SYSTEMD_OK" == "1" ]]; then
861
- ctl restart ework-web.service || { warn "systemctl restart failed"; SYSTEMD_OK=0; }
917
+ # ─── Write ework-web systemd unit (only with `install systemd`) ────────────
918
+ SYSTEMD_OK=0
919
+ if [[ "$USE_SYSTEMD" == "1" ]]; then
920
+ WEB_UNIT="$UNIT_DIR/ework-web.service"
921
+ write_unit_file \
922
+ "ework-web" \
923
+ "ework — multi-project issue tracker" \
924
+ "$EWORK_WEB_BIN" \
925
+ "$WEB_DATA_DIR" \
926
+ "$WEB_ENV" \
927
+ "$WEB_UNIT"
928
+ ok "Wrote $WEB_UNIT"
929
+
930
+ # systemd calls here are best-effort. If systemd isn't functional on this host
931
+ # (containers without systemd PID 1, polkit redirects under sudo, missing
932
+ # /etc/systemd/system) we still want install to complete so the user can run
933
+ # `ework-aio start` (PID-file mode) against the scaffolded .env.
934
+ SYSTEMD_OK=1
935
+ if ! ctl daemon-reload; then
936
+ warn "systemctl daemon-reload failed — systemd may be unavailable on this host."
937
+ SYSTEMD_OK=0
862
938
  fi
863
- if [[ "$SYSTEMD_OK" == "1" ]]; then
864
- for i in $(seq 1 60); do
865
- if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
866
- ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
867
- break
868
- fi
869
- sleep 0.5
870
- [[ $i -eq 60 ]] && { warn "ework-web did not come up via systemd in 30s. Falling back to PID-file mode."; SYSTEMD_OK=0; }
871
- done
939
+
940
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
941
+ log "Starting ework-web (systemd)..."
942
+ ctl enable ework-web.service || { warn "systemctl enable failed"; SYSTEMD_OK=0; }
943
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
944
+ ctl restart ework-web.service || { warn "systemctl restart failed"; SYSTEMD_OK=0; }
945
+ fi
946
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
947
+ for i in $(seq 1 60); do
948
+ if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
949
+ ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
950
+ break
951
+ fi
952
+ sleep 0.5
953
+ [[ $i -eq 60 ]] && { warn "ework-web did not come up via systemd in 30s. Falling back to PID-file mode."; SYSTEMD_OK=0; }
954
+ done
955
+ fi
956
+ else
957
+ warn "--no-start: unit enabled but not started"
958
+ ctl enable ework-web.service 2>/dev/null || true
872
959
  fi
873
- else
874
- warn "--no-start: unit enabled but not started"
875
- ctl enable ework-web.service 2>/dev/null || true
876
960
  fi
877
961
 
878
- # ─── PID-file mode fallback for bot bootstrap ──────────────────────────────
879
- # If systemd couldn't start web (or we're on a host without systemd), bring
880
- # web up briefly via the PID-file path so the bot user + PAT bootstrap can
881
- # still complete. We leave web running in PID-file mode so subsequent curl
882
- # calls to :WORK_PORT succeed.
962
+ # ─── Start ework-web in PID-file mode ──────────────────────────────────────
963
+ # Default path (no `systemd` arg) OR systemd fallback. We bring web up via
964
+ # setsid+nohup so the bot bootstrap below can HTTP-probe it. Web stays
965
+ # running in PID-file mode after install — use `ework-aio stop` to kill it.
883
966
  if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "0" ]]; then
884
- log "Starting ework-web via PID-file mode (nohup) for bot bootstrap..."
967
+ log "Starting ework-web (PID-file mode)..."
885
968
  WEB_LOG_FILE="$DATA_DIR/run/web.log"
886
969
  mkdir -p "$(dirname "$WEB_LOG_FILE")"
887
970
  setsid nohup "$EWORK_WEB_BIN" >>"$WEB_LOG_FILE" 2>&1 </dev/null &
@@ -975,42 +1058,55 @@ fi
975
1058
  # ─── Write ework-daemon .env ───────────────────────────────────────────────
976
1059
  write_daemon_env "$BOT_TOKEN"
977
1060
 
978
- # ─── Write ework-daemon systemd unit ───────────────────────────────────────
979
- DAEMON_UNIT="$UNIT_DIR/ework-daemon.service"
980
- write_unit_file \
981
- "ework-daemon" \
982
- "ework-daemon — issue-driven AI dev daemon" \
983
- "$EWORK_DAEMON_BIN" \
984
- "$DAEMON_DATA_DIR" \
985
- "$DAEMON_ENV" \
986
- "$DAEMON_UNIT" \
987
- " \"\""
988
- # (trailing " \"\"" is a no-op separator; kept for future extra env injection)
989
- ok "Wrote $DAEMON_UNIT"
990
-
991
- ctl daemon-reload 2>/dev/null || true
992
- if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
993
- log "Starting ework-daemon..."
994
- ctl enable ework-daemon.service 2>/dev/null || true
995
- ctl restart ework-daemon.service 2>/dev/null || warn "ework-daemon restart via systemd failed; use 'ework-aio start daemon' for PID-file mode"
996
- sleep 1
997
- if ctl is-active --quiet ework-daemon.service 2>/dev/null; then
998
- ok "ework-daemon active"
1061
+ # ─── Write + start ework-daemon (systemd path, only with `install systemd`) ─
1062
+ if [[ "$USE_SYSTEMD" == "1" ]]; then
1063
+ DAEMON_UNIT="$UNIT_DIR/ework-daemon.service"
1064
+ write_unit_file \
1065
+ "ework-daemon" \
1066
+ "ework-daemon — issue-driven AI dev daemon" \
1067
+ "$EWORK_DAEMON_BIN" \
1068
+ "$DAEMON_DATA_DIR" \
1069
+ "$DAEMON_ENV" \
1070
+ "$DAEMON_UNIT" \
1071
+ " \"\""
1072
+ # (trailing " \"\"" is a no-op separator; kept for future extra env injection)
1073
+ ok "Wrote $DAEMON_UNIT"
1074
+
1075
+ ctl daemon-reload 2>/dev/null || true
1076
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
1077
+ log "Starting ework-daemon (systemd)..."
1078
+ ctl enable ework-daemon.service 2>/dev/null || true
1079
+ ctl restart ework-daemon.service 2>/dev/null || warn "ework-daemon restart via systemd failed; falling back to PID-file mode"
1080
+ sleep 1
1081
+ if ctl is-active --quiet ework-daemon.service 2>/dev/null; then
1082
+ ok "ework-daemon active (systemd)"
1083
+ else
1084
+ warn "ework-daemon not active via systemd — falling back to PID-file mode"
1085
+ SYSTEMD_OK=0
1086
+ fi
999
1087
  else
1000
- warn "ework-daemon did not report active; run 'ework-aio start daemon' for PID-file mode"
1088
+ ctl enable ework-daemon.service 2>/dev/null || true
1001
1089
  fi
1002
- else
1003
- ctl enable ework-daemon.service 2>/dev/null || true
1004
1090
  fi
1005
1091
 
1006
- if [[ "$SYSTEMD_OK" == "0" ]]; then
1092
+ # ─── Start ework-daemon in PID-file mode (default path) ────────────────────
1093
+ # Mirrors the web PID-file start. Skipped if systemd already brought it up.
1094
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "0" ]]; then
1095
+ log "Starting ework-daemon (PID-file mode)..."
1096
+ DAEMON_LOG_FILE="$DATA_DIR/run/daemon.log"
1097
+ mkdir -p "$(dirname "$DAEMON_LOG_FILE")"
1098
+ setsid nohup "$EWORK_DAEMON_BIN" >>"$DAEMON_LOG_FILE" 2>&1 </dev/null &
1099
+ DAEMON_PID=$!
1100
+ disown "$DAEMON_PID" 2>/dev/null || true
1101
+ printf '%s\n' "$DAEMON_PID" > "$DATA_DIR/run/daemon.pid"
1102
+ ok "ework-daemon started in PID-file mode (pid $DAEMON_PID, log $DAEMON_LOG_FILE)"
1103
+ fi
1104
+
1105
+ if [[ "$USE_SYSTEMD" == "1" && "$SYSTEMD_OK" == "0" ]]; then
1007
1106
  hr
1008
1107
  warn "systemd mode did not come up cleanly on this host."
1009
- warn "Services are scaffolded but not auto-started under systemd."
1010
- warn "Use PID-file mode instead:"
1011
- echo " ework-aio start # start web + daemon in background"
1012
- echo " ework-aio ps # check status"
1013
- echo " ework-aio logs web # tail web log"
1108
+ warn "Services started in PID-file mode instead. systemd unit files were"
1109
+ warn "written but are not active. To clean them up: ework-aio uninstall"
1014
1110
  hr
1015
1111
  fi
1016
1112
 
@@ -1071,7 +1167,7 @@ fi
1071
1167
 
1072
1168
  # ─── Done ──────────────────────────────────────────────────────────────────
1073
1169
  hr
1074
- ok "Install complete."
1170
+ ok "Install complete ($([[ "$USE_SYSTEMD" == "1" ]] && echo "systemd mode" || echo "PID-file mode"))."
1075
1171
  hr
1076
1172
  printf '\n%s→%s Open %shttp://127.0.0.1:%s/login%s\n' \
1077
1173
  "$c_bold" "$c_reset" "$c_dim" "$WORK_PORT" "$c_reset"
@@ -1083,7 +1179,14 @@ printf ' Bot user: %s%s%s (auto-created, used by ework-daemon)\n' \
1083
1179
  printf ' Data dir: %s%s%s\n' "$c_dim" "$DATA_DIR" "$c_reset"
1084
1180
  printf ' Logs: ework-aio logs web | ework-aio logs daemon\n'
1085
1181
  printf ' Status: ework-aio status\n'
1182
+ printf ' Stop: ework-aio stop\n'
1086
1183
  printf ' Uninstall: ework-aio uninstall\n'
1184
+ if [[ "$USE_SYSTEMD" != "1" ]]; then
1185
+ hr
1186
+ printf '\n%sPID-file mode note%s (services run via nohup, not systemd)\n' "$c_bold" "$c_reset"
1187
+ printf ' • To enable auto-restart on boot, re-run with: ework-aio install systemd\n'
1188
+ printf ' • Services stop when you kill them (no supervisor). Use ework-aio stop to stop cleanly.\n'
1189
+ fi
1087
1190
  hr
1088
1191
  printf '\n%sNext steps (optional config)%s\n' "$c_bold" "$c_reset"
1089
1192
  printf ' • 朗读 (TTS): %shttp://127.0.0.1:%s/admin/tts%s — needs an OpenAI-compat /audio/speech endpoint\n' \
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "All-in-one installer for ework (issue tracker) + ework-daemon (AI bridge) + opencode-ework (plugin). One command: npm i -g ework-aio && ework-aio install.",
5
5
  "type": "module",
6
6
  "license": "MIT",