ework-aio 0.1.10 → 0.1.12

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 (2) hide show
  1. package/bin/install.sh +183 -33
  2. package/package.json +1 -1
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'
@@ -32,6 +39,7 @@ NO_START=0
32
39
  ASSUME_YES=0
33
40
  NO_RESTART=0
34
41
  CFG_ARGS=()
42
+ MODE_ARGS=()
35
43
 
36
44
  usage() {
37
45
  cat <<'EOF'
@@ -73,10 +81,19 @@ while [[ $# -gt 0 ]]; do
73
81
  MODE="config"
74
82
  shift
75
83
  # Consume remaining args as config subcommand + its positionals.
84
+ # Global flags (--data-dir, --user, --system, etc.) are still
85
+ # respected here so `config set K V --data-dir X` works as expected.
76
86
  while [[ $# -gt 0 ]]; do
77
87
  case "$1" in
78
88
  --no-restart) NO_RESTART=1; shift ;;
79
89
  -h|--help) CFG_ARGS=("help"); shift ;;
90
+ --user) SCOPENAME="--user"; shift ;;
91
+ --system) SCOPENAME="--system"; shift ;;
92
+ --data-dir) DATA_DIR="$2"; shift 2 ;;
93
+ --port) WORK_PORT="$2"; shift 2 ;;
94
+ --daemon-port) DAEMON_PORT="$2"; shift 2 ;;
95
+ --bot-name) BOT_NAME="$2"; shift 2 ;;
96
+ --yes|-y) ASSUME_YES=1; shift ;;
80
97
  *) CFG_ARGS+=("$1"); shift ;;
81
98
  esac
82
99
  done
@@ -91,7 +108,8 @@ while [[ $# -gt 0 ]]; do
91
108
  --no-restart) NO_RESTART=1; shift ;;
92
109
  --yes|-y) ASSUME_YES=1; shift ;;
93
110
  -h|--help) usage; exit 0 ;;
94
- *) die "Unknown argument: $1 (try --help)" ;;
111
+ --*) die "Unknown option: $1 (try --help)" ;;
112
+ *) MODE_ARGS+=("$1"); shift ;;
95
113
  esac
96
114
  done
97
115
 
@@ -137,12 +155,13 @@ ensure_user_session || true # warn but don't hard-fail — let later steps rep
137
155
  need_cmd() {
138
156
  command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1. $2"
139
157
  }
140
- # Commands every mode needs (status/logs/env/config/uninstall all use systemctl).
141
- need_cmd systemctl "This installer requires systemd."
142
158
  need_cmd awk "Should be present on any Linux."
143
159
 
144
- # Install-only deps. Other modes work even if these are missing.
160
+ # install needs systemctl (to write unit files + reload). Other modes
161
+ # (status/logs/env/config/uninstall) can fall back to PID-file mode via
162
+ # the 'ework-aio' dispatcher if systemd is unreachable on this host.
145
163
  if [[ "$MODE" == "install" ]]; then
164
+ 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)."
146
165
  need_cmd bun "Install from https://bun.sh"
147
166
  need_cmd npm "Install Node.js or Bun (ships npm)"
148
167
  need_cmd opencode "Install from https://opencode.ai"
@@ -151,6 +170,23 @@ if [[ "$MODE" == "install" ]]; then
151
170
  need_cmd jq "Install jq (for opencode.json merge)."
152
171
  fi
153
172
 
