From 163e593d26c5af5be6de7f990d7b6a740726802e Mon Sep 17 00:00:00 2001 From: Rody Davis Date: Tue, 13 Oct 2026 12:07:50 -0700 Subject: [PATCH] fix: extract deploy script to separate file to resolve YAML parsing error --- pages-deploy/action.yml | 192 +------------------------------------- pages-deploy/deploy.sh | 200 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 191 deletions(-) create mode 100755 pages-deploy/deploy.sh diff --git a/pages-deploy/action.yml b/pages-deploy/action.yml index 787997c..cbd261f 100644 --- a/pages-deploy/action.yml +++ b/pages-deploy/action.yml @@ -82,194 +82,4 @@ runs: GITHUB_REPOSITORY: ${{ github.repository }} GITHUB_ACTOR: ${{ github.actor }} run: | - set -euo pipefail - - echo "🚀 Starting Static Pages Deployment..." - - # 1. Resolve repository name fallback - if [ -z "${REPO_NAME:-}" ]; then - REPO_NAME=$(basename "${GITHUB_REPOSITORY:-app}") - fi - - # 2. Locate static build assets - ASSET_DIR="$INPUT_PATH" - if [ ! -d "$ASSET_DIR" ]; then - echo "Specified path '$ASSET_DIR' not found. Searching common output directories..." - if [ -d "./dist" ]; then - ASSET_DIR="./dist" - elif [ -d "./build/web" ]; then - ASSET_DIR="./build/web" - elif [ -d "./web/dist" ]; then - ASSET_DIR="./web/dist" - elif [ -d "./public" ]; then - ASSET_DIR="./public" - elif [ -d "./build" ]; then - ASSET_DIR="./build" - elif [ -d "./out" ]; then - ASSET_DIR="./out" - else - echo "::error::Could not find static asset directory! Checked: '$INPUT_PATH', ./dist, ./build/web, ./public, ./build, ./out" - exit 1 - fi - fi - echo "📁 Using static assets from: $ASSET_DIR" - - # 3. Resolve target domain & router name - DOMAIN="$INPUT_DOMAIN" - if [ -n "$DOMAIN" ]; then - DOMAIN=$(echo "$DOMAIN" | sed -E 's#^https?://##' | sed 's#/.*$##') - else - CLEAN_REPO=$(echo "$REPO_NAME" | tr '_' '-' | tr '[:upper:]' '[:lower:]') - DOMAIN="${CLEAN_REPO}.rodydavis.dev" - fi - - APP_NAME="${INPUT_APP_NAME:-$REPO_NAME}" - APP_NAME=$(echo "$APP_NAME" | tr '_' '-' | tr '[:upper:]' '[:lower:]') - ROUTER_NAME=$(echo "$APP_NAME" | tr -cd '[:alnum:]') - - # 4. Resolve container image tag - REGISTRY="${INPUT_REGISTRY:-git.rodydavis.dev}" - if [ -n "$INPUT_IMAGE_NAME" ]; then - IMAGE_NAME="$INPUT_IMAGE_NAME" - else - REPO_LOWER=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') - IMAGE_NAME="${REGISTRY}/${REPO_LOWER}:latest" - fi - - echo "🌐 Routing Target: https://${DOMAIN}" - echo "📦 Container Name: ${APP_NAME}-web" - echo "🏷️ Image Tag: ${IMAGE_NAME}" - - # 5. Prepare build bundle in temporary staging directory - BUILD_STAGING=$(mktemp -d) - trap 'rm -rf "$BUILD_STAGING"' EXIT - - mkdir -p "${BUILD_STAGING}/html" - cp -R "${ASSET_DIR}/." "${BUILD_STAGING}/html/" - - # Configure SPA fallback rule - if [ "$INPUT_SPA" = "true" ]; then - SPA_DIRECTIVE='try_files $uri $uri/ /index.html;' - else - SPA_DIRECTIVE='try_files $uri $uri/ =404;' - fi - - cat < "${BUILD_STAGING}/nginx.conf" -server { - listen 80; - listen [::]:80; - server_name localhost; - - root /usr/share/nginx/html; - index index.html index.htm; - - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json application/manifest+json image/svg+xml; - - location = /sw.js { - add_header Cache-Control "no-cache, no-store, must-revalidate"; - expires 0; - } - - location = /manifest.json { - add_header Cache-Control "no-cache, must-revalidate"; - expires 0; - } - - location / { - ${SPA_DIRECTIVE} - - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "strict-origin-when-cross-origin" always; - } - - location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff|woff2)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} -EOF - - cat < "${BUILD_STAGING}/Dockerfile" -FROM nginx:alpine -WORKDIR /usr/share/nginx/html -RUN rm -rf ./* -COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY html/ ./ -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -EOF - - cat < "${BUILD_STAGING}/docker-compose.yml" -version: '3.8' - -services: - ${APP_NAME}-web: - image: ${IMAGE_NAME} - container_name: ${APP_NAME}-web - restart: unless-stopped - networks: - - ${INPUT_NETWORK} - labels: - - "traefik.enable=true" - - "traefik.http.routers.${ROUTER_NAME}.rule=Host(\`${DOMAIN}\`)" - - "traefik.http.routers.${ROUTER_NAME}.entrypoints=${INPUT_ENTRYPOINTS}" - - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=80" - -networks: - ${INPUT_NETWORK}: - external: true -EOF - - # 6. Docker Build - echo "🔨 Building Docker image..." - docker build -t "$IMAGE_NAME" -f "${BUILD_STAGING}/Dockerfile" "$BUILD_STAGING" - - # 7. Push image to registry - if [ "$INPUT_PUSH_IMAGE" = "true" ]; then - if [ -n "$INPUT_GIT_TOKEN" ]; then - echo "🔐 Logging into ${REGISTRY}..." - echo "$INPUT_GIT_TOKEN" | docker login "$REGISTRY" -u "${GITHUB_ACTOR}" --password-stdin - fi - echo "📤 Pushing image to ${IMAGE_NAME}..." - docker push "$IMAGE_NAME" - else - echo "⏭️ Skipping image push (push_image=false)" - fi - - # 8. Deploy to TrueNAS - if [ "$INPUT_DEPLOY" = "true" ]; then - if [ -z "$INPUT_SSH_KEY" ]; then - echo "⚠️ No ssh_key provided for TrueNAS. Skipping remote deployment step." - else - echo "🚢 Deploying to TrueNAS SCALE (${INPUT_HOST})..." - mkdir -p ~/.ssh - echo "$INPUT_SSH_KEY" > ~/.ssh/truenas_deploy_key - chmod 600 ~/.ssh/truenas_deploy_key - ssh-keyscan -H "$INPUT_HOST" >> ~/.ssh/known_hosts 2>/dev/null - - REMOTE_DIR="/mnt/jupiter/apps/${APP_NAME}" - echo "Ensuring remote directory ${REMOTE_DIR} on ${INPUT_HOST}..." - ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${INPUT_USER}@${INPUT_HOST}" "mkdir -p ${REMOTE_DIR}" - - echo "Syncing docker-compose.yml to ${REMOTE_DIR}..." - scp -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${BUILD_STAGING}/docker-compose.yml" "${INPUT_USER}@${INPUT_HOST}:${REMOTE_DIR}/docker-compose.yml" - - echo "Pulling latest image and restarting stack..." - ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${INPUT_USER}@${INPUT_HOST}" "cd ${REMOTE_DIR} && docker compose pull && docker compose up -d --remove-orphans" - - rm -f ~/.ssh/truenas_deploy_key - echo "✅ Deployment complete! Accessible at: https://${DOMAIN}" - fi - else - echo "⏭️ Skipping deployment step (deploy=false)" - fi + bash "${{ github.action_path }}/deploy.sh" diff --git a/pages-deploy/deploy.sh b/pages-deploy/deploy.sh new file mode 100755 index 0000000..5bce166 --- /dev/null +++ b/pages-deploy/deploy.sh @@ -0,0 +1,200 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "🚀 Starting Static Pages Deployment..." + +# 1. Resolve repository name fallback +if [ -z "${REPO_NAME:-}" ]; then + REPO_NAME=$(basename "${GITHUB_REPOSITORY:-app}") +fi + +# 2. Locate static build assets +ASSET_DIR="${INPUT_PATH:-./dist}" +if [ ! -d "$ASSET_DIR" ]; then + echo "Specified path '$ASSET_DIR' not found. Searching common output directories..." + if [ -d "./dist" ]; then + ASSET_DIR="./dist" + elif [ -d "./build/web" ]; then + ASSET_DIR="./build/web" + elif [ -d "./web/dist" ]; then + ASSET_DIR="./web/dist" + elif [ -d "./public" ]; then + ASSET_DIR="./public" + elif [ -d "./build" ]; then + ASSET_DIR="./build" + elif [ -d "./out" ]; then + ASSET_DIR="./out" + elif [ -d "./_site" ]; then + ASSET_DIR="./_site" + else + echo "::error::Could not find static asset directory! Checked: '$ASSET_DIR', ./dist, ./build/web, ./public, ./build, ./out, ./_site" + exit 1 + fi +fi +echo "📁 Using static assets from: $ASSET_DIR" + +# 3. Resolve target domain & router name +DOMAIN="${INPUT_DOMAIN:-}" +if [ -n "$DOMAIN" ]; then + DOMAIN=$(echo "$DOMAIN" | sed -E 's#^https?://##' | sed 's#/.*$##') +else + CLEAN_REPO=$(echo "$REPO_NAME" | tr '_' '-' | tr '[:upper:]' '[:lower:]') + DOMAIN="${CLEAN_REPO}.rodydavis.dev" +fi + +APP_NAME="${INPUT_APP_NAME:-$REPO_NAME}" +APP_NAME=$(echo "$APP_NAME" | tr '_' '-' | tr '[:upper:]' '[:lower:]') +ROUTER_NAME=$(echo "$APP_NAME" | tr -cd '[:alnum:]') + +# 4. Resolve container image tag +REGISTRY="${INPUT_REGISTRY:-git.rodydavis.dev}" +if [ -n "${INPUT_IMAGE_NAME:-}" ]; then + IMAGE_NAME="$INPUT_IMAGE_NAME" +else + REPO_LOWER=$(echo "$GITHUB_REPOSITORY" | tr '[:upper:]' '[:lower:]') + IMAGE_NAME="${REGISTRY}/${REPO_LOWER}:latest" +fi + +echo "🌐 Routing Target: https://${DOMAIN}" +echo "📦 Container Name: ${APP_NAME}-web" +echo "🏷️ Image Tag: ${IMAGE_NAME}" + +# 5. Prepare build bundle in temporary staging directory +BUILD_STAGING=$(mktemp -d) +trap 'rm -rf "$BUILD_STAGING"' EXIT + +mkdir -p "${BUILD_STAGING}/html" +cp -R "${ASSET_DIR}/." "${BUILD_STAGING}/html/" + +# Configure SPA fallback rule +if [ "${INPUT_SPA:-true}" = "true" ]; then + SPA_DIRECTIVE='try_files $uri $uri/ /index.html;' +else + SPA_DIRECTIVE='try_files $uri $uri/ =404;' +fi + +cat < "${BUILD_STAGING}/nginx.conf" +server { + listen 80; + listen [::]:80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html index.htm; + + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/javascript application/json application/manifest+json image/svg+xml; + + location = /sw.js { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + expires 0; + } + + location = /manifest.json { + add_header Cache-Control "no-cache, must-revalidate"; + expires 0; + } + + location / { + ${SPA_DIRECTIVE} + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + } + + location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff|woff2)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} +EOF + +cat < "${BUILD_STAGING}/Dockerfile" +FROM nginx:alpine +WORKDIR /usr/share/nginx/html +RUN rm -rf ./* +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY html/ ./ +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +EOF + +NETWORK="${INPUT_NETWORK:-apps-net}" +ENTRYPOINTS="${INPUT_ENTRYPOINTS:-web}" + +cat < "${BUILD_STAGING}/docker-compose.yml" +version: '3.8' + +services: + ${APP_NAME}-web: + image: ${IMAGE_NAME} + container_name: ${APP_NAME}-web + restart: unless-stopped + networks: + - ${NETWORK} + labels: + - "traefik.enable=true" + - "traefik.http.routers.${ROUTER_NAME}.rule=Host(\`${DOMAIN}\`)" + - "traefik.http.routers.${ROUTER_NAME}.entrypoints=${ENTRYPOINTS}" + - "traefik.http.services.${ROUTER_NAME}.loadbalancer.server.port=80" + +networks: + ${NETWORK}: + external: true +EOF + +# 6. Docker Build +echo "🔨 Building Docker image..." +docker build -t "$IMAGE_NAME" -f "${BUILD_STAGING}/Dockerfile" "$BUILD_STAGING" + +# 7. Push image to registry +if [ "${INPUT_PUSH_IMAGE:-true}" = "true" ]; then + if [ -n "${INPUT_GIT_TOKEN:-}" ]; then + echo "🔐 Logging into ${REGISTRY}..." + echo "$INPUT_GIT_TOKEN" | docker login "$REGISTRY" -u "${GITHUB_ACTOR}" --password-stdin + fi + echo "📤 Pushing image to ${IMAGE_NAME}..." + docker push "$IMAGE_NAME" +else + echo "⏭️ Skipping image push (push_image=false)" +fi + +# 8. Deploy to TrueNAS +if [ "${INPUT_DEPLOY:-true}" = "true" ]; then + if [ -z "${INPUT_SSH_KEY:-}" ]; then + echo "⚠️ No ssh_key provided for TrueNAS. Skipping remote deployment step." + else + HOST="${INPUT_HOST:-10.0.0.218}" + USER="${INPUT_USER:-root}" + + echo "🚢 Deploying to TrueNAS SCALE (${HOST})..." + mkdir -p ~/.ssh + echo "$INPUT_SSH_KEY" > ~/.ssh/truenas_deploy_key + chmod 600 ~/.ssh/truenas_deploy_key + ssh-keyscan -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null + + REMOTE_DIR="/mnt/jupiter/apps/${APP_NAME}" + echo "Ensuring remote directory ${REMOTE_DIR} on ${HOST}..." + ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "mkdir -p ${REMOTE_DIR}" + + echo "Syncing docker-compose.yml to ${REMOTE_DIR}..." + scp -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${BUILD_STAGING}/docker-compose.yml" "${USER}@${HOST}:${REMOTE_DIR}/docker-compose.yml" + + echo "Pulling latest image and restarting stack..." + ssh -i ~/.ssh/truenas_deploy_key -o StrictHostKeyChecking=no "${USER}@${HOST}" "cd ${REMOTE_DIR} && docker compose pull && docker compose up -d --remove-orphans" + + rm -f ~/.ssh/truenas_deploy_key + echo "✅ Deployment complete! Accessible at: https://${DOMAIN}" + fi +else + echo "⏭️ Skipping deployment step (deploy=false)" +fi