mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
fix(deploy): restore www-data ownership after deploy-code to prevent HTTP 500
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# Scoped passwordless sudo for the deploy permission-restore step.
|
||||||
|
#
|
||||||
|
# `just deploy-permissions` (and thereby `just deploy-code`) resyncs the app
|
||||||
|
# tree as the deploy SSH user, then runs `fix-permissions.sh` as root to
|
||||||
|
# restore www-data:xamxam ownership. Using an interactive remote pty for that
|
||||||
|
# sudo is fragile: `ssh -t` silently drops the pseudo-terminal when the local
|
||||||
|
# process stdin is not a TTY, so sudo's password prompt prints but cannot
|
||||||
|
# receive keystrokes. Because the script is fixed-path, takes no arguments and
|
||||||
|
# only touches /var/www/xamxam, it is safe to whitelist with NOPASSWD.
|
||||||
|
#
|
||||||
|
# Install via `just deploy-sudoers` (must be run interactively once as a
|
||||||
|
# password-lessed sudoer, or with root).
|
||||||
|
theophile ALL=(root) NOPASSWD: /bin/bash /tmp/fix-permissions.sh
|
||||||
+16
-8
@@ -104,6 +104,7 @@ If you ever rotate `APP_KEY`, re-encrypt the SMTP password with
|
|||||||
| `just deploy-migrate` | Run pending DB migrations on the server |
|
| `just deploy-migrate` | Run pending DB migrations on the server |
|
||||||
| `just deploy-env` | Upload `app/.env` (only if the remote `.env` is absent — never overwrites a key) |
|
| `just deploy-env` | Upload `app/.env` (only if the remote `.env` is absent — never overwrites a key) |
|
||||||
| `just deploy-nginx` | Upload + apply + reload nginx config |
|
| `just deploy-nginx` | Upload + apply + reload nginx config |
|
||||||
|
| `just deploy-permissions` | Restore `www-data:xamxam` ownership + 2775/664 perms (via `scripts/fix-permissions.sh`, needs sudo) |
|
||||||
| `just deploy-db` | Push local `xamxam.db` → remote (**refuses** if a remote DB already exists) |
|
| `just deploy-db` | Push local `xamxam.db` → remote (**refuses** if a remote DB already exists) |
|
||||||
| `just deploy-verify-permissions` | Check ownership / permissions on the server |
|
| `just deploy-verify-permissions` | Check ownership / permissions on the server |
|
||||||
|
|
||||||
@@ -182,14 +183,21 @@ Ownership and permissions are applied by `scripts/deploy-server.sh` (run via
|
|||||||
- `app/.env`: **640**
|
- `app/.env`: **640**
|
||||||
|
|
||||||
`just deploy-code` only rsyncs code (as the SSH/deploy user); it does **not**
|
`just deploy-code` only rsyncs code (as the SSH/deploy user); it does **not**
|
||||||
set ownership. Ownership is normalised afterwards by `deploy-server.sh`
|
set ownership itself. It therefore finishes by running `just deploy-permissions`
|
||||||
(`chown -R www-data:xamxam /var/www/xamxam`, setgid dirs, locked DBs). On a
|
(`scripts/fix-permissions.sh` via sudo) so a standalone `deploy-code` can never
|
||||||
`xamxam` group-writable tree the rsync succeeds, then `deploy-nginx` (within
|
leave the site broken. Ownership is normalised by `deploy-server.sh`
|
||||||
`just deploy`) fixes ownership/perms. If `deploy-code` reports `Permission
|
(`chown -R www-data:xamxam /var/www/xamxam`, setgid dirs, locked DBs) too, via
|
||||||
denied` on `storage/` (e.g. a fresh box where the dirs are not yet
|
`just deploy-nginx`.
|
||||||
`www-data:xamxam` group-writable), re-apply `scripts/setup-server.sh` /
|
|
||||||
`deploy-server.sh` first, or add `--chown=www-data:xamxam` to the rsync so it
|
> **Why this matters (HTTP 500):** php-fpm runs as `www-data`. After a plain
|
||||||
normalises ownership while transferring.
|
> `rsync -az` (run as the deploy user, no `--chown`), synced files/dirs are
|
||||||
|
> owned by the deploy user, and top-level dirs such as `storage/` lose the
|
||||||
|
> `www-data` group-write bit. The app opens the SQLite DB in **WAL mode**, so
|
||||||
|
> www-data must be able to create `xamxam.db-wal` / `xamxam.db-shm` **in**
|
||||||
|
> `storage/`; without write access there the request fatals → **500**. If this
|
||||||
|
> ever recurs (e.g. a manual rsync that skipped `deploy-permissions`), run
|
||||||
|
> `just deploy-permissions` (`sudo chown -R www-data:xamxam` + 2775/664) to
|
||||||
|
> restore it.
|
||||||
|
|
||||||
The nginx/`deploy-server.sh` step (`just deploy-nginx`, or
|
The nginx/`deploy-server.sh` step (`just deploy-nginx`, or
|
||||||
`sudo DEPLOY_USER=$USER bash /tmp/deploy-server.sh` via `just deploy-script`)
|
`sudo DEPLOY_USER=$USER bash /tmp/deploy-server.sh` via `just deploy-script`)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ build-check:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy: build deploy-code deploy-nginx deploy-deps deploy-migrate
|
deploy: build deploy-code deploy-nginx deploy-deps deploy-migrate deploy-sudoers deploy-permissions
|
||||||
@just deploy-env
|
@just deploy-env
|
||||||
@just deploy-verify-permissions
|
@just deploy-verify-permissions
|
||||||
@echo ""
|
@echo ""
|
||||||
@@ -114,9 +114,48 @@ deploy: build deploy-code deploy-nginx deploy-deps deploy-migrate
|
|||||||
deploy-code:
|
deploy-code:
|
||||||
# Sync application code only (no Composer deps, no migrations, no nginx config).
|
# Sync application code only (no Composer deps, no migrations, no nginx config).
|
||||||
# nginx + server-side setup are handled by `deploy-nginx` (via deploy).
|
# nginx + server-side setup are handled by `deploy-nginx` (via deploy).
|
||||||
rsync -az --info=progress2 --delete \
|
# No -p/-t/-o/-g: the destination tree is owned by www-data:xamxam (setgid),
|
||||||
|
# so this SSH user can read/write it but cannot chmod/chown/settime files it
|
||||||
|
# doesn't own — preserving perms/times would fail every file with
|
||||||
|
# "Operation not permitted" and exit rsync 23. Ownership/perms are restored
|
||||||
|
# by `deploy-permissions` right after. Times are only used as a transfer
|
||||||
|
# heuristic here; --size-only keeps unchanged files from being re-uploaded
|
||||||
|
# since their remote mtimes are no longer preserved.
|
||||||
|
rsync -rlDz --size-only --info=progress2 --delete \
|
||||||
--exclude-from=.rsync-exclude \
|
--exclude-from=.rsync-exclude \
|
||||||
app/ xamxam:/var/www/xamxam/
|
app/ xamxam:/var/www/xamxam/
|
||||||
|
# Plain rsync (as this user) leaves newly-synced files owned by the caller,
|
||||||
|
# not www-data:xamxam — php-fpm then can't create SQLite journals in
|
||||||
|
# storage/ → HTTP 500. Restore ownership right after, so `just deploy-code`
|
||||||
|
# alone can never break the live site.
|
||||||
|
@just deploy-permissions
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-permissions:
|
||||||
|
# Fix app-tree ownership/permissions on the host so www-data (php-fpm) can
|
||||||
|
# read code and write storage (/sqlite wal+shm), cache/, tmp/, var/. Needs
|
||||||
|
# sudo. Run after any deploy-code resync and as a dep of `deploy`.
|
||||||
|
#
|
||||||
|
# sudo here is NOPASSWD-scoped to /tmp/fix-permissions.sh via the
|
||||||
|
# deploy/xamxam-fix-permissions.sudoers drop-in (installed once by `just
|
||||||
|
# deploy-sudoers`). That avoids relying on an interactive remote pty, which
|
||||||
|
# is fragile: `ssh -t` silently drops the pty when local stdin is not a TTY,
|
||||||
|
# so sudo's prompt prints but accepts no input. If you have not installed
|
||||||
|
# the drop-in yet, this step will prompt for your password interactively.
|
||||||
|
@echo "🔒 Fixing www-data ownership/permissions…"
|
||||||
|
rsync -v scripts/fix-permissions.sh xamxam:/tmp/fix-permissions.sh
|
||||||
|
ssh -t xamxam "sudo bash /tmp/fix-permissions.sh && rm -f /tmp/fix-permissions.sh"
|
||||||
|
|
||||||
|
[group('deploy')]
|
||||||
|
deploy-sudoers:
|
||||||
|
# One-time install: scoped NOPASSWD sudo so `deploy-permissions` never needs
|
||||||
|
# an interactive remote pty (see deploy/xamxam-fix-permissions.sudoers for
|
||||||
|
# rationale). Privileged write to /etc/sudoers.d requires an interactive sudo
|
||||||
|
# password, so run this from a real terminal the first time.
|
||||||
|
@echo "🔒 Installing NOPASSWD sudo rule for fix-permissions.sh…"
|
||||||
|
rsync -v deploy/xamxam-fix-permissions.sudoers xamxam:/tmp/xamxam-fix-permissions.sudoers
|
||||||
|
ssh -t xamxam "sudo install -o root -g root -m 0440 /tmp/xamxam-fix-permissions.sudoers /etc/sudoers.d/xamxam-fix-permissions && sudo visudo -c -f /etc/sudoers.d/xamxam-fix-permissions && rm -f /tmp/xamxam-fix-permissions.sudoers"
|
||||||
|
@echo "✅ NOPASSWD rule installed. deploy-permissions will run without a password prompt."
|
||||||
|
|
||||||
[group('deploy')]
|
[group('deploy')]
|
||||||
deploy-deps:
|
deploy-deps:
|
||||||
|
|||||||
Executable
+66
@@ -0,0 +1,66 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Fix XAMXAM file ownership + permissions on the production host.
|
||||||
|
#
|
||||||
|
# php-fpm runs as www-data, so the whole app tree must be group-owned by
|
||||||
|
# www-data:xamxam with setgid dirs (2775) and 664 files — otherwise www-data
|
||||||
|
# cannot create the SQLite WAL/journal side-cars in storage/ (→ HTTP 500) nor
|
||||||
|
# write cache/tmp/log dirs.
|
||||||
|
#
|
||||||
|
# This is the ownership half of scripts/deploy-server.sh, factored out so
|
||||||
|
# `just deploy-code` can restore correct ownership after an rsync resync
|
||||||
|
# (plain `rsync -az` as a non-root user preserves the calling user's owner,
|
||||||
|
# not www-data:xamxam).
|
||||||
|
#
|
||||||
|
# Usage: sudo bash /tmp/fix-permissions.sh
|
||||||
|
#
|
||||||
|
# Wired into the justfile as `deploy-permissions` (run at the end of
|
||||||
|
# `deploy-code`, and as a dependency of `deploy`).
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||||
|
ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
|
||||||
|
ok "Fixing permissions"
|
||||||
|
|
||||||
|
[ "$EUID" -eq 0 ] || { printf "${RED}✗${NC} Run as root (sudo)\n" >&2; exit 1; }
|
||||||
|
|
||||||
|
APP_DIR="/var/www/xamxam"
|
||||||
|
|
||||||
|
chown -R www-data:xamxam "$APP_DIR/"
|
||||||
|
ok "Ownership: www-data:xamxam"
|
||||||
|
|
||||||
|
find "$APP_DIR" -type d -exec chmod 2775 {} \;
|
||||||
|
ok "Directories: 2775 (setgid)"
|
||||||
|
|
||||||
|
find "$APP_DIR" -type f -exec chmod 664 {} \;
|
||||||
|
ok "Files: 664"
|
||||||
|
|
||||||
|
if [ -d "$APP_DIR/storage" ]; then
|
||||||
|
chmod 2775 "$APP_DIR/storage"
|
||||||
|
# SQLite + WAL/SHM side-cars must be writable by www-data
|
||||||
|
find "$APP_DIR/storage" -name "*.db" -exec chmod 660 {} \;
|
||||||
|
find "$APP_DIR/storage" -name "*.db-wal" -exec chmod 660 {} \;
|
||||||
|
find "$APP_DIR/storage" -name "*.db-shm" -exec chmod 660 {} \;
|
||||||
|
ok "Storage: 2775, databases (+WAL/SHM): 660"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .env config is a secret — extra-restrictive regardless of the 664 sweep
|
||||||
|
if [ -f "$APP_DIR/.env" ]; then
|
||||||
|
chmod 640 "$APP_DIR/.env"
|
||||||
|
ok ".env: 640"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# App var/ dirs (cache/logs/tmp) must stay writable by php-fpm
|
||||||
|
mkdir -p "$APP_DIR/var/{cache,logs,tmp}"
|
||||||
|
chown -R www-data:xamxam "$APP_DIR/var"
|
||||||
|
chmod -R 2775 "$APP_DIR/var"
|
||||||
|
ok "var/ dirs: www-data:xamxam (2775)"
|
||||||
|
|
||||||
|
# Cache + PHP upload temp (create so first request doesn't race)
|
||||||
|
mkdir -p "$APP_DIR/storage/cache/rate_limit"
|
||||||
|
chown -R www-data:xamxam "$APP_DIR/storage/cache"
|
||||||
|
chmod -R 2775 "$APP_DIR/storage/cache"
|
||||||
|
mkdir -p "$APP_DIR/storage/tmp/php-uploads"
|
||||||
|
chown www-data:xamxam "$APP_DIR/storage/tmp/php-uploads"
|
||||||
|
chmod 2775 "$APP_DIR/storage/tmp/php-uploads"
|
||||||
|
ok "Cache + upload tmp dirs ready"
|
||||||
Reference in New Issue
Block a user