Dev Updater's unit is a project -- a directory in a checkout -- that produces components, so this declaration belongs at the repository root where it can say where each half lives. `cwd` is how a component says that, and it is what the old file was working around: sitting in `app/`, it reached the backend with `../server/Cargo.toml` and `../server/service`, which described the layout backwards. So the file says what this repository produces: the backend, built and serviced in `server/`, and then the APK, built in `app/`. The order is unchanged and still the point -- a failing APK build leaves the phone the APK it already had rather than half of a matched pair. The service script gains dev-updater's note about lingering. A user service stops at logout unless `loginctl enable-linger` is set, which for this one matters more than for a build server: the phone reaches ai-server whether or not anyone is logged in at the desk. **This changes the project path, so the phone's existing entry (at ~/repos/ai-app/app) has to be removed and the checkout root added instead**, and its components accepted once. Verified against dev-updater's current parser rather than by reading its schema: the declaration loads, `cargo build --release` resolves into server/, app/build-apk.sh into app/, the service script to an absolute path, and APK discovery from the root still finds the built APK. `./server/service status` prints not-installed and exits 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
165 lines
5.2 KiB
Bash
Executable File
165 lines
5.2 KiB
Bash
Executable File
#!/bin/sh
|
|
# Installs and controls ai-server as a user service, for Dev Updater to
|
|
# drive from the phone -- and for you to drive by hand, which is the same
|
|
# thing.
|
|
#
|
|
# Declared as the Server component of this checkout in `.dev-updater.ron`
|
|
# at the repository root, so the card drives it like any other project's.
|
|
# One button is worth knowing about before pressing it: on a phone that
|
|
# reaches ai-server through the tunnel rather than through Dev Updater,
|
|
# Stop leaves this server down until someone starts it again here.
|
|
#
|
|
# ./service install | uninstall | start | stop | restart | status
|
|
#
|
|
# `status` prints exactly one of `running`, `stopped` or `not-installed`
|
|
# and exits 0. Anything else it prints, or any non-zero exit, means it
|
|
# could not tell -- which the card shows as "couldn't check" rather than
|
|
# as a service that is down.
|
|
#
|
|
# Nothing here ever prompts. Dev Updater runs this with stdin closed and no
|
|
# terminal, so a `sudo` password prompt would not fail, it would hang until
|
|
# the timeout with the card stuck mid-action. Anything needing root exits
|
|
# with a message telling you to run it yourself once instead.
|
|
#
|
|
# User services on purpose: a system unit needs root to install, and a
|
|
# build machine's own account is where a dev server belongs. Note that a
|
|
# user service stops at logout unless lingering is enabled
|
|
# (`loginctl enable-linger $USER`), which you want for this one: the phone
|
|
# reaches this server whether or not anyone is logged in at the desk.
|
|
set -eu
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
|
NAME=ai-server
|
|
BINARY="$SCRIPT_DIR/target/release/$NAME"
|
|
|
|
# Which init system is here, decided by asking rather than by looking for a
|
|
# binary: a machine can carry both, and an OpenRC older than 0.60 has
|
|
# rc-service but no --user at all.
|
|
detect() {
|
|
if command -v systemctl >/dev/null 2>&1 &&
|
|
systemctl --user show-environment >/dev/null 2>&1; then
|
|
echo systemd
|
|
elif command -v rc-service >/dev/null 2>&1 &&
|
|
rc-service --user --help >/dev/null 2>&1; then
|
|
echo openrc
|
|
else
|
|
echo none
|
|
fi
|
|
}
|
|
|
|
MANAGER=$(detect)
|
|
if [ "$MANAGER" = none ]; then
|
|
echo "No user-service manager here: this needs systemd with a user bus," >&2
|
|
echo "or OpenRC 0.60+ (older ones have no --user). Run $NAME by hand." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# OpenRC keeps user-service state under XDG_RUNTIME_DIR and refuses without
|
|
# it. Worth saying plainly: from a server started outside a login session
|
|
# it can be unset, and the failure otherwise reads as a broken service
|
|
# rather than a missing variable.
|
|
if [ "$MANAGER" = openrc ] && [ -z "${XDG_RUNTIME_DIR:-}" ]; then
|
|
echo "XDG_RUNTIME_DIR is unset, and OpenRC stores user-service state in it." >&2
|
|
echo "Set it at login (elogind or pam_xdg) and try again." >&2
|
|
exit 1
|
|
fi
|
|
|
|
SYSTEMD_UNIT="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$NAME.service"
|
|
OPENRC_UNIT="${XDG_CONFIG_HOME:-$HOME/.config}/rc/init.d/$NAME"
|
|
|
|
installed() {
|
|
case "$MANAGER" in
|
|
systemd) [ -f "$SYSTEMD_UNIT" ] ;;
|
|
openrc) [ -f "$OPENRC_UNIT" ] ;;
|
|
esac
|
|
}
|
|
|
|
require_binary() {
|
|
[ -x "$BINARY" ] && return 0
|
|
echo "No built server at $BINARY -- build it first." >&2
|
|
exit 1
|
|
}
|
|
|
|
do_install() {
|
|
require_binary
|
|
case "$MANAGER" in
|
|
systemd)
|
|
mkdir -p "$(dirname "$SYSTEMD_UNIT")"
|
|
cat > "$SYSTEMD_UNIT" <<UNIT
|
|
[Unit]
|
|
Description=$NAME (installed by $SCRIPT_DIR/service)
|
|
|
|
[Service]
|
|
ExecStart=$BINARY
|
|
Restart=on-failure
|
|
WorkingDirectory=$SCRIPT_DIR
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
UNIT
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable "$NAME" >/dev/null
|
|
;;
|
|
openrc)
|
|
mkdir -p "$(dirname "$OPENRC_UNIT")"
|
|
cat > "$OPENRC_UNIT" <<UNIT
|
|
#!/sbin/openrc-run
|
|
name="$NAME"
|
|
description="$NAME (installed by $SCRIPT_DIR/service)"
|
|
command="$BINARY"
|
|
command_background=true
|
|
directory="$SCRIPT_DIR"
|
|
pidfile="\${XDG_RUNTIME_DIR}/$NAME.pid"
|
|
UNIT
|
|
chmod +x "$OPENRC_UNIT"
|
|
rc-update --user add "$NAME" >/dev/null
|
|
;;
|
|
esac
|
|
}
|
|
|
|
do_uninstall() {
|
|
installed || return 0
|
|
case "$MANAGER" in
|
|
systemd)
|
|
systemctl --user disable --now "$NAME" >/dev/null 2>&1 || true
|
|
rm -f "$SYSTEMD_UNIT"
|
|
systemctl --user daemon-reload
|
|
;;
|
|
openrc)
|
|
rc-service --user "$NAME" stop >/dev/null 2>&1 || true
|
|
rc-update --user del "$NAME" >/dev/null 2>&1 || true
|
|
rm -f "$OPENRC_UNIT"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
control() {
|
|
installed || { echo "$NAME is not installed" >&2; exit 1; }
|
|
case "$MANAGER" in
|
|
systemd) systemctl --user "$1" "$NAME" ;;
|
|
openrc) rc-service --user "$NAME" "$1" ;;
|
|
esac
|
|
}
|
|
|
|
case "${1:-}" in
|
|
install) do_install ;;
|
|
uninstall) do_uninstall ;;
|
|
start | stop | restart) control "$1" ;;
|
|
status)
|
|
if ! installed; then
|
|
echo not-installed
|
|
elif case "$MANAGER" in
|
|
systemd) systemctl --user --quiet is-active "$NAME" ;;
|
|
openrc) rc-service --user "$NAME" status >/dev/null 2>&1 ;;
|
|
esac then
|
|
echo running
|
|
else
|
|
echo stopped
|
|
fi
|
|
;;
|
|
*)
|
|
echo "usage: $0 install|uninstall|start|stop|restart|status" >&2
|
|
exit 2
|
|
;;
|
|
esac
|