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.
156 lines
6.7 KiB
Bash
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"
|