173
+ # EWORK_AIO_BIN: always resolvable (used by status/logs/config/uninstall
174
+ # to delegate to PID-file mode operations when systemd is unreachable).
175
+ EWORK_AIO_BIN="$(command -v ework-aio 2>/dev/null || true)"
176
+ [[ -n "$EWORK_AIO_BIN" ]] || EWORK_AIO_BIN="$(npm root -g 2>/dev/null)/ework-aio/bin/ework-aio"
177
+
178
+ # systemd_reachable: 0 if `systemctl $SCOPENAME` can talk to systemd, 1 if not.
179
+ # Used by status/logs/config/uninstall to decide whether to use systemd
180
+ # (preferred) or fall back to PID-file mode via the ework-aio dispatcher.
181
+ # Hosts where this returns 1: containers without systemd PID 1, polkit
182
+ # redirects under sudo that drop the bus, hosts where the user isn't
183
+ # permissioned for the system bus.
184
+ systemd_reachable() {
185
+ command -v systemctl >/dev/null 2>&1 || return 1
186
+ systemctl "$SCOPENAME" is-system-running >/dev/null 2>&1 \
187
+ || systemctl "$SCOPENAME" list-units --no-pager >/dev/null 2>&1
188
+ }
189
+
154
190
  # Verify the 3 npm packages are reachable. If not, try to install them now.
155
191
  ensure_pkg() {
156
192
  local pkg="$1"
@@ -462,6 +498,19 @@ service_for_key() {
462
498
  restart_service() {
463
499
  local svc="$1"
464
500
  local rc=0
501
+ if ! systemd_reachable; then
502
+ log "systemd not reachable — using PID-file mode restart"
503
+ if [[ "$svc" == "both" ]]; then
504
+ "$EWORK_AIO_BIN" restart both && ok "Restarted ework-web + ework-daemon (PID-file mode)" || rc=$?
505
+ else
506
+ "$EWORK_AIO_BIN" restart "$svc" && ok "Restarted ework-$svc (PID-file mode)" || rc=$?
507
+ fi
508
+ if [[ $rc -ne 0 ]]; then
509
+ warn "PID-file restart of '$svc' failed (exit $rc)."
510
+ warn "The .env changes are saved; run 'ework-aio restart $svc' manually."
511
+ fi
512
+ return $rc
513
+ fi
465
514
  case "$svc" in
466
515
  web) ctl restart ework-web.service && ok "Restarted ework-web" || rc=$? ;;
467
516
  daemon) ctl restart ework-daemon.service && ok "Restarted ework-daemon" || rc=$? ;;
@@ -615,7 +664,22 @@ config)
615
664
  ;;
616
665
 
617
666
  status)
618
- hr; log "ework-aio status ($SCOPENAME)"; hr
667
+ hr; log "ework-aio status"; hr
668
+ if ! systemd_reachable; then
669
+ log "(systemd unreachable — showing PID-file mode status)"
670
+ "$EWORK_AIO_BIN" ps 2>/dev/null || warn "ework-aio ps failed"
671
+ hr
672
+ log "Port listeners:"
673
+ p=""
674
+ for p in $(grep -hE '^(WORK_PORT|DAEMON_PORT)=' "$WEB_ENV" "$DAEMON_ENV" 2>/dev/null | cut -d= -f2 | sort -u); do
675
+ if curl -sf -o /dev/null --max-time 1 "http://127.0.0.1:$p/login" 2>/dev/null; then
676
+ printf ' :%s ✓ listening\n' "$p"
677
+ else
678
+ printf ' :%s ✗ not responding\n' "$p"
679
+ fi
680
+ done
681
+ exit 0
682
+ fi
619
683
  ctl is-active ework-web.service || true
620
684
  ctl is-active ework-daemon.service || true
621
685
  ctl status --no-pager --lines=0 ework-web.service 2>/dev/null || true
@@ -625,18 +689,34 @@ status)
625
689
  ;;
626
690
 
627
691
  logs)
628
- svc="${1:-ework-web.service}"
692
+ svc="${MODE_ARGS[0]:-ework-web.service}"
629
693
  [[ "$svc" == "daemon" || "$svc" == "ework-daemon" ]] && svc="ework-daemon.service"
630
694
  [[ "$svc" == "web" || "$svc" == "ework-web" ]] && svc="ework-web.service"
