diff --git a/.gitea/workflows/build-container.yml b/.gitea/workflows/build-container.yml new file mode 100644 index 0000000..f94ce8c --- /dev/null +++ b/.gitea/workflows/build-container.yml @@ -0,0 +1,174 @@ +# FoundryVTT Container Build Pipeline +name: FoundryVTT Container Build +run-name: ${{ gitea.actor }} is building FoundryVTT Container +on: + push: + branches: + - main + paths: + - 'Containerfile' + - 'scripts/**' + - '.gitea/workflows/build-container.yml' + workflow_dispatch: + inputs: + foundry_version: + description: 'FoundryVTT version (or "latest")' + required: false + default: 'latest' + force_build: + description: 'Force build even if version exists' + required: false + default: 'false' + +env: + IMAGE_NAMESPACE: public/foundryvtt + IMAGE_REGISTRY: registry.belway.me + REGISTRY_USER: ${{ secrets.BELWAY_REGISTRY_USER }} + REGISTRY_TOKEN: ${{ secrets.BELWAY_REGISTRY_TOKEN }} + +jobs: + Build: + name: Build FoundryVTT Container + runs-on: podman-stable + steps: + - name: Fail fast on missing secrets + env: + REPO_TOKEN: ${{ secrets.REPO_TOKEN }} + FOUNDRY_USERNAME: ${{ secrets.FOUNDRY_USERNAME }} + FOUNDRY_PASSWORD: ${{ secrets.FOUNDRY_PASSWORD }} + run: | + if [[ -z "${REGISTRY_USER}" || -z "${REGISTRY_TOKEN}" || -z "${REPO_TOKEN}" ]]; then + echo "Error: Required registry secrets are missing" + exit 1 + fi + if [[ -z "${FOUNDRY_USERNAME}" || -z "${FOUNDRY_PASSWORD}" ]]; then + echo "Error: FOUNDRY_USERNAME and FOUNDRY_PASSWORD secrets are required" + exit 1 + fi + + - name: Registry Login + run: | + echo "${REGISTRY_TOKEN}" | podman login -u "${REGISTRY_USER}" --password-stdin "${IMAGE_REGISTRY}" + + - name: Setup Container Tools + run: | + dnf install -y buildah git jq curl fuse-overlayfs podman + dnf -y reinstall shadow-utils + dnf clean all && rm -rf /var/cache/dnf + + - name: Clone Repository + run: | + git clone "https://${{ secrets.REPO_TOKEN }}@gitea.belway.me/Public/foundryvtt_container.git" + + - name: Set Git Information + working-directory: ./foundryvtt_container + run: | + echo "GIT_COMMIT=$(git rev-parse HEAD)" >> $GITHUB_ENV + echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV + + - name: Get FoundryVTT Version + working-directory: ./foundryvtt_container + run: | + if [[ "${{ gitea.event_name }}" == "workflow_dispatch" && "${{ inputs.foundry_version }}" != "latest" ]]; then + FOUNDRY_VERSION="${{ inputs.foundry_version }}" + else + echo "Fetching latest FoundryVTT version..." + chmod +x scripts/get_foundry_version.sh + FOUNDRY_VERSION=$(bash scripts/get_foundry_version.sh --version-only) + fi + + echo "FOUNDRY_VERSION=${FOUNDRY_VERSION}" >> $GITHUB_ENV + echo "FoundryVTT version: ${FOUNDRY_VERSION}" + + - name: Check Existing Image + run: | + if podman pull "${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}:${FOUNDRY_VERSION}" 2>/dev/null; then + echo "EXISTING_VERSION=${FOUNDRY_VERSION}" >> $GITHUB_ENV + else + echo "EXISTING_VERSION=none" >> $GITHUB_ENV + fi + + - name: Get FoundryVTT Download URL + working-directory: ./foundryvtt_container + env: + FOUNDRY_USERNAME: ${{ secrets.FOUNDRY_USERNAME }} + FOUNDRY_PASSWORD: ${{ secrets.FOUNDRY_PASSWORD }} + run: | + echo "Authenticating to FoundryVTT..." + chmod +x scripts/get_foundry_download.sh + FOUNDRY_URL=$(bash scripts/get_foundry_download.sh --url-only --version "${FOUNDRY_VERSION}") + + if [[ -z "${FOUNDRY_URL}" || "${FOUNDRY_URL}" == "null" ]]; then + echo "Error: Failed to get download URL from FoundryVTT" + exit 1 + fi + + # Mask the URL in logs (contains timed token) + echo "::add-mask::${FOUNDRY_URL}" + echo "FOUNDRY_URL=${FOUNDRY_URL}" >> $GITHUB_ENV + echo "✅ Successfully obtained download URL" + + - name: Build and Push Container Image + working-directory: ./foundryvtt_container + run: | + set -e + + SHOULD_BUILD=false + FORCE_BUILD="${{ inputs.force_build }}" + + if [[ "${FORCE_BUILD}" == "true" ]]; then + SHOULD_BUILD=true + elif [[ "${EXISTING_VERSION}" == "none" ]]; then + SHOULD_BUILD=true + elif [[ "${{ gitea.event_name }}" == "workflow_dispatch" ]]; then + SHOULD_BUILD=true + elif [[ "${{ gitea.event_name }}" == "push" ]]; then + SHOULD_BUILD=true + fi + + if [[ "${SHOULD_BUILD}" == "true" ]]; then + echo "Building FoundryVTT image..." + + IMAGE_NAME="${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}:${FOUNDRY_VERSION}" + + podman build \ + --platform linux/amd64 \ + --format oci \ + --layers \ + --build-arg FOUNDRY_URL="${FOUNDRY_URL}" \ + --build-arg FOUNDRY_VERSION="${FOUNDRY_VERSION}" \ + --label "org.opencontainers.image.version=${FOUNDRY_VERSION}" \ + --label "org.opencontainers.image.revision=${GIT_COMMIT}" \ + --label "org.opencontainers.image.created=${BUILD_DATE}" \ + -f Containerfile \ + -t "${IMAGE_NAME}" \ + . + + podman push "${IMAGE_NAME}" + + # Also tag as latest + podman tag "${IMAGE_NAME}" "${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}:latest" + podman push "${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}:latest" + + echo "BUILD_STATUS=success" >> $GITHUB_ENV + echo "✅ Successfully built FoundryVTT ${FOUNDRY_VERSION}" + else + echo "BUILD_STATUS=skipped" >> $GITHUB_ENV + echo "â„šī¸ No build required" + fi + + - name: Registry Logout + if: always() + run: podman logout "${IMAGE_REGISTRY}" || true + + - name: Build Summary + run: | + echo "" + echo "🎲 FoundryVTT Container Build Summary" + echo "=====================================" + echo " FoundryVTT Version: ${FOUNDRY_VERSION}" + echo " Architecture: amd64" + echo " Build Status: ${BUILD_STATUS:-unknown}" + echo " Registry: ${IMAGE_REGISTRY}/${IMAGE_NAMESPACE}" + echo " Tags: ${FOUNDRY_VERSION}, latest" + echo "" diff --git a/scripts/get_foundry_download.sh b/scripts/get_foundry_download.sh new file mode 100755 index 0000000..577cf2c --- /dev/null +++ b/scripts/get_foundry_download.sh @@ -0,0 +1,155 @@ +#!/bin/bash +# +# Get FoundryVTT download URL using account credentials +# Based on felddy/foundryvtt-docker authentication flow +# +# Usage: get_foundry_download.sh [--url-only] [--version VERSION] +# +# Required environment variables: +# FOUNDRY_USERNAME - FoundryVTT account username/email +# FOUNDRY_PASSWORD - FoundryVTT account password +# + +set -euo pipefail + +# Configuration +FOUNDRY_BASE_URL="https://foundryvtt.com" + +# Check required environment variables +if [[ -z "${FOUNDRY_USERNAME:-}" || -z "${FOUNDRY_PASSWORD:-}" ]]; then + echo "Error: FOUNDRY_USERNAME and FOUNDRY_PASSWORD environment variables are required" >&2 + exit 1 +fi + +# Create temp file for cookies +COOKIE_JAR=$(mktemp) +trap "rm -f $COOKIE_JAR" EXIT + +# Step 1: Get CSRF token from the main page (not login page) +get_csrf_token() { + local main_page + main_page=$(curl -fsSL -c "$COOKIE_JAR" -b "$COOKIE_JAR" \ + -H "DNT: 1" \ + -H "Referer: ${FOUNDRY_BASE_URL}" \ + -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \ + "${FOUNDRY_BASE_URL}" 2>/dev/null) + + # Extract csrfmiddlewaretoken from the form + local csrf_token + csrf_token=$(echo "$main_page" | grep -oP 'name="csrfmiddlewaretoken" value="\K[^"]+' 2>/dev/null | head -1) + + echo "$csrf_token" +} + +# Step 2: Authenticate +authenticate() { + local csrf_token="$1" + + # POST to login + curl -fsSL -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ + -X POST "${FOUNDRY_BASE_URL}/auth/login/" \ + -H "DNT: 1" \ + -H "Referer: ${FOUNDRY_BASE_URL}" \ + -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "csrfmiddlewaretoken=${csrf_token}&username=${FOUNDRY_USERNAME}&password=${FOUNDRY_PASSWORD}&next=/" \ + -o /dev/null 2>/dev/null || true + + # Check for sessionid cookie (indicates successful login) + if grep -q "sessionid" "$COOKIE_JAR" 2>/dev/null; then + return 0 + else + return 1 + fi +} + +# Step 3: Get download URL +get_download_url() { + local version="$1" + + # Extract build number from version (e.g., "13.351" -> "351") + local build + build="${version##*.}" + + # Fetch presigned URL from API + local response + response=$(curl -fsSL -b "$COOKIE_JAR" \ + -H "DNT: 1" \ + -H "Referer: ${FOUNDRY_BASE_URL}" \ + -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" \ + "${FOUNDRY_BASE_URL}/releases/download?build=${build}&platform=node&response_type=json" 2>/dev/null) + + # Extract URL from JSON response + local download_url + download_url=$(echo "$response" | grep -oP '"url":\s*"\K[^"]+' 2>/dev/null || true) + + echo "$download_url" +} + +# Main execution +main() { + local url_only=false + local version_override="" + + # Parse arguments + while [[ $# -gt 0 ]]; do + case "$1" in + --url-only) + url_only=true + shift + ;; + --version) + version_override="$2" + shift 2 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac + done + + # Get version + local version="${version_override:-}" + if [[ -z "$version" ]]; then + # Get latest stable version + version=$(bash "$(dirname "$0")/get_foundry_version.sh" --version-only 2>/dev/null || echo "") + if [[ -z "$version" ]]; then + echo "Error: Could not determine FoundryVTT version" >&2 + exit 1 + fi + fi + + if [[ "$url_only" != true ]]; then + echo "Authenticating with FoundryVTT..." >&2 + fi + + local csrf_token + csrf_token=$(get_csrf_token) + + if [[ -z "$csrf_token" ]]; then + echo "Error: Could not get CSRF token from FoundryVTT" >&2 + exit 1 + fi + + if ! authenticate "$csrf_token"; then + echo "Error: Authentication failed. Check your credentials." >&2 + exit 1 + fi + + if [[ "$url_only" != true ]]; then + echo "Fetching download URL for version ${version}..." >&2 + fi + + local download_url + download_url=$(get_download_url "$version") + + if [[ -z "$download_url" || "$download_url" == "null" ]]; then + echo "Error: Could not retrieve download URL. Make sure you have a valid license." >&2 + exit 1 + fi + + echo "$download_url" +} + +main "$@" diff --git a/scripts/get_foundry_version.sh b/scripts/get_foundry_version.sh new file mode 100755 index 0000000..dc2f726 --- /dev/null +++ b/scripts/get_foundry_version.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Get FoundryVTT version information +# Usage: get_foundry_version.sh [--version-only] +# --version-only: just print the latest stable version and exit +# + +set -euo pipefail + +# Fetch releases page and find latest stable +get_stable_version() { + local releases_page + releases_page=$(curl -fsSL "https://foundryvtt.com/releases/" 2>/dev/null || true) + + if [[ -z "$releases_page" ]]; then + echo "" + return 1 + fi + + # Find stable releases - look for version in entries with "stable" tag + # The HTML has "Release X.Y" followed later by 'class="release-tag stable"' + local version + version=$(echo "$releases_page" | grep -B5 'class="release-tag stable"' | grep -oE 'Release [0-9]+\.[0-9]+' | head -1 | sed 's/Release //') + + echo "$version" +} + +# Handle --version-only flag +if [[ "${1:-}" == "--version-only" ]]; then + VERSION=$(get_stable_version) + if [[ -z "$VERSION" ]]; then + echo "Error: Failed to fetch version from FoundryVTT" >&2 + exit 1 + fi + echo "$VERSION" + exit 0 +fi + +# Default: show version with context +echo "Fetching latest stable FoundryVTT version..." +VERSION=$(get_stable_version) + +if [[ -z "$VERSION" ]]; then + echo "Error: Could not determine latest stable version" >&2 + exit 1 +fi + +echo "Latest stable version: $VERSION"