feat(runner): add non110 user runner installer [skip ci]

This commit is contained in:
Your Name
2026-06-28 20:35:57 +08:00
parent 0f697b05f5
commit 5f20d654d4
5 changed files with 291 additions and 6 deletions

View File

@@ -1,3 +1,24 @@
## 2026-06-28 — 20:30 188 non-110 user runner prepare-only 實作與 readback
**完成內容**
- 新增 `ops/runner/install-awoooi-non110-user-runner.sh`,支援 188 `ollama` user-level Docker runner預設 `PREPARE_ONLY=1` 時只安裝 config、user systemd service 與 rollback unit不啟動、不註冊、不讀 token。
- 新增 `ops/runner/awoooi-non110-runner.user.service.example``ops/runner/awoooi-non110-runner-rollback.user.service.example`,補齊無 passwordless sudo 時的 user service / rollback source-of-truth。
- `ops/runner/check-awoooi-non110-runner-readiness.sh` 擴充 188 實際路徑:`/home/ollama/awoooi-non110-runner/data/config.yaml``/home/ollama/awoooi-non110-runner/data/.runner`,並接受 `gitea/act_runner:latest` Docker image 作為 runner runtime evidence仍要求 `.runner` registration metadata 存在且 service active 才能 `AWOOOI_NON110_RUNNER_READY=1`
- 已在 188 執行 `PREPARE_ONLY=1 bash -s < ops/runner/install-awoooi-non110-user-runner.sh`readback `AWOOOI_NON110_USER_RUNNER_INSTALL_OK prepare_only=1 register_now=0 start_now=0`
**188 readback**
- `READY_CONFIG_COUNT=2``READY_BINARY_COUNT=2``READY_SERVICE_COUNT=1`、rollback unit installed `1`
- `ACTIVE_ACTION_CONTAINERS=0``HEAVY_PROCESS_COUNT=0`、load/core 約 `0.12-0.14`host selector 正確在 `192.168.0.188`
- 仍為 `AWOOOI_NON110_RUNNER_READY=0`;剩餘 blockers`runner_registration_missing``runner_service_not_active:awoooi-non110-runner.service``no_active_runner_service`
- safe next step 變成 `complete_runner_registration_without_printing_token_then_enable_service_and_rerun_this_verifier`
**驗證結果**
- `bash -n ops/runner/check-awoooi-non110-runner-readiness.sh ops/runner/install-awoooi-non110-user-runner.sh scripts/reboot-recovery/enforce-110-runner-failclosed.sh scripts/reboot-recovery/awoooi-enforce-runner-failclosed-110.sh`:通過。
- `python3.11 -m pytest ops/runner/test_guard_gitea_runner_pressure.py ops/runner/test_check_awoooi_non110_runner_readiness.py -q``6 passed`
- `python3 ops/runner/guard-gitea-runner-pressure.py --root .``GITEA_RUNNER_PRESSURE_GUARD_OK workflow_files=10 scheduled_workflows=3 auto_branch_events_on_110=0 generic_runner_labels=0`
**邊界**:沒有讀 `.runner` 內容、runner token、secret、raw session、SQLite、auth 或 `.env`;沒有啟動 runner service沒有重啟 Docker / Nginx / firewall / K3s / DB沒有使用 GitHub沒有 force push。
## 2026-06-28 — 20:28 production deploy readback snapshot refresh
**完成內容**

View File

@@ -0,0 +1,10 @@
[Unit]
Description=Rollback AWOOOI non-110 user Gitea runner to inactive
[Service]
Type=oneshot
ExecStart=-/usr/bin/systemctl --user stop awoooi-non110-runner.service
ExecStart=-/usr/bin/systemctl --user disable awoooi-non110-runner.service
ExecStart=-/usr/bin/systemctl --user reset-failed awoooi-non110-runner.service
ExecStart=-/usr/bin/docker rm -f awoooi-non110-runner
RemainAfterExit=no

View File

