feat: share named volumes across Docker and rootless Podman updates [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
This commit is contained in:
16 files changed
+473
-156
No files matched your search
@@ -1,89 +1,155 @@
|
||||
#!/bin/sh
|
||||
# 在容器节点的宿主机上安装更新器:
|
||||
# install-updater.sh <agent|probe> --data-dir <挂载到容器 /etc/monitor 的宿主机目录> --image-repo <镜像仓库,不带 tag> --unit <容器的 systemd 单元名>
|
||||
# [--policy auto|manual] [--authfile <拉镜像用的 Registry 认证文件>]
|
||||
# 需要 root、systemd、podman、openssl。做的事:装脚本和发布公钥,写本机配置,装两个 systemd 单元(有新清单就检查,有「更新」请求就更新)。
|
||||
# 容器的单元要从 EnvironmentFile=/etc/monitor-update/<组件>.image 读 IMAGE=…,并用 ${IMAGE} 启动容器(没有这个文件时先写一份当前镜像)。
|
||||
# Install signed OCI updates for system services (Podman/Docker) or rootless Podman user services.
|
||||
# Rootless: install-updater.sh agent --user --volume V --helper-image I --image-repo R --unit U
|
||||
# System: sudo install-updater.sh agent --engine podman|docker --volume V --helper-image I --image-repo R --unit U
|
||||
set -eu
|
||||
|
||||
COMPONENT="${1:-}"; shift || true
|
||||
case "$COMPONENT" in agent|probe) ;; *) echo "usage: install-updater.sh <agent|probe> --data-dir D --image-repo R --unit U [--policy auto|manual] [--authfile F]" >&2; exit 2 ;; esac
|
||||
DATA_DIR=""; IMAGE_REPO=""; UNIT=""; POLICY="manual"; AUTHFILE=""
|
||||
case "$COMPONENT" in agent|probe) ;; *) echo "usage: install-updater.sh <agent|probe> [--user] [--engine podman|docker] --data-dir D --image-repo R --unit U [--policy auto|manual] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
|
||||
DATA_DIR=""; IMAGE_REPO=""; UNIT=""; POLICY=manual; AUTHFILE=""; ENGINE=podman; SERVICE_SCOPE=system; ENABLE=true; DATA_VOLUME=""; STATUS_VOLUME=""; HELPER_IMAGE=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--user) SERVICE_SCOPE=user; shift ;;
|
||||
--engine) ENGINE="$2"; shift 2 ;;
|
||||
--volume) DATA_VOLUME="$2"; shift 2 ;;
|
||||
--status-volume) STATUS_VOLUME="$2"; shift 2 ;;
|
||||
--helper-image) HELPER_IMAGE="$2"; shift 2 ;;
|
||||
--data-dir) DATA_DIR="$2"; shift 2 ;;
|
||||
--image-repo) IMAGE_REPO="$2"; shift 2 ;;
|
||||
--unit) UNIT="$2"; shift 2 ;;
|
||||
--policy) POLICY="$2"; shift 2 ;;
|
||||
--authfile) AUTHFILE="$2"; shift 2 ;;
|
||||
--no-enable) ENABLE=false; shift ;;
|
||||
*) echo "unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$DATA_DIR" ] && [ -n "$IMAGE_REPO" ] && [ -n "$UNIT" ] || { echo "--data-dir, --image-repo and --unit are required" >&2; exit 2; }
|
||||
if [ -z "$IMAGE_REPO" ] || [ -z "$UNIT" ] || { [ -z "$DATA_DIR" ] && [ -z "$DATA_VOLUME" ]; }; then
|
||||
echo "--image-repo, --unit and --volume (or legacy --data-dir) are required" >&2; exit 2
|
||||
fi
|
||||
case "$POLICY" in auto|manual) ;; *) echo "--policy must be auto or manual" >&2; exit 2 ;; esac
|
||||
[ "$(id -u)" -eq 0 ] || { echo "run as root" >&2; exit 1; }
|
||||
case "$ENGINE" in podman|docker) ;; *) echo "--engine must be podman or docker" >&2; exit 2 ;; esac
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
[ -z "$DATA_DIR" ] || { echo "use --volume or --data-dir, not both" >&2; exit 2; }
|
||||
[ -n "$HELPER_IMAGE" ] || { echo "--volume needs --helper-image (an already pulled Monitor image)" >&2; exit 2; }
|
||||
STATUS_VOLUME="${STATUS_VOLUME:-monitor-$COMPONENT-status}"
|
||||
"$ENGINE" image inspect "$HELPER_IMAGE" >/dev/null
|
||||
"$ENGINE" volume inspect "$DATA_VOLUME" >/dev/null 2>&1 || "$ENGINE" volume create "$DATA_VOLUME" >/dev/null
|
||||
"$ENGINE" volume inspect "$STATUS_VOLUME" >/dev/null 2>&1 || "$ENGINE" volume create "$STATUS_VOLUME" >/dev/null
|
||||
else
|
||||
case "$DATA_DIR" in /*) ;; *) echo "--data-dir must be absolute" >&2; exit 2 ;; esac
|
||||
fi
|
||||
|
||||
if [ "$SERVICE_SCOPE" = user ]; then
|
||||
[ "$(id -u)" -ne 0 ] || { echo "--user must run as the rootless Podman user, without sudo" >&2; exit 1; }
|
||||
[ "$ENGINE" = podman ] || { echo "--user currently supports rootless Podman; Docker uses system services" >&2; exit 2; }
|
||||
CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/monitor"
|
||||
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/monitor-update"
|
||||
UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
|
||||
BIN_DIR="$HOME/.local/bin"
|
||||
TARGET=default.target
|
||||
else
|
||||
[ "$(id -u)" -eq 0 ] || { echo "system installation needs root; use --user for rootless Podman" >&2; exit 1; }
|
||||
CONF_DIR=/etc/monitor-update
|
||||
STATE_DIR=/var/lib/monitor-update
|
||||
UNIT_DIR=/etc/systemd/system
|
||||
BIN_DIR=/usr/local/sbin
|
||||
TARGET=multi-user.target
|
||||
fi
|
||||
service_ctl() {
|
||||
if [ "$SERVICE_SCOPE" = user ]; then systemctl --user "$@"; else systemctl "$@"; fi
|
||||
}
|
||||
# Config is sourced by the updater; quote arguments as shell data.
|
||||
shell_quote() { printf "'"; printf '%s' "$1" | sed "s/'/'\"'\"'/g"; printf "'"; }
|
||||
unit_escape() { printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'; }
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
install -d -m 755 /etc/monitor-update /var/lib/monitor-update
|
||||
install -m 755 "$HERE/monitor-update.sh" /usr/local/sbin/monitor-update
|
||||
install -m 644 "$HERE/release-key.pub.pem" /etc/monitor-update/release-key.pub.pem
|
||||
install -d -m 755 "$CONF_DIR" "$STATE_DIR" "$UNIT_DIR" "$BIN_DIR"
|
||||
BIN="$BIN_DIR/monitor-update"
|
||||
install -m 755 "$HERE/monitor-update.sh" "$BIN"
|
||||
install -m 644 "$HERE/release-key.pub.pem" "$CONF_DIR/release-key.pub.pem"
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
: # Named volumes are initialized by the image; no host directory or chown needed.
|
||||
elif [ "$SERVICE_SCOPE" = user ]; then
|
||||
install -d -m 700 "$DATA_DIR/update"
|
||||
else
|
||||
install -d -m 755 -o 1654 -g 1654 "$DATA_DIR/update"
|
||||
fi
|
||||
if [ -z "$DATA_VOLUME" ]; then install -d -m 755 "$DATA_DIR/update-status"; fi
|
||||
# Rootless service templates mount update-status separately read-only.
|
||||
ENV_FILE="$CONF_DIR/$COMPONENT.image"
|
||||
PUBKEY="$CONF_DIR/release-key.pub.pem"
|
||||
{
|
||||
for name in DATA_DIR IMAGE_REPO UNIT PUBKEY ENV_FILE STATE_DIR POLICY AUTHFILE ENGINE SERVICE_SCOPE DATA_VOLUME STATUS_VOLUME HELPER_IMAGE; do
|
||||
case "$name" in
|
||||
DATA_DIR) value="$DATA_DIR" ;; IMAGE_REPO) value="$IMAGE_REPO" ;; UNIT) value="$UNIT" ;;
|
||||
PUBKEY) value="$PUBKEY" ;; ENV_FILE) value="$ENV_FILE" ;; STATE_DIR) value="$STATE_DIR" ;;
|
||||
DATA_VOLUME) value="$DATA_VOLUME" ;; STATUS_VOLUME) value="$STATUS_VOLUME" ;; HELPER_IMAGE) value="$HELPER_IMAGE" ;;
|
||||
POLICY) value="$POLICY" ;; AUTHFILE) value="$AUTHFILE" ;; ENGINE) value="$ENGINE" ;; SERVICE_SCOPE) value="$SERVICE_SCOPE" ;;
|
||||
esac
|
||||
printf '%s=' "$name"; shell_quote "$value"; printf '\n'
|
||||
done
|
||||
} > "$CONF_DIR/$COMPONENT.conf"
|
||||
chmod 600 "$CONF_DIR/$COMPONENT.conf"
|
||||
|
||||
# 容器用户(UID 1654)能写 update/(放清单和请求);update-status/ 归 root,业务进程只读
|
||||
install -d -m 755 -o 1654 -g 1654 "$DATA_DIR/update"
|
||||
install -d -m 755 "$DATA_DIR/update-status"
|
||||
|
||||
cat > "/etc/monitor-update/$COMPONENT.conf" <<CONF
|
||||
DATA_DIR=$DATA_DIR
|
||||
IMAGE_REPO=$IMAGE_REPO
|
||||
UNIT=$UNIT
|
||||
PUBKEY=/etc/monitor-update/release-key.pub.pem
|
||||
ENV_FILE=/etc/monitor-update/$COMPONENT.image
|
||||
STATE_DIR=/var/lib/monitor-update
|
||||
POLICY=$POLICY
|
||||
AUTHFILE=$AUTHFILE
|
||||
CONF
|
||||
chmod 644 "/etc/monitor-update/$COMPONENT.conf"
|
||||
|
||||
cat > "/etc/systemd/system/monitor-update-check-$COMPONENT.service" <<UNITFILE
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
cat > "$UNIT_DIR/monitor-update-$COMPONENT.service" <<UNITFILE
|
||||
[Unit]
|
||||
Description=Check the signed update offer for monitor-$COMPONENT
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/monitor-update check $COMPONENT
|
||||
UNITFILE
|
||||
cat > "/etc/systemd/system/monitor-update-apply-$COMPONENT.service" <<UNITFILE
|
||||
[Unit]
|
||||
Description=Apply the verified update for monitor-$COMPONENT
|
||||
Description=Check/apply signed volume update for monitor-$COMPONENT
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
TimeoutStartSec=300
|
||||
ExecStart=/usr/local/sbin/monitor-update apply $COMPONENT
|
||||
Environment="MONITOR_UPDATE_CONF_DIR=$(unit_escape "$CONF_DIR")"
|
||||
ExecStart="$(unit_escape "$BIN")" check $COMPONENT
|
||||
UNITFILE
|
||||
cat > "/etc/systemd/system/monitor-update-check-$COMPONENT.path" <<UNITFILE
|
||||
cat > "$UNIT_DIR/monitor-update-$COMPONENT.timer" <<UNITFILE
|
||||
[Unit]
|
||||
Description=Watch for a new update offer for monitor-$COMPONENT
|
||||
Description=Poll the update volume for monitor-$COMPONENT
|
||||
|
||||
[Path]
|
||||
PathChanged=$DATA_DIR/update/offer.sig
|
||||
Unit=monitor-update-check-$COMPONENT.service
|
||||
[Timer]
|
||||
OnBootSec=10s
|
||||
OnUnitInactiveSec=30s
|
||||
Unit=monitor-update-$COMPONENT.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=$TARGET
|
||||
UNITFILE
|
||||
cat > "/etc/systemd/system/monitor-update-apply-$COMPONENT.path" <<UNITFILE
|
||||
else
|
||||
for action in check apply; do
|
||||
cat > "$UNIT_DIR/monitor-update-$action-$COMPONENT.service" <<UNITFILE
|
||||
[Unit]
|
||||
Description=Watch for an update request for monitor-$COMPONENT
|
||||
Description=$action the signed update for monitor-$COMPONENT
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
TimeoutStartSec=300
|
||||
Environment="MONITOR_UPDATE_CONF_DIR=$(unit_escape "$CONF_DIR")"
|
||||
ExecStart="$(unit_escape "$BIN")" $action $COMPONENT
|
||||
UNITFILE
|
||||
watched=offer.sig
|
||||
[ "$action" != apply ] || watched=apply.request
|
||||
cat > "$UNIT_DIR/monitor-update-$action-$COMPONENT.path" <<UNITFILE
|
||||
[Unit]
|
||||
Description=Watch $action events for monitor-$COMPONENT
|
||||
|
||||
[Path]
|
||||
PathChanged=$DATA_DIR/update/apply.request
|
||||
Unit=monitor-update-apply-$COMPONENT.service
|
||||
PathChanged=$DATA_DIR/update/$watched
|
||||
Unit=monitor-update-$action-$COMPONENT.service
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=$TARGET
|
||||
UNITFILE
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now "monitor-update-check-$COMPONENT.path" "monitor-update-apply-$COMPONENT.path"
|
||||
echo "updater installed for $COMPONENT (policy: $POLICY)"
|
||||
done
|
||||
fi
|
||||
if [ "$ENABLE" = true ]; then
|
||||
service_ctl daemon-reload
|
||||
if [ -n "$DATA_VOLUME" ]; then
|
||||
service_ctl disable --now "monitor-update-check-$COMPONENT.path" "monitor-update-apply-$COMPONENT.path" >/dev/null 2>&1 || true
|
||||
service_ctl enable --now "monitor-update-$COMPONENT.timer"
|
||||
else
|
||||
service_ctl disable --now "monitor-update-$COMPONENT.timer" >/dev/null 2>&1 || true
|
||||
service_ctl enable --now "monitor-update-check-$COMPONENT.path" "monitor-update-apply-$COMPONENT.path"
|
||||
fi
|
||||
fi
|
||||
echo "updater installed for $COMPONENT (engine: $ENGINE, scope: $SERVICE_SCOPE, policy: $POLICY)"
|
||||
echo "container service must read IMAGE from $ENV_FILE"
|
||||
Reference in new issue
Block a user