695
+ if ! systemd_reachable; then
696
+ svc_short="${svc%.service}"
697
+ svc_short="${svc_short#ework-}"
698
+ log_file="$DATA_DIR/run/${svc_short}.log"
699
+ if [[ ! -f "$log_file" ]]; then
700
+ die "systemd unreachable and no PID-file log at $log_file. Start services with: ework-aio start $svc_short"
701
+ fi
702
+ log "(systemd unreachable — tailing PID-file log $log_file)"
703
+ exec tail -n 200 -f "$log_file"
704
+ fi
631
705
  exec journalctl "$SCOPENAME" -u "$svc" -f
632
706
  ;;
633
707
 
634
708
  uninstall)
635
709
  hr; log "Uninstalling ework-aio services (keeping data)"; hr
636
- ctl stop ework-web.service ework-daemon.service 2>/dev/null || true
637
- ctl disable ework-web.service ework-daemon.service 2>/dev/null || true
638
- rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service"
639
- ctl daemon-reload
710
+ "$EWORK_AIO_BIN" stop both 2>/dev/null || true
711
+ if systemd_reachable; then
712
+ ctl stop ework-web.service ework-daemon.service 2>/dev/null || true
713
+ ctl disable ework-web.service ework-daemon.service 2>/dev/null || true
714
+ rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service"
715
+ ctl daemon-reload 2>/dev/null || true
716
+ else
717
+ log "(systemd unreachable — skipped unit cleanup; PID-file mode services stopped above)"
718
+ rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service" 2>/dev/null || true
719
+ fi
640
720
  ok "Services removed. Data preserved at $DATA_DIR"
641
721
  warn "To fully remove: rm -rf $DATA_DIR && npm uninstall -g ework-aio ework-web ework-daemon opencode-ework"
642
722
  exit 0
@@ -681,22 +761,59 @@ write_unit_file \
681
761
  ok "Wrote $WEB_UNIT"
682
762
 
683
763
  # ─── Reload + start ework-web ──────────────────────────────────────────────
684
- ctl daemon-reload
685
- if [[ "$NO_START" == "0" ]]; then
764
+ # systemd calls here are best-effort. If systemd isn't functional on this host
765
+ # (containers without systemd PID 1, polkit redirects under sudo, missing
766
+ # /etc/systemd/system) we still want install to complete so the user can run
767
+ # `ework-aio start` (PID-file mode) against the scaffolded .env.
768
+ SYSTEMD_OK=1
769
+ if ! ctl daemon-reload; then
770
+ warn "systemctl daemon-reload failed — systemd may be unavailable on this host."
771
+ SYSTEMD_OK=0
772
+ fi
773
+
774
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
686
775
  log "Starting ework-web..."
687
- ctl enable ework-web.service
688
- ctl restart ework-web.service
776
+ ctl enable ework-web.service || { warn "systemctl enable failed"; SYSTEMD_OK=0; }
777
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
778
+ ctl restart ework-web.service || { warn "systemctl restart failed"; SYSTEMD_OK=0; }
779
+ fi
780
+ if [[ "$SYSTEMD_OK" == "1" ]]; then
781
+ for i in $(seq 1 60); do
782
+ if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
783
+ ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
784
+ break
785
+ fi
786
+ sleep 0.5
787
+ [[ $i -eq 60 ]] && { warn "ework-web did not come up via systemd in 30s. Falling back to PID-file mode."; SYSTEMD_OK=0; }
788
+ done
789
+ fi
790
+ else
791
+ warn "--no-start: unit enabled but not started"
792
+ ctl enable ework-web.service 2>/dev/null || true
793
+ fi
794
+
795
+ # ─── PID-file mode fallback for bot bootstrap ──────────────────────────────
796
+ # If systemd couldn't start web (or we're on a host without systemd), bring
797
+ # web up briefly via the PID-file path so the bot user + PAT bootstrap can
798
+ # still complete. We leave web running in PID-file mode so subsequent curl
799
+ # calls to :WORK_PORT succeed.
800
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "0" ]]; then
801
+ log "Starting ework-web via PID-file mode (nohup) for bot bootstrap..."
802
+ WEB_LOG_FILE="$DATA_DIR/run/web.log"
803
+ mkdir -p "$(dirname "$WEB_LOG_FILE")"
804
+ setsid nohup "$EWORK_WEB_BIN" >>"$WEB_LOG_FILE" 2>&1 </dev/null &
805
+ WEB_PID=$!
806
+ disown "$WEB_PID" 2>/dev/null || true
807
+ printf '%s\n' "$WEB_PID" > "$DATA_DIR/run/web.pid"
808
+ ok "ework-web started in PID-file mode (pid $WEB_PID, log $WEB_LOG_FILE)"
689
809
  for i in $(seq 1 60); do
