mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-06-25 16:19:19 +02:00
- Fix 413 Request Entity Too Large: bump client_max_body_size to 256M, PHP post_max_size/upload_max_filesize to 256M, fastcgi timeouts to 300s - Fix missing v_smtp_active view: add IF NOT EXISTS to all CREATE VIEW statements in schema.sql for idempotent migrates - Fix bars.svg 404: create animated SVG spinner in app/public/assets/img/ - Fix nginx rate limiting: increase admin zone from 60r/m (1 r/s) to 300r/m (5 r/s) with burst=30 to handle ~11 concurrent HTMX fragment GETs on contenus.php page load - Add deploy-nginx recipe to justfile for uploading nginx config to server - Database readonly issue mitigated by existing --chown + deploy-server.sh permissions fix - Add comprehensive PHP/JS debugging logs for settings checkboxes: per-field raw POST values in error_log, console.log on htmx:beforeSend, htmx:sendError, htmx:afterRequest, toast lifecycle - Fix toast auto-remove script: use getElementById with unique ID instead of querySelector which could remove wrong toast on rapid clicks
325 lines
12 KiB
Makefile
325 lines
12 KiB
Makefile
default:
|
|
@just --list
|
|
|
|
# XAMXAM Justfile
|
|
|
|
# ============================================================================
|
|
# Development
|
|
# ============================================================================
|
|
|
|
[group('dev')]
|
|
setup:
|
|
@bash scripts/setup-dev.sh
|
|
|
|
[group('dev')]
|
|
serve: migrate
|
|
@xdg-open http://127.0.0.1:8000 &
|
|
@xdg-open http://127.0.0.1:8000/admin/ &
|
|
@php \
|
|
-d upload_max_filesize=512M \
|
|
-d post_max_size=520M \
|
|
-d memory_limit=256M \
|
|
-d max_execution_time=300 \
|
|
-d max_input_time=300 \
|
|
-S 127.0.0.1:8000 -t app/public/ app/router.php 2>&1 \
|
|
| stdbuf -oL grep -Ev '(Accepted|Closing|live-reload\.php|assets/|favicon)'
|
|
|
|
[group('dev')]
|
|
stop:
|
|
@pkill -f "php -S 127.0.0.1:8000" 2>/dev/null && echo "stopped" || echo "no server running"
|
|
|
|
[group('dev')]
|
|
logs:
|
|
@tail -n 20 error.log 2>/dev/null || echo "no error log"
|
|
|
|
# ============================================================================
|
|
# Deploy
|
|
# ============================================================================
|
|
|
|
[group('deploy')]
|
|
deploy:
|
|
# Main deploy (code + assets) then run any pending DB migrations
|
|
rsync -vur --progress --delete \
|
|
--chown="www-data:xamxam" \
|
|
--exclude 'vendor' \
|
|
--exclude 'tests' \
|
|
--exclude '*.md' \
|
|
--exclude '.git*' \
|
|
--exclude '.jj' \
|
|
--exclude '.claude' \
|
|
--exclude '.pi' \
|
|
--exclude '.DS_Store' \
|
|
--exclude '.env' \
|
|
--exclude 'storage/xamxam.db' \
|
|
--exclude 'storage/theses' \
|
|
--exclude 'storage/covers' \
|
|
--exclude 'storage/backup_*' \
|
|
--exclude 'storage/cache/*' \
|
|
--exclude 'storage/maintenance.flag' \
|
|
--exclude 'storage/fixtures' \
|
|
--exclude 'storage/docs' \
|
|
--exclude 'var/' \
|
|
app/ xamxam:/var/www/xamxam/
|
|
# Upload deploy-server.sh for post-deploy permission fix
|
|
rsync -v scripts/deploy-server.sh xamxam:/tmp/deploy-server.sh
|
|
ssh xamxam "sudo bash /tmp/deploy-server.sh"
|
|
ssh xamxam "rm -f /tmp/deploy-server.sh"
|
|
ssh xamxam "mkdir -p /var/www/xamxam/var/{cache,logs,tmp}"
|
|
ssh xamxam "cd /var/www/xamxam && php -r 'if (!file_exists(\"/var/www/xamxam/storage/xamxam.db\")) { \$db = new PDO(\"sqlite:/var/www/xamxam/storage/xamxam.db\"); \$db->exec(file_get_contents(\"/var/www/xamxam/storage/schema.sql\")); echo \"Database created from schema.\\n\"; } else { echo \"Database already exists.\\n\"; }'"
|
|
# Run pending migrations
|
|
ssh xamxam "cd /var/www/xamxam && bash scripts/migrate.sh"
|
|
# Deploy nginx configuration
|
|
@just deploy-nginx
|
|
# Sync .env separately (excluded above to avoid accidental overwrite on subsequent deploys)
|
|
@just deploy-env
|
|
@just deploy-verify-permissions
|
|
|
|
[group('deploy')]
|
|
deploy-env:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# Upload app/.env only if it exists locally; never overwrites a remote .env that already has APP_KEY.
|
|
if [ ! -f app/.env ]; then
|
|
echo "WARNING: app/.env not found locally — skipping."
|
|
exit 0
|
|
fi
|
|
if ssh xamxam '[ -f /var/www/xamxam/.env ]'; then
|
|
echo "Remote .env already exists — skipping to avoid overwriting key."
|
|
echo "Run 'just reencrypt-password' if you rotated APP_KEY."
|
|
else
|
|
rsync -v --progress app/.env xamxam:/var/www/xamxam/.env
|
|
ssh xamxam "chmod 640 /var/www/xamxam/.env && chown www-data:xamxam /var/www/xamxam/.env"
|
|
echo ".env uploaded."
|
|
fi
|
|
|
|
[group('deploy')]
|
|
reencrypt-password new_key_b64="":
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
# Re-encrypt the SMTP password in the remote DB after rotating APP_KEY.
|
|
# Usage:
|
|
# 1. Generate a new key: php -r "echo base64_encode(random_bytes(32));"
|
|
# 2. Run: just reencrypt-password <new_base64_key>
|
|
# 3. Update app/.env locally with the new key, then run: just deploy-env
|
|
if [ -z "{{new_key_b64}}" ]; then
|
|
echo "Usage: just reencrypt-password <new_base64_key>"
|
|
echo "Generate a key: php -r \"echo base64_encode(random_bytes(32));\""
|
|
exit 1
|
|
fi
|
|
# Run the re-encryption script on the server using the current key (from remote .env)
|
|
# and the supplied new key.
|
|
ssh xamxam "php /var/www/xamxam/scripts/reencrypt-smtp-password.php '{{new_key_b64}}' /var/www/xamxam/storage/xamxam.db"
|
|
|
|
[group('deploy')]
|
|
deploy-db:
|
|
@ssh xamxam '[ ! -f /var/www/xamxam/storage/xamxam.db ]' || (echo "ERROR: remote database already exists. Remove it manually if you intend to overwrite." && exit 1)
|
|
rsync -v --progress app/storage/xamxam.db xamxam:/var/www/xamxam/storage/xamxam.db
|
|
ssh xamxam "chown www-data:xamxam /var/www/xamxam/storage/xamxam.db && chmod 660 /var/www/xamxam/storage/xamxam.db"
|
|
|
|
[group('deploy')]
|
|
deploy-verify-permissions:
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
APP_DIR="/var/www/xamxam"
|
|
WEB_USER="www-data"
|
|
APP_GROUP="xamxam"
|
|
ERRORS=0
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
|
|
err() { printf "${RED}✗${NC} %s\n" "$*" >&2; ERRORS=$((ERRORS + 1)); }
|
|
warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }
|
|
|
|
printf "🔍 Verifying permissions on %s…\n\n" "$APP_DIR"
|
|
|
|
# ── Ownership ──────────────────────────────────────────────────────────────────
|
|
echo "── Ownership ───────────────────────────────────"
|
|
while IFS= read -r line; do
|
|
owner=$(echo "$line" | awk '{print $3}')
|
|
group=$(echo "$line" | awk '{print $4}')
|
|
path=$(echo "$line" | awk '{print $NF}')
|
|
if [ "$owner" != "$WEB_USER" ] || [ "$group" != "$APP_GROUP" ]; then
|
|
err "$path → $owner:$group (expected $WEB_USER:$APP_GROUP)"
|
|
else
|
|
ok "$path → $owner:$group"
|
|
fi
|
|
done < <(ssh xamxam "stat -c '%U %G %n' $APP_DIR $APP_DIR/app $APP_DIR/storage $APP_DIR/var 2>/dev/null")
|
|
|
|
# ── Key directories: 2775 ─────────────────────────────────────────────────────
|
|
echo "── Directory permissions (expected 2775) ───────"
|
|
while IFS= read -r line; do
|
|
perms=$(echo "$line" | awk '{print $1}')
|
|
path=$(echo "$line" | awk '{print $NF}')
|
|
if [ "$perms" != "drwxrwsr-x" ]; then
|
|
err "$path → $perms (expected drwxrwsr-x / 2775)"
|
|
else
|
|
ok "$path → $perms"
|
|
fi
|
|
done < <(ssh xamxam "find $APP_DIR -maxdepth 2 -type d -exec stat -c '%A %n' {} \\; 2>/dev/null | sort")
|
|
|
|
# ── Key files: 664 ────────────────────────────────────────────────────────────
|
|
echo "── File permissions (expected 664 / 660) ───────"
|
|
# Spot-check a few critical files
|
|
while IFS= read -r path; do
|
|
perms=$(ssh xamxam "stat -c '%a %U %G' '$path' 2>/dev/null" || echo "MISSING")
|
|
if [ "$perms" = "MISSING" ]; then
|
|
err "$path → FILE MISSING"
|
|
else
|
|
perm_num=$(echo "$perms" | awk '{print $1}')
|
|
owner=$(echo "$perms" | awk '{print $2}')
|
|
group=$(echo "$perms" | awk '{print $3}')
|
|
case "$path" in
|
|
*/storage/xamxam.db|*/storage/*.db)
|
|
expected_perm="660" ;;
|
|
*)
|
|
expected_perm="664" ;;
|
|
esac
|
|
if [ "$perm_num" != "$expected_perm" ]; then
|
|
err "$path → $perm_num ($owner:$group), expected $expected_perm $WEB_USER:$APP_GROUP"
|
|
elif [ "$owner" != "$WEB_USER" ]; then
|
|
err "$path → owner $owner, expected $WEB_USER (perm $perm_num OK)"
|
|
else
|
|
ok "$path → $perm_num $owner:$group"
|
|
fi
|
|
fi
|
|
done < <(printf '%s\n' \
|
|
"$APP_DIR/.env" \
|
|
"$APP_DIR/app/router.php" \
|
|
"$APP_DIR/storage/xamxam.db")
|
|
|
|
# ── var/ subdirectories must be writable ──────────────────────────────────────
|
|
echo "── var/ writability ────────────────────────────"
|
|
for subdir in cache logs tmp; do
|
|
if ssh xamxam "[ -w /var/www/xamxam/var/$subdir ]"; then
|
|
ok "var/$subdir → writable"
|
|
else
|
|
err "var/$subdir → NOT WRITABLE"
|
|
fi
|
|
done
|
|
|
|
# ── storage/cache/rate_limit writable ─────────────────────────────────────────
|
|
if ssh xamxam "[ -w /var/www/xamxam/storage/cache/rate_limit ]"; then
|
|
ok "storage/cache/rate_limit → writable"
|
|
else
|
|
err "storage/cache/rate_limit → NOT WRITABLE"
|
|
fi
|
|
|
|
# ── .env must be 640 ──────────────────────────────────────────────────────────
|
|
env_perm=$(ssh xamxam "stat -c '%a' /var/www/xamxam/.env 2>/dev/null" || echo "")
|
|
if [ "$env_perm" = "640" ]; then
|
|
ok ".env → 640"
|
|
elif [ -z "$env_perm" ]; then
|
|
warn ".env → MISSING"
|
|
else
|
|
err ".env → $env_perm (expected 640)"
|
|
fi
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
if [ "$ERRORS" -eq 0 ]; then
|
|
printf "${GREEN}✅ All permissions OK${NC}\n"
|
|
else
|
|
printf "${RED}❌ %d permission error(s) found${NC}\n" "$ERRORS"
|
|
printf "${YELLOW}Fix with: sudo bash /tmp/deploy-server.sh${NC}\n"
|
|
exit 1
|
|
fi
|
|
|
|
[group('deploy')]
|
|
deploy-nginx:
|
|
# Upload nginx config to the server, test it, and reload.
|
|
# Uses the scripts/deploy-server.sh helper that handles the nginx
|
|
# config installation and reload (steps 2-4).
|
|
@echo "📋 Deploying nginx configuration…"
|
|
rsync -v nginx/xamxam.conf xamxam:/tmp/xamxam.conf
|
|
rsync -v scripts/deploy-server.sh xamxam:/tmp/deploy-server.sh
|
|
ssh xamxam "sudo DEPLOY_USER=\$USER bash /tmp/deploy-server.sh"
|
|
ssh xamxam "rm -f /tmp/deploy-server.sh /tmp/xamxam.conf"
|
|
|
|
[group('deploy')]
|
|
deploy-script script_name:
|
|
# Generic script deployer (e.g., just deploy-script setup-server)
|
|
rsync -v scripts/{{script_name}}.sh xamxam:/tmp/{{script_name}}.sh
|
|
@echo ""
|
|
@echo "Script uploaded. SSH into the server and run:"
|
|
@echo ""
|
|
@echo " sudo DEPLOY_USER=\$USER bash /tmp/{{script_name}}.sh"
|
|
@echo ""
|
|
|
|
# ============================================================================
|
|
# Testing
|
|
# ============================================================================
|
|
|
|
[group('test')]
|
|
test:
|
|
# Run all tests. To run a subset, use:
|
|
# php app/tests/Unit/DatabaseTest.php
|
|
# php app/tests/Integration/SearchTest.php
|
|
@php app/tests/run-tests.php
|
|
|
|
[group('test')]
|
|
lint-biome:
|
|
@biome lint app/public/assets/js/
|
|
|
|
[group('test')]
|
|
phpstan:
|
|
@vendor/bin/phpstan analyse --memory-limit=512M
|
|
|
|
[group('test')]
|
|
cs-check:
|
|
@vendor/bin/php-cs-fixer check --no-interaction
|
|
|
|
[group('test')]
|
|
cs-fix:
|
|
@vendor/bin/php-cs-fixer fix --no-interaction
|
|
|
|
[group('test')]
|
|
syntax:
|
|
@find app/ -name '*.php' -exec php -l {} \; 2>/dev/null | grep -v 'No syntax errors' || true
|
|
@echo '✅ Syntax OK'
|
|
|
|
# ============================================================================
|
|
# Database
|
|
# ============================================================================
|
|
|
|
[group('database')]
|
|
migrate:
|
|
@echo "Running migrations…"
|
|
@bash scripts/migrate.sh
|
|
|
|
[group('database')]
|
|
init-db:
|
|
@sqlite3 app/storage/xamxam.db < app/storage/schema.sql
|
|
@sqlite3 app/storage/xamxam.db "SELECT COUNT(*) || ' tables' FROM sqlite_master WHERE type='table';"
|
|
|
|
[group('database')]
|
|
reset-db:
|
|
@rm -f app/storage/xamxam.db
|
|
@just init-db
|
|
|
|
[group('database')]
|
|
query:
|
|
@sqlite3 app/storage/xamxam.db
|
|
|
|
[group('database')]
|
|
backup:
|
|
@sqlite3 app/storage/xamxam.db .dump > app/storage/backup_$(date +%Y%m%d_%H%M%S).sql
|
|
|
|
[group('database')]
|
|
backup-snapshot:
|
|
# Hot backup using SQLite's .backup API (WAL-safe), then gzip.
|
|
@DB_PATH=app/storage/xamxam.db BACKUP_DIR=app/storage/backups RETENTION_DAYS=30 bash scripts/backup-sqlite.sh
|
|
|
|
# ============================================================================
|
|
# Utils
|
|
# ============================================================================
|
|
|
|
[group('utils')]
|
|
clean:
|
|
@rm -f app/error.log
|
|
@rm -rf app/storage/cache/rate_limit/*
|
|
@rm -f /tmp/xamxam-*.log /tmp/xamxam-*.pid
|