feat: add in copy of updated shared deployment workflows for now until they are
published
This commit is contained in:
parent
bcb7f86b7f
commit
e245bcbe96
12 changed files with 1113 additions and 0 deletions
45
.github/actions/determine-build-metadata/action.yml
vendored
Normal file
45
.github/actions/determine-build-metadata/action.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
name: "Determine Build Metadata"
|
||||
description: "Extract repository metadata and build configuration from GitHub context"
|
||||
author: "Portfolio CI/CD"
|
||||
|
||||
inputs:
|
||||
repository:
|
||||
description: "Repository name in format owner/repo"
|
||||
required: true
|
||||
github-ref:
|
||||
description: "GitHub ref (e.g., refs/heads/main, refs/tags/v1.0.0)"
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
repo-name:
|
||||
description: "Repository name only (without owner)"
|
||||
value: ${{ steps.meta.outputs.repo-name }}
|
||||
repo-path:
|
||||
description: "Server deployment path"
|
||||
value: ${{ steps.meta.outputs.repo-path }}
|
||||
prod:
|
||||
description: "Whether this is a production build (true/false)"
|
||||
value: ${{ steps.meta.outputs.prod }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
set -eu -o pipefail
|
||||
|
||||
REPO="${{ inputs.repository }}"
|
||||
REPO_NAME_ONLY="$(echo "${REPO}" | cut -d'/' -f2)"
|
||||
REPO_PROJECT_PATH="/srv/${REPO_NAME_ONLY}"
|
||||
PROD="${{ startsWith(inputs.github-ref, 'refs/heads/main') }}"
|
||||
|
||||
|
||||
echo "🖊️ Writing determined values:"
|
||||
echo "repo-name=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
|
||||
echo "repo-name -> $REPO_NAME_ONLY"
|
||||
echo "repo-path=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT
|
||||
echo "repo-path -> $REPO_PROJECT_PATH"
|
||||
echo "prod=$PROD" >> $GITHUB_OUTPUT
|
||||
echo "prod -> $PROD"
|
||||
57
.github/actions/push-to-origin/action.yml
vendored
Normal file
57
.github/actions/push-to-origin/action.yml
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
name: "Push to Git Origin"
|
||||
description: "Push branches and tags to Git origin repository"
|
||||
author: "Portfolio CI/CD"
|
||||
|
||||
inputs:
|
||||
git-origin-url:
|
||||
description: "Git origin repository URL (e.g., git@example.com:user/repo.git)"
|
||||
required: true
|
||||
branches:
|
||||
description: 'Space-separated list of branches to push (e.g., "main staging dev" or "staging")'
|
||||
required: true
|
||||
push-tags:
|
||||
description: "Whether to push tags along with branches"
|
||||
required: false
|
||||
default: "false"
|
||||
force:
|
||||
description: "Whether to force push"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup Git Remotes
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🔧 Setting up git remotes..."
|
||||
|
||||
git remote remove git-origin 2>/dev/null || true
|
||||
git remote add git-origin ${{ inputs.git-origin-url }}
|
||||
|
||||
echo "✅ git-origin remote configured"
|
||||
|
||||
- name: Push to Git Origin
|
||||
shell: bash
|
||||
run: |
|
||||
echo "📤 Pushing to Git Origin..."
|
||||
|
||||
PUSH_ARGS="${{ inputs.branches }}"
|
||||
|
||||
if [[ "${{ inputs.push-tags }}" == "true" ]]; then
|
||||
PUSH_ARGS="$PUSH_ARGS --tags"
|
||||
echo "🏷️ Including tags in Git origin push"
|
||||
fi
|
||||
|
||||
if [[ "${{ inputs.force }}" == "true" ]]; then
|
||||
PUSH_ARGS="$PUSH_ARGS --force"
|
||||
echo "🔥 Forcing push to Git origin"
|
||||
fi
|
||||
|
||||
if git push git-origin $PUSH_ARGS; then
|
||||
echo "✅ Successfully pushed to Git origin"
|
||||
else
|
||||
echo "❌ Failed to push to Git origin"
|
||||
echo "::warning::Push to Git origin failed, but GitHub push succeeded"
|
||||
exit 1
|
||||
fi
|
||||
78
.github/actions/setup-environment/action.yml
vendored
Normal file
78
.github/actions/setup-environment/action.yml
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
name: 'Setup Environment'
|
||||
description: 'Create environment files and variables for deployment'
|
||||
author: 'Portfolio CI/CD'
|
||||
|
||||
inputs:
|
||||
release-type:
|
||||
description: 'Release type (staging or prod)'
|
||||
required: true
|
||||
domain:
|
||||
description: 'Domain name'
|
||||
required: true
|
||||
android-sms-gateway-url:
|
||||
description: 'Android SMS Gateway URL'
|
||||
required: true
|
||||
android-sms-gateway-login:
|
||||
description: 'Android SMS Gateway login'
|
||||
required: true
|
||||
android-sms-gateway-password:
|
||||
description: 'Android SMS Gateway password'
|
||||
required: true
|
||||
my-phone-number:
|
||||
description: 'Phone number for SMS delivery'
|
||||
required: true
|
||||
super-secret-salt:
|
||||
description: 'Secret salt for TOTP generation'
|
||||
required: true
|
||||
wireguard-allowed-ips:
|
||||
description: 'WireGuard allowed IPs'
|
||||
required: true
|
||||
wireguard-private-key:
|
||||
description: 'WireGuard private key'
|
||||
required: true
|
||||
wireguard-addresses:
|
||||
description: 'WireGuard addresses'
|
||||
required: true
|
||||
wireguard-public-key:
|
||||
description: 'WireGuard public key'
|
||||
required: true
|
||||
wireguard-endpoint-host:
|
||||
description: 'WireGuard endpoint host'
|
||||
required: true
|
||||
wireguard-endpoint-port:
|
||||
description: 'WireGuard endpoint port'
|
||||
required: true
|
||||
prod:
|
||||
description: 'Whether this is a production deployment (true/false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
env-file:
|
||||
description: 'Path to the created .env file'
|
||||
value: '.env'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Create .env from secrets and environment variables
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🔧 Creating environment configuration..."
|
||||
|
||||
echo "DOMAIN=\"${{ inputs.domain }}\"" > .env
|
||||
echo "NUXT_ANDROID_SMS_GATEWAY_URL=\"${{ inputs.android-sms-gateway-url }}\"" >> .env
|
||||
echo "NUXT_ANDROID_SMS_GATEWAY_LOGIN=\"${{ inputs.android-sms-gateway-login }}\"" >> .env
|
||||
echo "NUXT_ANDROID_SMS_GATEWAY_PASSWORD=\"${{ inputs.android-sms-gateway-password }}\"" >> .env
|
||||
echo "NUXT_MY_PHONE_NUMBER=\"${{ inputs.my-phone-number }}\"" >> .env
|
||||
echo "NUXT_SUPER_SECRET_SALT=\"${{ inputs.super-secret-salt }}\"" >> .env
|
||||
echo "WIREGUARD_ALLOWED_IPS=\"${{ inputs.wireguard-allowed-ips }}\"" >> .env
|
||||
echo "WIREGUARD_PRIVATE_KEY=\"${{ inputs.wireguard-private-key }}\"" >> .env
|
||||
echo "WIREGUARD_ADDRESSES=\"${{ inputs.wireguard-addresses }}\"" >> .env
|
||||
echo "WIREGUARD_PUBLIC_KEY=\"${{ inputs.wireguard-public-key }}\"" >> .env
|
||||
echo "WIREGUARD_ENDPOINT_HOST=\"${{ inputs.wireguard-endpoint-host }}\"" >> .env
|
||||
echo "WIREGUARD_ENDPOINT_PORT=\"${{ inputs.wireguard-endpoint-port }}\"" >> .env
|
||||
echo "RELEASE_TYPE=\"${{ inputs.release-type }}\"" >> .env
|
||||
echo "PROD=\"${{ inputs.prod }}\"" >> .env
|
||||
|
||||
echo "✅ Environment file created with $(wc -l < .env) variables"
|
||||
52
.github/actions/setup-git-ssh/action.yml
vendored
Normal file
52
.github/actions/setup-git-ssh/action.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
name: "Setup Git SSH"
|
||||
description: "Configure SSH key and Git signing for commits and pushes"
|
||||
author: "Portfolio CI/CD"
|
||||
|
||||
inputs:
|
||||
commit-ssh-private-key:
|
||||
description: "SSH private key for commit signing"
|
||||
required: false
|
||||
push-ssh-private-key:
|
||||
description: "SSH private key for git-origin operations"
|
||||
required: false
|
||||
actor:
|
||||
description: "GitHub actor name for git user configuration"
|
||||
default: ""
|
||||
required: false
|
||||
actor-id:
|
||||
description: "GitHub actor ID for git email configuration"
|
||||
default: ""
|
||||
required: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Setup git user.name and user.email
|
||||
shell: bash
|
||||
run: |
|
||||
git config user.name "${{ inputs.actor != '' && inputs.actor || github.actor }}"
|
||||
git config user.email "${{ inputs.actor-id != '' && inputs.actor-id || github.actor_id }}+${{ inputs.actor != '' && inputs.actor || github.actor }}@users.noreply.github.com"
|
||||
- name: Setup SSH for key-based push
|
||||
shell: bash
|
||||
if: ${{ inputs.push-ssh-private-key != '' }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ inputs.push-ssh-private-key }}" > ~/.ssh/id-push
|
||||
chmod 600 ~/.ssh/id-push
|
||||
cat > ~/.ssh/config <<EOF
|
||||
Host *
|
||||
UserKnownHostsFile /dev/null
|
||||
StrictHostKeyChecking no
|
||||
IdentityFile $HOME/.ssh/id-push
|
||||
LogLevel QUIET
|
||||
EOF
|
||||
- name: Setup git for commit signing
|
||||
shell: bash
|
||||
if: ${{ inputs.commit-ssh-private-key != '' }}
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ inputs.commit-ssh-private-key }}" > ~/.ssh/id-commit
|
||||
chmod 600 ~/.ssh/id-commit
|
||||
git config gpg.format ssh
|
||||
git config user.signingkey ~/.ssh/id-commit
|
||||
git config commit.gpgsign true
|
||||
140
.github/actions/validate-build-status/action.yml
vendored
Normal file
140
.github/actions/validate-build-status/action.yml
vendored
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
name: "Validate Build Status"
|
||||
description: "Validate that a build workflow has completed successfully for a specific branch and commit"
|
||||
author: "Portfolio CI/CD"
|
||||
|
||||
inputs:
|
||||
from-branch:
|
||||
description: "Name of the branch to check (e.g., dev, staging)"
|
||||
required: true
|
||||
to-branch:
|
||||
description: "Base branch to compare against (e.g., staging, main)"
|
||||
required: true
|
||||
job-name:
|
||||
description: "Name of the build job to check"
|
||||
required: true
|
||||
workflow-name:
|
||||
description: "Name of the workflow to check"
|
||||
required: false
|
||||
default: "Build/Deploy"
|
||||
force:
|
||||
description: "Force validation even if other conditions fail"
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
outputs:
|
||||
should-proceed:
|
||||
description: "Whether the validation passed and should proceed"
|
||||
value: ${{ steps.validation.outputs.should_proceed }}
|
||||
commits-ahead:
|
||||
description: "Number of commits ahead of comparison branch"
|
||||
value: ${{ steps.validation.outputs.commits_ahead }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Run validation checks
|
||||
id: validation
|
||||
shell: bash
|
||||
run: |
|
||||
echo "🔍 Running build validation checks for ${{ inputs.from-branch }}..."
|
||||
|
||||
# Check if there are new commits in branch since last comparison
|
||||
git checkout ${{ inputs.from-branch }}
|
||||
# git fetch --tags
|
||||
# git pull --tags
|
||||
COMMITS_AHEAD=$(git rev-list --count origin/${{ inputs.to-branch }}..${{ inputs.from-branch }})
|
||||
echo "📊 Commits ahead of ${{ inputs.to-branch }}: $COMMITS_AHEAD"
|
||||
echo "commits_ahead=$COMMITS_AHEAD" >> $GITHUB_OUTPUT
|
||||
|
||||
if [[ "${{ inputs.force }}" == "true" ]]; then
|
||||
echo "⚡ Force flag enabled - skipping all validation checks"
|
||||
echo "should_proceed=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$COMMITS_AHEAD" -eq 0 ]; then
|
||||
echo "ℹ️ No new commits to process"
|
||||
echo "should_proceed=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for any [skip ci] in recent commit messages
|
||||
SKIP_CI=$(git log --oneline origin/${{ inputs.to-branch }}..${{ inputs.from-branch }} | grep -i "\[skip ci\]" || true)
|
||||
if [ -n "$SKIP_CI" ]; then
|
||||
echo "⏭️ Skipping processing due to [skip ci] flag in commits"
|
||||
echo "should_proceed=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get the actual branch HEAD commit
|
||||
COMMIT_SHA=$(git rev-parse ${{ inputs.from-branch }})
|
||||
echo "🔍 Checking build job status for ${{ inputs.from-branch }} commit: $COMMIT_SHA"
|
||||
|
||||
# Get workflow runs for the specific commit
|
||||
echo "🔍 Querying GitHub API for workflow runs..."
|
||||
RUNS_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/actions/runs?head_sha=${COMMIT_SHA}")
|
||||
|
||||
# Check if API call was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Failed to call GitHub API"
|
||||
echo "::error::GitHub API call failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if response contains total_count
|
||||
WORKFLOW_RUNS_COUNT=$(echo "$RUNS_RESPONSE" | jq -r '.total_count' 2>/dev/null || echo "error")
|
||||
|
||||
if [ "$WORKFLOW_RUNS_COUNT" = "error" ] || [ "$WORKFLOW_RUNS_COUNT" = "null" ]; then
|
||||
echo "❌ Invalid API response format"
|
||||
echo "API Response: $RUNS_RESPONSE"
|
||||
echo "::error::Invalid response from GitHub API"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📊 Found $WORKFLOW_RUNS_COUNT workflow run(s) for commit $COMMIT_SHA"
|
||||
|
||||
# Extract run ID for build_deploy.yml workflow (safely handle empty results)
|
||||
BUILD_RUN_ID=$(echo "$RUNS_RESPONSE" | jq -r '.workflow_runs[] | select(.name == "${{ inputs.workflow-name }}") | .id' 2>/dev/null | head -1)
|
||||
|
||||
# Remove any whitespace/newlines
|
||||
BUILD_RUN_ID=$(echo "$BUILD_RUN_ID" | tr -d '\n\r ')
|
||||
|
||||
if [ -z "$BUILD_RUN_ID" ] || [ "$BUILD_RUN_ID" = "null" ]; then
|
||||
echo "❌ No build workflow run found for commit ${COMMIT_SHA}"
|
||||
echo "::error::Expected build workflow run not found - this indicates a problem with the CI/CD pipeline"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Found build workflow run: $BUILD_RUN_ID"
|
||||
|
||||
# Get jobs for the workflow run
|
||||
JOBS_RESPONSE=$(curl -s -H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/actions/runs/${BUILD_RUN_ID}/jobs")
|
||||
|
||||
# Find the build job status
|
||||
BUILD_JOB_STATUS=$(echo "$JOBS_RESPONSE" | jq -r '.jobs[] | select(.name == "${{ inputs.job-name }}") | .status')
|
||||
BUILD_JOB_CONCLUSION=$(echo "$JOBS_RESPONSE" | jq -r '.jobs[] | select(.name == "${{ inputs.job-name }}") | .conclusion')
|
||||
|
||||
echo "📊 Build job status: $BUILD_JOB_STATUS, conclusion: $BUILD_JOB_CONCLUSION"
|
||||
|
||||
if [ "$BUILD_JOB_STATUS" = "completed" ] && ([ "$BUILD_JOB_CONCLUSION" = "success" ] || [ "$BUILD_JOB_CONCLUSION" = "skipped" ]); then
|
||||
if [ "$BUILD_JOB_CONCLUSION" = "skipped" ]; then
|
||||
echo "✅ Build job skipped (artifact already exists)!"
|
||||
else
|
||||
echo "✅ Build job completed successfully!"
|
||||
fi
|
||||
else
|
||||
echo "❌ Build job not successful (status: $BUILD_JOB_STATUS, conclusion: $BUILD_JOB_CONCLUSION)"
|
||||
echo "🔗 View job details: https://github.com/${{ github.repository }}/actions/runs/${BUILD_RUN_ID}"
|
||||
echo "If job is in_progress, wait for it to complete, then re-run this workflow"
|
||||
echo "should_proceed=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ No blockers, ready to proceed"
|
||||
echo "should_proceed=true" >> $GITHUB_OUTPUT
|
||||
240
.github/workflows/build.yml
vendored
Normal file
240
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
# yaml-language-server: $schema=https://json-schema.org/draft-07/schema#
|
||||
name: _build
|
||||
|
||||
concurrency:
|
||||
group: build
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
outputs:
|
||||
should-build: ${{ steps.check.outputs.should-build }}
|
||||
artifact-run-id: ${{ steps.check.outputs.artifact-run-id }}
|
||||
artifact-id: ${{ steps.check.outputs.artifact-id }}
|
||||
build-sha: ${{ steps.check.outputs.build-sha }}
|
||||
repo-name: ${{ steps.meta.outputs.repo-name }}
|
||||
repo-path: ${{ steps.meta.outputs.repo-path }}
|
||||
image-tar: ${{ steps.check.outputs.image-tar }}
|
||||
prod: ${{ steps.meta.outputs.prod }}
|
||||
tag: ${{ steps.check.outputs.tag }}
|
||||
should-deploy: ${{ steps.check.outputs.should-deploy }}
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine build metadata
|
||||
id: meta
|
||||
uses: ./.github/actions/determine-build-metadata
|
||||
with:
|
||||
repository: ${{ github.repository }}
|
||||
github-ref: ${{ github.ref }}
|
||||
|
||||
- name: Check if build/deploy needed
|
||||
id: check
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -eu -o pipefail
|
||||
|
||||
SHOULD_BUILD=false
|
||||
SHOULD_DEPLOY=false
|
||||
|
||||
SHORT_SHA="${{ github.sha }}"
|
||||
SHORT_SHA="${SHORT_SHA:0:7}"
|
||||
IMAGE_TAR="${{ steps.meta.outputs.repo-name }}-${{github.sha}}.tar"
|
||||
VERSION="$(git tag --points-at ${{ github.sha }})"
|
||||
IS_VERSION_TAGGED="$([[ "$VERSION" =~ ^v ]] && echo "true" || echo "false")"
|
||||
BRANCH_NAME="$([[ "${{ github.ref }}" =~ ^refs/tags/v ]] && echo "main" || echo "${{ github.ref }}" | sed 's/refs\/heads\///')"
|
||||
COMMIT_MESSAGE="$(git log --format=%s -n 1 ${{ github.sha }})"
|
||||
if [[ "${{ steps.meta.outputs.prod }}" == "true" ]]; then
|
||||
TAG="${{ steps.meta.outputs.repo-name }}:sha-${SHORT_SHA},${{ steps.meta.outputs.repo-name }}:latest"
|
||||
else
|
||||
TAG="${{ steps.meta.outputs.repo-name }}-dev:sha-${SHORT_SHA}"
|
||||
fi
|
||||
|
||||
if [[ "$BRANCH_NAME" == "staging" ]]; then
|
||||
echo "🔨 Build is on staging - will also deploy to staging"
|
||||
SHOULD_DEPLOY="true"
|
||||
elif [[ "$BRANCH_NAME" == "main" ]]; then
|
||||
if [[ ! "$COMMIT_MESSAGE" =~ ^chore:\ Release ]] || [[ "$IS_VERSION_TAGGED" == "false" ]]; then
|
||||
echo "❌ Skipping build for main: $COMMIT_MESSAGE. Not a version release. Missing tag or proper commit message for release."
|
||||
exit 1
|
||||
fi
|
||||
echo "🏷️ Version tagged build on main: $VERSION - will also deploy to production"
|
||||
SHOULD_DEPLOY="true"
|
||||
fi
|
||||
|
||||
echo "🔍 Checking for in progress builds with git SHA ${{github.sha}}"
|
||||
IN_PROGRESS_BUILDS=""
|
||||
if ! IN_PROGRESS_BUILDS=$(gh api repos/${{github.repository}}/actions/runs --jq '.workflow_runs[] | select(.head_sha == "${{github.sha}}" and .status == "in_progress" and .name == "Build/Deploy" and .id != ${{ github.run_id }}) | .id' 2>/dev/null); then
|
||||
echo "❌ Failed to check for in-progress builds via GitHub API"
|
||||
echo "::error::GitHub API call failed while checking for in-progress builds"
|
||||
exit 1
|
||||
fi
|
||||
IN_PROGRESS_BUILDS=$(echo "$IN_PROGRESS_BUILDS" | head -1)
|
||||
|
||||
if [[ -n "$IN_PROGRESS_BUILDS" ]]; then
|
||||
echo "❌ Found in-progress build for SHA ${{github.sha}}: $IN_PROGRESS_BUILDS - wait for existing build to complete before triggering a new one or rerunning this workflow"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔍 Checking for existing build artifacts with git SHA ${{github.sha}}"
|
||||
|
||||
# Check for existing artifacts with this SHA across all workflow runs
|
||||
ARTIFACT_INFO=""
|
||||
if ! ARTIFACT_INFO=$(gh api repos/${{github.repository}}/actions/artifacts --jq '.artifacts[] | select(.name | endswith("-${{github.sha}}.tar")) | {id, name, workflow_run} | @json' 2>/dev/null); then
|
||||
echo "❌ Failed to check for existing artifacts via GitHub API"
|
||||
echo "::error::GitHub API call failed while checking for existing artifacts"
|
||||
exit 1
|
||||
fi
|
||||
ARTIFACT_INFO=$(echo "$ARTIFACT_INFO" | head -1)
|
||||
|
||||
if [[ -n "$ARTIFACT_INFO" ]]; then
|
||||
ARTIFACT_ID=$(echo "$ARTIFACT_INFO" | jq -r '.id')
|
||||
ARTIFACT_NAME=$(echo "$ARTIFACT_INFO" | jq -r '.name')
|
||||
RUN_ID=$(echo "$ARTIFACT_INFO" | jq -r '.workflow_run.id')
|
||||
|
||||
echo "✅ Found existing artifact $ARTIFACT_NAME from run: $RUN_ID"
|
||||
echo "artifact-id=$ARTIFACT_ID" >> $GITHUB_OUTPUT
|
||||
echo "artifact-id -> $ARTIFACT_ID"
|
||||
else
|
||||
RUN_ID="${{ github.run_id }}"
|
||||
SHOULD_BUILD="true"
|
||||
echo "🔨 No existing build artifact found; will build..."
|
||||
fi
|
||||
|
||||
echo "should-build=$SHOULD_BUILD" >> $GITHUB_OUTPUT
|
||||
echo "should-build -> $SHOULD_BUILD"
|
||||
echo "should-deploy=$SHOULD_DEPLOY" >> $GITHUB_OUTPUT
|
||||
echo "should-deploy -> $SHOULD_DEPLOY"
|
||||
echo "artifact-run-id=$RUN_ID" >> $GITHUB_OUTPUT
|
||||
echo "artifact-run-id -> $RUN_ID"
|
||||
echo "build-sha=${{github.sha}}" >> $GITHUB_OUTPUT
|
||||
echo "build-sha -> ${{github.sha}}"
|
||||
echo "image-tar=$IMAGE_TAR" >> $GITHUB_OUTPUT
|
||||
echo "image-tar -> $IMAGE_TAR"
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
echo "tag -> $TAG"
|
||||
|
||||
build:
|
||||
needs: check
|
||||
if: ${{ needs.check.outputs.should-build == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
artifact-id: ${{ steps.upload-artifact.outputs.artifact-id }}
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Ensure scripts are executable
|
||||
run: chmod +x deploy/scripts/*.sh
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Generate cache keys
|
||||
id: cache-keys
|
||||
run: |
|
||||
# Generate cache key based on package-lock.json for dependencies
|
||||
DEPS_HASH=$(sha256sum package-lock.json | cut -d' ' -f1 | cut -c1-8)
|
||||
echo "deps-hash=${DEPS_HASH}" >> $GITHUB_OUTPUT
|
||||
echo "deps-hash -> $DEPS_HASH"
|
||||
|
||||
# Generate cache key based on source files and paths
|
||||
SOURCE_PATHS="app/ server/ public/ nuxt.config.ts tsconfig.json tailwind.config.js package.json"
|
||||
SRC_HASH=$(find $SOURCE_PATHS -type f 2>/dev/null -exec sha256sum {} + | sha256sum | cut -d' ' -f1 | cut -c1-8 || echo "fallback-$(date +%s)")
|
||||
echo "src-hash=${SRC_HASH}" >> $GITHUB_OUTPUT
|
||||
echo "src-hash -> $SRC_HASH"
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}-src-${{ steps.cache-keys.outputs.src-hash }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}-src-${{ steps.cache-keys.outputs.src-hash }}-
|
||||
${{ runner.os }}-buildx-deps-${{ steps.cache-keys.outputs.deps-hash }}-
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Extract version for Docker build
|
||||
id: extract_version
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)(-.*)?$ ]]; then
|
||||
VERSION="${BASH_REMATCH[1]}"
|
||||
if [[ -n "${BASH_REMATCH[2]}" ]]; then
|
||||
VERSION="${VERSION}${BASH_REMATCH[2]}"
|
||||
fi
|
||||
echo "🏷️ Using git tag version: ${VERSION}"
|
||||
else
|
||||
VERSION=$(node -p "require('./package.json').version || '0.0.0'")
|
||||
SHA="${{ github.sha }}"
|
||||
GIT_SHA_SHORT="${SHA:0:7}"
|
||||
VERSION="${VERSION}+${GIT_SHA_SHORT}"
|
||||
echo "📦 Using package.json + SHA version: ${VERSION}"
|
||||
fi
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build container
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
outputs: type=docker,dest=${{ runner.temp }}/${{ needs.check.outputs.image-tar }}
|
||||
tags: ${{ needs.check.outputs.tag }}
|
||||
build-args: |
|
||||
VERSION=${{ env.VERSION }}
|
||||
context: .
|
||||
target: runtime
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||
platforms: linux/amd64
|
||||
|
||||
- name: Rotate cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
- name: Upload container as artifact
|
||||
id: upload-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ needs.check.outputs.image-tar }}
|
||||
path: ${{ runner.temp }}/${{ needs.check.outputs.image-tar }}
|
||||
if-no-files-found: error
|
||||
retention-days: 30
|
||||
compression-level: 0
|
||||
|
||||
- name: Notify successful build
|
||||
run: |
|
||||
echo "🎉 Build completed successfully!"
|
||||
echo "📋 Summary:"
|
||||
echo " - Source: ${{ github.ref }}"
|
||||
echo " - Status: ✅ Build Successful"
|
||||
echo " - Next: Stage this build manually or wait for the next scheduled staging."
|
||||
|
||||
deploy:
|
||||
needs: [check, build]
|
||||
if: ${{ always() && needs.check.outputs.should-deploy == 'true' }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
uses: ./.github/workflows/deploy.yml
|
||||
with:
|
||||
artifact-run-id: ${{ needs.check.outputs.artifact-run-id }}
|
||||
artifact-id: ${{ needs.check.outputs.should-build == 'true' && needs.build.outputs.artifact-id || needs.check.outputs.artifact-id }}
|
||||
build-sha: ${{ needs.check.outputs.build-sha }}
|
||||
repo-name: ${{ needs.check.outputs.repo-name }}
|
||||
repo-path: ${{ needs.check.outputs.repo-path }}
|
||||
image-tar: ${{ needs.check.outputs.image-tar }}
|
||||
prod: ${{ needs.check.outputs.prod == 'true' }}
|
||||
tag: ${{ needs.check.outputs.tag }}
|
||||
secrets: inherit
|
||||
159
.github/workflows/deploy.yml
vendored
Normal file
159
.github/workflows/deploy.yml
vendored
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
# yaml-language-server: $schema=https://json-schema.org/draft-07/schema#
|
||||
name: _deploy
|
||||
|
||||
concurrency:
|
||||
group: deploy
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag to deploy"
|
||||
required: true
|
||||
type: string
|
||||
prod:
|
||||
description: "Whether to deploy to production"
|
||||
required: true
|
||||
type: boolean
|
||||
image-tar:
|
||||
description: "Name of image tarball"
|
||||
required: true
|
||||
type: string
|
||||
artifact-run-id:
|
||||
description: "ID of workflow run where artifact was created"
|
||||
required: true
|
||||
type: string
|
||||
artifact-id:
|
||||
description: "ID of artifact"
|
||||
required: true
|
||||
type: string
|
||||
build-sha:
|
||||
description: "SHA of build"
|
||||
required: true
|
||||
type: string
|
||||
repo-name:
|
||||
description: "Name of repository"
|
||||
required: true
|
||||
type: string
|
||||
repo-path:
|
||||
description: "Path to repository on server"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ inputs.prod && 'production' || 'staging' }}
|
||||
env:
|
||||
RELEASE_TYPE: ${{ inputs.prod && 'prod' || 'staging' }}
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout the repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Ensure scripts are executable
|
||||
run: chmod +x deploy/scripts/*.sh
|
||||
|
||||
- name: Setup environment configuration
|
||||
uses: ./.github/actions/setup-environment
|
||||
with:
|
||||
release-type: ${{ inputs.prod && 'prod' || 'staging' }}
|
||||
domain: ${{ secrets.DOMAIN }}
|
||||
android-sms-gateway-url: ${{ secrets.NUXT_ANDROID_SMS_GATEWAY_URL }}
|
||||
android-sms-gateway-login: ${{ secrets.NUXT_ANDROID_SMS_GATEWAY_LOGIN }}
|
||||
android-sms-gateway-password: ${{ secrets.NUXT_ANDROID_SMS_GATEWAY_PASSWORD }}
|
||||
my-phone-number: ${{ secrets.NUXT_MY_PHONE_NUMBER }}
|
||||
super-secret-salt: ${{ secrets.NUXT_SUPER_SECRET_SALT }}
|
||||
wireguard-allowed-ips: ${{ secrets.WIREGUARD_ALLOWED_IPS }}
|
||||
wireguard-private-key: ${{ secrets.WIREGUARD_PRIVATE_KEY }}
|
||||
wireguard-addresses: ${{ secrets.WIREGUARD_ADDRESSES }}
|
||||
wireguard-public-key: ${{ secrets.WIREGUARD_PUBLIC_KEY }}
|
||||
wireguard-endpoint-host: ${{ secrets.WIREGUARD_ENDPOINT_HOST }}
|
||||
wireguard-endpoint-port: ${{ secrets.WIREGUARD_ENDPOINT_PORT }}
|
||||
prod: ${{ inputs.prod }}
|
||||
|
||||
- name: Set up SSH
|
||||
run: |
|
||||
mkdir -p $HOME/.ssh
|
||||
echo -e "${{ secrets.DEPLOY_KEY }}" > $HOME/.ssh/deploy.key
|
||||
chmod 700 $HOME/.ssh
|
||||
chmod 600 $HOME/.ssh/deploy.key
|
||||
|
||||
cat >>$HOME/.ssh/config <<END
|
||||
Host deploy
|
||||
HostName ${{ secrets.DEPLOY_HOST }}
|
||||
Port ${{ secrets.DEPLOY_PORT }}
|
||||
User ${{ secrets.DEPLOY_USER }}
|
||||
IdentityFile $HOME/.ssh/deploy.key
|
||||
UserKnownHostsFile /dev/null
|
||||
StrictHostKeyChecking no
|
||||
ControlMaster auto
|
||||
ControlPath $HOME/.ssh/control-%C
|
||||
ControlPersist yes
|
||||
LogLevel QUIET
|
||||
ConnectionAttempts 3
|
||||
ConnectTimeout 10
|
||||
ServerAliveInterval 10
|
||||
END
|
||||
|
||||
- name: Download container image artifact
|
||||
id: download-artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ${{ runner.temp }}
|
||||
github-token: ${{ github.token }}
|
||||
artifact-ids: ${{ inputs.artifact-id }}
|
||||
run-id: ${{ inputs.artifact-run-id }}
|
||||
|
||||
- name: Configure HAProxy
|
||||
env:
|
||||
DOCKER_HOST: ssh://deploy
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
DOMAIN: ${{ secrets.DOMAIN }}
|
||||
BACKEND_NAME: ${{ inputs.repo-name }}-${{ env.RELEASE_TYPE }}
|
||||
CF_PEM_CERT: ${{ secrets.CF_PEM_CERT }}
|
||||
CF_PEM_CA: ${{ secrets.CF_PEM_CA }}
|
||||
run: |
|
||||
echo "✅ Exit on any error"
|
||||
set -eu -o pipefail
|
||||
|
||||
echo "🔄 Load environment variables"
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
|
||||
echo "🎯 Running HAProxy configuration script"
|
||||
./deploy/scripts/configure-haproxy.sh add "$BACKEND_NAME"
|
||||
|
||||
- name: Deploy to Server
|
||||
env:
|
||||
IMAGE_TAR: ${{ steps.download-artifact.outputs.download-path }}/${{ inputs.image-tar }}/${{ inputs.image-tar }}
|
||||
REPO_PROJECT_PATH: ${{ inputs.repo-path }}
|
||||
IMAGE_TAG: ${{ inputs.tag }}
|
||||
GIT_SHA: ${{ inputs.build-sha }}
|
||||
DOCKER_HOST: ssh://deploy
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
run: |
|
||||
echo "✅ Exit on any error"
|
||||
set -eu -o pipefail
|
||||
|
||||
echo "🔄 Load environment variables"
|
||||
set -a
|
||||
source .env
|
||||
set +a
|
||||
|
||||
echo "🎯 Running deployment script"
|
||||
./deploy/scripts/deploy-blue-green.sh
|
||||
|
||||
- name: Notify successful deployment
|
||||
run: |
|
||||
echo "🎉 Deployment completed successfully!"
|
||||
echo "📋 Summary:"
|
||||
echo " - Source: ${{ inputs.prod && 'main (production)' || 'staging' }} branch"
|
||||
echo " - Status: ✅ Deployed Successfully"
|
||||
echo " - Next: Verify deployment status!"
|
||||
162
.github/workflows/release.yml
vendored
Normal file
162
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
name: _release
|
||||
|
||||
#TODO test without these
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
force_release:
|
||||
description: "Force release even if validation fails"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
validate-staging-build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_release: ${{ steps.validation.outputs.should-proceed }}
|
||||
steps:
|
||||
- name: Checkout staging
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: staging
|
||||
|
||||
- name: Validate staging build status
|
||||
id: validation
|
||||
uses: ./.github/actions/validate-build-status
|
||||
with:
|
||||
job-name: trigger-staging-build / build
|
||||
from-branch: staging
|
||||
to-branch: main
|
||||
force: ${{ inputs.force_release }}
|
||||
|
||||
generate-release-and-push-all:
|
||||
needs: validate-staging-build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ inputs.force_release == true || needs.validate-staging-build.outputs.should_release == 'true' }}
|
||||
outputs:
|
||||
skip-release: ${{steps.semantic-release.outputs.skip-release }}
|
||||
steps:
|
||||
- name: Checkout staging
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: staging
|
||||
|
||||
- name: Setup Git SSH for commit signing and push operations
|
||||
uses: ./.github/actions/setup-git-ssh
|
||||
with:
|
||||
commit-ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
push-ssh-private-key: ${{ secrets.PUSH_SSH_PRIVATE_KEY }}
|
||||
actor: ${{ github.actor }}
|
||||
actor-id: ${{ github.actor_id }}
|
||||
|
||||
- name: Set git origin to GIT_ORIGIN_REPO_URL
|
||||
run: |
|
||||
GIT_ORIGIN_URL="${{ secrets.GIT_ORIGIN_REPO_URL }}"
|
||||
if [ -n "$GIT_ORIGIN_URL" ]; then
|
||||
git remote remove origin
|
||||
if git remote add -f --tags origin "$GIT_ORIGIN_URL"; then
|
||||
echo "✅ Git origin configured correctly"
|
||||
else
|
||||
echo "❌ Failed to configure Git origin"
|
||||
echo "::error::Failed to configure Git origin"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ No Git origin URL configured - cannot proceed without origin URL"
|
||||
echo "::error::GIT_ORIGIN_REPO_URL secret is required but not configured"
|
||||
exit 1
|
||||
fi
|
||||
- name: Merge staging commits to main
|
||||
run: |
|
||||
echo "🔄 Merging staging commits to main..."
|
||||
git checkout main
|
||||
|
||||
STAGING_COMMITS=$(git log --oneline main..staging --pretty=format:"* %s")
|
||||
|
||||
if [ -z "$STAGING_COMMITS" ]; then
|
||||
if [[ "${{inputs.force_release}}" == "true" ]]; then
|
||||
echo "⚠️ No new commits in staging to merge, but force release is enabled"
|
||||
echo "::warning::No commits to merge from staging to main"
|
||||
exit 0
|
||||
else
|
||||
echo "❌ No new commits in staging to merge"
|
||||
echo "::error::No commits to merge from staging to main"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "📋 Commits to be merged:"
|
||||
echo "$STAGING_COMMITS"
|
||||
|
||||
if git merge staging -X theirs -m "chore: merge staging changes for next release"; then
|
||||
echo "✅ Staging commits merged to main successfully"
|
||||
else
|
||||
echo "⚠️ Merge conflicts detected - resolving with staging preference..."
|
||||
|
||||
git checkout staging -- .
|
||||
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
git checkout HEAD -- CHANGELOG.md
|
||||
echo "📋 Kept main's CHANGELOG.md (managed by semantic-release)"
|
||||
fi
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: merge staging changes for next release"
|
||||
echo "✅ Conflicts resolved - staging files preferred, main's CHANGELOG.md kept"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: ".nvmrc"
|
||||
|
||||
- name: Install semantic-release
|
||||
run: npm install semantic-release
|
||||
|
||||
- name: Run semantic-release on main, generate release, and push
|
||||
id: semantic-release
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ github.token }}" # used for release generation
|
||||
GIT_AUTHOR_NAME: "${{ github.actor }}"
|
||||
GIT_AUTHOR_EMAIL: "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
|
||||
GIT_COMMITTER_NAME: "${{ github.actor }}"
|
||||
GIT_COMMITTER_EMAIL: "${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com"
|
||||
run: |
|
||||
echo "🚀 Running semantic-release on main branch..."
|
||||
npx semantic-release
|
||||
|
||||
if git log main -1 --format="%s" | grep -q "^chore: Release"; then
|
||||
echo "✅ Semantic-release completed and pushed to origin/main"
|
||||
echo "skip-release=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ Semantic-release did not generate a release, exiting."
|
||||
echo "skip-release=true" >> $GITHUB_OUTPUT
|
||||
[[ "${{ inputs.force_release }}" == "true" ]] && exit 1
|
||||
fi
|
||||
|
||||
- name: Notify successful release
|
||||
if: ${{ steps.semantic-release.outputs.skip-release != 'true' }}
|
||||
run: |
|
||||
echo "🎉 Production release completed successfully!"
|
||||
echo "📋 Summary:"
|
||||
echo " - Source: staging branch commits (regular merged)"
|
||||
echo " - Target: main branch (semantic-release)"
|
||||
echo " - Semantic-release: ✅ Complete on main"
|
||||
echo " - staging branch: ℹ️ Maintains independent history"
|
||||
echo " - dev branch: ℹ️ Maintains independent history"
|
||||
echo " - Tags: ✅ Created and pushed"
|
||||
echo " - GitHub & Git Origin: ✅ Main branch synchronized"
|
||||
echo " - Status: 🚀 Ready to build and deploy!"
|
||||
37
.github/workflows/scheduler-release.yml
vendored
Normal file
37
.github/workflows/scheduler-release.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: Release
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Mondays 12:00 UTC/04:00 PST (UTC-08)
|
||||
- cron: "0 12 * * 1"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_release:
|
||||
description: "Force release even if validation fails"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
note_for_dispatch:
|
||||
description: "Note: This workflow will always call release.yml@staging, but it MUST BE RUN FROM THE MAIN BRANCH to ensure environment consistency."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
call-release-workflow:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
uses: badbl0cks/portfolio/.github/workflows/release.yml@staging
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
force_release: ${{ inputs.force_release || false }}
|
||||
secrets: inherit
|
||||
33
.github/workflows/scheduler-stage.yml
vendored
Normal file
33
.github/workflows/scheduler-stage.yml
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
name: Stage
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run daily Monday-Friday at 13:00 UTC/05:00 PST (UTC-08)
|
||||
- cron: "0 13 * * 1-5"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
force_staging:
|
||||
description: "Force staging even if validation fails"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
note_for_dispatch:
|
||||
description: "Note: This workflow will always call stage.yml@dev, but it MUST BE RUN FROM THE MAIN BRANCH to ensure environment consistency."
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
call-staging-workflow:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
uses: badbl0cks/portfolio/.github/workflows/stage.yml@dev
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
force_staging: ${{ inputs.force_staging || false }}
|
||||
secrets: inherit
|
||||
80
.github/workflows/stage.yml
vendored
Normal file
80
.github/workflows/stage.yml
vendored
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
name: _stage
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: stage
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
force_staging:
|
||||
description: "Force staging even if validation fails"
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
validate-dev-build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_stage: ${{ steps.validation.outputs.should-proceed }}
|
||||
steps:
|
||||
- name: Checkout dev
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: dev
|
||||
|
||||
- name: Validate dev build status
|
||||
id: validation
|
||||
uses: ./.github/actions/validate-build-status
|
||||
with:
|
||||
job-name: trigger-dev-build / build
|
||||
from-branch: dev
|
||||
to-branch: staging
|
||||
force: ${{ inputs.force_staging }}
|
||||
|
||||
push-to-staging:
|
||||
needs: validate-dev-build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ inputs.force_staging == true || needs.validate-dev-build.outputs.should_stage == 'true' }}
|
||||
steps:
|
||||
- name: Checkout dev
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: dev
|
||||
|
||||
- name: Setup Git SSH for push operations
|
||||
uses: ./.github/actions/setup-git-ssh
|
||||
with:
|
||||
push-ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||
actor: ${{ github.actor }}
|
||||
actor-id: ${{ github.actor_id }}
|
||||
|
||||
- name: Reset staging branch to refs/heads/dev
|
||||
run: |
|
||||
echo "🚀 Resetting staging branch..."
|
||||
|
||||
git checkout staging
|
||||
git reset --hard dev
|
||||
echo "✅ Staging branch reset to refs/heads/dev"
|
||||
|
||||
- name: Push to git origin
|
||||
uses: ./.github/actions/push-to-origin
|
||||
with:
|
||||
git-origin-url: ${{ secrets.GIT_ORIGIN_REPO_URL }}
|
||||
branches: staging
|
||||
force: true
|
||||
|
||||
- name: Notify successful staging
|
||||
run: |
|
||||
echo "🎉 Staging completed successfully!"
|
||||
echo "📋 Summary:"
|
||||
echo " - Source: dev branch"
|
||||
echo " - Status: ✅ Pushed to staging"
|
||||
echo " - Next: Perform staging and integration tests. If satisfactory, manually dispatch the staging-to-main workflow to generate a production release, which will then trigger a production build and deployment."
|
||||
30
.github/workflows/trigger-build.yml
vendored
Normal file
30
.github/workflows/trigger-build.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# yaml-language-server: $schema=https://json-schema.org/draft-07/schema#
|
||||
name: Build/Deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [dev, staging, main]
|
||||
|
||||
jobs:
|
||||
trigger-dev-build:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
uses: badbl0cks/portfolio/.github/workflows/build.yml@dev
|
||||
secrets: inherit
|
||||
trigger-staging-build:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
if: ${{ github.ref == 'refs/heads/staging' }}
|
||||
uses: badbl0cks/portfolio/.github/workflows/build.yml@staging
|
||||
secrets: inherit
|
||||
trigger-main-build:
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
if: ${{ github.ref == 'refs/heads/main' }}
|
||||
uses: badbl0cks/portfolio/.github/workflows/build.yml@main
|
||||
secrets: inherit
|
||||
Loading…
Add table
Add a link
Reference in a new issue