690
810
  if curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
691
- ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds)"
811
+ ok "ework-web listening on :$WORK_PORT (after ${i} half-seconds, PID-file mode)"
692
812
  break
693
813
  fi
694
814
  sleep 0.5
695
- [[ $i -eq 60 ]] && die "ework-web did not come up in 30s. Check: journalctl $SCOPENAME -u ework-web.service -n 50"
815
+ [[ $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)"
696
816
  done
697
- else
698
- warn "--no-start: unit enabled but not started"
699
- ctl enable ework-web.service
700
817
  fi
701
818
 
702
819
  # ─── Bootstrap bot user + PAT (idempotent) ─────────────────────────────────
@@ -709,10 +826,23 @@ COOKIE_SIG=$(printf '%s' "$WORK_TOKEN_VAL" \
709
826
  AUTH_COOKIE="ework_auth=${WORK_TOKEN_VAL}.${COOKIE_SIG}"
710
827
 
711
828
  BOT_TOKEN=""
829
+ BOT_BOOTSTRAP_OK=1
712
830
  if [[ -f "$BOT_TOKEN_FILE" ]]; then
713
831
  BOT_TOKEN=$(cat "$BOT_TOKEN_FILE")
714
832
  ok "Reusing saved bot token from $BOT_TOKEN_FILE"
715
833
  else
834
+ # Pre-flight: web must be reachable for the bot bootstrap HTTP calls below.
835
+ # In --no-start mode OR on hosts where systemd couldn't bring web up, skip
836
+ # the bootstrap entirely — daemon .env still gets written (with empty token)
837
+ # so the user can `ework-aio start` and re-run install later.
838
+ if ! curl -sf -o /dev/null "http://127.0.0.1:$WORK_PORT/login"; then
839
+ warn "ework-web not reachable at :$WORK_PORT — skipping bot bootstrap."
840
+ warn "Daemon will not be able to talk to web until you re-run install with web running."
841
+ BOT_BOOTSTRAP_OK=0
842
+ fi
843
+ fi
844
+
845
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
716
846
  log "Bootstrapping bot user '$BOT_NAME'..."
717
847
  BOT_PW=$(openssl rand -hex 24)
718
848
  CREATE_CODE=$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
@@ -725,9 +855,11 @@ else
725
855
  case "$CREATE_CODE" in
726
856
  303) ok "Bot user '$BOT_NAME' created" ;;
727
857
  400|409) warn "Bot user '$BOT_NAME' already exists (continuing)" ;;
728
- *) die "Failed to create bot user: HTTP $CREATE_CODE" ;;
858
+ *) 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 ;;
729
859
  esac
860
+ fi
730
861
 
862
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
731
863
  log "Logging in as bot to mint PAT..."
732
864
  COOKIE_JAR=$(mktemp)
733
865
  LOGIN_CODE=$(curl -sS -c "$COOKIE_JAR" -X POST "http://127.0.0.1:$WORK_PORT/login" \
@@ -736,18 +868,25 @@ else
736
868
  -o /dev/null -w '%{http_code}') || LOGIN_CODE=000
737
869
  BOT_COOKIE=$(awk '/ework_auth/ {print $7}' "$COOKIE_JAR")
738
870
  rm -f "$COOKIE_JAR"
