diff --git a/.env.example b/.env.example index aa43c6e..6b33689 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,8 @@ NODE_ENV=production HOST=0.0.0.0 PORT=4321 +# Optional application base path (use "/" for root, or "/mirror" for subpath deployments) +BASE_URL=/ # Database Configuration # For self-hosted, SQLite is used by default @@ -31,6 +33,12 @@ BETTER_AUTH_URL=http://localhost:4321 # PUBLIC_BETTER_AUTH_URL=https://gitea-mirror.example.com # BETTER_AUTH_TRUSTED_ORIGINS=https://gitea-mirror.example.com # +# If your app is served from a path prefix (e.g. https://git.example.com/mirror), set: +# BASE_URL=/mirror +# BETTER_AUTH_URL=https://git.example.com +# PUBLIC_BETTER_AUTH_URL=https://git.example.com +# BETTER_AUTH_TRUSTED_ORIGINS=https://git.example.com +# # BETTER_AUTH_URL - Used server-side for auth callbacks and redirects # PUBLIC_BETTER_AUTH_URL - Used client-side (browser) for auth API calls # BETTER_AUTH_TRUSTED_ORIGINS - Comma-separated list of origins allowed to make auth requests diff --git a/Dockerfile b/Dockerfile index f809791..3214a06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ RUN apt-get update && apt-get -y upgrade && apt-get install -y --no-install-reco # ---------------------------- FROM base AS builder +ARG BASE_URL=/ +ENV BASE_URL=${BASE_URL} COPY package.json ./ COPY bun.lock* ./ RUN bun install --frozen-lockfile @@ -73,6 +75,7 @@ ENV NODE_ENV=production ENV HOST=0.0.0.0 ENV PORT=4321 ENV DATABASE_URL=file:data/gitea-mirror.db +ENV BASE_URL=/ # Create directories and setup permissions RUN mkdir -p /app/certs && \ @@ -90,6 +93,6 @@ VOLUME /app/data EXPOSE 4321 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:4321/api/health || exit 1 + CMD sh -c 'BASE="${BASE_URL:-/}"; if [ "$BASE" = "/" ]; then BASE=""; else BASE="${BASE%/}"; fi; wget --no-verbose --tries=1 --spider "http://localhost:4321${BASE}/api/health" || exit 1' ENTRYPOINT ["./docker-entrypoint.sh"] diff --git a/README.md b/README.md index 75276df..c7ac38e 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,19 @@ CLEANUP_DRY_RUN=false # Set to true to test without changes ### Reverse Proxy Configuration -If using a reverse proxy (e.g., nginx proxy manager) and experiencing issues with JavaScript files not loading properly, try enabling HTTP/2 support in your proxy configuration. While not required by the application, some proxy configurations may have better compatibility with HTTP/2 enabled. See [issue #43](https://github.com/RayLabsHQ/gitea-mirror/issues/43) for reference. +If you run behind a reverse proxy on a subpath (for example `https://git.example.com/mirror`), configure: + +```bash +BASE_URL=/mirror +BETTER_AUTH_URL=https://git.example.com +PUBLIC_BETTER_AUTH_URL=https://git.example.com +BETTER_AUTH_TRUSTED_ORIGINS=https://git.example.com +``` + +Notes: +- `BASE_URL` sets the application path prefix. +- `BETTER_AUTH_TRUSTED_ORIGINS` should contain origins only (no path). +- When building Docker images, pass `BASE_URL` at build time as well. ### Mirror Token Rotation (GitHub Token Changed) diff --git a/astro.config.mjs b/astro.config.mjs index 293315f..2aec23c 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -4,8 +4,25 @@ import tailwindcss from '@tailwindcss/vite'; import react from '@astrojs/react'; import node from '@astrojs/node'; +const normalizeBaseUrl = (value) => { + if (!value || value.trim() === '') { + return '/'; + } + + let normalized = value.trim(); + if (!normalized.startsWith('/')) { + normalized = `/${normalized}`; + } + + normalized = normalized.replace(/\/+$/, ''); + return normalized || '/'; +}; + +const base = normalizeBaseUrl(process.env.BASE_URL); + // https://astro.build/config export default defineConfig({ + base, output: 'server', adapter: node({ mode: 'standalone', diff --git a/docker-compose.alt.yml b/docker-compose.alt.yml index 296bfba..3631a75 100644 --- a/docker-compose.alt.yml +++ b/docker-compose.alt.yml @@ -22,6 +22,8 @@ services: # BETTER_AUTH_URL=https://gitea-mirror.example.com # PUBLIC_BETTER_AUTH_URL=https://gitea-mirror.example.com # BETTER_AUTH_TRUSTED_ORIGINS=https://gitea-mirror.example.com + # NOTE: Path-prefix deployments (e.g. /mirror) require BASE_URL at build time. + # Use docker-compose.yml (which builds from source) and set BASE_URL there. # === CORE SETTINGS === # These are technically required but have working defaults @@ -29,6 +31,7 @@ services: - DATABASE_URL=file:data/gitea-mirror.db - HOST=0.0.0.0 - PORT=4321 + - BASE_URL=${BASE_URL:-/} - PUBLIC_BETTER_AUTH_URL=${PUBLIC_BETTER_AUTH_URL:-http://localhost:4321} # Optional concurrency controls (defaults match in-app defaults) # If you want perfect ordering of issues and PRs, set these at 1 @@ -36,7 +39,11 @@ services: - MIRROR_PULL_REQUEST_CONCURRENCY=${MIRROR_PULL_REQUEST_CONCURRENCY:-5} healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=3", "--spider", "http://localhost:4321/api/health"] + test: + [ + "CMD-SHELL", + "BASE=\"${BASE_URL:-/}\"; if [ \"$${BASE}\" = \"/\" ]; then BASE=\"\"; else BASE=\"$${BASE%/}\"; fi; wget --no-verbose --tries=3 --spider \"http://localhost:4321$${BASE}/api/health\"", + ] interval: 30s timeout: 10s retries: 5 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index b7b97d5..0c50d4b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -45,6 +45,8 @@ services: build: context: . dockerfile: Dockerfile + args: + BASE_URL: ${BASE_URL:-/} platforms: - linux/amd64 - linux/arm64 @@ -66,6 +68,7 @@ services: - DATABASE_URL=file:data/gitea-mirror.db - HOST=0.0.0.0 - PORT=4321 + - BASE_URL=${BASE_URL:-/} - BETTER_AUTH_SECRET=dev-secret-key # GitHub/Gitea Mirror Config - GITHUB_USERNAME=${GITHUB_USERNAME:-your-github-username} @@ -89,7 +92,11 @@ services: # Optional: Skip TLS verification (insecure, use only for testing) # - GITEA_SKIP_TLS_VERIFY=${GITEA_SKIP_TLS_VERIFY:-false} healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:4321/api/health"] + test: + [ + "CMD-SHELL", + "BASE=\"${BASE_URL:-/}\"; if [ \"$${BASE}\" = \"/\" ]; then BASE=\"\"; else BASE=\"$${BASE%/}\"; fi; wget --no-verbose --tries=1 --spider \"http://localhost:4321$${BASE}/api/health\"", + ] interval: 30s timeout: 5s retries: 3 diff --git a/docker-compose.yml b/docker-compose.yml index c278b44..ee4533b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ services: build: context: . dockerfile: Dockerfile + args: + BASE_URL: ${BASE_URL:-/} platforms: - linux/amd64 - linux/arm64 @@ -30,6 +32,7 @@ services: - DATABASE_URL=file:data/gitea-mirror.db - HOST=0.0.0.0 - PORT=4321 + - BASE_URL=${BASE_URL:-/} - BETTER_AUTH_SECRET=${BETTER_AUTH_SECRET:-your-secret-key-change-this-in-production} - BETTER_AUTH_URL=${BETTER_AUTH_URL:-http://localhost:4321} # REVERSE PROXY: If you access Gitea Mirror through a reverse proxy (e.g. Nginx, Caddy, Traefik), @@ -37,6 +40,11 @@ services: # BETTER_AUTH_URL=https://gitea-mirror.example.com # PUBLIC_BETTER_AUTH_URL=https://gitea-mirror.example.com # BETTER_AUTH_TRUSTED_ORIGINS=https://gitea-mirror.example.com + # If deployed under a path prefix (e.g. https://git.example.com/mirror), also set: + # BASE_URL=/mirror + # BETTER_AUTH_URL=https://git.example.com + # PUBLIC_BETTER_AUTH_URL=https://git.example.com + # BETTER_AUTH_TRUSTED_ORIGINS=https://git.example.com - PUBLIC_BETTER_AUTH_URL=${PUBLIC_BETTER_AUTH_URL:-http://localhost:4321} - BETTER_AUTH_TRUSTED_ORIGINS=${BETTER_AUTH_TRUSTED_ORIGINS:-} # Optional: ENCRYPTION_SECRET will be auto-generated if not provided @@ -81,7 +89,11 @@ services: - HEADER_AUTH_AUTO_PROVISION=${HEADER_AUTH_AUTO_PROVISION:-false} - HEADER_AUTH_ALLOWED_DOMAINS=${HEADER_AUTH_ALLOWED_DOMAINS:-} healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=3", "--spider", "http://localhost:4321/api/health"] + test: + [ + "CMD-SHELL", + "BASE=\"${BASE_URL:-/}\"; if [ \"$${BASE}\" = \"/\" ]; then BASE=\"\"; else BASE=\"$${BASE%/}\"; fi; wget --no-verbose --tries=3 --spider \"http://localhost:4321$${BASE}/api/health\"", + ] interval: 30s timeout: 10s retries: 5 diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md index 6a83963..a6d8a6d 100644 --- a/docs/ENVIRONMENT_VARIABLES.md +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -33,6 +33,7 @@ Essential application settings required for running Gitea Mirror. | `NODE_ENV` | Application environment | `production` | No | | `HOST` | Server host binding | `0.0.0.0` | No | | `PORT` | Server port | `4321` | No | +| `BASE_URL` | Application base path. Use `/` for root deployments, or a prefix such as `/mirror` when serving behind a reverse-proxy path prefix. | `/` | No | | `DATABASE_URL` | Database connection URL | `sqlite://data/gitea-mirror.db` | No | | `BETTER_AUTH_SECRET` | Secret key for session signing (generate with: `openssl rand -base64 32`) | - | Yes | | `BETTER_AUTH_URL` | Primary base URL for authentication. This should be the main URL where your application is accessed. | `http://localhost:4321` | No | @@ -302,6 +303,7 @@ services: environment: # Core Configuration - NODE_ENV=production + - BASE_URL=/ - DATABASE_URL=file:data/gitea-mirror.db - BETTER_AUTH_SECRET=your-secure-secret-here # Primary access URL: @@ -370,6 +372,21 @@ This setup allows you to: **Important:** When accessing from different origins (IP vs domain), you'll need to log in separately on each origin as cookies cannot be shared across different origins for security reasons. +### Path Prefix Deployments + +If you serve Gitea Mirror under a subpath such as `https://git.example.com/mirror`, set: + +```bash +BASE_URL=/mirror +BETTER_AUTH_URL=https://git.example.com +PUBLIC_BETTER_AUTH_URL=https://git.example.com +BETTER_AUTH_TRUSTED_ORIGINS=https://git.example.com +``` + +Notes: +- `BETTER_AUTH_TRUSTED_ORIGINS` must contain origins only (no path). +- `BASE_URL` is applied at build time, so set it for image builds too. + ### Trusted Origins The `BETTER_AUTH_TRUSTED_ORIGINS` variable serves multiple purposes: diff --git a/src/components/NotFound.tsx b/src/components/NotFound.tsx index 8dee1cb..4e63ea2 100644 --- a/src/components/NotFound.tsx +++ b/src/components/NotFound.tsx @@ -1,6 +1,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Home, ArrowLeft, GitBranch, BookOpen, Settings, FileQuestion } from "lucide-react"; +import { withBase } from "@/lib/base-path"; export function NotFound() { return ( @@ -21,7 +22,7 @@ export function NotFound() { {/* Action Buttons */}