diff --git a/systemd/gunicorn.conf.py b/systemd/gunicorn.conf.py new file mode 100644 index 0000000..3cdca6b --- /dev/null +++ b/systemd/gunicorn.conf.py @@ -0,0 +1,29 @@ +# Gunicorn configuration for FastAPI +import multiprocessing + +# Server socket +bind = "127.0.0.1:8000" +backlog = 2048 + +# Worker processes +# Rule of thumb: (2 * CPU cores) + 1 +workers = multiprocessing.cpu_count() * 2 + 1 +worker_class = "uvicorn.workers.UvicornWorker" +worker_connections = 1000 +timeout = 30 +keepalive = 2 + +# Logging +accesslog = "/var/log/karl/access.log" +errorlog = "/var/log/karl/error.log" +loglevel = "info" +access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s' + +# Process naming +proc_name = "karl" + +# Graceful shutdown +graceful_timeout = 30 + +# Preload app for faster worker spawning +preload_app = True diff --git a/systemd/install.sh b/systemd/install.sh new file mode 100644 index 0000000..07563b3 --- /dev/null +++ b/systemd/install.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# --- Kolory do logowania --- +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +log() { echo -e "${GREEN}[INFO]${NC} $1"; } +err() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; } + +# --- Sprawdzenie uprawnień roota --- +if [[ $EUID -ne 0 ]]; then + err "Ten skrypt wymaga uprawnień roota. Uruchom z sudo." +fi + +# --- 1. Wykrycie menedżera pakietów (apt / dnf) --- +if command -v apt-get &>/dev/null; then + PKG_MGR="apt-get" + PKG_UPDATE="apt-get update -y" + PKG_INSTALL="apt-get install -y" +elif command -v dnf &>/dev/null; then + PKG_MGR="dnf" + PKG_UPDATE="dnf check-update || true" + PKG_INSTALL="dnf install -y" +else + err "Nie znaleziono obsługiwanego menedżera pakietów (apt/dnf)." +fi + +log "Wykryto menedżer pakietów: $PKG_MGR" +$PKG_UPDATE + +# --- 2. Instalacja python3, curl, unzip --- +log "Instalacja python3, curl, unzip..." +$PKG_INSTALL python3 python3-pip curl unzip, git +$PKG_INSTALL python3-venv || true + +# --- 3. Instalacja Dockera (jeśli nie zainstalowany) --- +if command -v docker &>/dev/null; then + log "Docker już zainstalowany — pomijam." +else + log "Instalacja Dockera..." + curl -fsSL https://get.docker.com | bash + systemctl enable --now docker + log "Docker zainstalowany i uruchomiony." +fi + +# --- 4. Utworzenie użytkownika 'karl' --- +if id "karl" &>/dev/null; then + log "Użytkownik 'karl' już istnieje — pomijam." +else + log "Tworzenie użytkownika 'karl'..." + useradd -m -s /bin/bash karl +fi + +# --- 5. Dodanie 'karl' do grupy 'docker' --- +log "Dodawanie użytkownika 'karl' do grupy 'docker'..." +usermod -aG docker karl + +# --- 6. Pobranie i rozpakowanie archiwum --- +APP_URL="${1:?Podaj URL archiwum ZIP jako pierwszy argument}" +APP_DIR="/opt/karl" + +mkdir -p "$APP_DIR" +chown karl: "$APP_DIR" + +# --- 7. Backup i czyszczenie istniejącego katalogu --- +if [[ -d "$APP_DIR" ]] && [[ -n "$(ls -A "$APP_DIR" 2>/dev/null)" ]]; then + log "Katalog $APP_DIR istnieje i nie jest pusty — tworzenie backupu..." + CONFIG_DIR="$APP_DIR/config" + if [[ -d "$CONFIG_DIR" ]]; then + BACKUP_DIR="/home/karl/backups" + TIMESTAMP=$(date +%Y%m%d_%H%M%S) + BACKUP_PATH="$BACKUP_DIR/config_backup_$TIMESTAMP" + mkdir -p "$BACKUP_DIR" + cp -r "$CONFIG_DIR" "$BACKUP_PATH" + chown -R karl:karl "$BACKUP_DIR" + log "Backup utworzony: $BACKUP_PATH" + else + log "Katalog config nie istnieje — pomijam backup." + fi + log "Usuwanie katalogu $APP_DIR..." + rm -rf "$APP_DIR" + log "Katalog $APP_DIR usunięty." +fi + +# --- 8. Instalacja / aktualizacja --- +log "Pobieranie archiwum z: $APP_URL" +mkdir -p "$APP_DIR" +TMPZIP=$(mktemp /tmp/karl-app-XXXXXX.zip) +curl -fSL -o "$TMPZIP" "$APP_URL" +unzip -o "$TMPZIP" -d "$APP_DIR" +rm -f "$TMPZIP" +chown -R karl:karl "$APP_DIR" +log "Aplikacja rozpakowana do $APP_DIR" + +# --- 9. Instalacja uv i synchronizacja zależności (uv sync) --- +sudo -u karl bash -c "cd /opt/karl/app && python3 -m venv .venv && source .venv/bin/activate && pip install uv && uv sync" + +# --- 10. Kopiowanie pliku usługi systemd --- +SERVICE_SRC="$APP_DIR/systemd/karl.service" +SERVICE_DST="/etc/systemd/system/karl.service" + +if [[ ! -f "$SERVICE_SRC" ]]; then + err "Nie znaleziono pliku $SERVICE_SRC" +fi + +log "Kopiowanie $SERVICE_SRC -> $SERVICE_DST" +cp "$SERVICE_SRC" "$SERVICE_DST" +chmod 644 "$SERVICE_DST" + +# --- 11. Przeładowanie systemctl i uruchomienie usługi --- +log "Przeładowanie systemd i włączanie usługi karl..." +systemctl daemon-reload +systemctl enable karl.service +systemctl start karl.service + +log "Instalacja zakończona pomyślnie! ✅" diff --git a/systemd/karl.service b/systemd/karl.service new file mode 100644 index 0000000..69f09cc --- /dev/null +++ b/systemd/karl.service @@ -0,0 +1,32 @@ +[Unit] +Description=Karl +After=network.target +Wants=network.target +StartLimitBurst=3 +StartLimitIntervalSec=60 + +[Service] +User=karl +WorkingDirectory=/opt/karl +ExecStart=/opt/karl/.venv/bin/python3 /opt/karl/src/karl/__init__.py + +# Graceful reload (sends SIGHUP) +ExecReload=/bin/kill -s HUP $MAINPID + +# Restart on failure +Restart=on-failure +RestartSec=5s + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ReadWritePaths=/var/log/karl /opt/karl/repository + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=karl + +[Install] +WantedBy=multi-user.target