#!/bin/sh # Praxis — Install the systemd service for Docker-based deployment. # # Adapted from coreci/scripts/install-service.sh. # Coreci installs a Go binary + systemd unit; praxis creates the env # file from lxc.environment vars, installs the systemd unit that runs # `docker compose up` (foreground, Type=simple per RESEARCH.md Q8), # and starts it. The Docker image is built by ExecStartPre. # # This script runs INSIDE the CT (called by firstboot-hook.sh via pct exec). # It must run as root. set -e USER_NAME="praxis" GROUP_NAME="praxis" DATA_DIR="/var/lib/praxis/data" LOG_DIR="/var/log/praxis" ENV_FILE="/etc/praxis/server.env" SERVICE_FILE="/etc/systemd/system/praxis.service" APP_DIR="/opt/praxis" if [ "$(id -u)" -ne 0 ]; then echo "install-service.sh: must run as root" >&2 exit 1 fi # Create the praxis user if it does not exist. if ! id "$USER_NAME" >/dev/null 2>&1; then echo "Creating user $USER_NAME" useradd --system --home "$DATA_DIR" --shell /usr/sbin/nologin "$USER_NAME" fi # Create data, log, and config directories. mkdir -p "$DATA_DIR" "$LOG_DIR" /etc/praxis "$APP_DIR" chown -R "$USER_NAME:$GROUP_NAME" "$DATA_DIR" "$LOG_DIR" chown "root:$GROUP_NAME" /etc/praxis chmod 0750 "$DATA_DIR" "$LOG_DIR" /etc/praxis # Write the env file from the current environment (lxc.environment vars # are available inside the CT's environment). This file is read by # docker-compose.yml via env_file (G-101/G-102 secret injection chain). # G-103 FIX: include ALL env vars the server reads. cat > "$ENV_FILE" </dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq curl fi # Install the systemd unit. cat > "$SERVICE_FILE" <<'UNIT' [Unit] Description=Praxis — voice-first AI apprenticeship platform Documentation=https://git.cloudinit.dev/coreci/praxis After=network-online.target docker.service Wants=network-online.target Requires=docker.service [Service] Type=simple User=praxis Group=praxis WorkingDirectory=/opt/praxis EnvironmentFile=-/etc/praxis/server.env # Build the image first (ExecStartPre), then run in foreground. # Type=simple + foreground `docker compose up` (no -d) so systemd # tracks the process. TimeoutStartSec=600 covers the build (RESEARCH Q8). ExecStartPre=/usr/bin/docker compose build ExecStart=/usr/bin/docker compose up ExecStop=/usr/bin/docker compose down Restart=on-failure RestartSec=5 TimeoutStartSec=600 TimeoutStopSec=60 # NOTE: Do NOT use coreci's hardening directives (ProtectSystem, PrivateDevices, # etc.) — they break Docker's need to access /var/run/docker.sock, cgroups, # and namespaces. Docker-in-LXC requires relaxed sandboxing (RESEARCH Q8). StandardOutput=journal StandardError=journal SyslogIdentifier=praxis [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable praxis.service # Start the service (this triggers ExecStartPre=docker compose build, # which may take 3-5 min on first boot). echo "Starting praxis service (Docker build may take 3-5 min)..." systemctl start praxis.service || { echo "Failed to start praxis; check 'journalctl -u praxis -n 50'" >&2 exit 1 } echo "Praxis service installed and started."