Files
MoShiro/packaging/update/install-updater.sh
T
justme 83e5120377 refactor!: rename the project from Monitor to MoShiro
Namespaces, assemblies, projects and directories, the solution, binaries,
images, systemd units, config paths, environment variables, protocol method
names, the media type and the raw chunk magic all move to MoShiro/moshiro.
Existing data and deployments are not compatible and must be recreated.
Also fix the architecture tests after the solution layout change and make
the update tests independent of their order.
2026-10-07 01:49:04 +08:00

156 lines
6.7 KiB
Bash

#!/bin/sh
# 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> [--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
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
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 MoShiro image)" >&2; exit 2; }
STATUS_VOLUME="${STATUS_VOLUME:-moshiro-$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}/moshiro"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/moshiro-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/moshiro-update
STATE_DIR=/var/lib/moshiro-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 "$CONF_DIR" "$STATE_DIR" "$UNIT_DIR" "$BIN_DIR"
BIN="$BIN_DIR/moshiro-update"
install -m 755 "$HERE/moshiro-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"
if [ -n "$DATA_VOLUME" ]; then
cat > "$UNIT_DIR/moshiro-update-$COMPONENT.service" <<UNITFILE
[Unit]
Description=Check/apply signed volume update for moshiro-$COMPONENT
[Service]
Type=oneshot
TimeoutStartSec=300
Environment="MOSHIRO_UPDATE_CONF_DIR=$(unit_escape "$CONF_DIR")"
ExecStart="$(unit_escape "$BIN")" check $COMPONENT
UNITFILE
cat > "$UNIT_DIR/moshiro-update-$COMPONENT.timer" <<UNITFILE
[Unit]
Description=Poll the update volume for moshiro-$COMPONENT
[Timer]
OnBootSec=10s
OnUnitInactiveSec=30s
Unit=moshiro-update-$COMPONENT.service
[Install]
WantedBy=$TARGET
UNITFILE
else
for action in check apply; do
cat > "$UNIT_DIR/moshiro-update-$action-$COMPONENT.service" <<UNITFILE
[Unit]
Description=$action the signed update for moshiro-$COMPONENT
[Service]
Type=oneshot
TimeoutStartSec=300
Environment="MOSHIRO_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/moshiro-update-$action-$COMPONENT.path" <<UNITFILE
[Unit]
Description=Watch $action events for moshiro-$COMPONENT
[Path]
PathChanged=$DATA_DIR/update/$watched
Unit=moshiro-update-$action-$COMPONENT.service
[Install]
WantedBy=$TARGET
UNITFILE
done
fi
if [ "$ENABLE" = true ]; then
service_ctl daemon-reload
if [ -n "$DATA_VOLUME" ]; then
service_ctl disable --now "moshiro-update-check-$COMPONENT.path" "moshiro-update-apply-$COMPONENT.path" >/dev/null 2>&1 || true
service_ctl enable --now "moshiro-update-$COMPONENT.timer"
else
service_ctl disable --now "moshiro-update-$COMPONENT.timer" >/dev/null 2>&1 || true
service_ctl enable --now "moshiro-update-check-$COMPONENT.path" "moshiro-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"