739
- [[ "$LOGIN_CODE" == "302" && -n "$BOT_COOKIE" ]] \
740
- || die "Bot login failed: HTTP $LOGIN_CODE"
871
+ if [[ "$LOGIN_CODE" != "302" || -z "$BOT_COOKIE" ]]; then
872
+ warn "Bot login failed: HTTP $LOGIN_CODE — skipping PAT mint"
873
+ BOT_BOOTSTRAP_OK=0
874
+ fi
875
+ fi
741
876
 
877
+ if [[ "$BOT_BOOTSTRAP_OK" == "1" && ! -f "$BOT_TOKEN_FILE" ]]; then
742
878
  log "Minting PAT..."
743
879
  PAT_RES=$(curl -sS -X POST "http://127.0.0.1:$WORK_PORT/me/tokens/create" \
744
880
  -H "Cookie: ework_auth=$BOT_COOKIE" \
745
881
  --data-urlencode "name=aio-$(date +%s)")
746
882
  BOT_TOKEN=$(printf '%s' "$PAT_RES" | grep -oE 'id="t">[a-f0-9]{40}<' | grep -oE '[a-f0-9]{40}' | head -1 || true)
747
- [[ -n "$BOT_TOKEN" ]] || die "Could not extract PAT from token-create response"
748
- printf '%s' "$BOT_TOKEN" > "$BOT_TOKEN_FILE"
749
- chmod 600 "$BOT_TOKEN_FILE"
750
- ok "Bot PAT saved to $BOT_TOKEN_FILE"
883
+ if [[ -z "$BOT_TOKEN" ]]; then
884
+ warn "Could not extract PAT from token-create response — daemon .env will have empty token"
885
+ else
886
+ printf '%s' "$BOT_TOKEN" > "$BOT_TOKEN_FILE"
887
+ chmod 600 "$BOT_TOKEN_FILE"
888
+ ok "Bot PAT saved to $BOT_TOKEN_FILE"
889
+ fi
751
890
  fi
752
891
 
753
892
  # ─── Write ework-daemon .env ───────────────────────────────────────────────
@@ -766,19 +905,30 @@ write_unit_file \
766
905
  # (trailing " \"\"" is a no-op separator; kept for future extra env injection)
767
906
  ok "Wrote $DAEMON_UNIT"
768
907
 
769
- ctl daemon-reload
770
- if [[ "$NO_START" == "0" ]]; then
908
+ ctl daemon-reload 2>/dev/null || true
909
+ if [[ "$NO_START" == "0" && "$SYSTEMD_OK" == "1" ]]; then
771
910
  log "Starting ework-daemon..."
772
- ctl enable ework-daemon.service
773
- ctl restart ework-daemon.service
911
+ ctl enable ework-daemon.service 2>/dev/null || true
912
+ ctl restart ework-daemon.service 2>/dev/null || warn "ework-daemon restart via systemd failed; use 'ework-aio start daemon' for PID-file mode"
774
913
  sleep 1
775
- if ctl is-active --quiet ework-daemon.service; then
914
+ if ctl is-active --quiet ework-daemon.service 2>/dev/null; then
776
915
  ok "ework-daemon active"
777
916
  else
778
- warn "ework-daemon did not report active; check: journalctl $SCOPENAME -u ework-daemon.service -n 50"
917
+ warn "ework-daemon did not report active; run 'ework-aio start daemon' for PID-file mode"
779
918
  fi
780
919
  else
781
- ctl enable ework-daemon.service
920
+ ctl enable ework-daemon.service 2>/dev/null || true
921
+ fi
922
+
923
+ if [[ "$SYSTEMD_OK" == "0" ]]; then
924
+ hr
925
+ warn "systemd mode did not come up cleanly on this host."
926
+ warn "Services are scaffolded but not auto-started under systemd."
927
+ warn "Use PID-file mode instead:"
928
+ echo " ework-aio start # start web + daemon in background"
929
+ echo " ework-aio ps # check status"
930
+ echo " ework-aio logs web # tail web log"
931
+ hr
782
932
  fi
783
933
 
784
934
  # ─── Register opencode-ework plugin ────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
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",