@@ -0,0 +1,27 @@
[Unit]
Description=AWOOOI non-110 Gitea runner, user Docker, capacity 1
Documentation=file:/opt/awoooi/ops/runner/check-awoooi-non110-runner-readiness.sh
After=default.target
ConditionPathExists=/home/ollama/awoooi-non110-runner/data/.runner
[Service]
Type=simple
WorkingDirectory=/home/ollama/awoooi-non110-runner
Environment=CONFIG_FILE=/data/config.yaml
ExecStartPre=-/usr/bin/docker rm -f awoooi-non110-runner
ExecStart=/usr/bin/docker run --rm --name awoooi-non110-runner -v /home/ollama/awoooi-non110-runner/data:/data -v /var/run/docker.sock:/var/run/docker.sock -e CONFIG_FILE=/data/config.yaml gitea/act_runner:latest
ExecStop=/usr/bin/docker stop -t 3700 awoooi-non110-runner
ExecStopPost=-/usr/bin/docker rm -f awoooi-non110-runner
Restart=always
RestartSec=20
KillSignal=SIGINT
TimeoutStartSec=120
TimeoutStopSec=3700
CPUQuota=200%
MemoryHigh=6G
MemoryMax=8G
TasksMax=512
NoNewPrivileges=true
[Install]
WantedBy=default.target

View File

