mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
The prefix holds the installed clients and game (about 40 GB). The Nix
launcher always exported DPREFIX=${DPREFIX:-~/.darling}, so the prefix saved
in build/config.json was never used and the only way to move it was an
environment variable in every launch context.
- The launcher uses DPREFIX if set, then the saved prefix, then ~/.darling.
- darling-lol --set-prefix PATH runs scripts/nix-prefix.py in the namespace,
whose root owns the prefix files, so cp -a keeps their owners and xattrs.
It renames the prefix when possible; across filesystems it copies to
PATH.partial and removes the old prefix and its overlay work directory
only after the copy completes. A PATH that already holds a Darling prefix
is used as it is. It refuses non-empty directories that are not prefixes,
nested paths and paths with commas or colons (overlayfs separators), then
saves the path with configure.py --prefix.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
96 lines
4.0 KiB
Bash
96 lines
4.0 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
WORKSPACE="${XDG_DATA_HOME:-$HOME/.local/share}/darling-lol"
|
|
SHARE_DIR="@SHARE_DIR@"
|
|
FHS_BIN="@FHS_BIN@"
|
|
UNSHARE_BIN="@UNSHARE_BIN@"
|
|
# The store package is both a compatibility identity and a closure GC root.
|
|
export DARLING_PACKAGE_ID="$SHARE_DIR"
|
|
export DISPLAY="${DISPLAY:-:0}"
|
|
export EGL_PLATFORM="${EGL_PLATFORM:-x11}"
|
|
# The guest prefix: DPREFIX if set, else the one saved by --set-prefix, else ~/.darling.
|
|
if [ -z "${DPREFIX:-}" ] && [ -f "$WORKSPACE/build/config.json" ]; then
|
|
DPREFIX=$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1])).get("prefix", ""))' \
|
|
"$WORKSPACE/build/config.json" 2>/dev/null) || DPREFIX=
|
|
fi
|
|
export DPREFIX="${DPREFIX:-$HOME/.darling}"
|
|
if [ "${1:-}" = --set-prefix ]; then
|
|
[ $# -eq 2 ] || { echo 'Usage: darling-lol --set-prefix PATH' >&2; exit 2; }
|
|
# Resolve here: the namespace starts in the workspace, not in this directory.
|
|
set -- --set-prefix "$(realpath -m -- "$2")"
|
|
fi
|
|
export DARLING_HOST_UID="$(id -u)"
|
|
export DARLING_HOST_GID="$(id -g)"
|
|
export DARLING_WORKSPACE="$WORKSPACE"
|
|
|
|
python3 - "$WORKSPACE" <<'PY'
|
|
import pathlib, sys
|
|
workspace = pathlib.Path(sys.argv[1])
|
|
for path in [workspace, *workspace.parents]:
|
|
if path.is_symlink():
|
|
raise SystemExit(f'Refusing symlink workspace ancestor: {path}')
|
|
if (workspace / '.launcher.lock').is_symlink():
|
|
raise SystemExit('Refusing symlink workspace lock')
|
|
PY
|
|
mkdir -p "$WORKSPACE"
|
|
if [ "${1:-}" = --stop ]; then
|
|
exec python3 "$SHARE_DIR/scripts/nix-stop.py" "$WORKSPACE" "$DPREFIX"
|
|
fi
|
|
# One mutable base runtime per workspace; do not refresh scripts mid-session.
|
|
exec 9<>"$WORKSPACE/.launcher.lock"
|
|
flock -n 9 || { echo 'Another darling-lol workspace session is active; use its terminal.' >&2; exit 1; }
|
|
# Do not attempt to reconnect across separately created user namespaces. No kill,
|
|
# sudo, or shutdown is attempted here (the PID might even be stale/reused).
|
|
if [ -f "$DPREFIX/.init.pid" ]; then
|
|
read -r server_pid < "$DPREFIX/.init.pid" || true
|
|
if [[ "${server_pid:-}" =~ ^[1-9][0-9]*$ ]] && [ -d "/proc/$server_pid" ]; then
|
|
state=$(awk '/^State:/ {print $2}' "/proc/$server_pid/status")
|
|
if [ "$state" != Z ]; then
|
|
echo 'Prefix has a live server marker; namespace reconnect is unsupported.' >&2
|
|
echo 'Run darling-lol --stop to stop it, then retry. No processes were stopped.' >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
python3 "$SHARE_DIR/scripts/nix-copy.py" "$SHARE_DIR" "$WORKSPACE"
|
|
mkdir -p "$WORKSPACE/usr-local"
|
|
cd "$WORKSPACE"
|
|
# Map subordinate IDs before bubblewrap sets no_new_privs. UID 0 is not host root.
|
|
fhs_command=("$UNSHARE_BIN" --user --map-auto --map-current-user --setuid=0 --setgid=0 "$FHS_BIN")
|
|
stop_request="$WORKSPACE/.stop-request"
|
|
rm -f "$stop_request"
|
|
# Ctrl+C, a closed terminal or SIGTERM must reach nix-lifecycle.py, which shuts
|
|
# Darling down in its namespace. They would kill bubblewrap first, and
|
|
# bubblewrap's --die-with-parent would kill the lifecycle with it. So bubblewrap
|
|
# starts with them ignored and env restores them inside. This supervisor turns
|
|
# them into a stop request, the same one darling-lol --stop makes.
|
|
session() {
|
|
(trap '' INT QUIT HUP TERM
|
|
exec "${fhs_command[@]}" env --default-signal=INT,QUIT,HUP,TERM bash ./scripts/nix-session.sh "$@") 0<&0 &
|
|
local pid=$! status
|
|
trap 'touch "$stop_request"' INT HUP TERM
|
|
while :; do
|
|
wait "$pid" && status=0 || status=$?
|
|
kill -0 "$pid" 2>/dev/null || break
|
|
done
|
|
trap - INT HUP TERM
|
|
rm -f "$stop_request"
|
|
return "$status"
|
|
}
|
|
# Keep this supervisor alive to hold the lock: bubblewrap may close extra FDs.
|
|
# It never owns or signals game processes.
|
|
case "${1:-}" in
|
|
--shell) shift; "${fhs_command[@]}" bash "$@";;
|
|
--update) shift; "${fhs_command[@]}" ./scripts/update.sh "$@";;
|
|
# In the namespace, whose root owns the prefix files, so a copy keeps owners.
|
|
--set-prefix) "${fhs_command[@]}" python3 ./scripts/nix-prefix.py "$2";;
|
|
--installer) shift; session --installer "$@";;
|
|
*)
|
|
if [ $# -gt 0 ]; then
|
|
"${fhs_command[@]}" "$@"
|
|
else
|
|
session
|
|
fi;;
|
|
esac
|