ework-aio 0.1.9 → 0.1.11

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.
package/bin/ework-aio CHANGED
@@ -253,10 +253,11 @@ function startOne(svc: SvcName, foreground: boolean): boolean {
253
253
  }
254
254
  const envPath = svcEnvPath(svc);
255
255
  if (!existsSync(envPath)) {
256
- console.error(
257
- `${SVC_CONFIG[svc].label}: missing .env at ${envPath}.`
258
- );
259
- console.error(" Run `ework-aio install --no-start` first to scaffold config + data dir.");
256
+ console.error(`${SVC_CONFIG[svc].label}: missing .env at ${envPath}.`);
257
+ console.error(" Run `ework-aio install` first to scaffold config + data dir.");
258
+ console.error(" If install fails with npm ENOTEMPTY, a stale temp dir is blocking it.");
259
+ console.error(" Manual fix: sudo rm -rf \"$(npm root -g)/.ework-*\" \"$(npm root -g)\"/*.Trash*");
260
+ console.error(" Then re-run: ework-aio install");
260
261
  return false;
261
262
  }
262
263
 
package/bin/install.sh CHANGED
@@ -12,6 +12,13 @@
12
12
 
13
13
  set -euo pipefail
14
14
 
15
+ # ERR trap: when `set -e` kills the script, print the line + last command so
16
+ # the user sees why instead of an empty prompt. Tricky failure modes (root
17
+ # scope + polkit redirect, missing systemd PID 1, journalctl-as-root) all
18
+ # manifest as silent exit at a systemctl call — without this trace, the user
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
21
+
15
22
  # ─── Pretty output ──────────────────────────────────────────────────────────
16
23
  c_reset=$'\033[0m'; c_bold=$'\033[1m'; c_dim=$'\033[2m'
17
24
  c_red=$'\033[31m'; c_grn=$'\033[32m'; c_ylw=$'\033[33m'; c_blu=$'\033[34m'
@@ -162,12 +169,59 @@ ensure_pkg() {
162
169
  }
163
170
  case "$MODE" in
164
171
  install)
