Add an env-driven `user:`/`group_add` to the filestash service plus tooling/filestash-set-run-user.sh. Running the container as a real host account (oxps: oier=1002) makes every file it writes into the share owned by that account, so scp/ssh and the web UI share one owner — no chown dance. Defaults FILES_UID/FILES_GID=1000, leaving cochi (image's own uid) untouched. group_add 1000 keeps the 0730 image binary + 0770 /app/data usable under any uid; the helper also chowns the filestash_state volume (1000:1000) so a non-1000 uid doesn't crash-loop unable to write its own sqlite/config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
82 lines
4.0 KiB
Bash
Executable File
82 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Recreate the filestash container running as a chosen uid/gid so that every
|
|
# file it writes into the share is owned by a host account (e.g. oier).
|
|
#
|
|
# Reads FILES_UID / FILES_GID from .env, chowns the filestash_state volume and
|
|
# the FILES_DATA_DIR share to that uid, then force-recreates the container.
|
|
# Run it FROM THE REPO ROOT on the target host (needs sudo + docker).
|
|
#
|
|
# Idempotent and cautious: halts on any ambiguity rather than guessing.
|
|
# Background + rationale: plan/20-files.md ("Share dir must be owned by uid …").
|
|
set -euo pipefail
|
|
|
|
die() { echo "!! $*" >&2; exit 1; }
|
|
info() { echo ">> $*"; }
|
|
|
|
# --- preconditions -----------------------------------------------------------
|
|
[ -f docker-compose.yml ] || die "run this from the repo root (no docker-compose.yml here)"
|
|
[ -f .env ] || die ".env not found — this is a host-local file; run on the target host"
|
|
|
|
# The compose file must carry the env-driven `user:` directive. If it doesn't,
|
|
# this checkout predates the change — pull before running so the recreate below
|
|
# actually applies the uid.
|
|
grep -q 'user: "${FILES_UID' docker-compose.yml \
|
|
|| die "docker-compose.yml has no FILES_UID user directive — 'git pull' first"
|
|
|
|
# --- resolve target uid/gid from .env ---------------------------------------
|
|
FILES_UID="$(grep -E '^FILES_UID=' .env | tail -1 | cut -d= -f2- | tr -d '\047"' )"
|
|
FILES_GID="$(grep -E '^FILES_GID=' .env | tail -1 | cut -d= -f2- | tr -d '\047"' )"
|
|
[ -n "${FILES_UID:-}" ] || die "FILES_UID unset in .env — add e.g. 'FILES_UID=1002' (oier)"
|
|
[ -n "${FILES_GID:-}" ] || die "FILES_GID unset in .env — add e.g. 'FILES_GID=1002' (oier)"
|
|
case "$FILES_UID$FILES_GID" in *[!0-9]*) die "FILES_UID/FILES_GID must be numeric (got $FILES_UID:$FILES_GID)";; esac
|
|
info "target run user = ${FILES_UID}:${FILES_GID}"
|
|
|
|
# --- resolve the share dir + the state volume host path ----------------------
|
|
FILES_DATA_DIR="$(grep -E '^FILES_DATA_DIR=' .env | tail -1 | cut -d= -f2- | tr -d '\047"' )"
|
|
[ -n "${FILES_DATA_DIR:-}" ] || die "FILES_DATA_DIR unset in .env"
|
|
[ -d "$FILES_DATA_DIR" ] || die "share dir does not exist: $FILES_DATA_DIR"
|
|
|
|
PROJECT="$(basename "$PWD")" # compose project name = dir name
|
|
VOL="${PROJECT}_filestash_state"
|
|
docker volume inspect "$VOL" >/dev/null 2>&1 || die "docker volume '$VOL' not found"
|
|
STATE_DIR="$(docker volume inspect "$VOL" --format '{{ .Mountpoint }}')"
|
|
[ -n "$STATE_DIR" ] && [ -d "$STATE_DIR" ] || die "could not resolve state volume mountpoint"
|
|
|
|
info "share dir = $FILES_DATA_DIR"
|
|
info "state dir = $STATE_DIR (volume $VOL)"
|
|
|
|
# --- confirm (destructive-ish: chown -R) -------------------------------------
|
|
echo
|
|
echo "About to:"
|
|
echo " sudo chown -R ${FILES_UID}:${FILES_GID} $STATE_DIR"
|
|
echo " sudo chown -R ${FILES_UID}:${FILES_GID} $FILES_DATA_DIR"
|
|
echo " docker compose up -d --force-recreate filestash (brief downtime)"
|
|
read -r -p "Proceed? [y/N] " ans
|
|
[ "$ans" = "y" ] || [ "$ans" = "Y" ] || die "aborted by user"
|
|
|
|
# --- apply -------------------------------------------------------------------
|
|
info "stopping filestash"
|
|
docker compose stop filestash
|
|
|
|
info "chowning state volume -> ${FILES_UID}:${FILES_GID}"
|
|
sudo chown -R "${FILES_UID}:${FILES_GID}" "$STATE_DIR"
|
|
|
|
info "chowning share -> ${FILES_UID}:${FILES_GID}"
|
|
sudo chown -R "${FILES_UID}:${FILES_GID}" "$FILES_DATA_DIR"
|
|
|
|
info "recreating filestash as ${FILES_UID}:${FILES_GID}"
|
|
docker compose up -d --force-recreate filestash
|
|
|
|
# --- verify ------------------------------------------------------------------
|
|
sleep 3
|
|
running="$(docker inspect -f '{{.State.Running}}' filestash 2>/dev/null || echo false)"
|
|
[ "$running" = "true" ] || { docker compose logs --tail=40 filestash; die "filestash is not running — see logs above"; }
|
|
|
|
info "write test as uid ${FILES_UID}"
|
|
if docker exec -u "$FILES_UID" filestash sh -c 'touch /data/files/.wtest && rm /data/files/.wtest'; then
|
|
info "OK — filestash writes the share as ${FILES_UID}:${FILES_GID}; new files owned by that user"
|
|
else
|
|
docker compose logs --tail=40 filestash
|
|
die "write test FAILED — check share perms + logs above"
|
|
fi
|