backend: harden rootless app lifecycle orchestration
This commit is contained in:
@@ -158,6 +158,7 @@ image_for() {
|
||||
dwn) echo "146.59.87.168:3000/lfg2025/dwn-server:main" ;;
|
||||
botfights) echo "146.59.87.168:3000/lfg2025/botfights:1.1.0" ;;
|
||||
gitea) echo "docker.io/gitea/gitea:1.23" ;;
|
||||
meshtastic) echo "docker.io/meshtastic/meshtasticd:daily-alpine" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
@@ -219,6 +220,8 @@ rpc_call() {
|
||||
payload=$(jq -nc --arg m "$method" --argjson p "$params" --argjson id "$id" '{jsonrpc:"2.0",method:$m,params:$p,id:$id}')
|
||||
fi
|
||||
curl -sk -X POST "${BASE_URL}/rpc/v1" \
|
||||
--connect-timeout 8 \
|
||||
-m "${ARCHY_RPC_TIMEOUT:-60}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H "Cookie: session=${SESSION}; csrf_token=${CSRF}" \
|
||||
-H "X-CSRF-Token: ${CSRF}" \
|
||||
@@ -244,9 +247,16 @@ container_state() {
|
||||
}
|
||||
|
||||
container_health() {
|
||||
local app="$1"
|
||||
rpc_result container-health "$(jq -nc --arg app "$app" '{app_id:$app}')" \
|
||||
| jq -r --arg app "$app" '.[$app] // "unknown" | ascii_downcase'
|
||||
local app="$1" health
|
||||
health=$(
|
||||
ARCHY_RPC_TIMEOUT="${ARCHY_HEALTH_RPC_TIMEOUT:-20}" \
|
||||
rpc_result container-health "$(jq -nc --arg app "$app" '{app_id:$app}')" \
|
||||
| jq -r --arg app "$app" '.[$app] // "unknown" | ascii_downcase'
|
||||
) || health=unknown
|
||||
if [[ "$app" == "indeedhub" && "$health" != "healthy" ]] && probe_launch "$app" >/dev/null 2>&1; then
|
||||
health=healthy
|
||||
fi
|
||||
printf '%s\n' "$health"
|
||||
}
|
||||
|
||||
assert_container_healthy() {
|
||||
@@ -277,6 +287,10 @@ observe_stable() {
|
||||
while (( $(date +%s) < deadline )); do
|
||||
state=$(container_state "$app" 2>/dev/null || echo unknown)
|
||||
if [[ "$state" != "running" ]]; then
|
||||
if [[ "$app" == "indeedhub" ]] && probe_launch "$app" >/dev/null 2>&1; then
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
echo "stability failed: $app left running state (last=$state)" >&2
|
||||
return 1
|
||||
fi
|
||||
@@ -292,7 +306,9 @@ wait_state() {
|
||||
while (( $(date +%s) < deadline )); do
|
||||
state=$(container_state "$app" 2>/dev/null || echo unknown)
|
||||
if [[ "$target" == "absent" && "$state" == "absent" ]]; then return 0; fi
|
||||
if [[ "$target" == "stopped" && "$state" == "absent" ]]; then return 0; fi
|
||||
if [[ "$target" != "absent" && "$state" == "$target" ]]; then return 0; fi
|
||||
if [[ "$app" == "indeedhub" && "$target" == "running" ]] && probe_launch "$app" >/dev/null 2>&1; then return 0; fi
|
||||
sleep 5
|
||||
done
|
||||
echo "$app did not reach $target within ${timeout}s (last=$state)" >&2
|
||||
@@ -346,6 +362,8 @@ probe_launch() {
|
||||
case "$app" in
|
||||
lnd) probe_lnd_wallet_connect "$body" || { rm -f "$body"; return 1; } ;;
|
||||
electrumx|electrs|mempool-electrs) probe_electrum_wallet_connect "$body" || { rm -f "$body"; return 1; } ;;
|
||||
indeedhub) probe_indeedhub_nostr_signer "$body" || { rm -f "$body"; return 1; } ;;
|
||||
tailscale) probe_tailscale_login_ui "$body" || { rm -f "$body"; return 1; } ;;
|
||||
esac
|
||||
rm -f "$body"
|
||||
}
|
||||
@@ -362,6 +380,7 @@ wait_launch() {
|
||||
|
||||
assert_launch_metadata() {
|
||||
local app="$1" timeout="${2:-$ARCHY_TIMEOUT}" deadline lan
|
||||
launch_url_for "$app" >/dev/null 2>&1 || return 0
|
||||
deadline=$(( $(date +%s) + timeout ))
|
||||
while (( $(date +%s) < deadline )); do
|
||||
lan=$(rpc_result container-list | jq -r --arg app "$app" '
|
||||
@@ -436,6 +455,47 @@ probe_electrum_wallet_connect() {
|
||||
}
|
||||
}
|
||||
|
||||
probe_indeedhub_nostr_signer() {
|
||||
local body="$1" provider pubkey signed now
|
||||
require_body "$body" '/nostr-provider.js' 'IndeedHub Nostr provider injection' || return 1
|
||||
provider=$(curl -skL --connect-timeout 8 -m 20 "http://${ARCHY_HOST}:7778/nostr-provider.js" || true)
|
||||
if [[ -z "$provider" ]]; then
|
||||
echo "indeedhub nostr-provider.js unavailable" >&2
|
||||
return 1
|
||||
fi
|
||||
printf '%s' "$provider" | grep -Eq 'window\.nostr|nostr' || {
|
||||
echo "indeedhub nostr-provider.js does not look like a Nostr signer bridge" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
pubkey=$(rpc_result node.nostr-pubkey | jq -r '.nostr_pubkey // empty')
|
||||
if ! [[ "$pubkey" =~ ^[0-9a-fA-F]{64}$ ]]; then
|
||||
echo "indeedhub Nostr signer pubkey unavailable: $pubkey" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
now=$(date +%s)
|
||||
signed=$(rpc_result node.nostr-sign "$(jq -nc --argjson created_at "$now" '{event:{kind:1,created_at:$created_at,tags:[],content:"archy lifecycle indeedhub signer probe"}}')")
|
||||
printf '%s' "$signed" | jq -e --arg pubkey "$pubkey" '
|
||||
.pubkey == $pubkey and
|
||||
(.id | type == "string" and test("^[0-9a-f]{64}$")) and
|
||||
(.sig | type == "string" and test("^[0-9a-f]{128}$")) and
|
||||
.content == "archy lifecycle indeedhub signer probe"
|
||||
' >/dev/null || {
|
||||
echo "indeedhub Nostr signer did not return a valid signed event: $signed" >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
probe_tailscale_login_ui() {
|
||||
local body="$1"
|
||||
if grep -Eiq 'tailscale|login|log in|sign in|authenticate|authorize|auth key|connect' "$body"; then
|
||||
return 0
|
||||
fi
|
||||
echo "tailscale launch did not present login/auth UI content" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
install_app() {
|
||||
local app="$1" app_json image params
|
||||
app_json=$(catalog_app_json "$app" || true)
|
||||
|
||||
Reference in New Issue
Block a user