172
+ # Pre-clean stale npm rename temp-dirs from any previous failed install.
173
+ # npm's atomic-install pattern renames the existing package dir to a
174
+ # `.<pkg>-<rand>` sibling before extracting the new tarball; if a prior
175
+ # install crashed mid-flight, that temp dir stays behind and the NEXT
176
+ # install fails with ENOTEMPTY on the rename. Symptoms:
177
+ # npm error code ENOTEMPTY
178
+ # npm error syscall rename
179
+ # npm error path .../node_modules/ework-web
180
+ # npm error dest .../node_modules/.ework-web-Cx2Tkt83
181
+ clean_npm_stale_dirs() {
182
+ local npm_root
183
+ npm_root="$(npm root -g 2>/dev/null)" || return 0
184
+ [[ -d "$npm_root" ]] || return 0
185
+ # Stale temp patterns: .ework-web-XXXX, .ework-daemon-XXXX, .opencode-ework-XXXX,
186
+ # .ework-aio-XXXX, plus the .Trash suffix some npm versions use.
187
+ local found=()
188
+ while IFS= read -r p; do
189
+ [[ -n "$p" ]] && found+=("$p")
190
+ done < <(find "$npm_root" -maxdepth 1 \( \
191
+ -name '.ework-web-*' -o \
192
+ -name '.ework-daemon-*' -o \
193
+ -name '.ework-aio-*' -o \
194
+ -name '.opencode-ework-*' -o \
195
+ -name 'ework-*.Trash*' -o \
196
+ -name 'opencode-ework.Trash*' \) 2>/dev/null || true)
197
+ if [[ ${#found[@]} -gt 0 ]]; then
198
+ log "Found ${#found[@]} stale npm temp dir(s) under $npm_root from a previous failed install:"
199
+ printf ' %s\n' "${found[@]}"
200
+ log "Removing..."
201
+ local d
202
+ for d in "${found[@]}"; do
203
+ rm -rf "$d" 2>/dev/null || warn "could not remove $d (try: sudo rm -rf $d)"
204
+ done
205
+ fi
206
+ }
207
+
208
+ npm_install_global() {
209
+ # One retry after a cleanup pass — covers the ENOTEMPTY case where npm
210
+ # itself produced a fresh temp dir during this same failed run.
211
+ if ! npm install -g "$1@latest"; then
212
+ warn "npm install -g $1@latest failed; cleaning stale temp dirs and retrying once..."
213
+ clean_npm_stale_dirs
214
+ npm install -g "$1@latest"
215
+ fi
216
+ }
217
+
218
+ clean_npm_stale_dirs
165
219
  log "Installing/updating npm packages globally..."
166
220
  # Install each as a top-level global so bins are linked to PATH.
167
221
  # (npm v9 doesn't hoist nested deps' bins from `npm i -g ework-aio`.)
168
222
  # Always run `npm install -g` (not gated on presence) so re-runs pick up new versions.
169
223
  for pkg in ework-web ework-daemon opencode-ework ework-aio; do
170
- npm install -g "$pkg@latest" || die "npm install -g $pkg@latest failed"
224
+ npm_install_global "$pkg" || die "npm install -g $pkg@latest failed (even after cleanup retry; manual fix: sudo rm -rf $(npm root -g)/.$pkg-* $(npm root -g)/$pkg.Trash*)"
171
225
  done
172
226
  ok "npm packages ready"
173
227
 
@@ -634,22 +688,59 @@ write_unit_file \
634
688
  ok "Wrote $WEB_UNIT"
635
689
 
636
690
  # ─── Reload + start ework-web ──────────────────────────────────────────────
637
- ctl daemon-reload
638
- if [[ "$NO_START" == "0" ]]; then
691
+ # systemd calls here are best-effort. If systemd isn't functional on this host
692
+ # (containers without systemd PID 1, polkit redirects under sudo, missing
693
+ # /etc/systemd/system) we still want install to complete so the user can run
694
+ # `ework-aio start` (PID-file mode) against the scaffolded .env.
695
+ SYSTEMD_OK=1
696
+ if ! ctl daemon-reload; then
697
+ warn "systemctl daemon-reload failed — systemd may be unavailable on this host."
698
+ SYSTEMD_OK=0
699
+ fi
700
+
701
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
639
702
  log "Starting ework-web..."
640
- ctl enable ework-web.service
641
- ctl restart ework-web.service
703
+ ctl enable ework-web.service || { warn "systemctl enable failed"; SYSTEMD_OK=0; }
704
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
705
+ ctl restart ework-web.service || { warn "systemctl restart failed"; SYSTEMD_OK=0; }
706
+ fi
707
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
708
+ for i in $(seq 1 60); do
709
+ if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
710
+ ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
711
+ break
712
+ fi
713
+ sleep 0.5
714
+ [[ $i -eq 60 ]] && { warn "ework-web did not come up via systemd in 30s. Falling back to PID-file mode."; SYSTEMD_OK=0; }
715
+ done
716
+ fi
717
+ else
718
+ warn "--no-start: unit enabled but not started"
719
+ ctl enable ework-web.service 2>/dev/null || true
720
+ fi
721
+
722
+ # ─── PID-file mode fallback for bot bootstrap ──────────────────────────────
723
+ # If systemd couldn't start web (or we're on a host without systemd), bring
724
+ # web up briefly via the PID-file path so the bot user + PAT bootstrap can
725
+ # still complete. We leave web running in PID-file mode so subsequent curl
726
+ # calls to :WORK_PORT succeed.
727
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "0" ]]; then
728
+ log "Starting ework-web via PID-file mode (nohup) for bot bootstrap..."
729
+ WEB_LOG_FILE="$DATA_DIR/run/web.log"
730
+ mkdir -p "$(dirname "$WEB_LOG_FILE")"
731
+ setsid nohup "$EWORK_WEB_BIN" >>"$WEB_LOG_FILE" 2>&1 </dev/null &
732
+ WEB_PID=$!
733
+ disown "$WEB_PID" 2>/dev/null || true
734
+ printf '%s\n' "$WEB_PID" > "$DATA_DIR/run/web.pid"
735
+ ok "ework-web started in PID-file mode (pid $WEB_PID, log $WEB_LOG_FILE)"
642
736
  for i in $(seq 1 60); do
643
737
  if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
644
- ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
738
+ ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds, PID-file mode)"
645
739
  break
