ework-aio 0.1.11 → 0.1.13

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 +154 -10
  2. package/package.json +1 -1
package/bin/install.sh CHANGED
@@ -38,7 +38,10 @@ BOT_NAME="ework-daemon"
38
38
  NO_START=0
39
39
  ASSUME_YES=0
40
40
  NO_RESTART=0
41
+ AS_USER=""
42
+ ALLOW_ROOT=0
41
43
  CFG_ARGS=()
44
+ MODE_ARGS=()
42
45
 
43
46
  usage() {
44
47
  cat <<'EOF'
@@ -66,6 +69,10 @@ Install options:
66
69
  --bot-name <login> Bot username (default: ework-daemon)
67
70
  --no-start Install units but don't start services
68
71
  --yes Skip all prompts (use generated defaults)
72
+ --as-user <login> (sudo only) drop priv: re-exec install as <login>
73
+ after enabling linger. Data + opencode + npm
74
+ resolved under that user's HOME.
75
+ --allow-root (sudo only) override default refuse-on-root
69
76
 
70
77
  Global options:
71
78
  --no-restart With `config set`: edit .env but skip the restart
@@ -80,10 +87,19 @@ while [[ $# -gt 0 ]]; do
80
87
  MODE="config"
81
88
  shift
82
89
  # Consume remaining args as config subcommand + its positionals.
90
+ # Global flags (--data-dir, --user, --system, etc.) are still
91
+ # respected here so `config set K V --data-dir X` works as expected.
83
92
  while [[ $# -gt 0 ]]; do
84
93
  case "$1" in
85
94
  --no-restart) NO_RESTART=1; shift ;;
86
95
  -h|--help) CFG_ARGS=("help"); shift ;;
96
+ --user) SCOPENAME="--user"; shift ;;
97
+ --system) SCOPENAME="--system"; shift ;;
98
+ --data-dir) DATA_DIR="$2"; shift 2 ;;
99
+ --port) WORK_PORT="$2"; shift 2 ;;
100
+ --daemon-port) DAEMON_PORT="$2"; shift 2 ;;
101
+ --bot-name) BOT_NAME="$2"; shift 2 ;;
102
+ --yes|-y) ASSUME_YES=1; shift ;;
87
103
  *) CFG_ARGS+=("$1"); shift ;;
88
104
  esac
89
105
  done
@@ -97,11 +113,77 @@ while [[ $# -gt 0 ]]; do
97
113
  --no-start) NO_START=1; shift ;;
98
114
  --no-restart) NO_RESTART=1; shift ;;
99
115
  --yes|-y) ASSUME_YES=1; shift ;;
116
+ --as-user) AS_USER="$2"; shift 2 ;;
117
+ --allow-root) ALLOW_ROOT=1; shift ;;
100
118
  -h|--help) usage; exit 0 ;;
101
- *) die "Unknown argument: $1 (try --help)" ;;
119
+ --*) die "Unknown option: $1 (try --help)" ;;
120
+ *) MODE_ARGS+=("$1"); shift ;;
102
121
  esac
103
122
  done
104
123
 
