#!/bin/sh
set -eu
umask 077
# Release engineering substitutes these values; this template cannot install.
BASE='https://get.ufied.org/releases'
UFIED_VERSION='0.1.0-alpha.8'
SEQUENCE='8'
ARTIFACT_DIR=''
if [ "$#" -eq 2 ] && [ "$1" = --artifact-dir ]; then
 ARTIFACT_DIR=$2
 case "$ARTIFACT_DIR" in /*) ;; *) echo 'Artifact directory must be absolute.' >&2; exit 1;; esac
 shift 2
fi
[ "$#" -eq 0 ] || { echo 'Usage: install.sh [--artifact-dir /private/release/linux-ARCH]' >&2; exit 1; }
case "$BASE" in https://*) ;; *) echo 'Release hosting is not configured.' >&2; exit 1;; esac
[ "$(id -u)" -eq 0 ] || { echo 'Run with sudo on a fresh Ubuntu 24.04 server.' >&2; exit 1; }
. /etc/os-release
[ "$ID" = ubuntu ] && [ "$VERSION_ID" = 24.04 ] || { echo 'Ubuntu 24.04 required.' >&2; exit 1; }
[ -d /run/systemd/system ] || { echo 'systemd required.' >&2; exit 1; }
[ ! -e /opt/ufied ] && [ ! -e /etc/ufied ] && [ ! -e /var/lib/ufied ] || { echo 'Existing or partial Ufied installation found. Inspect before retrying.' >&2; exit 1; }
for command in curl openssl sha256sum useradd systemctl; do command -v "$command" >/dev/null || { echo "Missing prerequisite: $command" >&2; exit 1; }; done
case "$(uname -m)" in x86_64) ARCH=amd64;; aarch64) ARCH=arm64;; *) echo 'Unsupported architecture' >&2; exit 1;; esac
TEMP_DIR=$(mktemp -d)
trap 'rm -rf -- "$TEMP_DIR"' EXIT HUP INT TERM
cat > "$TEMP_DIR/release.pub" <<'PUBLIC_KEY'
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAch6LaQP8rKPAVSjhPWU40uGUBMwSRz50qS6j40D/p1E=
-----END PUBLIC KEY-----
PUBLIC_KEY
fetch() { curl --fail --show-error --silent --location --proto '=https' --proto-redir '=https' --tlsv1.2 --connect-timeout 10 --max-time 300 --retry 2 --max-filesize "$3" "$1" -o "$2"; }
if [ -n "$ARTIFACT_DIR" ]; then
 cp -- "$ARTIFACT_DIR/SHA256SUMS" "$ARTIFACT_DIR/SHA256SUMS.sig" "$TEMP_DIR/"
else
 fetch "$BASE/$UFIED_VERSION/linux-$ARCH/SHA256SUMS" "$TEMP_DIR/SHA256SUMS" 1024
 fetch "$BASE/$UFIED_VERSION/linux-$ARCH/SHA256SUMS.sig" "$TEMP_DIR/SHA256SUMS.sig" 1024
fi
openssl pkeyutl -verify -pubin -inkey "$TEMP_DIR/release.pub" -rawin -in "$TEMP_DIR/SHA256SUMS" -sigfile "$TEMP_DIR/SHA256SUMS.sig" >/dev/null
[ "$(wc -l < "$TEMP_DIR/SHA256SUMS")" -eq 1 ] && grep -Eq '^[a-f0-9]{64}  ufied$' "$TEMP_DIR/SHA256SUMS" || { echo 'Invalid release checksum format.' >&2; exit 1; }
if [ -n "$ARTIFACT_DIR" ]; then
 cp -- "$ARTIFACT_DIR/ufied" "$TEMP_DIR/ufied"
else
 fetch "$BASE/$UFIED_VERSION/linux-$ARCH/ufied" "$TEMP_DIR/ufied" 268435456
fi
(cd "$TEMP_DIR" && sha256sum -c SHA256SUMS)
# Privileged writes only begin after binary signature and digest verification.
if id ufied >/dev/null 2>&1; then echo 'Existing ufied user; inspect identity before installation.' >&2; exit 1; fi
useradd --system --user-group --home-dir /var/lib/ufied --no-create-home --shell /usr/sbin/nologin ufied
install -d -m 0755 "/opt/ufied/releases/$UFIED_VERSION"
install -m 0755 "$TEMP_DIR/ufied" "/opt/ufied/releases/$UFIED_VERSION/ufied"
ln -s "/opt/ufied/releases/$UFIED_VERSION" /opt/ufied/current
install -d -m 0750 -o root -g ufied /etc/ufied
install -d -m 0750 -o root -g ufied /var/lib/ufied
install -d -m 0700 -o ufied -g ufied /var/lib/ufied/control
printf '{"version":"%s","sequence":%s,"mode":"native"}\n' "$UFIED_VERSION" "$SEQUENCE" > /etc/ufied/installed.json
openssl rand -hex 32 > /etc/ufied/agent.token
chown root:ufied /etc/ufied/agent.token
chmod 0640 /etc/ufied/agent.token
install -m 0644 "$TEMP_DIR/release.pub" /etc/ufied/release.pub
cat > /etc/systemd/system/ufied-agent.service <<'AGENT'
[Unit]
Description=Ufied privileged reconciliation agent (alpha)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=root
Group=ufied
RuntimeDirectory=ufied
RuntimeDirectoryMode=0750
ExecStart=/opt/ufied/current/ufied agent --native --socket /run/ufied/agent.sock --agent-token /etc/ufied/agent.token
Restart=on-failure
RestartSec=5
TimeoutStopSec=960
UMask=0027
PrivateTmp=true
ProtectHome=true
[Install]
WantedBy=multi-user.target
AGENT
cat > /etc/systemd/system/ufied-api.service <<'API'
[Unit]
Description=Ufied control plane (alpha)
After=ufied-agent.service
Requires=ufied-agent.service
[Service]
Type=simple
User=ufied
Group=ufied
ExecStart=/opt/ufied/current/ufied serve --data /var/lib/ufied/control --listen 127.0.0.1:8787 --socket /run/ufied/agent.sock --agent-token /etc/ufied/agent.token
Restart=on-failure
RestartSec=5
TimeoutStopSec=960
UMask=0077
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/ufied/control
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
[Install]
WantedBy=multi-user.target
API
systemctl daemon-reload
systemctl enable --now ufied-agent.service ufied-api.service
systemctl is-active --quiet ufied-agent.service
systemctl is-active --quiet ufied-api.service
attempt=0
until curl --fail --silent --max-time 3 http://127.0.0.1:8787/ >/dev/null; do
 attempt=$((attempt + 1)); [ "$attempt" -lt 15 ] || { echo 'API health check failed; inspect journalctl -u ufied-api.' >&2; exit 1; }; sleep 1
done
echo "Ufied $UFIED_VERSION installed. API binds only to 127.0.0.1:8787."
echo 'Connect with: ssh -L 8787:127.0.0.1:8787 user@server'
echo 'Read your credential with: sudo cat /var/lib/ufied/control/admin.token'
echo 'Then open http://127.0.0.1:8787 on your own computer.'
