@ddtcorex/dsh-maestro-supervisor 0.6.7 → 0.7.0
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/lib/cli.d.ts +13 -0
- package/lib/cli.js +87 -55
- package/lib/debug-agent.js +52 -16
- package/lib/health-poller.js +4 -2
- package/lib/plugin.d.ts +1 -1
- package/lib/plugin.js +73 -1
- package/lib/restart-guards.d.ts +11 -0
- package/lib/restart-guards.js +23 -0
- package/lib/restart-tool.d.ts +70 -0
- package/lib/restart-tool.js +241 -0
- package/lib/scan.d.ts +20 -0
- package/lib/scan.js +71 -0
- package/lib/self-kill-guard.d.ts +26 -0
- package/lib/self-kill-guard.js +63 -0
- package/lib/skill-provider.d.ts +6 -0
- package/lib/skill-provider.js +70 -0
- package/lib/supervisor.d.ts +9 -0
- package/lib/supervisor.js +73 -1
- package/package.json +2 -1
- package/skills/dsh-safe-restart/SKILL.md +90 -0
- package/skills/dsh-safe-restart/scripts/restart-dsh-web.sh +279 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-safe-restart
|
|
3
|
+
description: Use before updating any dsh-maestro-* client bundle or DSH Web asset, or before reloading the running dsh web host process; validate first and perform a user-approved host restart through the bundled guarded recipe. In-session agents use the `dsh_web_restart` tool; hand off to a human only if the tool is unavailable.
|
|
4
|
+
compatibility: dsh
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Safe DSH Web Restart
|
|
8
|
+
|
|
9
|
+
## Purpose
|
|
10
|
+
|
|
11
|
+
One `dsh web` process serves both the live Web UI on port 3080 and the
|
|
12
|
+
GitLab-webhook service on port 3000. A host restart drops the live socket and
|
|
13
|
+
the in-flight turn, although sessions rehydrate from the append-only log when
|
|
14
|
+
the browser reconnects. Treat a restart as disruptive: validate first and get
|
|
15
|
+
explicit user consent before a real swap.
|
|
16
|
+
|
|
17
|
+
## Classify the change before touching the live process
|
|
18
|
+
|
|
19
|
+
- **Static asset in `apps/web/dist/`** — patch the asset and verify served
|
|
20
|
+
bytes with `curl`; no restart.
|
|
21
|
+
- **Client plugin bundle** (client JavaScript, CSS, slots, React) — run that
|
|
22
|
+
package's `build:client`, confirm a bundle marker, then refresh the browser;
|
|
23
|
+
no host restart.
|
|
24
|
+
- **Host Node library, `cordis.patch.yml`, or profile plugin composition** —
|
|
25
|
+
validate a candidate and restart only after the user says to do so.
|
|
26
|
+
|
|
27
|
+
## Required preflight for a host restart
|
|
28
|
+
|
|
29
|
+
1. Run relevant package tests and build steps, then check the output really
|
|
30
|
+
carries the expected marker.
|
|
31
|
+
2. Dry-boot the candidate on an ephemeral port with an isolated `DSH_HOME`.
|
|
32
|
+
Keep the live process and its sessions/settings untouched. If the review
|
|
33
|
+
webhook conflicts on port 3000, exclude that provider for the candidate or
|
|
34
|
+
run a no-server composition check instead.
|
|
35
|
+
3. Verify HTTP 200 and the new marker on the candidate. Retain last-known-good
|
|
36
|
+
assets until the real swap has passed post-swap checks.
|
|
37
|
+
4. Ask for explicit consent and timing. “restart đi” is consent; silence is
|
|
38
|
+
not.
|
|
39
|
+
|
|
40
|
+
## Run the bundled helper only after consent
|
|
41
|
+
|
|
42
|
+
The skill loader provides this directory as the skill resource base. Substitute
|
|
43
|
+
that real path for `<skill-resource-base>`; never use a copied, machine-specific
|
|
44
|
+
path from documentation.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# Safe inspection only: resolves the serving tree but changes nothing.
|
|
48
|
+
bash <skill-resource-base>/scripts/restart-dsh-web.sh \
|
|
49
|
+
--repo /path/to/deepseek-harness --dry-run
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Run the `dsh_web_restart` tool instead of executing the helper — the helper is
|
|
53
|
+
for external agents and humans. In-session agents must never run it from their
|
|
54
|
+
own turn (see "Never do these" below).
|
|
55
|
+
|
|
56
|
+
`--repo` may be replaced by `DSH_REPO`; `--log` or `DSH_RESTART_LOG` changes
|
|
57
|
+
the append-only log destination. The helper refuses a real swap without
|
|
58
|
+
`--confirm`, dynamically resolves listeners rather than trusting saved PIDs,
|
|
59
|
+
and refuses to launch if ports are still occupied. `--dry-run` never runs
|
|
60
|
+
`kill` or `setsid`.
|
|
61
|
+
|
|
62
|
+
## Post-swap checks
|
|
63
|
+
|
|
64
|
+
Do not read the top of an old append-only log as liveness evidence. Instead:
|
|
65
|
+
|
|
66
|
+
1. Confirm exactly one healthy listener tree owns ports 3000 and 3080 with
|
|
67
|
+
`ss -tlnp`.
|
|
68
|
+
2. Confirm HTTP 200 from port 3080.
|
|
69
|
+
3. Check served bytes contain the **new**, unique marker; or make a fresh
|
|
70
|
+
browser/Playwright probe for the changed UI. Shared third-party markers are
|
|
71
|
+
not sufficient proof.
|
|
72
|
+
4. If a marker or live behaviour is wrong, restore the retained last-known-good
|
|
73
|
+
assets and relaunch with the same guarded process.
|
|
74
|
+
|
|
75
|
+
## Never do these
|
|
76
|
+
|
|
77
|
+
- Kill the process from an inline agent shell; the killing action can terminate
|
|
78
|
+
that shell before it relaunches DSH.
|
|
79
|
+
- **Never invoke this skill from inside a DSH chat session's agent turn.** The agent itself runs inside `dsh web`; killing `dsh web` from its own turn interrupts that turn (`interrupted` in the append-only log) and the `setsid nohup` relaunch never completes from the killed shell. If you are an agent, run the `dsh_web_restart` tool instead — it performs the swap from the safe side of the tree. Print the exact `setsid nohup ... --confirm` command only when the tool is unavailable, and only for the human to run in their own terminal; do not execute it yourself.
|
|
80
|
+
- Restart for a client-only or static change.
|
|
81
|
+
- Hard-code a PID or a local developer's workspace path.
|
|
82
|
+
- Treat a 200 response alone as proof that a rebuilt plugin was loaded.
|
|
83
|
+
|
|
84
|
+
## Agent handoff (required when you are an agent)
|
|
85
|
+
|
|
86
|
+
If you are running inside `dsh web` (any `dsh-*` skill, `maestro-*` skill, or subagent), you **must not** execute the helper or the restart yourself:
|
|
87
|
+
|
|
88
|
+
1. Run all preflight checks (`--dry-run`, `pnpm verify`, marker grep) and report the results.
|
|
89
|
+
2. Run the `dsh_web_restart` tool instead of executing the helper — the helper is for external agents and humans.
|
|
90
|
+
3. If the tool reports it is unavailable, print the exact detached `setsid nohup ... --confirm` command for the human to run; then wait for explicit human confirmation in a new terminal. The interrupted turn will rehydrate from the log when the browser reconnects — no data is lost.
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Restart the DSH Web process tree only after the caller has obtained consent.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
repo="${DSH_REPO:-}"
|
|
6
|
+
log="${DSH_RESTART_LOG:-/tmp/dsh-web-restart.log}"
|
|
7
|
+
# Coordination with dsh-web-supervisor (see workspace docs/specs/
|
|
8
|
+
# 2026-08-28-supervisor-planned-restart-design.md): the supervisor treats a
|
|
9
|
+
# down poll as a crash unless this marker is fresh, so it never races this
|
|
10
|
+
# script's own kill -> dry-boot -> relaunch sequence with its own rollback.
|
|
11
|
+
marker="${DSH_SUPERVISOR_MARKER:-$HOME/.dsh/.supervisor/planned-restart}"
|
|
12
|
+
confirmed=false
|
|
13
|
+
dry_run=false
|
|
14
|
+
auto_mode=false
|
|
15
|
+
|
|
16
|
+
dry_boot_and_verify() {
|
|
17
|
+
local dsh_repo="$1"
|
|
18
|
+
local port="${2:-0}"
|
|
19
|
+
local marker="${3:-}"
|
|
20
|
+
local dsh_home
|
|
21
|
+
dsh_home="$(mktemp -d)"
|
|
22
|
+
local log_tmp
|
|
23
|
+
log_tmp="$(mktemp)"
|
|
24
|
+
# ephemeral boot with isolated DSH_HOME
|
|
25
|
+
DSH_HOME="$dsh_home" pnpm --dir "$dsh_repo" exec dsh web --port "$port" --no-open >"$log_tmp" 2>&1 &
|
|
26
|
+
local pid=$!
|
|
27
|
+
local ok=false
|
|
28
|
+
for _ in $(seq 1 15); do
|
|
29
|
+
if curl -s "http://127.0.0.1:$port/" 2>/dev/null | grep -q "${marker:-}"; then
|
|
30
|
+
ok=true
|
|
31
|
+
break
|
|
32
|
+
fi
|
|
33
|
+
if ! kill -0 "$pid" 2>/dev/null; then break; fi
|
|
34
|
+
sleep 1
|
|
35
|
+
done
|
|
36
|
+
kill -TERM "$pid" 2>/dev/null || true
|
|
37
|
+
wait "$pid" 2>/dev/null || true
|
|
38
|
+
rm -rf "$dsh_home" "$log_tmp"
|
|
39
|
+
[[ "$ok" == true ]]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
usage() {
|
|
43
|
+
cat <<'EOF'
|
|
44
|
+
Usage: restart-dsh-web.sh --repo <deepseek-harness> [--log <path>] [--confirm|--auto] [--dry-run]
|
|
45
|
+
|
|
46
|
+
Safely hand over the DSH Web process that owns ports 3000 and 3080.
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--repo <path> DeepSeek Harness checkout (or set DSH_REPO).
|
|
50
|
+
--log <path> Append-only launch log (or set DSH_RESTART_LOG).
|
|
51
|
+
--confirm Permit a real process handover (human-gated). Required unless --dry-run.
|
|
52
|
+
--auto Permit auto handover (supervisor, no consent prompt). Alias for --confirm with auto log prefix.
|
|
53
|
+
--dry-run Print the resolved process tree; never stop or launch anything.
|
|
54
|
+
-h, --help Show this help text.
|
|
55
|
+
EOF
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fail() {
|
|
59
|
+
printf '[restart] FAIL: %s\n' "$1" >&2
|
|
60
|
+
exit "${2:-1}"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
while (($#)); do
|
|
64
|
+
case "$1" in
|
|
65
|
+
--repo)
|
|
66
|
+
(($# >= 2)) || fail '--repo requires a path' 64
|
|
67
|
+
repo="$2"
|
|
68
|
+
shift 2
|
|
69
|
+
;;
|
|
70
|
+
--log)
|
|
71
|
+
(($# >= 2)) || fail '--log requires a path' 64
|
|
72
|
+
log="$2"
|
|
73
|
+
shift 2
|
|
74
|
+
;;
|
|
75
|
+
--confirm)
|
|
76
|
+
confirmed=true
|
|
77
|
+
shift
|
|
78
|
+
;;
|
|
79
|
+
--auto)
|
|
80
|
+
confirmed=true
|
|
81
|
+
auto_mode=true
|
|
82
|
+
shift
|
|
83
|
+
;;
|
|
84
|
+
--dry-run)
|
|
85
|
+
dry_run=true
|
|
86
|
+
shift
|
|
87
|
+
;;
|
|
88
|
+
-h|--help)
|
|
89
|
+
usage
|
|
90
|
+
exit 0
|
|
91
|
+
;;
|
|
92
|
+
*)
|
|
93
|
+
fail "unknown option: $1" 64
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
[[ -n "$repo" ]] || fail 'provide --repo or DSH_REPO' 64
|
|
99
|
+
[[ -f "$repo/package.json" ]] || fail "repo has no package.json: $repo" 64
|
|
100
|
+
if [[ "$dry_run" != true && "$confirmed" != true ]]; then
|
|
101
|
+
fail 'refusing live restart without --confirm' 64
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
for command in ss ps grep sort; do
|
|
105
|
+
command -v "$command" >/dev/null 2>&1 || fail "required command is unavailable: $command"
|
|
106
|
+
done
|
|
107
|
+
|
|
108
|
+
listener_pids="$({ ss -tlnp 2>/dev/null || true; } | grep -E ':(3000|3080)([[:space:]]|$)' | grep -oE 'pid=[0-9]+' | cut -d= -f2 | sort -u || true)"
|
|
109
|
+
[[ -n "$listener_pids" ]] || fail 'no listeners found on ports 3000 or 3080'
|
|
110
|
+
|
|
111
|
+
resolve_tree() {
|
|
112
|
+
local current="$1"
|
|
113
|
+
local parent command_line
|
|
114
|
+
while [[ -n "$current" && "$current" != 1 ]]; do
|
|
115
|
+
command_line="$(ps -o cmd= -p "$current" 2>/dev/null || true)"
|
|
116
|
+
# Never walk into a service manager: when dsh-web runs as a systemd
|
|
117
|
+
# --user unit (ExecStart=node ... directly, no intervening pnpm
|
|
118
|
+
# wrapper), the parent of the listener process IS the manager itself.
|
|
119
|
+
# Without this guard the loop keeps climbing (no "pnpm" match, parent
|
|
120
|
+
# != 1 yet) and includes the manager's own PID in tree_pids -- SIGTERM
|
|
121
|
+
# to it tears down every user unit, not just dsh-web (2026-08-28
|
|
122
|
+
# incident: killed the whole systemd --user session).
|
|
123
|
+
case "$command_line" in
|
|
124
|
+
*systemd\ --user*) break ;;
|
|
125
|
+
esac
|
|
126
|
+
printf '%s\n' "$current"
|
|
127
|
+
[[ "$command_line" == *pnpm* ]] && break
|
|
128
|
+
parent="$(ps -o ppid= -p "$current" 2>/dev/null | tr -d '[:space:]')"
|
|
129
|
+
[[ "$parent" == "$current" ]] && break
|
|
130
|
+
current="$parent"
|
|
131
|
+
done
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
tree_pids="$(for pid in $listener_pids; do resolve_tree "$pid"; done | sort -u)"
|
|
135
|
+
[[ -n "$tree_pids" ]] || fail 'could not resolve a process tree for the listeners'
|
|
136
|
+
|
|
137
|
+
# dsh-web.service has Restart=always: a raw `kill -TERM` on its MainPID looks
|
|
138
|
+
# like a crash to systemd, which immediately relaunches it -- racing this
|
|
139
|
+
# script's own relaunch for ports 3000/3080 (observed live 2026-08-28: restart
|
|
140
|
+
# counter climbed past 20 before either side won). When systemd owns it,
|
|
141
|
+
# defer the whole stop/start to systemctl instead of managing PIDs directly.
|
|
142
|
+
systemd_managed=false
|
|
143
|
+
systemctl --user is-active --quiet dsh-web.service 2>/dev/null && systemd_managed=true
|
|
144
|
+
|
|
145
|
+
printf '[restart] listener pids: %s\n' "$(tr '\n' ' ' <<<"$listener_pids")"
|
|
146
|
+
printf '[restart] process tree: %s\n' "$(tr '\n' ' ' <<<"$tree_pids")"
|
|
147
|
+
printf '[restart] managed by: %s\n' "$([[ "$systemd_managed" == true ]] && echo 'systemd (dsh-web.service)' || echo 'raw process (no systemd unit)')"
|
|
148
|
+
|
|
149
|
+
if [[ "$dry_run" == true ]]; then
|
|
150
|
+
printf '[restart] dry-run: no process will be stopped or launched\n'
|
|
151
|
+
exit 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# --- safe-guard: don't restart while tools are still running (torn prevention) ---
|
|
155
|
+
# Scan for dangling open turns (turn/start without turn/end) within last 5m.
|
|
156
|
+
# If found, wait up to 30s for them to finish, then require --auto to force.
|
|
157
|
+
check_dangling() {
|
|
158
|
+
local count_dangling
|
|
159
|
+
count_dangling() {
|
|
160
|
+
node --input-type=module <<'NODE' 2>/dev/null || echo 0
|
|
161
|
+
import fs from 'node:fs'
|
|
162
|
+
import { execSync } from 'node:child_process'
|
|
163
|
+
try{
|
|
164
|
+
const root = (process.env.DSH_HOME || (await import('node:os')).homedir() + '/.dsh') + '/sessions'
|
|
165
|
+
let count=0
|
|
166
|
+
for(const proj of fs.readdirSync(root)){
|
|
167
|
+
const pp = root + '/' + proj
|
|
168
|
+
try{ if(!fs.statSync(pp).isDirectory()) continue }catch{continue}
|
|
169
|
+
for(const sess of fs.readdirSync(pp)){
|
|
170
|
+
const p = pp + '/' + sess + '/session.jsonl.zstd'
|
|
171
|
+
try{ fs.statSync(p) }catch{continue}
|
|
172
|
+
try{
|
|
173
|
+
const st = fs.statSync(p)
|
|
174
|
+
if(Date.now() - st.mtimeMs > 5*60*1000) continue
|
|
175
|
+
}catch{continue}
|
|
176
|
+
try{
|
|
177
|
+
const out = execSync(`zstd -d -c ${JSON.stringify(p)} 2>/dev/null | tail -n 20`, {encoding:'utf8', timeout:2000})
|
|
178
|
+
const lastStart = out.lastIndexOf('"type":"turn/start"')
|
|
179
|
+
if(lastStart!==-1 && !out.slice(lastStart).includes('"type":"turn/end"')) count++
|
|
180
|
+
}catch{}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
console.log(count)
|
|
184
|
+
}catch{ console.log(0) }
|
|
185
|
+
NODE
|
|
186
|
+
}
|
|
187
|
+
local dangling
|
|
188
|
+
dangling="$(count_dangling)"
|
|
189
|
+
if [[ "$dangling" != "0" && -n "$dangling" ]]; then
|
|
190
|
+
printf '[restart] WARN: %s dangling open turn(s) within 5m — tools may be running\n' "$dangling" | tee -a "$log" >&2
|
|
191
|
+
if [[ "$auto_mode" != true ]]; then
|
|
192
|
+
printf '[restart] waiting 30s for tools to finish (re-run with --auto to force)\n' | tee -a "$log" >&2
|
|
193
|
+
for _ in $(seq 1 30); do sleep 1; done
|
|
194
|
+
local dangling2
|
|
195
|
+
dangling2="$(count_dangling)"
|
|
196
|
+
if [[ "$dangling2" != "0" && -n "$dangling2" ]]; then
|
|
197
|
+
printf '[restart] still %s dangling after wait — aborting (use --auto to force)\n' "$dangling2" | tee -a "$log" >&2
|
|
198
|
+
fail "refusing restart with $dangling2 dangling turn(s) — tools still running" 64
|
|
199
|
+
fi
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
}
|
|
203
|
+
check_dangling
|
|
204
|
+
|
|
205
|
+
mkdir -p "$(dirname "$log")"
|
|
206
|
+
printf '[restart] stopping process tree: %s\n' "$(tr '\n' ' ' <<<"$tree_pids")" >> "$log"
|
|
207
|
+
|
|
208
|
+
# Mark this as an intentional restart before the port goes down, so
|
|
209
|
+
# dsh-web-supervisor's health poll does not race us with its own rollback.
|
|
210
|
+
# Removed on every exit path (success or failure) via the trap.
|
|
211
|
+
mkdir -p "$(dirname "$marker")"
|
|
212
|
+
date -Iseconds > "$marker"
|
|
213
|
+
trap 'rm -f "$marker"' EXIT
|
|
214
|
+
|
|
215
|
+
if [[ "$systemd_managed" == true ]]; then
|
|
216
|
+
# systemctl stop is a clean, intentional stop -- Restart=always does not
|
|
217
|
+
# fire for it, unlike an out-of-band kill of the unit's MainPID.
|
|
218
|
+
printf '[restart] stopping dsh-web.service via systemctl\n' >> "$log"
|
|
219
|
+
systemctl --user stop dsh-web.service 2>>"$log" || fail 'systemctl --user stop dsh-web.service failed'
|
|
220
|
+
else
|
|
221
|
+
kill -TERM $tree_pids 2>/dev/null || true
|
|
222
|
+
|
|
223
|
+
tree_stopped=false
|
|
224
|
+
for _ in $(seq 1 20); do
|
|
225
|
+
alive=false
|
|
226
|
+
for pid in $tree_pids; do
|
|
227
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
228
|
+
alive=true
|
|
229
|
+
break
|
|
230
|
+
fi
|
|
231
|
+
done
|
|
232
|
+
if [[ "$alive" == false ]]; then
|
|
233
|
+
tree_stopped=true
|
|
234
|
+
break
|
|
235
|
+
fi
|
|
236
|
+
sleep 0.5
|
|
237
|
+
done
|
|
238
|
+
|
|
239
|
+
if [[ "$tree_stopped" != true ]]; then
|
|
240
|
+
printf '[restart] process tree did not stop after TERM; sending KILL\n' >> "$log"
|
|
241
|
+
kill -KILL $tree_pids 2>/dev/null || true
|
|
242
|
+
sleep 1
|
|
243
|
+
fi
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
for port in 3000 3080; do
|
|
247
|
+
if ss -tln 2>/dev/null | grep -q ":$port "; then
|
|
248
|
+
fail "port $port remains held; refusing to double-boot"
|
|
249
|
+
fi
|
|
250
|
+
done
|
|
251
|
+
|
|
252
|
+
command -v curl >/dev/null 2>&1 || fail 'required command is unavailable: curl'
|
|
253
|
+
|
|
254
|
+
if [[ "$systemd_managed" == true ]]; then
|
|
255
|
+
printf '[restart] starting dsh-web.service via systemctl\n' >> "$log"
|
|
256
|
+
systemctl --user start dsh-web.service 2>>"$log" || fail 'systemctl --user start dsh-web.service failed'
|
|
257
|
+
else
|
|
258
|
+
command -v setsid >/dev/null 2>&1 || fail 'required command is unavailable: setsid'
|
|
259
|
+
command -v pnpm >/dev/null 2>&1 || fail 'required command is unavailable: pnpm'
|
|
260
|
+
|
|
261
|
+
printf '[restart] launching DSH Web from %s\n' "$repo" >> "$log"
|
|
262
|
+
(
|
|
263
|
+
cd "$repo"
|
|
264
|
+
setsid nohup "$(command -v pnpm)" dsh web --no-open </dev/null >> "$log" 2>&1 &
|
|
265
|
+
)
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
for _ in $(seq 1 90); do
|
|
269
|
+
# 401 is healthy: dsh-web is up but requires the browser token (matches
|
|
270
|
+
# dsh-web-supervisor's own health-poller convention since DSH 0.1.2).
|
|
271
|
+
code="$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3080/ || true)"
|
|
272
|
+
if [[ "$code" == 200 || "$code" == 401 ]]; then
|
|
273
|
+
printf '[restart] DSH Web is serving HTTP %s on port 3080\n' "$code"
|
|
274
|
+
exit 0
|
|
275
|
+
fi
|
|
276
|
+
sleep 1
|
|
277
|
+
done
|
|
278
|
+
|
|
279
|
+
fail 'DSH Web did not serve HTTP 200/401 on port 3080 before timeout'
|