Make runner setup race-safe and resumable
This commit is contained in:
1 parent
db6ded4075
commit
4110aa54c9
7 files changed
+66
-14
No files matched your search
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## Не выпущено
|
||||
|
||||
- Runner installer отклоняет symlink-компоненты в путях конфигурации и повторно использует хранилище только при активных mount-опциях `nosuid,nodev`.
|
||||
- Runner installer безопасно повторяет настройку каталогов от имени runner, включая восстановление промежуточных каталогов `0755`, и принимает хранилище только с активными mount-опциями `nosuid,nodev`.
|
||||
- Backend `0.11.3`: ключ подписи поисковых снимков читается из Docker secret-файла, а не из аргументов процесса. Добавлена базовая инфраструктура VPS, Caddy HTTPS proxy с разделёнными Compose-сетями и CI-проверка конфигурации; публикация образов и production-деплой остаются выключены до проверки runner, registry, backup/restore и ограниченного deploy-helper.
|
||||
- Frontend `0.1.2`: на планшетной ширине экран поиска переходит в одну колонку, чтобы фильтры, результаты и документ помещались в окно.
|
||||
- Backend `0.11.2`: смена пароля требует текущий пароль и запрещает оставить прежний, включая временный пароль при первом входе.
|
||||
|
||||
@@ -57,10 +57,14 @@ a non-root system UID below `UID_MIN`, its expected home, primary group, and
|
||||
sole supplementary group. The mount
|
||||
is started only when it is not already active, and an active mount must be the
|
||||
expected ext4 loop device backed by the runner image with both `nosuid` and
|
||||
`nodev` active. Before root creates runner configuration directories, it checks
|
||||
every path component for symlinks and validates the existing runner-owned
|
||||
directories' ownership and mode. Conflicting files or state stop the installer
|
||||
for manual inspection. A new image requires at least 25 GiB free on `/`.
|
||||
`nodev` active. Runner-home directories and user configuration files are
|
||||
created or normalized as `akyldash-runner`, never by a privileged file-write
|
||||
operation; this avoids a symlink swap turning setup into a root write outside
|
||||
the home. The installer rejects static symlink components and validates
|
||||
ownership and mode after setup. This also normalizes the `0755` intermediate
|
||||
directories left by the first installer version. Conflicting files or state
|
||||
stop the installer for manual inspection. A new image requires at least 25 GiB
|
||||
free on `/`.
|
||||
|
||||
If the reviewed checkout is not present at the inventoried path, first verify
|
||||
that the target is absent and clone the reviewed `main` there. Do not overwrite
|
||||
|
||||
@@ -90,8 +90,10 @@ UFW rule details, and protected file metadata remain unverified.
|
||||
loop-backed store is active on `/dev/loop0` after manually enabling and
|
||||
starting its mount unit when the installer stopped. Remaining components,
|
||||
repository registration, and Online state are unverified; this PR makes no
|
||||
VPS changes. The reviewed installer now rejects symlinks in runner config
|
||||
directory paths and requires the active store mount to include `nosuid,nodev`.
|
||||
VPS changes. The reviewed installer now performs runner-home directory and
|
||||
config writes as `akyldash-runner` (including recovery from legacy `0755`
|
||||
intermediate directories), and requires active store mounts to include
|
||||
`nosuid,nodev`.
|
||||
- Gitea repository runner inventory could not be read by the current API
|
||||
principal (403). The owner has not supplied runner state, and no runner
|
||||
Online status or runner-hosted workflow is claimed. The manual
|
||||
|
||||
@@ -72,15 +72,31 @@ runner_ensure_private_directory() {
|
||||
fi
|
||||
[[ -d $current ]] || { runner_die "runner directory path component is not a directory: $current"; return 1; }
|
||||
if [[ $current == /var/lib/akyldash-runner || $current == /var/lib/akyldash-runner/* ]]; then
|
||||
[[ $(stat -c '%U:%G:%a' "$current") == "$owner:$group:700" ]] || { runner_die "runner directory ancestor has unexpected ownership or permissions: $current"; return 1; }
|
||||
[[ $(stat -c '%U:%G' "$current") == "$owner:$group" ]] || { runner_die "runner directory ancestor has unexpected ownership: $current"; return 1; }
|
||||
fi
|
||||
done
|
||||
if [[ -e $directory ]]; then
|
||||
[[ $(stat -c '%U:%G:%a' "$directory") == "$owner:$group:700" ]] || { runner_die "runner directory has unexpected ownership or permissions: $directory"; return 1; }
|
||||
# These paths are runner-writable; make changes as that user so a path swap
|
||||
# cannot turn a root operation into a write outside its home.
|
||||
runuser -u "$owner" -- install -d -m 0700 "$directory" || return 1
|
||||
[[ -d $directory && ! -L $directory && $(stat -c '%U:%G:%a' "$directory") == "$owner:$group:700" ]] || { runner_die "runner directory failed validation: $directory"; return 1; }
|
||||
}
|
||||
|
||||
runner_install_user_content() {
|
||||
local destination=$1 owner=$2 group=$3 mode=${4#0} temporary
|
||||
temporary=$(mktemp)
|
||||
cat >"$temporary"
|
||||
if [[ -e $destination || -L $destination ]]; then
|
||||
[[ -f $destination && ! -L $destination ]] && cmp -s "$temporary" "$destination" || { rm -f "$temporary"; runner_die "existing runner file differs from reviewed configuration: $destination"; return 1; }
|
||||
[[ $(stat -c '%U:%G:%a' "$destination") == "$owner:$group:$mode" ]] || { rm -f "$temporary"; runner_die "existing runner file has unexpected ownership or permissions: $destination"; return 1; }
|
||||
rm -f "$temporary"
|
||||
return
|
||||
fi
|
||||
install -d -o "$owner" -g "$group" -m 0700 "$directory" || return 1
|
||||
[[ -d $directory && ! -L $directory && $(stat -c '%U:%G:%a' "$directory") == "$owner:$group:700" ]] || { runner_die "runner directory failed validation: $directory"; return 1; }
|
||||
chown "$owner:$group" "$temporary" && chmod "0$mode" "$temporary" || { rm -f "$temporary"; return 1; }
|
||||
local result=0
|
||||
runuser -u "$owner" -- install -m "0$mode" "$temporary" "$destination" || result=$?
|
||||
rm -f "$temporary"
|
||||
((result == 0)) || return 1
|
||||
[[ -f $destination && ! -L $destination && $(stat -c '%U:%G:%a' "$destination") == "$owner:$group:$mode" ]] || { runner_die "runner file failed validation: $destination"; return 1; }
|
||||
}
|
||||
|
||||
runner_validate_mount() {
|
||||
|
||||
@@ -113,14 +113,14 @@ else
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
runner_install_content "$runner_home/.config/docker/daemon.json" "$runner_user" "$runner_user" 0600 <<EOF
|
||||
runner_install_user_content "$runner_home/.config/docker/daemon.json" "$runner_user" "$runner_user" 0600 <<EOF
|
||||
{
|
||||
"data-root": "$runner_store",
|
||||
"log-driver": "local",
|
||||
"log-opts": {"max-size": "10m", "max-file": "2"}
|
||||
}
|
||||
EOF
|
||||
runner_install_content "$runner_home/.config/systemd/user/docker.service.d/limits.conf" "$runner_user" "$runner_user" 0644 <<'EOF'
|
||||
runner_install_user_content "$runner_home/.config/systemd/user/docker.service.d/limits.conf" "$runner_user" "$runner_user" 0644 <<'EOF'
|
||||
[Service]
|
||||
Delegate=yes
|
||||
CPUQuota=200%
|
||||
|
||||
@@ -46,6 +46,12 @@ chmod +x "$tmp/bin/"*
|
||||
PATH="$tmp/bin:$PATH"
|
||||
export PATH
|
||||
source "$runner_dir/install-runner-lib.sh"
|
||||
runuser() {
|
||||
shift 2
|
||||
[[ $1 == -- ]] || return 2
|
||||
shift
|
||||
"$@"
|
||||
}
|
||||
getent() {
|
||||
[[ $1 == passwd ]] || return 2
|
||||
if [[ -n ${RUNNER_TEST_PASSWD:-} ]]; then
|
||||
@@ -129,6 +135,28 @@ if runner_ensure_private_directory "$private_root/linked/docker" "$runner_owner"
|
||||
fi
|
||||
[[ ! -e $tmp/outside/docker ]]
|
||||
|
||||
legacy_root=$tmp/legacy
|
||||
mkdir -p "$legacy_root/.config/systemd/user/docker.service.d"
|
||||
chmod 0755 "$legacy_root" "$legacy_root/.config" "$legacy_root/.config/systemd" "$legacy_root/.config/systemd/user" "$legacy_root/.config/systemd/user/docker.service.d"
|
||||
for legacy_dir in "$legacy_root" "$legacy_root/.config" "$legacy_root/.config/systemd" "$legacy_root/.config/systemd/user" "$legacy_root/.config/systemd/user/docker.service.d"; do
|
||||
runner_ensure_private_directory "$legacy_dir" "$runner_owner" "$runner_group"
|
||||
[[ $(stat -c %a "$legacy_dir") == 700 ]]
|
||||
done
|
||||
user_config=$private_root/daemon.json
|
||||
runner_install_user_content "$user_config" "$runner_owner" "$runner_group" 0600 <<'EOF'
|
||||
{"data-root":"/private/runner"}
|
||||
EOF
|
||||
runner_install_user_content "$user_config" "$runner_owner" "$runner_group" 0600 <<'EOF'
|
||||
{"data-root":"/private/runner"}
|
||||
EOF
|
||||
if runner_install_user_content "$user_config" "$runner_owner" "$runner_group" 0600 <<'EOF' 2>/dev/null
|
||||
{"data-root":"/unexpected"}
|
||||
EOF
|
||||
then
|
||||
echo 'Conflicting runner user config was accepted' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
image=$tmp/docker-store.img
|
||||
runner_ensure_image "$image" 1M 1048576
|
||||
runner_ensure_image "$image" 1M 1048576
|
||||
|
||||
@@ -22,6 +22,8 @@ grep -Fq 'fallocate -l "$size" "$image"' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'mkfs.ext4 -F -q "$image"' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'systemctl enable "$unit_name"' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'systemctl start "$unit_name"' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'runuser -u "$owner" -- install -d -m 0700 "$directory"' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'runner_install_user_content "$runner_home/.config/docker/daemon.json"' "$installer"
|
||||
assert_absent 'basename.*runner_mount_unit|enable --now.*runner_mount_unit' "$installer"
|
||||
grep -Fq 'groups=$(id -nG "$user")' infra/runner/install-runner-lib.sh
|
||||
grep -Fq 'id -gn "$user"' infra/runner/install-runner-lib.sh
|
||||
|
||||
Reference in new issue
Block a user