diff --git a/.env.example b/.env.example index f00c806..2e4d4cd 100644 --- a/.env.example +++ b/.env.example @@ -17,3 +17,7 @@ REQUIRE_SIGNIN_VIEW=false # Linux host UID/GID for the rootless image. Keep the defaults on Windows/macOS. USER_UID=1000 USER_GID=1000 + +# Only needed when migration cannot uniquely find the older named volumes. +# LEGACY_GITEA_VOLUME=git-docker-server_gitea_data +# LEGACY_POSTGRES_VOLUME=git-docker-server_postgres_data diff --git a/README.md b/README.md index 394f4b7..b67bae1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,26 @@ docker compose up -d 4. Open [http://localhost:3000](http://localhost:3000). 5. Create the first account. The first account becomes an administrator. +The `init-permissions` service runs once at startup to prepare the `docker/` data directories for Gitea and PostgreSQL. It will also correct ownership after restoring data from a backup. + +## Migrate Existing Data + +If you updated from the previous setup that used Docker named volumes, migrate your existing repositories and database before starting the updated stack: + +```sh +sh ./migrate-to-bind-mounts.sh +docker compose up -d +``` + +This works on Linux, TrueNAS, and macOS because it uses POSIX shell and Docker. The script discovers the old Gitea and PostgreSQL volumes from their Docker Compose labels, stops the stack, then copies them into `docker/gitea` and `docker/postgres`. It refuses to overwrite non-empty destination directories. On Windows, run it from WSL or Git Bash. + +If multiple old stacks exist on the same Docker host, identify the correct volume names with `docker volume ls`, then set `LEGACY_GITEA_VOLUME` and `LEGACY_POSTGRES_VOLUME` before running the migration: + +```dotenv +LEGACY_GITEA_VOLUME=your-project_gitea_data +LEGACY_POSTGRES_VOLUME=your-project_postgres_data +``` + The Git SSH endpoint is available on port `2222`: ```powershell diff --git a/docker-compose.yml b/docker-compose.yml index 7c993a6..732ce75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,28 @@ services: + init-permissions: + image: alpine:3.21 + container_name: git-server-init-permissions + restart: "no" + environment: + USER_UID: ${USER_UID:-1000} + USER_GID: ${USER_GID:-1000} + volumes: + - ./docker/gitea:/gitea + - ./docker/postgres:/postgres + command: + - /bin/sh + - -ec + - | + chown -R "$${USER_UID}:$${USER_GID}" /gitea + chown -R 70:70 /postgres + db: image: postgres:16-alpine container_name: git-server-db restart: unless-stopped + depends_on: + init-permissions: + condition: service_completed_successfully environment: POSTGRES_USER: ${POSTGRES_USER:-gitea} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me-in-env} @@ -22,6 +42,8 @@ services: container_name: git-server restart: unless-stopped depends_on: + init-permissions: + condition: service_completed_successfully db: condition: service_healthy environment: diff --git a/migrate-to-bind-mounts.sh b/migrate-to-bind-mounts.sh new file mode 100644 index 0000000..93c9b3b --- /dev/null +++ b/migrate-to-bind-mounts.sh @@ -0,0 +1,59 @@ +#!/bin/sh +set -eu + +project_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) + +find_legacy_volume() { + volume_label=$1 + paired_label=$2 + volumes=$(docker volume ls -q --filter "label=com.docker.compose.volume=$volume_label") + + if [ -z "$volumes" ]; then + echo "No legacy '$volume_label' Docker volume was found." >&2 + exit 1 + fi + + matching_volumes='' + for volume in $volumes; do + project=$(docker volume inspect --format '{{ index .Labels "com.docker.compose.project" }}' "$volume") + paired_volume=$(docker volume ls -q \ + --filter "label=com.docker.compose.project=$project" \ + --filter "label=com.docker.compose.volume=$paired_label") + if [ -n "$paired_volume" ]; then + matching_volumes="$matching_volumes $volume" + fi + done + + set -- $matching_volumes + if [ "$#" -ne 1 ]; then + echo "Could not identify one '$volume_label' volume paired with the legacy '$paired_label' volume." >&2 + echo "Set LEGACY_GITEA_VOLUME and LEGACY_POSTGRES_VOLUME explicitly and rerun." >&2 + exit 1 + fi + + printf '%s\n' "$1" +} + +gitea_volume=${LEGACY_GITEA_VOLUME:-$(find_legacy_volume gitea_data postgres_data)} +postgres_volume=${LEGACY_POSTGRES_VOLUME:-$(find_legacy_volume postgres_data gitea_data)} + +for destination in "$project_dir/docker/gitea" "$project_dir/docker/postgres"; do + mkdir -p "$destination" + if [ -n "$(find "$destination" -mindepth 1 -print -quit)" ]; then + echo "Migration destination '$destination' is not empty; refusing to overwrite data." >&2 + exit 1 + fi +done + +docker compose down + +docker run --rm \ + --volume "$gitea_volume:/source/gitea:ro" \ + --volume "$postgres_volume:/source/postgres:ro" \ + --volume "$project_dir/docker:/destination" \ + alpine:3.21 sh -ec ' + cp -a /source/gitea/. /destination/gitea/ + cp -a /source/postgres/. /destination/postgres/ + ' + +echo "Migration complete. Start the server with: docker compose up -d" \ No newline at end of file