646
740
  fi
647
741
  sleep 0.5
648
- [[ $i -eq 60 ]] && die "ework-web did not come up in 30s. Check: journalctl $SCOPENAME -u ework-web.service -n 50"
742
+ [[ $i -eq 60 ]] && die "ework-web did not come up in 30s. Tail of $WEB_LOG_FILE: $(tail -n 20 "$WEB_LOG_FILE" 2>/dev/null)"
649
743
  done
650
- else
651
- warn "--no-start: unit enabled but not started"
652
- ctl enable ework-web.service
653
744
  fi
654
745
 
655
746
  # ─── Bootstrap bot user + PAT (idempotent) ─────────────────────────────────
@@ -662,10 +753,23 @@ COOKIE_SIG=$(printf '%s' "$WORK_TOKEN_VAL" \
662
753
  AUTH_COOKIE="ework_auth=${WORK_TOKEN_VAL}.${COOKIE_SIG}"
663
754
 
664
755
  BOT_TOKEN=""
756
+ BOT_BOOTSTRAP_OK=1
665
757
  if [[ -f "$BOT_TOKEN_FILE" ]]; then
666
758
  BOT_TOKEN=$(cat "$BOT_TOKEN_FILE")
667
759
  ok "Reusing saved bot token from $BOT_TOKEN_FILE"
668
760
  else
761
+ # Pre-flight: web must be reachable for the bot bootstrap HTTP calls below.
762
+ # In --no-start mode OR on hosts where systemd couldn't bring web up, skip
763
+ # the bootstrap entirely — daemon .env still gets written (with empty token)
764
+ # so the user can `ework-aio start` and re-run install later.
765
+ if ! curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
766
+ warn "ework-web not reachable at :$WORK_PORT — skipping bot bootstrap."
767
+ warn "Daemon will not be able to talk to web until you re-run install with web running."
768
+ BOT_BOOTSTRAP_OK=0
769
+ fi
770
+ fi
771
+
772
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
669
773
  log "Bootstrapping bot user '$BOT_NAME'..."
670
774
  BOT_PW=$(openssl rand -hex 24)
671
775
  CREATE_CODE=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
@@ -678,9 +782,11 @@ else
678
782
  case "$CREATE_CODE" in
679
783
  303) ok "Bot user '$BOT_NAME' created" ;;
680
784
  400|409) warn "Bot user '$BOT_NAME' already exists (continuing)" ;;
681
- *) die "Failed to create bot user: HTTP $CREATE_CODE" ;;
785
+ *) warn "Failed to create bot user: HTTP $CREATE_CODE — skipping PAT mint; daemon will not be able to talk to web until re-run"; BOT_BOOTSTRAP_OK=0 ;;
682
786
  esac
787
+ fi
683
788
 
789
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
684
790
  log "Logging in as bot to mint PAT..."
685
791
  COOKIE_JAR=$(mktemp)
686
792
  LOGIN_CODE=$(curl -sS -c "$COOKIE_JAR" -X POST "http://127.0.0.1:$WORK_PORT/login" \
@@ -689,18 +795,25 @@ else
689
795
  -o /dev/null -w '%{http_code}') || LOGIN_CODE=000
690
796
  BOT_COOKIE=$(awk '/ework_auth/ {print $7}' "$COOKIE_JAR")