124
+ # ─── Root guard: refuse install as root by default ──────────────────────────
125
+ # Running install as root puts data under /root/.local/share/ework-aio, bakes
126
+ # root-only paths into systemd units, and looks for opencode in root's PATH
127
+ # (usually missing). The right answer is almost always: don't use sudo.
128
+ # --as-user re-execs the script as the named user after enabling linger.
129
+ # --allow-root is an explicit acknowledgement that you've set up root's
130
+ # environment (opencode installed, npm prefix configured, etc.).
131
+ if [[ "$MODE" == "install" && "${EUID:-$(id -u)}" == "0" ]]; then
132
+ if [[ -n "$AS_USER" ]]; then
133
+ AS_USER_HOME="$(getent passwd "$AS_USER" 2>/dev/null | cut -d: -f6 || true)"
134
+ AS_USER_UID="$(getent passwd "$AS_USER" 2>/dev/null | cut -d: -f3 || true)"
135
+ if [[ -z "$AS_USER_HOME" || -z "$AS_USER_UID" ]]; then
136
+ die "--as-user: user '$AS_USER' not found in passwd database"
137
+ fi
138
+ if [[ "$AS_USER_UID" == "0" ]]; then
139
+ die "--as-user: target user is root — just use --allow-root instead"
140
+ fi
141
+ log "Enable linger for '$AS_USER' so --user services survive logout..."
142
+ loginctl enable-linger "$AS_USER" \
143
+ || die "Failed to enable linger for '$AS_USER'. Try: sudo loginctl enable-linger $AS_USER"
144
+ ok "Linger enabled for '$AS_USER'"
145
+ log "Re-execing as '$AS_USER' (HOME=$AS_USER_HOME)..."
146
+ # Strip --as-user <name> from argv, preserve everything else.
147
+ new_args=()
148
+ skip=0
149
+ for a in "$@"; do
150
+ if [[ "$skip" == "1" ]]; then skip=0; continue; fi
151
+ if [[ "$a" == "--as-user" ]]; then skip=1; continue; fi
152
+ new_args+=("$a")
153
+ done
154
+ # Resolve our own path (can't rely on $0 after sudo changes PATH).
155
+ SELF="$(command -v ework-aio 2>/dev/null || true)"
156
+ [[ -n "$SELF" ]] || SELF="${BASH_SOURCE[0]:-$0}"
157
+ exec sudo -u "$AS_USER" --login -- "$SELF" "${new_args[@]}"
158
+ fi
159
+ if [[ "$ALLOW_ROOT" != "1" ]]; then
160
+ cat >&2 <<EOF
161
+ ${c_red}ework-aio install refuses to run as root by default.${c_reset}
162
+
163
+ Why this matters:
164
+ - Data goes under /root/.local/share/ework-aio (unreadable by other users)
165
+ - opencode is searched in root's PATH (usually not installed there)
166
+ - npm packages install to system-wide prefix owned by root
167
+ - systemd units bake root-only paths that other users can't run
168
+
169
+ ${c_bold}Option A (recommended):${c_reset} run as a regular user.
170
+ npm config set prefix '~/.local' # one-time, makes -g user-writable
171
+ npm install -g ework-aio # no sudo needed
172
+ ework-aio install # uses --user systemd scope
173
+
174
+ ${c_bold}Option B:${c_reset} install with sudo but target a regular user.
175
+ sudo ework-aio install --as-user $(logname 2>/dev/null || echo '<your-user>')
176
+ # All data + opencode + npm resolved under that user's HOME.
177
+ # systemd --user scope (linger auto-enabled for the target user).
178
+
179
+ ${c_bold}Option C:${c_reset} really install as root (you've set up root's env).
180
+ sudo ework-aio install --allow-root
181
+ EOF
182
+ exit 1
183
+ fi
184
+ warn "Running install as root with --allow-root — data will live under /root."
185
+ fi
186
+
105
187
  # Resolve scope: default by uid
106
188
  if [[ "$SCOPENAME" == "--user" && "${EUID:-$(id -u)}" == "0" ]]; then
107
189
  SCOPENAME="--system"
@@ -144,12 +226,13 @@ ensure_user_session || true # warn but don't hard-fail — let later steps rep
144
226
  need_cmd() {
145
227
  command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1. $2"
146
228
  }
147
- # Commands every mode needs (status/logs/env/config/uninstall all use systemctl).
148
- need_cmd systemctl "This installer requires systemd."
149
229
  need_cmd awk "Should be present on any Linux."
150
230
 
151
- # Install-only deps. Other modes work even if these are missing.
231
+ # install needs systemctl (to write unit files + reload). Other modes
232
+ # (status/logs/env/config/uninstall) can fall back to PID-file mode via
233
+ # the 'ework-aio' dispatcher if systemd is unreachable on this host.
152
234
  if [[ "$MODE" == "install" ]]; then
235
+ 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)."
153
236
  need_cmd bun "Install from https://bun.sh"
154
237
  need_cmd npm "Install Node.js or Bun (ships npm)"
155
238
  need_cmd opencode "Install from https://opencode.ai"
@@ -158,6 +241,23 @@ if [[ "$MODE" == "install" ]]; then
158
241
  need_cmd jq "Install jq (for opencode.json merge)."
159
242
  fi
160
243
 
244
+ # EWORK_AIO_BIN: always resolvable (used by status/logs/config/uninstall
245
+ # to delegate to PID-file mode operations when systemd is unreachable).
246
+ EWORK_AIO_BIN="$(command -v ework-aio 2>/dev/null || true)"
247
+ [[ -n "$EWORK_AIO_BIN" ]] || EWORK_AIO_BIN="$(npm root -g 2>/dev/null)/ework-aio/bin/ework-aio"
248
+
249
+ # systemd_reachable: 0 if `systemctl $SCOPENAME` can talk to systemd, 1 if not.
250
+ # Used by status/logs/config/uninstall to decide whether to use systemd
251
+ # (preferred) or fall back to PID-file mode via the ework-aio dispatcher.
252
+ # Hosts where this returns 1: containers without systemd PID 1, polkit
253
+ # redirects under sudo that drop the bus, hosts where the user isn't
254
+ # permissioned for the system bus.
255
+ systemd_reachable() {
256
+ command -v systemctl >/dev/null 2>&1 || return 1
257
+ systemctl "$SCOPENAME" is-system-running >/dev/null 2>&1 \
258
+ || systemctl "$SCOPENAME" list-units --no-pager >/dev/null 2>&1
259
+ }
260
+
161
261
  # Verify the 3 npm packages are reachable. If not, try to install them now.
