name: Site Pack on: push: branches: - main repository_dispatch: types: - build-pack schedule: # Poll all source repos every 15 minutes; rebuild only if a component changed. - cron: "*/15 * * * *" workflow_dispatch: inputs: force: description: "Force rebuild even if nothing changed" required: false default: "false" jobs: pack: runs-on: shell env: GITEA_API: ${{ gitea.api_url }} GITEA_REPO: ${{ gitea.repository }} TOKEN: ${{ secrets.GITEA_TOKEN }} FORCE: ${{ github.event.inputs.force }} CACHE_DIR: /var/tmp/pack-cache steps: - name: Check for upstream changes id: check run: | set -e exec 2>&1 CMS_REF="$(git -C /docker/gitea/gitea/git/repositories/remco/epicnext-cms.git rev-parse main 2>/dev/null \ || git ls-remote https://gitlab.epicnabbo.nl/remco/EpicNext-Cms.git main 2>/dev/null | awk '{print $1}')" CAT_REF="$(git ls-remote https://gitlab.epicnabbo.nl/remco/Epicnabbo-Catalogus-Updated-Daily.git Beta-3 2>/dev/null | awk '{print $1}')" NITRO_REF="$(git ls-remote https://github.com/duckietm/Nitro-V3.git main 2>/dev/null | awk '{print $1}')" RENDER_REF="$(git ls-remote https://github.com/duckietm/Nitro_Render_V3.git main 2>/dev/null | awk '{print $1}')" EMU_REF="$(git ls-remote https://github.com/duckietm/Polaris-Emulator.git main 2>/dev/null | awk '{print $1}')" COMBINED="${CMS_REF}:${CAT_REF}:${NITRO_REF}:${RENDER_REF}:${EMU_REF}" echo "Current refs: $COMBINED" STATE="$CACHE_DIR/refs.state" mkdir -p "$CACHE_DIR" PREV="$(cat "$STATE" 2>/dev/null || echo "")" CHANGED="" if [ "${FORCE:-false}" = "true" ]; then CHANGED="force" elif [ "$COMBINED" != "$PREV" ]; then CHANGED="upstream" fi if [ -n "$CHANGED" ]; then echo "Change detected ($CHANGED)" echo "changed=true" >> "$GITHUB_OUTPUT" else echo "No upstream changes since last pack" echo "changed=false" >> "$GITHUB_OUTPUT" fi echo "$COMBINED" > "$STATE" # Expose individual refs so later steps can compare per-component. echo "cms_ref=$CMS_REF" >> "$GITHUB_OUTPUT" echo "cat_ref=$CAT_REF" >> "$GITHUB_OUTPUT" echo "nitro_ref=$NITRO_REF" >> "$GITHUB_OUTPUT" echo "render_ref=$RENDER_REF" >> "$GITHUB_OUTPUT" echo "emu_ref=$EMU_REF" >> "$GITHUB_OUTPUT" - name: Skip if unchanged if: steps.check.outputs.changed != 'true' run: | echo "Nothing to rebuild. Exiting." exit 0 - name: Setup if: steps.check.outputs.changed == 'true' run: | set -e exec 2>&1 export PACK_VERSION="pack-$(date +%Y%m%d-%H%M)" echo "=== Building site pack ${PACK_VERSION} ===" WORK="/tmp/pack-build" rm -rf "$WORK" && mkdir -p "$WORK/src" "$WORK/out" "$CACHE_DIR" echo "WORK=$WORK" >> "$GITHUB_ENV" echo "PACK_VERSION=$PACK_VERSION" >> "$GITHUB_ENV" # ---- Per-component: pull only if ref changed, reuse cached build otherwise ---- - name: CMS (this repo) if: steps.check.outputs.changed == 'true' run: | set -e S="$CACHE_DIR/cms"; mkdir -p "$S" BARE="/docker/gitea/gitea/git/repositories/remco/epicnext-cms.git" if [ "${{ steps.check.outputs.cms_ref }}" != "$(cat "$S/ref" 2>/dev/null)" ]; then rm -rf "$WORK/src/cms" git clone --depth 1 "file://$BARE" "$WORK/src/cms" 2>/dev/null \ || git clone --depth 1 "${{ gitea.server_url }}/${{ gitea.repository }}.git" "$WORK/src/cms" 2>/dev/null \ || git -C "$WORK/src/cms" pull --ff-only 2>/dev/null echo "${{ steps.check.outputs.cms_ref }}" > "$S/ref" rm -rf "$S/snapshot"; cp -r "$WORK/src/cms" "$S/snapshot" CMS_ST="new" else cp -r "$S/snapshot" "$WORK/src/cms" echo "CMS unchanged — reused cache" CMS_ST="cached" fi echo "cms:$CMS_ST" >> "$WORK/status.txt" echo "cms: $(git -C "$WORK/src/cms" rev-parse --short HEAD 2>/dev/null)" | tee -a "$WORK/components.txt" - name: Catalogus (sparse) if: steps.check.outputs.changed == 'true' run: | set -e S="$CACHE_DIR/catalogus"; mkdir -p "$S" if [ "${{ steps.check.outputs.cat_ref }}" != "$(cat "$S/ref" 2>/dev/null)" ]; then rm -rf "$WORK/src/catalogus" for attempt in 1 2 3; do if git clone --depth 1 --filter=blob:none --sparse \ https://gitlab.epicnabbo.nl/remco/Epicnabbo-Catalogus-Updated-Daily.git "$WORK/src/catalogus" 2>/dev/null; then break fi echo "Catalogus clone attempt $attempt failed, retrying..." >&2 sleep 5 done git -C "$WORK/src/catalogus" sparse-checkout set . 2>/dev/null || true echo "${{ steps.check.outputs.cat_ref }}" > "$S/ref" rm -rf "$S/snapshot"; cp -r "$WORK/src/catalogus" "$S/snapshot" else cp -r "$S/snapshot" "$WORK/src/catalogus" echo "Catalogus unchanged — reused cache" CAT_ST="cached" fi [ -z "$CAT_ST" ] && CAT_ST="new" echo "catalogus:$CAT_ST" >> "$WORK/status.txt" echo "catalogus: $(git -C "$WORK/src/catalogus" rev-parse --short HEAD 2>/dev/null)" | tee -a "$WORK/components.txt" - name: Nitro-Render-V3 if: steps.check.outputs.changed == 'true' run: | set -e S="$CACHE_DIR/render"; mkdir -p "$S" # Nitro-V3's vite.config expects the renderer at ../Nitro_Render_V3 RENDER_DIR="$WORK/src/Nitro_Render_V3" if [ "${{ steps.check.outputs.render_ref }}" != "$(cat "$S/ref" 2>/dev/null)" ]; then rm -rf "$RENDER_DIR" for attempt in 1 2 3; do if git clone --depth 1 https://github.com/duckietm/Nitro_Render_V3.git "$RENDER_DIR" 2>/dev/null; then break fi echo "Render clone attempt $attempt failed, retrying..." >&2 sleep 5 done [ -f "$RENDER_DIR/package.json" ] || { echo "Render clone failed"; exit 1; } cd "$RENDER_DIR" yarn install --frozen-lockfile --prefer-offline 2>&1 || yarn install 2>&1 rm -rf "$S/files"; mkdir -p "$S/files"; cp -r . "$S/files/" 2>/dev/null || true rm -rf "$S/files/node_modules" echo "${{ steps.check.outputs.render_ref }}" > "$S/ref" REN_ST="new" else echo "Render unchanged — reused cache" rm -rf "$RENDER_DIR"; mkdir -p "$RENDER_DIR"; cp -r "$S/files/"* "$RENDER_DIR/" 2>/dev/null || true REN_ST="cached" fi [ -z "$REN_ST" ] && REN_ST="new" echo "render:$REN_ST" >> "$WORK/status.txt" mkdir -p "$WORK/out/render"; cp -r "$S/files/"* "$WORK/out/render/" 2>/dev/null || true echo "render: $(git -C "$RENDER_DIR" rev-parse --short HEAD 2>/dev/null || echo cached)" | tee -a "$WORK/components.txt" - name: Nitro-V3 if: steps.check.outputs.changed == 'true' run: | set -e S="$CACHE_DIR/nitro"; mkdir -p "$S" if [ "${{ steps.check.outputs.nitro_ref }}" != "$(cat "$S/ref" 2>/dev/null)" ]; then rm -rf "$WORK/src/nitro" for attempt in 1 2 3; do if git clone --depth 1 https://github.com/duckietm/Nitro-V3.git "$WORK/src/nitro" 2>/dev/null; then break fi echo "Nitro clone attempt $attempt failed, retrying..." >&2 sleep 5 done [ -f "$WORK/src/nitro/package.json" ] || { echo "Nitro clone failed"; exit 1; } cd "$WORK/src/nitro" yarn install --frozen-lockfile --prefer-offline 2>&1 || yarn install 2>&1 yarn build rm -rf "$S/dist"; mkdir -p "$S/dist"; cp -r dist/* "$S/dist/" 2>/dev/null || true echo "${{ steps.check.outputs.nitro_ref }}" > "$S/ref" NIT_ST="new" else echo "Nitro unchanged — reused cached dist" NIT_ST="cached" fi [ -z "$NIT_ST" ] && NIT_ST="new" echo "nitro:$NIT_ST" >> "$WORK/status.txt" mkdir -p "$WORK/out/nitro"; cp -r "$S/dist/"* "$WORK/out/nitro/" 2>/dev/null || true echo "nitro: $(git -C "$WORK/src/nitro" rev-parse --short HEAD 2>/dev/null || echo cached)" | tee -a "$WORK/components.txt" - name: Polaris Emulator if: steps.check.outputs.changed == 'true' run: | set -e S="$CACHE_DIR/emulator"; mkdir -p "$S" if [ "${{ steps.check.outputs.emu_ref }}" != "$(cat "$S/ref" 2>/dev/null)" ]; then rm -rf "$WORK/src/emulator" for attempt in 1 2 3; do if git clone --depth 1 https://github.com/duckietm/Polaris-Emulator.git "$WORK/src/emulator" 2>/dev/null; then break fi echo "Emulator clone attempt $attempt failed, retrying..." >&2 sleep 5 done [ -f "$WORK/src/emulator/Emulator/pom.xml" ] || { echo "Emulator clone failed (no pom.xml)"; exit 1; } cd "$WORK/src/emulator/Emulator" mvn package -q -T 1C -o -Dmaven.test.skip=true 2>&1 \ || mvn package -q -T 1C -Dmaven.test.skip=true 2>&1 rm -f "$S"/*.jar; find target -maxdepth 1 -name 'Polaris-*-jar-with-dependencies.jar' -exec cp {} "$S/" \; 2>/dev/null || true echo "${{ steps.check.outputs.emu_ref }}" > "$S/ref" EMU_ST="new" else echo "Emulator unchanged — reused cached jar" EMU_ST="cached" fi [ -z "$EMU_ST" ] && EMU_ST="new" echo "emulator:$EMU_ST" >> "$WORK/status.txt" mkdir -p "$WORK/out/emulator"; cp "$S"/*.jar "$WORK/out/emulator/" 2>/dev/null || true echo "emulator: $(git -C "$WORK/src/emulator" rev-parse --short HEAD 2>/dev/null || echo cached)" | tee -a "$WORK/components.txt" - name: Assemble Pack if: steps.check.outputs.changed == 'true' run: | set -e cd "$WORK/out" cp -r "$WORK/src/cms" "$WORK/out/cms" 2>/dev/null || true rm -rf "$WORK/out/cms/.git" "$WORK/out/cms/node_modules" 2>/dev/null || true cp -r "$WORK/src/catalogus" "$WORK/out/catalogus" 2>/dev/null || true rm -rf "$WORK/out/catalogus/.git" 2>/dev/null || true # Diagnostics: show each component size before packing. for d in cms catalogus nitro render emulator; do echo "out/$d: $(du -sh "$WORK/out/$d" 2>/dev/null | cut -f1) ($(find "$WORK/out/$d" -type f 2>/dev/null | wc -l) files)" done # Hard guard: every component must be present and non-empty. for d in catalogus nitro render emulator; do if [ ! -d "$WORK/out/$d" ] || [ -z "$(find "$WORK/out/$d" -type f 2>/dev/null | head -1)" ]; then echo "ERROR: component '$d' is missing or empty in the pack"; exit 1 fi done cp "$WORK/components.txt" "$WORK/out/COMPONENTS.txt" # Build a clear, human-readable manifest at the pack root. { echo "# Site Pack ${PACK_VERSION}" echo "" echo "Generated: $(date -u)" echo "" echo "## Inhoud (wat zit erin)" echo "" echo "| Component | Map | Versie | Status |" echo "|-----------|-----|--------|--------|" for pair in "CMS:cms:cms" "Catalogus:catalogus:catalogus" "Nitro-V3:nitro:nitro" "Nitro-Render-V3:render:render" "Polaris Emulator:emulator:emulator"; do label="${pair%%:*}"; d="${pair#*:}"; key="${pair##*:}" ver="$(grep "^${key}:" "$WORK/components.txt" 2>/dev/null | head -1 | cut -d: -f2- | tr -d ' ')" st="$(grep "^${key}:" "$WORK/status.txt" 2>/dev/null | head -1 | cut -d: -f2- | tr -d ' ')" [ -z "$st" ] && st="new" echo "| ${label} | ${d}/ | ${ver:-?} | ${st} |" done echo "" echo "## Structuur" echo '```' echo "pack/" echo "├── cms/ # EpicNext-CMS (deze repo)" echo "├── catalogus/ # Epicnabbo-Catalogus-Updated-Daily" echo "├── nitro/ # Nitro-V3 client build (dist)" echo "├── render/ # Nitro-Render-V3" echo "├── emulator/ # Polaris Emulator jar" echo "├── MANIFEST.md # dit bestand" echo "└── COMPONENTS.txt # raw versies" echo '```' } > "$WORK/out/MANIFEST.md" cp "$WORK/out/MANIFEST.md" "$WORK/out/COMPONENTS.txt" echo "Built at $(date -u)" > "$WORK/out/BUILD_INFO.txt" cat "$WORK/components.txt" >> "$WORK/out/BUILD_INFO.txt" PACK_FILE="/tmp/${PACK_VERSION}.tar.gz" tar -czf "$PACK_FILE" -C "$WORK/out" . echo "PACK_FILE=$PACK_FILE" >> "$GITHUB_ENV" echo "Pack created: $(du -h "$PACK_FILE" | cut -f1)" - name: Build Changelog if: steps.check.outputs.changed == 'true' run: | set -e BARE="/docker/gitea/gitea/git/repositories/remco/epicnext-cms.git" PREV="$(git -C "$BARE" tag --list 'pack-*' --sort=-creatordate | head -1 || echo '')" PREV_NOTE="${PREV:-initial}" { echo "## Site Pack ${PACK_VERSION}" echo "" echo "Volledige pack met alle componenten. Zie het bijgevoegde \`MANIFEST.md\` in de download voor de exacte versies per component." echo "" echo "### Wat zit erin (deze pack)" echo '```' while IFS= read -r line; do key="${line%%:*}"; rest="${line#*: }" st="$(grep "^${key}:" "$WORK/status.txt" 2>/dev/null | head -1 | cut -d: -f2- | tr -d ' ')" [ -z "$st" ] && st="new" printf "%-12s %s [%s]\n" "$key" "$rest" "$st" done < "$WORK/components.txt" echo '```' echo "" echo "### Wat is nieuw sinds pack ${PREV_NOTE}" for pair in "cms:$WORK/src/cms" "catalogus:$WORK/src/catalogus" "nitro:$WORK/src/nitro" "render:$WORK/src/Nitro_Render_V3" "emulator:$WORK/src/emulator"; do label="${pair%%:*}"; repo="${pair##*:}" echo "**${label}**" echo '```' git -C "$repo" log --oneline --no-decorated -10 2>/dev/null || echo "n/a" echo '```' done } > /tmp/pack-body.md cat /tmp/pack-body.md - name: Create Release + Upload Pack if: steps.check.outputs.changed == 'true' run: | set -e : "${PACK_FILE:?PACK_FILE not set}" : "${GITEA_API:?GITEA_API not set}" : "${GITEA_REPO:?GITEA_REPO not set}" T="${TOKEN:-${GITEA_TOKEN}}" [ -n "$T" ] || { echo "No token available"; exit 1; } # Diagnostics: surface exactly what the runner resolves. echo "GITEA_API=[${GITEA_API}]" echo "GITEA_REPO=[${GITEA_REPO}]" echo "pack-body.md bytes: $(wc -c < /tmp/pack-body.md 2>/dev/null || echo MISSING)" echo "pack-body.md last bytes (xxd):"; tail -c 40 /tmp/pack-body.md 2>/dev/null | xxd | tail -3 BODY="$(jq -Rs '.' < /tmp/pack-body.md)" PAYLOAD="$(jq -n --arg tag "${PACK_VERSION}" --arg name "${PACK_VERSION}" --arg body "$BODY" '{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')" echo "PAYLOAD bytes: ${#PAYLOAD}" echo "PAYLOAD head:"; echo "$PAYLOAD" | head -c 200; echo echo "Creating release via ${GITEA_API}/repos/${GITEA_REPO}/releases" HTTP="$(curl -s -w '%{http_code}' -o /tmp/pack-rel.json \ --retry 3 --retry-delay 3 \ -X POST "${GITEA_API}/repos/${GITEA_REPO}/releases" \ -H "Authorization: token ${T}" -H "Content-Type: application/json" -d "$PAYLOAD" || echo 000)" echo "Create release HTTP: ${HTTP}" cat /tmp/pack-rel.json | head -c 500; echo if [ "${HTTP}" = "409" ]; then REL_ID="$(curl -sf "${GITEA_API}/repos/${GITEA_REPO}/releases" -H "Authorization: token ${T}" | jq -r ".[] | select(.tag_name==\"${PACK_VERSION}\") | .id" || true)" HTTP="$(curl -s -w '%{http_code}' -o /tmp/pack-rel.json -X PATCH "${GITEA_API}/repos/${GITEA_REPO}/releases/${REL_ID}" -H "Authorization: token ${T}" -H "Content-Type: application/json" -d "$PAYLOAD" || echo 000)" echo "Patch release HTTP: ${HTTP}" fi REL_ID="$(jq -r '.id' /tmp/pack-rel.json 2>/dev/null || echo "")" [ -n "$REL_ID" ] || { echo "No release ID from response"; exit 1; } echo "Uploading asset to release ${REL_ID}" UP="$(curl -s -w '%{http_code}' -o /tmp/pack-asset.json \ -X POST "${GITEA_API}/repos/${GITEA_REPO}/releases/${REL_ID}/assets?name=${PACK_VERSION}.tar.gz" \ -H "Authorization: token ${T}" -H "Content-Type: application/gzip" --data-binary @"${PACK_FILE}" || echo 000)" echo "Asset upload HTTP: ${UP}" cat /tmp/pack-asset.json | head -c 300; echo echo "Release ${PACK_VERSION} created with pack asset"