@@ -8,9 +8,11 @@ set -euo pipefail
TARGET_HOST_IP="${TARGET_HOST_IP:-192.168.0.188}"
FORBIDDEN_HOST_IPS="${FORBIDDEN_HOST_IPS:-192.168.0.110}"
RUNNER_CONFIG_PATHS="${RUNNER_CONFIG_PATHS:-/home/wooo/act-runner-awoooi/config.yaml /home/wooo/awoooi-act-runner/config.yaml /home/wooo/awoooi-non110-runner/config.yaml /home/wooo/act-runner/config.yaml /home/ollama/act-runner-awoooi/config.yaml /home/ollama/awoooi-non110-runner/config.yaml}"
RUNNER_BINARY_PATHS="${RUNNER_BINARY_PATHS:-/home/wooo/act-runner-awoooi/act_runner /home/wooo/awoooi-act-runner/act_runner /home/wooo/awoooi-non110-runner/act_runner /home/wooo/act-runner/act_runner /home/ollama/act-runner-awoooi/act_runner /home/ollama/awoooi-non110-runner/act_runner}"
RUNNER_REGISTRATION_PATHS="${RUNNER_REGISTRATION_PATHS:-/home/wooo/act-runner-awoooi/.runner /home/wooo/awoooi-act-runner/.runner /home/wooo/awoooi-non110-runner/.runner /home/wooo/act-runner/.runner /home/ollama/act-runner-awoooi/.runner /home/ollama/awoooi-non110-runner/.runner}"
RUNNER_HOME="${RUNNER_HOME:-${HOME:-/home/wooo}}"
RUNNER_CONFIG_PATHS="${RUNNER_CONFIG_PATHS:-${RUNNER_HOME}/awoooi-non110-runner/data/config.yaml ${RUNNER_HOME}/awoooi-non110-runner/config.yaml ${RUNNER_HOME}/act-runner-awoooi/config.yaml /home/wooo/act-runner-awoooi/config.yaml /home/wooo/awoooi-act-runner/config.yaml /home/wooo/awoooi-non110-runner/config.yaml /home/wooo/act-runner/config.yaml}"
RUNNER_BINARY_PATHS="${RUNNER_BINARY_PATHS:-${RUNNER_HOME}/awoooi-non110-runner/act_runner ${RUNNER_HOME}/act-runner-awoooi/act_runner /home/wooo/act-runner-awoooi/act_runner /home/wooo/awoooi-act-runner/act_runner /home/wooo/awoooi-non110-runner/act_runner /home/wooo/act-runner/act_runner}"
RUNNER_DOCKER_IMAGES="${RUNNER_DOCKER_IMAGES:-gitea/act_runner:latest}"
RUNNER_REGISTRATION_PATHS="${RUNNER_REGISTRATION_PATHS:-${RUNNER_HOME}/awoooi-non110-runner/data/.runner ${RUNNER_HOME}/awoooi-non110-runner/.runner ${RUNNER_HOME}/act-runner-awoooi/.runner /home/wooo/act-runner-awoooi/.runner /home/wooo/awoooi-act-runner/.runner /home/wooo/awoooi-non110-runner/.runner /home/wooo/act-runner/.runner}"
RUNNER_SERVICE_NAMES="${RUNNER_SERVICE_NAMES:-awoooi-non110-runner.service gitea-act-runner-awoooi.service gitea-act-runner-host.service}"
ALLOWED_LABEL_NAMES="${ALLOWED_LABEL_NAMES:-awoooi-non110-host awoooi-non110-ubuntu awoooi-host awoooi-ubuntu}"
FORBIDDEN_LABEL_RE="${FORBIDDEN_LABEL_RE:-^(ubuntu-latest|ubuntu-[0-9].*|self-hosted|stockplatform.*|stock-platform.*|headless.*|playwright.*)$}"
@@ -244,8 +246,8 @@ check_configs() {
}
check_binaries() {
section "runner binary metadata"
local binary kind binary_ok
section "runner runtime metadata"
local binary image kind binary_ok image_ok
for binary in $RUNNER_BINARY_PATHS; do
binary_ok=0
if [ -x "$binary" ] && [ -f "$binary" ]; then
@@ -258,8 +260,24 @@ check_binaries() {
printf 'RUNNER_BINARY path=%s executable=%s kind=%s\n' "$binary" "$binary_ok" "$kind"
done
if command_exists docker; then
for image in $RUNNER_DOCKER_IMAGES; do
image_ok=0
if docker image inspect "$image" >/dev/null 2>&1; then
image_ok=1
kind="$(docker image inspect "$image" --format '{{.Id}} {{.Created}}' 2>/dev/null || echo present)"
READY_BINARY_COUNT=$((READY_BINARY_COUNT + 1))
else
kind="missing"
fi
printf 'RUNNER_DOCKER_IMAGE image=%s present=%s kind=%s\n' "$image" "$image_ok" "$kind"
done
else
printf 'RUNNER_DOCKER_IMAGE docker_available=0\n'
fi
if [ "$READY_BINARY_COUNT" -eq 0 ]; then
blocker "runner_binary_missing"
blocker "runner_runtime_missing"
fi
}

View File

@@ -0,0 +1,209 @@
#!/usr/bin/env bash
set -euo pipefail
# Controlled prepare-only installer for the 192.168.0.188 user-level AWOOOI
# Gitea runner. It never reads, prints, stores, or passes runner registration
# tokens. Registration must happen through an external credentialed channel.
TARGET_HOST_IP="${TARGET_HOST_IP:-192.168.0.188}"
FORBIDDEN_HOST_IPS="${FORBIDDEN_HOST_IPS:-192.168.0.110}"
RUNNER_DIR="${RUNNER_DIR:-${HOME}/awoooi-non110-runner}"
DATA_DIR="${DATA_DIR:-${RUNNER_DIR}/data}"
SERVICE_NAME="${SERVICE_NAME:-awoooi-non110-runner.service}"
ROLLBACK_SERVICE_NAME="${ROLLBACK_SERVICE_NAME:-awoooi-non110-runner-rollback.service}"
RUNNER_CONTAINER_NAME="${RUNNER_CONTAINER_NAME:-awoooi-non110-runner}"
ACT_RUNNER_IMAGE="${ACT_RUNNER_IMAGE:-gitea/act_runner:latest}"
RUNNER_LABELS="${RUNNER_LABELS:-awoooi-non110-host:host,awoooi-non110-ubuntu:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04}"
REGISTER_NOW="${REGISTER_NOW:-0}"
START_NOW="${START_NOW:-0}"
PREPARE_ONLY="${PREPARE_ONLY:-0}"
if [ "$PREPARE_ONLY" = "1" ]; then
REGISTER_NOW=0
START_NOW=0
fi
command_exists() {
command -v "$1" >/dev/null 2>&1
}
host_ips() {
if command_exists ip; then
ip -o -4 addr show 2>/dev/null | awk '{print $4}' | sed 's#/.*##' | sort -u
return 0
fi
hostname -I 2>/dev/null | tr ' ' '\n' | awk 'NF' | sort -u || true
}
host_has_ip() {
local ip="$1"
host_ips | grep -qx "$ip"
}
active_action_containers() {
docker ps --format '{{.Names}}' 2>/dev/null | grep -Ec '^GITEA-ACTIONS-TASK-' || true
}
heavy_process_count() {
{
pgrep -f '(^|/)(chrome|chromium|chromium-browser)( |$)' 2>/dev/null || true
pgrep -f 'playwright|stockplatform.*smoke|next build|turbo build|vite build' 2>/dev/null || true
} | sort -u | wc -l | tr -d ' '
}
write_config() {
local config_file="${DATA_DIR}/config.yaml"
mkdir -p "$DATA_DIR"
umask 077
cat >"$config_file" <<EOF_CONFIG
log:
level: info
runner:
file: /data/.runner
capacity: 1
envs: {}
env_file: ""
timeout: 3h
shutdown_timeout: 1h
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
fetch_interval_max: 5s
log_report_interval: 5s
log_report_max_latency: 3s
log_report_batch_size: 100
state_report_interval: 5s
github_mirror: ''
labels:
$(printf '%s' "$RUNNER_LABELS" | tr ',' '\n' | awk '{ printf " - \"%s\"\n", $0 }')
cache:
enabled: false
dir: ""
host: ""
port: 0
external_server: ""
external_secret: ""
container:
network: ""
privileged: false
options:
workdir_parent:
valid_volumes: []
docker_host: "unix:///var/run/docker.sock"
force_pull: false
force_rebuild: false
require_docker: true
docker_timeout: 30s
bind_workdir: false
host:
workdir_parent: /data/host-workdir
metrics:
enabled: false
addr: "127.0.0.1:9101"
EOF_CONFIG
}
write_user_units() {
local user_unit_dir="${HOME}/.config/systemd/user"
mkdir -p "$user_unit_dir"
cat >"${user_unit_dir}/${SERVICE_NAME}" <<EOF_SERVICE
[Unit]
Description=AWOOOI non-110 Gitea runner, user Docker, capacity 1
After=default.target
ConditionPathExists=${DATA_DIR}/.runner
[Service]
Type=simple
WorkingDirectory=${RUNNER_DIR}
Environment=CONFIG_FILE=/data/config.yaml
ExecStartPre=-/usr/bin/docker rm -f ${RUNNER_CONTAINER_NAME}
ExecStart=/usr/bin/docker run --rm --name ${RUNNER_CONTAINER_NAME} -v ${DATA_DIR}:/data -v /var/run/docker.sock:/var/run/docker.sock -e CONFIG_FILE=/data/config.yaml ${ACT_RUNNER_IMAGE}
ExecStop=/usr/bin/docker stop -t 3700 ${RUNNER_CONTAINER_NAME}
ExecStopPost=-/usr/bin/docker rm -f ${RUNNER_CONTAINER_NAME}
Restart=always
RestartSec=20
KillSignal=SIGINT
TimeoutStartSec=120
TimeoutStopSec=3700
CPUQuota=200%
MemoryHigh=6G
MemoryMax=8G
TasksMax=512
NoNewPrivileges=true
[Install]
WantedBy=default.target
EOF_SERVICE
cat >"${user_unit_dir}/${ROLLBACK_SERVICE_NAME}" <<EOF_ROLLBACK
[Unit]
Description=Rollback AWOOOI non-110 user Gitea runner to inactive
[Service]
Type=oneshot
ExecStart=-/usr/bin/systemctl --user stop ${SERVICE_NAME}
ExecStart=-/usr/bin/systemctl --user disable ${SERVICE_NAME}
ExecStart=-/usr/bin/systemctl --user reset-failed ${SERVICE_NAME}
ExecStart=-/usr/bin/docker rm -f ${RUNNER_CONTAINER_NAME}
RemainAfterExit=no
EOF_ROLLBACK
systemctl --user daemon-reload
}
register_runner() {
echo "BLOCKER runner_registration_requires_external_credentialed_channel"
echo "reason=act_runner_register_only_accepts_token_argument_no_stdin_or_token_file"
echo "content_read=false"
return 2
}
for forbidden in $FORBIDDEN_HOST_IPS; do
if host_has_ip "$forbidden"; then
echo "BLOCKER target_is_forbidden_host_${forbidden}"
exit 1
fi
done
if [ -n "$TARGET_HOST_IP" ] && ! host_has_ip "$TARGET_HOST_IP"; then
echo "BLOCKER target_host_ip_not_present_${TARGET_HOST_IP}"
exit 1
fi
command_exists docker || { echo "BLOCKER docker_missing"; exit 1; }
command_exists systemctl || { echo "BLOCKER systemctl_missing"; exit 1; }
docker image inspect "$ACT_RUNNER_IMAGE" >/dev/null 2>&1 || docker pull "$ACT_RUNNER_IMAGE"
if [ "$(active_action_containers)" != "0" ]; then
echo "BLOCKER active_action_containers_present"
exit 1
fi
if [ "$(heavy_process_count)" != "0" ]; then
echo "BLOCKER heavy_processes_present"
exit 1
fi
mkdir -p "$RUNNER_DIR" "$DATA_DIR"
write_config
write_user_units
if [ "$REGISTER_NOW" = "1" ]; then
register_runner
else
echo "RUNNER_REGISTRATION skipped=true content_read=false"
fi
if [ "$START_NOW" = "1" ]; then
systemctl --user enable "$SERVICE_NAME" >/dev/null
systemctl --user restart "$SERVICE_NAME"
else
echo "RUNNER_START skipped=true"
fi
echo "AWOOOI_NON110_USER_RUNNER_INSTALL_OK prepare_only=${PREPARE_ONLY} register_now=${REGISTER_NOW} start_now=${START_NOW}"