#!/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