691
797
  rm -f "$COOKIE_JAR"
692
- [[ "$LOGIN_CODE" == "302" && -n "$BOT_COOKIE" ]] \
693
- || die "Bot login failed: HTTP $LOGIN_CODE"
798
+ if [[ "$LOGIN_CODE" != "302" || -z "$BOT_COOKIE" ]]; then
799
+ warn "Bot login failed: HTTP $LOGIN_CODE — skipping PAT mint"
800
+ BOT_BOOTSTRAP_OK=0
801
+ fi
802
+ fi
694
803
 
804
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
695
805
  log "Minting PAT..."
696
806
  PAT_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/me/tokens/create" \
697
807
  -H "Cookie: ework_auth=$BOT_COOKIE" \
698
808
  --data-urlencode "name=aio-$(date +%s)")
699
809
  BOT_TOKEN=$(printf '%s' "$PAT_RES" | grep -oE 'id="t">[a-f0-9]{40}<' | grep -oE '[a-f0-9]{40}' | head -1 || true)
700
- [[ -n "$BOT_TOKEN" ]] || die "Could not extract PAT from token-create response"
701
- printf '%s' "$BOT_TOKEN" > "$BOT_TOKEN_FILE"
702
- chmod 600 "$BOT_TOKEN_FILE"
703
- ok "Bot PAT saved to $BOT_TOKEN_FILE"
810
+ if [[ -z "$BOT_TOKEN" ]]; then
811
+ warn "Could not extract PAT from token-create response — daemon .env will have empty token"
812
+ else
813
+ printf '%s' "$BOT_TOKEN" > "$BOT_TOKEN_FILE"
814
+ chmod 600 "$BOT_TOKEN_FILE"
815
+ ok "Bot PAT saved to $BOT_TOKEN_FILE"
816
+ fi
704
817
  fi
705
818
 
706
819
  # ─── Write ework-daemon .env ───────────────────────────────────────────────
@@ -719,19 +832,30 @@ write_unit_file \
719
832
  # (trailing " \"\"" is a no-op separator; kept for future extra env injection)
720
833
  ok "Wrote $DAEMON_UNIT"
721
834
 
722
- ctl daemon-reload
723
- if [[ "$NO_START" == "0" ]]; then
835
+ ctl daemon-reload 2>/dev/null || true
836
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
724
837
  log "Starting ework-daemon..."
725
- ctl enable ework-daemon.service
726
- ctl restart ework-daemon.service
838
+ ctl enable ework-daemon.service 2>/dev/null || true
839
+ ctl restart ework-daemon.service 2>/dev/null || warn "ework-daemon restart via systemd failed; use 'ework-aio start daemon' for PID-file mode"
727
840
  sleep 1
728
- if ctl is-active --quiet ework-daemon.service; then
841
+ if ctl is-active --quiet ework-daemon.service 2>/dev/null; then
729
842
  ok "ework-daemon active"
730
843
  else
731
- warn "ework-daemon did not report active; check: journalctl $SCOPENAME -u ework-daemon.service -n 50"
844
+ warn "ework-daemon did not report active; run 'ework-aio start daemon' for PID-file mode"
732
845
  fi
733
846
  else
734
- ctl enable ework-daemon.service
847
+ ctl enable ework-daemon.service 2>/dev/null || true
848
+ fi
849
+
850
+ if [[ "$SYSTEMD_OK" == "0" ]]; then
851
+ hr
852
+ warn "systemd mode did not come up cleanly on this host."
853
+ warn "Services are scaffolded but not auto-started under systemd."
854
+ warn "Use PID-file mode instead:"
855
+ echo " ework-aio start # start web + daemon in background"
856
+ echo " ework-aio ps # check status"
857
+ echo " ework-aio logs web # tail web log"
858
+ hr
735
859
  fi
736
860
 
737
861
  # ─── Register opencode-ework plugin ────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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",