diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index 0f3a6d7..446d4ba 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -127,15 +127,29 @@ state_revision() { } state_status() { - local service="$1" line value + local service="$1" line value=pending if [[ -r "$STATE_DIR/$service" ]]; then while IFS= read -r line; do case "$line" in status=*) value="${line#status=}" ;; esac done < "$STATE_DIR/$service" - printf '%s' "${value:-pending}" fi + printf '%s' "$value" +} + +register_changed_services() { + local base="$1" service recorded status + [[ -n "$base" ]] || return 0 + while IFS= read -r service; do + [[ -n "$service" ]] || continue + recorded="$(state_revision "$service")" + status="$(state_status "$service")" + if [[ "$status" != deployed || "$recorded" != "$REVISION" ]]; then + mark_status "$service" pending + fi + done < <(python3 scripts/deploy-plan.py --target "$TARGET" --repo "$REMOTE_DIR" \ + --base "$base" --head "$REVISION") } add_changed_services() { @@ -150,13 +164,19 @@ add_changed_services() { } add_pending_services() { - local service recorded changed + local service recorded status changed for state_file in "$STATE_DIR"/*; do [[ -f "$state_file" ]] || continue service="${state_file##*/}" recorded="$(state_revision "$service")" + status="$(state_status "$service")" + if [[ "$status" != deployed ]]; then + # A pending service was intentionally skipped or its handler + # failed. Retry it even when the checkout has not advanced. + SERVICES+=("$service") + continue + fi [[ -n "$recorded" && "$recorded" != "$REVISION" ]] || continue - [[ "$(state_status "$service")" != deployed ]] || continue if [[ "$TARGET" == dankclaw && "$service" == frank-default-small ]]; then continue; fi if [[ "$TARGET" == dankserver && "$service" == frank-default-large ]]; then continue; fi if ! changed="$(python3 scripts/deploy-plan.py --target "$TARGET" \ @@ -171,12 +191,24 @@ add_pending_services() { done } -copy_shared_config() { - run_privileged mkdir -p /opt/searxng/config /opt/prometheus/config /opt/loki/config /opt/alloy/config +copy_searxng_config() { + run_privileged mkdir -p /opt/searxng/config run_privileged cp shared/searxng/settings.yml /opt/searxng/config/settings.yml run_privileged cp shared/searxng/limiter.toml /opt/searxng/config/limiter.toml +} + +copy_prometheus_config() { + run_privileged mkdir -p /opt/prometheus/config run_privileged cp shared/prometheus/prometheus.yml /opt/prometheus/config/prometheus.yml +} + +copy_loki_config() { + run_privileged mkdir -p /opt/loki/config run_privileged cp shared/loki/loki-config.yaml /opt/loki/config/loki-config.yaml +} + +copy_alloy_config() { + run_privileged mkdir -p /opt/alloy/config run_privileged cp shared/alloy/config.alloy /opt/alloy/config/config.alloy } @@ -221,7 +253,6 @@ deploy_caddy() { echo "caddy is owned by dankclaw; refusing to deploy it on $TARGET" >&2 return 2 fi - copy_shared_config render_caddyfile deploy_homepage local cfhash oldhash image @@ -285,6 +316,15 @@ deploy_compose_service() { comfyui|loki|alloy) build_compose_image "$service" ;; + prometheus) + copy_prometheus_config + ;; + loki) + copy_loki_config + ;; + alloy) + copy_alloy_config + ;; esac podman-compose -f dankclaw/compose.yaml up -d --no-deps "$service" ;; @@ -331,7 +371,7 @@ install_quadlet() { } deploy_searxng() { - copy_shared_config + copy_searxng_config install_quadlet searxng dankclaw/searxng/quadlet searxng install_quadlet searxng-valkey dankclaw/searxng/quadlet searxng-valkey install_quadlet gluetun dankclaw/searxng/quadlet gluetun @@ -450,6 +490,12 @@ deploy_service() { esac } +if [[ -n "$FROM_REVISION" ]]; then + # Register every changed service before applying the requested scope. This + # preserves an explicit --service selection as pending work for the next + # ordinary deployment. + register_changed_services "$FROM_REVISION" +fi if [[ "$PENDING" -eq 1 ]]; then add_changed_services "$FROM_REVISION" add_pending_services diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 8c0bda3..82a2a0f 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -208,7 +208,8 @@ if [[ "$NEW_REVISION" != "$EXPECTED_REVISION" ]]; then echo "FAIL: target checkout is $NEW_REVISION, expected $EXPECTED_REVISION" >&2 exit 1 fi -exec ./scripts/deploy-remote.sh "$TARGET" "$NEW_REVISION" "$@" +exec ./scripts/deploy-remote.sh "$TARGET" "$NEW_REVISION" "$@" \ + --from-revision "$OLD_REVISION" REMOTE_EOF echo "=== Deploy complete: $TARGET ===" diff --git a/tests/deployment_scoping.bats b/tests/deployment_scoping.bats index f35d086..2e2c95e 100644 --- a/tests/deployment_scoping.bats +++ b/tests/deployment_scoping.bats @@ -110,6 +110,15 @@ REMOTE_DEPLOY="$REPO_ROOT/scripts/deploy-remote.sh" [ "$status" -eq 2 ] } +@test "remote runner recognizes a pending service at the same revision" { + local state_dir="$(mktemp -d)" + printf 'revision=%s\nstatus=pending\n' "$(git rev-parse HEAD)" > "$state_dir/unknown-service" + run env DEPLOY_STATE_DIR="$state_dir" REMOTE_DIR="$REPO_ROOT" \ + bash "$REMOTE_DEPLOY" dankclaw "$(git rev-parse HEAD)" --pending + [ "$status" -eq 2 ] + [[ "$output" == *"unknown-service"* || "$output" == *"not deployed"* ]] +} + @test "top-level deployer delegates to the service-scoped remote runner" { grep -q 'deploy-remote.sh' "$REPO_ROOT/scripts/deploy.sh" grep -qE 'SERVICE|service' "$REPO_ROOT/scripts/deploy.sh"