162
262
  ensure_pkg() {
163
263
  local pkg="$1"
@@ -469,6 +569,19 @@ service_for_key() {
469
569
  restart_service() {
470
570
  local svc="$1"
471
571
  local rc=0
572
+ if ! systemd_reachable; then
573
+ log "systemd not reachable — using PID-file mode restart"
574
+ if [[ "$svc" == "both" ]]; then
575
+ "$EWORK_AIO_BIN" restart both && ok "Restarted ework-web + ework-daemon (PID-file mode)" || rc=$?
576
+ else
577
+ "$EWORK_AIO_BIN" restart "$svc" && ok "Restarted ework-$svc (PID-file mode)" || rc=$?
578
+ fi
579
+ if [[ $rc -ne 0 ]]; then
580
+ warn "PID-file restart of '$svc' failed (exit $rc)."
581
+ warn "The .env changes are saved; run 'ework-aio restart $svc' manually."
582
+ fi
583
+ return $rc
584
+ fi
472
585
  case "$svc" in
473
586
  web) ctl restart ework-web.service && ok "Restarted ework-web" || rc=$? ;;
474
587
  daemon) ctl restart ework-daemon.service && ok "Restarted ework-daemon" || rc=$? ;;
@@ -622,7 +735,22 @@ config)
622
735
  ;;
623
736
 
624
737
  status)
625
- hr; log "ework-aio status ($SCOPENAME)"; hr
738
+ hr; log "ework-aio status"; hr
739
+ if ! systemd_reachable; then
740
+ log "(systemd unreachable — showing PID-file mode status)"
741
+ "$EWORK_AIO_BIN" ps 2>/dev/null || warn "ework-aio ps failed"
742
+ hr
743
+ log "Port listeners:"
744
+ p=""
745
+ for p in $(grep -hE '^(WORK_PORT|DAEMON_PORT)=' "$WEB_ENV" "$DAEMON_ENV" 2>/dev/null | cut -d= -f2 | sort -u); do
746
+ if curl -sf -o /dev/null --max-time 1 "http://127.0.0.1:$p/login" 2>/dev/null; then
747
+ printf ' :%s ✓ listening\n' "$p"
748
+ else
749
+ printf ' :%s ✗ not responding\n' "$p"
750
+ fi
751
+ done
752
+ exit 0
753
+ fi
626
754
  ctl is-active ework-web.service || true
627
755
  ctl is-active ework-daemon.service || true
628
756
  ctl status --no-pager --lines=0 ework-web.service 2>/dev/null || true
@@ -632,18 +760,34 @@ status)
632
760
  ;;
633
761
 
634
762
  logs)
635
- svc="${1:-ework-web.service}"
763
+ svc="${MODE_ARGS[0]:-ework-web.service}"
636
764
  [[ "$svc" == "daemon" || "$svc" == "ework-daemon" ]] && svc="ework-daemon.service"
637
765
  [[ "$svc" == "web" || "$svc" == "ework-web" ]] && svc="ework-web.service"
766
+ if ! systemd_reachable; then
767
+ svc_short="${svc%.service}"
768
+ svc_short="${svc_short#ework-}"
769
+ log_file="$DATA_DIR/run/${svc_short}.log"
770
+ if [[ ! -f "$log_file" ]]; then
771
+ die "systemd unreachable and no PID-file log at $log_file. Start services with: ework-aio start $svc_short"
772
+ fi
773
+ log "(systemd unreachable — tailing PID-file log $log_file)"
774
+ exec tail -n 200 -f "$log_file"
775
+ fi
638
776
  exec journalctl "$SCOPENAME" -u "$svc" -f
639
777
  ;;
640
778
 
641
779
  uninstall)
642
780
  hr; log "Uninstalling ework-aio services (keeping data)"; hr
643
- ctl stop ework-web.service ework-daemon.service 2>/dev/null || true
644
- ctl disable ework-web.service ework-daemon.service 2>/dev/null || true
645
- rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service"
646
- ctl daemon-reload
781
+ "$EWORK_AIO_BIN" stop both 2>/dev/null || true
782
+ if systemd_reachable; then
783
+ ctl stop ework-web.service ework-daemon.service 2>/dev/null || true
784
+ ctl disable ework-web.service ework-daemon.service 2>/dev/null || true
785
+ rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service"
786
+ ctl daemon-reload 2>/dev/null || true
787
+ else
788
+ log "(systemd unreachable — skipped unit cleanup; PID-file mode services stopped above)"
789
+ rm -f "$UNIT_DIR/ework-web.service" "$UNIT_DIR/ework-daemon.service" 2>/dev/null || true
790
+ fi
647
791
  ok "Services removed. Data preserved at $DATA_DIR"
648
792
  warn "To fully remove: rm -rf $DATA_DIR && npm uninstall -g ework-aio ework-web ework-daemon opencode-ework"
649
793
  exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ework-aio",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
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",