mirror of
https://github.com/BetterCorp/BetterFrame.git
synced 2026-05-26 19:06:34 +00:00
feat(deploy): single-file end-to-end installer/updater
Expand setup-pi-kiosk.sh to be the one-and-only entry point: clones (or git-pulls) the repo into the invoking user's home, installs Docker + compose plugin + GTK/GStreamer/WebKit/cage/seatd + rustup (if missing), brings up the docker-compose stack, builds the kiosk binary, and provisions the bfkiosk user + cage PAM + systemd unit. Every step is idempotent so re-running pulls latest, rebuilds, and redeploys. SKIP_DOCKER / SKIP_KIOSK / SKIP_BUILD env flags let an operator partition the work for kiosk-only or server-only hosts.
This commit is contained in:
parent
5656d430ff
commit
bfbaa72022
1 changed files with 159 additions and 72 deletions
|
|
@ -1,22 +1,29 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Provision a Raspberry Pi (Bookworm or later) as a BetterFrame kiosk.
|
# Single-host BetterFrame installer/updater for Raspberry Pi 5 (Bookworm+).
|
||||||
#
|
#
|
||||||
# - Creates the unprivileged `bfkiosk` user
|
# What it does (every step idempotent — safe to re-run for updates):
|
||||||
# - Installs `cage` (single-app Wayland compositor) and `seatd`
|
# 1. clone or `git pull` the BetterFrame repo into the invoking user's home
|
||||||
# - Disables any installed display manager and sets multi-user.target
|
# 2. install OS deps: Docker + compose, GTK4/GStreamer/WebKit dev libs,
|
||||||
# - Disables getty on tty1 (cage owns the tty)
|
# cage + seatd, rust toolchain (via rustup) if missing
|
||||||
# - Installs the betterframe-kiosk system unit + PAM stack
|
# 3. bring up the Docker stack (server + Angie proxy + Node-RED) via
|
||||||
# - Enables + starts the service
|
# `docker compose up -d --build`
|
||||||
|
# 4. build the Rust kiosk binary as the invoking user, install to
|
||||||
|
# /opt/betterframe/kiosk/betterframe-kiosk
|
||||||
|
# 5. provision the unprivileged `bfkiosk` user + cage PAM stack + systemd
|
||||||
|
# unit, disable any display manager, set multi-user.target as default
|
||||||
|
# 6. enable + start the kiosk service
|
||||||
#
|
#
|
||||||
# Re-run safely: every step is idempotent.
|
# Re-run after every git change to pull + rebuild + redeploy.
|
||||||
#
|
|
||||||
# By default the script builds the kiosk Rust binary (cargo build --release)
|
|
||||||
# and installs it at /opt/betterframe/kiosk/betterframe-kiosk. Set
|
|
||||||
# SKIP_BUILD=1 to skip the build step (expects the binary already present
|
|
||||||
# at kiosk/target/release/betterframe-kiosk in the repo).
|
|
||||||
#
|
#
|
||||||
# Usage: sudo ./deploy/scripts/setup-pi-kiosk.sh
|
# Usage: sudo ./deploy/scripts/setup-pi-kiosk.sh
|
||||||
# sudo SKIP_BUILD=1 ./deploy/scripts/setup-pi-kiosk.sh
|
#
|
||||||
|
# Env overrides:
|
||||||
|
# BF_HOME=/path/to/repo override repo location (default: $HOME/betterframe)
|
||||||
|
# BF_REPO_URL=git@… override clone URL (default: github)
|
||||||
|
# SKIP_BUILD=1 skip kiosk cargo build (expects existing binary)
|
||||||
|
# SKIP_DOCKER=1 skip `docker compose up -d --build`
|
||||||
|
# SKIP_KIOSK=1 server-only host (no kiosk service / cage / tty1)
|
||||||
|
# SKIP_SERVER=1 kiosk-only host (no Docker stack)
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -25,80 +32,160 @@ if [ "$EUID" -ne 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
REPO_URL="${BF_REPO_URL:-https://github.com/BetterCorp/BetterFrame.git}"
|
||||||
|
BUILD_USER="${SUDO_USER:-$(id -un)}"
|
||||||
echo "==> Installing cage + seatd"
|
if [ "${BUILD_USER}" = "root" ]; then
|
||||||
apt-get update
|
echo "error: refuses to run as root without SUDO_USER set. Use 'sudo ...' from a normal user." >&2
|
||||||
apt-get install -y --no-install-recommends cage seatd
|
exit 1
|
||||||
|
|
||||||
echo "==> Enabling seatd"
|
|
||||||
systemctl enable --now seatd
|
|
||||||
|
|
||||||
echo "==> Ensuring bfkiosk user exists"
|
|
||||||
if ! id -u bfkiosk >/dev/null 2>&1; then
|
|
||||||
useradd -m -s /usr/sbin/nologin bfkiosk
|
|
||||||
fi
|
fi
|
||||||
# nologin shell means no interactive login. The systemd unit launches cage
|
USER_HOME="$(getent passwd "${BUILD_USER}" | cut -d: -f6)"
|
||||||
# directly so a shell is not needed. The user joins the hardware-access
|
BF_HOME="${BF_HOME:-${USER_HOME}/betterframe}"
|
||||||
# groups required by GStreamer/GTK/cage.
|
|
||||||
for grp in video render input audio seat; do
|
run_as_user() {
|
||||||
if getent group "$grp" >/dev/null; then
|
# Run in a login shell so ~/.cargo/env, ~/.profile etc. are sourced.
|
||||||
usermod -a -G "$grp" bfkiosk
|
sudo -u "${BUILD_USER}" -H -i sh -c "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 1. Base packages
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
echo "==> Installing base packages"
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
git ca-certificates curl gnupg lsb-release sudo
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 2. Clone or update repo
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ -d "${BF_HOME}/.git" ]; then
|
||||||
|
echo "==> Updating repo at ${BF_HOME}"
|
||||||
|
run_as_user "git -C '${BF_HOME}' fetch --prune origin && git -C '${BF_HOME}' pull --ff-only"
|
||||||
|
else
|
||||||
|
echo "==> Cloning ${REPO_URL} → ${BF_HOME}"
|
||||||
|
install -d -o "${BUILD_USER}" -g "${BUILD_USER}" "$(dirname "${BF_HOME}")"
|
||||||
|
run_as_user "git clone '${REPO_URL}' '${BF_HOME}'"
|
||||||
|
fi
|
||||||
|
REPO_ROOT="${BF_HOME}"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
# 3. Docker engine + compose plugin
|
||||||
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ "${SKIP_SERVER:-0}" != "1" ] && [ "${SKIP_DOCKER:-0}" != "1" ]; then
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "==> Installing Docker (via convenience script)"
|
||||||
|
curl -fsSL https://get.docker.com | sh
|
||||||
|
else
|
||||||
|
echo "==> Docker already installed: $(docker --version)"
|
||||||
fi
|
fi
|
||||||
done
|
if ! docker compose version >/dev/null 2>&1; then
|
||||||
|
apt-get install -y --no-install-recommends docker-compose-plugin
|
||||||
|
fi
|
||||||
|
# Let BUILD_USER use docker without sudo (idempotent).
|
||||||
|
if ! id -nG "${BUILD_USER}" | tr ' ' '\n' | grep -qx docker; then
|
||||||
|
usermod -aG docker "${BUILD_USER}"
|
||||||
|
echo " added ${BUILD_USER} to docker group (re-login required for that user)"
|
||||||
|
fi
|
||||||
|
systemctl enable --now docker
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Disabling any display manager"
|
# ----------------------------------------------------------------------------
|
||||||
for dm in lightdm gdm gdm3 sddm; do
|
# 4. Kiosk runtime deps (GTK/GStreamer/WebKit + cage + seatd)
|
||||||
systemctl disable --now "${dm}.service" 2>/dev/null || true
|
# ----------------------------------------------------------------------------
|
||||||
done
|
if [ "${SKIP_KIOSK:-0}" != "1" ]; then
|
||||||
systemctl set-default multi-user.target
|
echo "==> Installing kiosk runtime deps"
|
||||||
|
|
||||||
echo "==> Disabling getty on tty1 (cage owns it)"
|
|
||||||
systemctl disable --now getty@tty1.service 2>/dev/null || true
|
|
||||||
|
|
||||||
echo "==> Building + installing kiosk binary"
|
|
||||||
BIN_SRC="${REPO_ROOT}/kiosk/target/release/betterframe-kiosk"
|
|
||||||
BIN_DST_DIR="/opt/betterframe/kiosk"
|
|
||||||
BIN_DST="${BIN_DST_DIR}/betterframe-kiosk"
|
|
||||||
|
|
||||||
if [ "${SKIP_BUILD:-0}" != "1" ]; then
|
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
|
cage seatd \
|
||||||
libgtk-4-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
libgtk-4-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||||
libwebkitgtk-6.0-dev pkg-config build-essential \
|
libwebkitgtk-6.0-dev pkg-config build-essential \
|
||||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
||||||
gstreamer1.0-plugins-bad gstreamer1.0-libav
|
gstreamer1.0-plugins-bad gstreamer1.0-libav \
|
||||||
|
v4l-utils wlr-randr
|
||||||
|
|
||||||
# Build as the invoking user (typically the dev/admin user), not root,
|
systemctl enable --now seatd
|
||||||
# so cargo registry caches stay in their home and rustup PATH applies.
|
|
||||||
# -i runs a login shell so ~/.cargo/env gets sourced from .profile/.bashrc.
|
|
||||||
BUILD_USER="${SUDO_USER:-root}"
|
|
||||||
echo " building as ${BUILD_USER}"
|
|
||||||
sudo -u "${BUILD_USER}" -i sh -c "cd '${REPO_ROOT}' && cargo build --release --manifest-path kiosk/Cargo.toml"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -f "${BIN_SRC}" ]; then
|
# ----------------------------------------------------------------------------
|
||||||
echo "error: kiosk binary not found at ${BIN_SRC}." >&2
|
# 5. Rust toolchain (kiosk build prerequisite)
|
||||||
echo " Run with SKIP_BUILD=0 (default) or build manually first." >&2
|
# ----------------------------------------------------------------------------
|
||||||
exit 1
|
if [ "${SKIP_KIOSK:-0}" != "1" ] && [ "${SKIP_BUILD:-0}" != "1" ]; then
|
||||||
|
if ! run_as_user "command -v cargo >/dev/null 2>&1"; then
|
||||||
|
echo "==> Installing rustup as ${BUILD_USER}"
|
||||||
|
run_as_user "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
install -d -m 755 "${BIN_DST_DIR}"
|
# ----------------------------------------------------------------------------
|
||||||
install -m 755 "${BIN_SRC}" "${BIN_DST}"
|
# 6. Build + start Docker stack
|
||||||
echo " installed → ${BIN_DST}"
|
# ----------------------------------------------------------------------------
|
||||||
|
if [ "${SKIP_SERVER:-0}" != "1" ] && [ "${SKIP_DOCKER:-0}" != "1" ]; then
|
||||||
|
echo "==> Building + starting Docker stack"
|
||||||
|
run_as_user "cd '${REPO_ROOT}' && docker compose -f deploy/docker/docker-compose.yml up -d --build"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "==> Installing PAM stack and systemd unit"
|
# ----------------------------------------------------------------------------
|
||||||
install -m 644 "${REPO_ROOT}/deploy/pam.d/cage" /etc/pam.d/cage
|
# 7. Build + install kiosk binary
|
||||||
install -m 644 "${REPO_ROOT}/deploy/systemd/betterframe-kiosk.service" \
|
# ----------------------------------------------------------------------------
|
||||||
/etc/systemd/system/betterframe-kiosk.service
|
if [ "${SKIP_KIOSK:-0}" != "1" ]; then
|
||||||
|
BIN_SRC="${REPO_ROOT}/kiosk/target/release/betterframe-kiosk"
|
||||||
|
BIN_DST_DIR="/opt/betterframe/kiosk"
|
||||||
|
BIN_DST="${BIN_DST_DIR}/betterframe-kiosk"
|
||||||
|
|
||||||
if [ ! -e /etc/default/betterframe-kiosk ]; then
|
if [ "${SKIP_BUILD:-0}" != "1" ]; then
|
||||||
cat > /etc/default/betterframe-kiosk <<'EOF'
|
echo "==> Building kiosk binary (release)"
|
||||||
|
run_as_user "cd '${REPO_ROOT}' && cargo build --release --manifest-path kiosk/Cargo.toml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "${BIN_SRC}" ]; then
|
||||||
|
echo "error: kiosk binary not found at ${BIN_SRC}." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
install -d -m 755 "${BIN_DST_DIR}"
|
||||||
|
install -m 755 "${BIN_SRC}" "${BIN_DST}"
|
||||||
|
echo " installed → ${BIN_DST}"
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# 8. bfkiosk user + PAM + systemd unit
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
echo "==> Provisioning bfkiosk user"
|
||||||
|
if ! id -u bfkiosk >/dev/null 2>&1; then
|
||||||
|
useradd -m -s /usr/sbin/nologin bfkiosk
|
||||||
|
fi
|
||||||
|
for grp in video render input audio seat; do
|
||||||
|
if getent group "$grp" >/dev/null; then
|
||||||
|
usermod -a -G "$grp" bfkiosk
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "==> Disabling display managers + getty@tty1"
|
||||||
|
for dm in lightdm gdm gdm3 sddm; do
|
||||||
|
systemctl disable --now "${dm}.service" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
systemctl set-default multi-user.target
|
||||||
|
systemctl disable --now getty@tty1.service 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "==> Installing PAM + systemd unit"
|
||||||
|
install -m 644 "${REPO_ROOT}/deploy/pam.d/cage" /etc/pam.d/cage
|
||||||
|
install -m 644 "${REPO_ROOT}/deploy/systemd/betterframe-kiosk.service" \
|
||||||
|
/etc/systemd/system/betterframe-kiosk.service
|
||||||
|
|
||||||
|
if [ ! -e /etc/default/betterframe-kiosk ]; then
|
||||||
|
cat > /etc/default/betterframe-kiosk <<'EOF'
|
||||||
# Runtime env for betterframe-kiosk. Edit and `systemctl restart betterframe-kiosk`.
|
# Runtime env for betterframe-kiosk. Edit and `systemctl restart betterframe-kiosk`.
|
||||||
# BETTERFRAME_SERVER=http://192.168.1.10
|
# BETTERFRAME_SERVER=http://192.168.1.10
|
||||||
EOF
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable betterframe-kiosk.service
|
||||||
|
# Restart picks up new binary on re-run.
|
||||||
|
systemctl restart betterframe-kiosk.service || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
systemctl daemon-reload
|
echo "==> Done."
|
||||||
systemctl enable betterframe-kiosk.service
|
if [ "${SKIP_SERVER:-0}" != "1" ] && [ "${SKIP_DOCKER:-0}" != "1" ]; then
|
||||||
|
echo " Server stack: http://$(hostname -I | awk '{print $1}')/"
|
||||||
echo "==> Done. Reboot to take effect, or 'systemctl start betterframe-kiosk' now."
|
fi
|
||||||
|
if [ "${SKIP_KIOSK:-0}" != "1" ]; then
|
||||||
|
echo " Kiosk service: systemctl status betterframe-kiosk"
|
||||||
|
fi
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue