Exclude local agent state from deploy archives, avoid recursive ownership over active runtime mounts, and let Alembic own schema upgrades before app startup.
189 lines
6.3 KiB
Bash
Executable file
189 lines
6.3 KiB
Bash
Executable file
#!/bin/sh
|
|
set -eu
|
|
|
|
usage() {
|
|
printf 'Usage: %s <ssh-user@host>\n' "$0" >&2
|
|
exit 2
|
|
}
|
|
|
|
if [ "$#" -ne 1 ]; then
|
|
usage
|
|
fi
|
|
|
|
target=$1
|
|
script_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
repo_root=$(CDPATH= cd -- "$script_dir/.." && pwd)
|
|
tmp_dir=$(mktemp -d)
|
|
archive="$tmp_dir/left4me.tar.gz"
|
|
|
|
cleanup() {
|
|
rm -rf "$tmp_dir"
|
|
}
|
|
trap cleanup EXIT INT HUP TERM
|
|
|
|
COPYFILE_DISABLE=1 tar -czf "$archive" \
|
|
--exclude .git \
|
|
--exclude .claude \
|
|
--exclude .venv \
|
|
--exclude __pycache__ \
|
|
--exclude .pytest_cache \
|
|
--exclude '*.egg-info' \
|
|
--exclude 'l4d2web.db*' \
|
|
--exclude '._*' \
|
|
-C "$repo_root" .
|
|
|
|
remote_tmp=$(ssh "$target" 'mktemp -d')
|
|
scp "$archive" "$target:$remote_tmp/left4me.tar.gz"
|
|
|
|
admin_username_file=
|
|
admin_password_file=
|
|
if [ "${LEFT4ME_ADMIN_USERNAME+x}" = x ] && [ "${LEFT4ME_ADMIN_PASSWORD+x}" = x ]; then
|
|
admin_username_file="$tmp_dir/admin_username"
|
|
admin_password_file="$tmp_dir/admin_password"
|
|
umask 077
|
|
printf '%s' "$LEFT4ME_ADMIN_USERNAME" > "$admin_username_file"
|
|
printf '%s' "$LEFT4ME_ADMIN_PASSWORD" > "$admin_password_file"
|
|
scp "$admin_username_file" "$target:$remote_tmp/admin_username"
|
|
scp "$admin_password_file" "$target:$remote_tmp/admin_password"
|
|
fi
|
|
|
|
ssh "$target" sh -s -- "$remote_tmp" <<'REMOTE'
|
|
set -eu
|
|
|
|
remote_tmp=$1
|
|
archive="$remote_tmp/left4me.tar.gz"
|
|
repo_tmp="$remote_tmp/repo"
|
|
|
|
if [ "$(id -u)" -eq 0 ]; then
|
|
sudo_cmd=
|
|
else
|
|
sudo_cmd=sudo
|
|
fi
|
|
|
|
run_as_left4me() {
|
|
sudo -u left4me "$@"
|
|
}
|
|
|
|
run_left4me_with_env() {
|
|
run_as_left4me sh -c 'set -a; . /etc/left4me/host.env; . /etc/left4me/web.env; set +a; exec "$@"' sh "$@"
|
|
}
|
|
|
|
cleanup_remote() {
|
|
rm -rf "$remote_tmp"
|
|
}
|
|
trap cleanup_remote EXIT INT HUP TERM
|
|
|
|
if ! id left4me >/dev/null 2>&1; then
|
|
$sudo_cmd useradd --system --home-dir /var/lib/left4me --create-home --shell /usr/sbin/nologin left4me
|
|
fi
|
|
|
|
if command -v apt-get >/dev/null 2>&1; then
|
|
$sudo_cmd apt-get update
|
|
$sudo_cmd apt-get install -y python3 python3-venv python3-pip curl ca-certificates tar gzip fuse-overlayfs fuse3 sudo
|
|
elif command -v dnf >/dev/null 2>&1; then
|
|
$sudo_cmd dnf install -y python3 python3-pip curl ca-certificates tar gzip fuse-overlayfs fuse3 sudo
|
|
else
|
|
printf 'Unsupported package manager: expected apt-get or dnf\n' >&2
|
|
exit 1
|
|
fi
|
|
|
|
$sudo_cmd mkdir -p \
|
|
/etc/left4me \
|
|
/opt/left4me \
|
|
/usr/local/lib/systemd/system \
|
|
/usr/local/libexec/left4me \
|
|
/var/lib/left4me/installation \
|
|
/var/lib/left4me/overlays \
|
|
/var/lib/left4me/instances \
|
|
/var/lib/left4me/runtime \
|
|
/var/lib/left4me/workshop_cache \
|
|
/var/lib/left4me/tmp
|
|
|
|
$sudo_cmd chown left4me:left4me \
|
|
/var/lib/left4me \
|
|
/var/lib/left4me/installation \
|
|
/var/lib/left4me/overlays \
|
|
/var/lib/left4me/instances \
|
|
/var/lib/left4me/runtime \
|
|
/var/lib/left4me/workshop_cache \
|
|
/var/lib/left4me/tmp
|
|
$sudo_cmd chown -R left4me:left4me /opt/left4me
|
|
|
|
mkdir -p "$repo_tmp"
|
|
tar -xzf "$archive" -C "$repo_tmp"
|
|
|
|
if [ -d /opt/left4me/.venv ]; then
|
|
$sudo_cmd mv /opt/left4me/.venv "$remote_tmp/venv"
|
|
fi
|
|
$sudo_cmd find /opt/left4me -mindepth 1 -maxdepth 1 -exec rm -rf {} +
|
|
$sudo_cmd cp -R "$repo_tmp"/. /opt/left4me/
|
|
if [ -d "$remote_tmp/venv" ]; then
|
|
$sudo_cmd mv "$remote_tmp/venv" /opt/left4me/.venv
|
|
fi
|
|
$sudo_cmd chown -R left4me:left4me /opt/left4me
|
|
|
|
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-web.service /usr/local/lib/systemd/system/left4me-web.service
|
|
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/lib/systemd/system/left4me-server@.service /usr/local/lib/systemd/system/left4me-server@.service
|
|
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-systemctl
|
|
$sudo_cmd cp /opt/left4me/deploy/files/usr/local/libexec/left4me/left4me-journalctl /usr/local/libexec/left4me/left4me-journalctl
|
|
$sudo_cmd chmod 0755 /usr/local/libexec/left4me/left4me-systemctl /usr/local/libexec/left4me/left4me-journalctl
|
|
$sudo_cmd cp /opt/left4me/deploy/files/etc/sudoers.d/left4me /etc/sudoers.d/left4me
|
|
$sudo_cmd chmod 0440 /etc/sudoers.d/left4me
|
|
$sudo_cmd visudo -cf /etc/sudoers.d/left4me
|
|
|
|
$sudo_cmd cp /opt/left4me/deploy/templates/etc/left4me/host.env /etc/left4me/host.env
|
|
$sudo_cmd chmod 0644 /etc/left4me/host.env
|
|
|
|
if [ ! -f /etc/left4me/web.env ]; then
|
|
secret_key=$(python3 -c 'import secrets; print(secrets.token_hex(32))')
|
|
tmp_web_env="$remote_tmp/web.env"
|
|
{
|
|
printf 'DATABASE_URL=sqlite:////var/lib/left4me/left4me.db\n'
|
|
printf 'SECRET_KEY=%s\n' "$secret_key"
|
|
printf 'JOB_WORKER_THREADS=4\n'
|
|
printf 'SESSION_COOKIE_SECURE=false\n'
|
|
} > "$tmp_web_env"
|
|
$sudo_cmd install -m 0640 -o root -g left4me "$tmp_web_env" /etc/left4me/web.env
|
|
fi
|
|
|
|
if [ ! -x /opt/left4me/.venv/bin/python ]; then
|
|
run_as_left4me python3 -m venv /opt/left4me/.venv
|
|
fi
|
|
run_as_left4me /opt/left4me/.venv/bin/python -m pip install --upgrade pip
|
|
run_as_left4me /opt/left4me/.venv/bin/pip install -e /opt/left4me/l4d2host -e /opt/left4me/l4d2web
|
|
|
|
run_as_left4me sh -c "cd /opt/left4me/l4d2web && set -a; . /etc/left4me/host.env; . /etc/left4me/web.env; set +a; env \
|
|
JOB_WORKER_ENABLED=false \
|
|
PYTHONPATH=/opt/left4me \
|
|
/opt/left4me/.venv/bin/alembic -c /opt/left4me/l4d2web/alembic.ini upgrade head"
|
|
|
|
if [ -f "$remote_tmp/admin_username" ] && [ -f "$remote_tmp/admin_password" ]; then
|
|
LEFT4ME_ADMIN_USERNAME=$(cat "$remote_tmp/admin_username")
|
|
LEFT4ME_ADMIN_PASSWORD=$(cat "$remote_tmp/admin_password")
|
|
if ! create_user_output=$(run_left4me_with_env env \
|
|
JOB_WORKER_ENABLED=false \
|
|
LEFT4ME_ADMIN_PASSWORD="$LEFT4ME_ADMIN_PASSWORD" \
|
|
/opt/left4me/.venv/bin/flask --app l4d2web.app:create_app create-user "$LEFT4ME_ADMIN_USERNAME" --admin 2>&1); then
|
|
case "$create_user_output" in
|
|
*'user already exists'*) printf '%s\n' "$create_user_output" ;;
|
|
*) printf '%s\n' "$create_user_output" >&2; exit 1 ;;
|
|
esac
|
|
else
|
|
printf '%s\n' "$create_user_output"
|
|
fi
|
|
fi
|
|
|
|
$sudo_cmd systemctl daemon-reload
|
|
$sudo_cmd systemctl enable --now left4me-web.service
|
|
$sudo_cmd systemctl restart left4me-web.service
|
|
for attempt in 1 2 3 4 5 6 7 8 9 10; do
|
|
if curl -fsS http://127.0.0.1:8000/health; then
|
|
exit 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
$sudo_cmd systemctl status left4me-web.service --no-pager >&2 || true
|
|
$sudo_cmd journalctl -u left4me-web.service -n 80 --no-pager >&2 || true
|
|
exit 1
|
|
REMOTE
|