From dce20883747cf72815a4452d9d8d58d73293dd09 Mon Sep 17 00:00:00 2001 From: hippasus Date: Thu, 26 Mar 2026 09:47:13 +0800 Subject: [PATCH] init --- blue-green-deploy/abort/action.yml | 57 +++++++++++++++++ blue-green-deploy/finalize/action.yml | 57 +++++++++++++++++ blue-green-deploy/stage/action.yml | 69 +++++++++++++++++++++ blue-green-deploy/status/action.yml | 75 ++++++++++++++++++++++ blue-green-deploy/switch/action.yml | 69 +++++++++++++++++++++ checkout-config/action.yml | 84 +++++++++++++++++++++++++ docker-build-push/action.yml | 89 +++++++++++++++++++++++++++ 7 files changed, 500 insertions(+) create mode 100644 blue-green-deploy/abort/action.yml create mode 100644 blue-green-deploy/finalize/action.yml create mode 100644 blue-green-deploy/stage/action.yml create mode 100644 blue-green-deploy/status/action.yml create mode 100644 blue-green-deploy/switch/action.yml create mode 100644 checkout-config/action.yml create mode 100644 docker-build-push/action.yml diff --git a/blue-green-deploy/abort/action.yml b/blue-green-deploy/abort/action.yml new file mode 100644 index 0000000..3c7f01e --- /dev/null +++ b/blue-green-deploy/abort/action.yml @@ -0,0 +1,57 @@ +name: 'Auto Deploy Abort' +description: 'Aborts a pending deployment on the app server' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + host: + description: 'Remote App Host' + required: true + port: + description: 'Remote SSH Port' + required: true + user: + description: 'Remote User' + required: true + app_key: + description: 'Application Key' + required: true + auto_deploy_path: + description: 'Path to auto-deploy repo on remote' + required: true + auto_deploy_branch: + description: 'Branch to checkout in auto-deploy repo' + required: false + default: 'main' +runs: + using: 'composite' + steps: + - name: SSH Deploy Abort + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + DEPLOY_HOST: ${{ inputs.host }} + DEPLOY_PORT: ${{ inputs.port }} + DEPLOY_USER: ${{ inputs.user }} + APP_KEY: ${{ inputs.app_key }} + AUTO_DEPLOY_PATH: ${{ inputs.auto_deploy_path }} + AUTO_DEPLOY_BRANCH: ${{ inputs.auto_deploy_branch }} + run: | + set -eu + + SSH_TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" > "$SSH_TMP_DIR/known_hosts" + + SSH_OPTS=( + -i "$SSH_TMP_DIR/id_rsa" + -o UserKnownHostsFile="$SSH_TMP_DIR/known_hosts" + -o StrictHostKeyChecking=yes + -p "$DEPLOY_PORT" + ) + + ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \ + "APP_KEY=$(printf '%q' "$APP_KEY") AUTO_DEPLOY_PATH=$(printf '%q' "$AUTO_DEPLOY_PATH") AUTO_DEPLOY_BRANCH=$(printf '%q' "$AUTO_DEPLOY_BRANCH") bash -se -c 'cd \"\$AUTO_DEPLOY_PATH\"; git checkout \"\$AUTO_DEPLOY_BRANCH\"; git pull --ff-only; git submodule sync --recursive; git submodule update --init --recursive; echo \"[remote] auto-deploy repo updated\"; ./common/deploy.sh abort-pending \"\$APP_KEY\"; echo \"[remote] \$APP_KEY pending deployment aborted\"'" diff --git a/blue-green-deploy/finalize/action.yml b/blue-green-deploy/finalize/action.yml new file mode 100644 index 0000000..f99e98d --- /dev/null +++ b/blue-green-deploy/finalize/action.yml @@ -0,0 +1,57 @@ +name: 'Auto Deploy Finalize' +description: 'Finalizes the deployment on the app server' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + host: + description: 'Remote App Host' + required: true + port: + description: 'Remote SSH Port' + required: true + user: + description: 'Remote User' + required: true + app_key: + description: 'Application Key' + required: true + auto_deploy_path: + description: 'Path to auto-deploy repo on remote' + required: true + auto_deploy_branch: + description: 'Branch to checkout in auto-deploy repo' + required: false + default: 'main' +runs: + using: 'composite' + steps: + - name: SSH Deploy Finalize + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + DEPLOY_HOST: ${{ inputs.host }} + DEPLOY_PORT: ${{ inputs.port }} + DEPLOY_USER: ${{ inputs.user }} + APP_KEY: ${{ inputs.app_key }} + AUTO_DEPLOY_PATH: ${{ inputs.auto_deploy_path }} + AUTO_DEPLOY_BRANCH: ${{ inputs.auto_deploy_branch }} + run: | + set -eu + + SSH_TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" > "$SSH_TMP_DIR/known_hosts" + + SSH_OPTS=( + -i "$SSH_TMP_DIR/id_rsa" + -o UserKnownHostsFile="$SSH_TMP_DIR/known_hosts" + -o StrictHostKeyChecking=yes + -p "$DEPLOY_PORT" + ) + + ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \ + "APP_KEY=$(printf '%q' "$APP_KEY") AUTO_DEPLOY_PATH=$(printf '%q' "$AUTO_DEPLOY_PATH") AUTO_DEPLOY_BRANCH=$(printf '%q' "$AUTO_DEPLOY_BRANCH") bash -se -c 'cd \"\$AUTO_DEPLOY_PATH\"; git checkout \"\$AUTO_DEPLOY_BRANCH\"; git pull --ff-only; git submodule sync --recursive; git submodule update --init --recursive; echo \"[remote] auto-deploy repo updated\"; ./common/deploy.sh finalize \"\$APP_KEY\"; echo \"[remote] \$APP_KEY deployment finalized\"'" diff --git a/blue-green-deploy/stage/action.yml b/blue-green-deploy/stage/action.yml new file mode 100644 index 0000000..7e78aae --- /dev/null +++ b/blue-green-deploy/stage/action.yml @@ -0,0 +1,69 @@ +name: 'Auto Deploy Stage' +description: 'Stages a new deployment on the app server' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + host: + description: 'Remote App Host' + required: true + port: + description: 'Remote SSH Port' + required: true + user: + description: 'Remote User' + required: true + harbor_user: + description: 'Harbor Username' + required: true + harbor_secret: + description: 'Harbor Secret' + required: true + image_tag: + description: 'Image Tag to deploy' + required: true + app_key: + description: 'Application Key' + required: true + auto_deploy_path: + description: 'Path to auto-deploy repo on remote' + required: true + auto_deploy_branch: + description: 'Branch to checkout in auto-deploy repo' + required: false + default: 'main' +runs: + using: 'composite' + steps: + - name: SSH Deploy Stage + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + DEPLOY_HOST: ${{ inputs.host }} + DEPLOY_PORT: ${{ inputs.port }} + DEPLOY_USER: ${{ inputs.user }} + HARBOR_USER: ${{ inputs.harbor_user }} + HARBOR_SECRET: ${{ inputs.harbor_secret }} + IMAGE_TAG: ${{ inputs.image_tag }} + APP_KEY: ${{ inputs.app_key }} + AUTO_DEPLOY_PATH: ${{ inputs.auto_deploy_path }} + AUTO_DEPLOY_BRANCH: ${{ inputs.auto_deploy_branch }} + run: | + set -eu + + SSH_TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" > "$SSH_TMP_DIR/known_hosts" + + SSH_OPTS=( + -i "$SSH_TMP_DIR/id_rsa" + -o UserKnownHostsFile="$SSH_TMP_DIR/known_hosts" + -o StrictHostKeyChecking=yes + -p "$DEPLOY_PORT" + ) + + ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \ + "APP_KEY=$(printf '%q' "$APP_KEY") IMAGE_TAG=$(printf '%q' "$IMAGE_TAG") HARBOR_USER=$(printf '%q' "$HARBOR_USER") HARBOR_SECRET=$(printf '%q' "$HARBOR_SECRET") AUTO_DEPLOY_PATH=$(printf '%q' "$AUTO_DEPLOY_PATH") AUTO_DEPLOY_BRANCH=$(printf '%q' "$AUTO_DEPLOY_BRANCH") bash -se -c 'printf \"%s\" \"\$HARBOR_SECRET\" | sudo /usr/bin/docker login harbor.hclife.co -u \"\$HARBOR_USER\" --password-stdin; echo \"[remote] Harbor login succeeded\"; cd \"\$AUTO_DEPLOY_PATH\"; git checkout \"\$AUTO_DEPLOY_BRANCH\"; git pull --ff-only; git submodule sync --recursive; git submodule update --init --recursive; echo \"[remote] auto-deploy repo updated\"; ./common/deploy.sh deploy \"\$APP_KEY\" \"\$IMAGE_TAG\"; echo \"[remote] \$APP_KEY deploy staged with IMAGE_TAG=\$IMAGE_TAG\"; echo \"[remote] current deployment status:\"; ./common/deploy.sh status \"\$APP_KEY\" --format env'" diff --git a/blue-green-deploy/status/action.yml b/blue-green-deploy/status/action.yml new file mode 100644 index 0000000..942b2b1 --- /dev/null +++ b/blue-green-deploy/status/action.yml @@ -0,0 +1,75 @@ +name: 'Auto Deploy Status' +description: 'Reads current deployment status from app server' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + host: + description: 'Remote App Host' + required: true + port: + description: 'Remote SSH Port' + required: true + user: + description: 'Remote User' + required: true + app_key: + description: 'Application Key' + required: true + auto_deploy_path: + description: 'Path to auto-deploy repo on remote' + required: true +outputs: + app_upstream_host: + description: 'Upstream Host or IP' + value: ${{ steps.get_status.outputs.app_upstream_host }} + pending_port: + description: 'Pending Port' + value: ${{ steps.get_status.outputs.pending_port }} + pending_color: + description: 'Pending Color' + value: ${{ steps.get_status.outputs.pending_color }} +runs: + using: 'composite' + steps: + - name: SSH Deploy Status + id: get_status + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + DEPLOY_HOST: ${{ inputs.host }} + DEPLOY_PORT: ${{ inputs.port }} + DEPLOY_USER: ${{ inputs.user }} + APP_KEY: ${{ inputs.app_key }} + AUTO_DEPLOY_PATH: ${{ inputs.auto_deploy_path }} + run: | + set -eu + + SSH_TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" > "$SSH_TMP_DIR/known_hosts" + + SSH_OPTS=( + -i "$SSH_TMP_DIR/id_rsa" + -o UserKnownHostsFile="$SSH_TMP_DIR/known_hosts" + -o StrictHostKeyChecking=yes + -p "$DEPLOY_PORT" + ) + + STATUS_OUTPUT="$(ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" "cd $(printf '%q' "$AUTO_DEPLOY_PATH") && ./common/deploy.sh status $(printf '%q' "$APP_KEY") --format env")" + printf '%s\n' "$STATUS_OUTPUT" + + APP_UPSTREAM_HOST="$(printf '%s\n' "$STATUS_OUTPUT" | sed -n 's/^APP_UPSTREAM_HOST=//p')" + PENDING_PORT="$(printf '%s\n' "$STATUS_OUTPUT" | sed -n 's/^PENDING_PORT=//p')" + PENDING_COLOR="$(printf '%s\n' "$STATUS_OUTPUT" | sed -n 's/^PENDING_COLOR=//p')" + + test -n "$APP_UPSTREAM_HOST" + test -n "$PENDING_PORT" + test -n "$PENDING_COLOR" + + echo "app_upstream_host=$APP_UPSTREAM_HOST" >> "$GITHUB_OUTPUT" + echo "pending_port=$PENDING_PORT" >> "$GITHUB_OUTPUT" + echo "pending_color=$PENDING_COLOR" >> "$GITHUB_OUTPUT" diff --git a/blue-green-deploy/switch/action.yml b/blue-green-deploy/switch/action.yml new file mode 100644 index 0000000..13471fd --- /dev/null +++ b/blue-green-deploy/switch/action.yml @@ -0,0 +1,69 @@ +name: 'Nginx Switch' +description: 'Switches the Nginx upstream to the newly staged color' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + host: + description: 'Remote Nginx Host' + required: true + port: + description: 'Remote SSH Port' + required: true + user: + description: 'Remote User' + required: true + app_key: + description: 'Application Key' + required: true + app_upstream_host: + description: 'App Server Host or IP' + required: true + pending_port: + description: 'Pending Port' + required: true + pending_color: + description: 'Pending Color' + required: true + auto_deploy_path: + description: 'Path to auto-deploy repo on remote' + required: true + auto_deploy_branch: + description: 'Branch to checkout in auto-deploy repo' + required: false + default: 'main' +runs: + using: 'composite' + steps: + - name: SSH Nginx Switch + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + DEPLOY_HOST: ${{ inputs.host }} + DEPLOY_PORT: ${{ inputs.port }} + DEPLOY_USER: ${{ inputs.user }} + APP_KEY: ${{ inputs.app_key }} + APP_UPSTREAM_HOST: ${{ inputs.app_upstream_host }} + PENDING_PORT: ${{ inputs.pending_port }} + PENDING_COLOR: ${{ inputs.pending_color }} + AUTO_DEPLOY_PATH: ${{ inputs.auto_deploy_path }} + AUTO_DEPLOY_BRANCH: ${{ inputs.auto_deploy_branch }} + run: | + set -eu + + SSH_TMP_DIR="$(mktemp -d)" + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$DEPLOY_PORT" "$DEPLOY_HOST" > "$SSH_TMP_DIR/known_hosts" + + SSH_OPTS=( + -i "$SSH_TMP_DIR/id_rsa" + -o UserKnownHostsFile="$SSH_TMP_DIR/known_hosts" + -o StrictHostKeyChecking=yes + -p "$DEPLOY_PORT" + ) + + ssh "${SSH_OPTS[@]}" "$DEPLOY_USER@$DEPLOY_HOST" \ + "APP_KEY=$(printf '%q' "$APP_KEY") APP_UPSTREAM_HOST=$(printf '%q' "$APP_UPSTREAM_HOST") PENDING_PORT=$(printf '%q' "$PENDING_PORT") PENDING_COLOR=$(printf '%q' "$PENDING_COLOR") AUTO_DEPLOY_PATH=$(printf '%q' "$AUTO_DEPLOY_PATH") AUTO_DEPLOY_BRANCH=$(printf '%q' "$AUTO_DEPLOY_BRANCH") bash -se -c 'cd \"\$AUTO_DEPLOY_PATH\"; git checkout \"\$AUTO_DEPLOY_BRANCH\"; git pull --ff-only; git submodule sync --recursive; git submodule update --init --recursive; echo \"[remote] auto-deploy repo updated on nginx server\"; ./common/nginx/manage-nginx.sh switch \"\$APP_KEY\" \"\$APP_UPSTREAM_HOST\" \"\$PENDING_PORT\"; echo \"[remote] nginx switched \$APP_KEY to \$APP_UPSTREAM_HOST:\$PENDING_PORT (\$PENDING_COLOR)\"'" diff --git a/checkout-config/action.yml b/checkout-config/action.yml new file mode 100644 index 0000000..b42d4fd --- /dev/null +++ b/checkout-config/action.yml @@ -0,0 +1,84 @@ +name: 'Checkout and Config Injection' +description: 'Clones the project via SSH and injects configs from a separate repo' +inputs: + ssh_key: + description: 'SSH Private Key' + required: true + config_repo: + description: 'Configuration Repository Path (e.g., dev-ops/configs.git)' + required: false + default: '' + config_repo_branch: + description: 'Configuration Repository Branch' + required: false + default: 'main' + app_key: + description: 'Application Key in the config repo' + required: true + gitea_host: + description: 'Gitea Hostname' + required: false + default: 'gitea.hclife.co' + gitea_port: + description: 'Gitea SSH Port' + required: false + default: '2222' +runs: + using: 'composite' + steps: + - name: Checkout and Config + shell: bash + env: + SSH_KEY: ${{ inputs.ssh_key }} + CONFIG_REPO: ${{ inputs.config_repo }} + CONFIG_REPO_BRANCH: ${{ inputs.config_repo_branch }} + APP_KEY: ${{ inputs.app_key }} + GITEA_HOST: ${{ inputs.gitea_host }} + GITEA_PORT: ${{ inputs.gitea_port }} + run: | + set -eu + + SSH_TMP_DIR=$(mktemp -d) + trap 'rm -rf "$SSH_TMP_DIR"' EXIT + + echo "$SSH_KEY" > "$SSH_TMP_DIR/id_rsa" + chmod 600 "$SSH_TMP_DIR/id_rsa" + ssh-keyscan -p "$GITEA_PORT" "$GITEA_HOST" > "$SSH_TMP_DIR/known_hosts" + + export GIT_SSH_COMMAND="ssh -i $SSH_TMP_DIR/id_rsa -o UserKnownHostsFile=$SSH_TMP_DIR/known_hosts -o StrictHostKeyChecking=yes" + + echo "Initializing project repository..." + git init + git config --global --add safe.directory "$GITHUB_WORKSPACE" + if git remote get-url origin >/dev/null 2>&1; then + git remote set-url origin "ssh://git@$GITEA_HOST:$GITEA_PORT/${{ github.repository }}.git" + else + git remote add origin "ssh://git@$GITEA_HOST:$GITEA_PORT/${{ github.repository }}.git" + fi + git fetch --depth 1 origin "${{ github.sha }}" + git checkout FETCH_HEAD + + if [ -n "$CONFIG_REPO" ]; then + echo "Fetching optional config repository..." + echo "Config repo: $CONFIG_REPO" + echo "Config repo branch: $CONFIG_REPO_BRANCH" + echo "Expected config app key: $APP_KEY" + git clone -b "$CONFIG_REPO_BRANCH" "ssh://git@$GITEA_HOST:$GITEA_PORT/$CONFIG_REPO" configs + + echo "Config repo top-level entries:" + find configs -maxdepth 2 -mindepth 1 | sort + + CONFIG_SOURCE_DIR="configs/${APP_KEY}" + if [ -d "$CONFIG_SOURCE_DIR" ]; then + echo "Applying config tree from '$CONFIG_SOURCE_DIR'..." + cp -Rv "$CONFIG_SOURCE_DIR"/. . + rm -rf configs + else + echo "Error: '$CONFIG_SOURCE_DIR' not found in config repository" + echo "Available directories under configs/:" + find configs -maxdepth 3 -type d | sort + exit 1 + fi + else + echo "No config repository configured; skipping config injection." + fi diff --git a/docker-build-push/action.yml b/docker-build-push/action.yml new file mode 100644 index 0000000..0d55123 --- /dev/null +++ b/docker-build-push/action.yml @@ -0,0 +1,89 @@ +name: 'Docker Build and Push' +description: 'Log into Harbor, build and push image' +inputs: + harbor_user: + description: 'Harbor Username' + required: true + harbor_secret: + description: 'Harbor Secret' + required: true + image_repo: + description: 'Harbor Image Repository (without tag)' + required: true + dockerfile: + description: 'Path to Dockerfile' + required: false + default: './Dockerfile' + build_context: + description: 'Docker Build Context' + required: false + default: '.' + docker_build_args: + description: 'Docker Build Args as multiple lines or single line' + required: false + default: '' + harbor_host: + description: 'Harbor Host' + required: false + default: 'harbor.hclife.co' +outputs: + image_tag: + description: 'Generated Image Tag (UTC timestamp)' + value: ${{ steps.build_push.outputs.image_tag }} +runs: + using: 'composite' + steps: + - name: Build and Push + id: build_push + shell: bash + env: + DOCKER_BUILDKIT: 1 + HARBOR_USER: ${{ inputs.harbor_user }} + HARBOR_SECRET: ${{ inputs.harbor_secret }} + COMMIT_SHA: ${{ github.sha }} + IMAGE_REPO: ${{ inputs.image_repo }} + DOCKERFILE: ${{ inputs.dockerfile }} + BUILD_CONTEXT: ${{ inputs.build_context }} + DOCKER_BUILD_ARGS: ${{ inputs.docker_build_args }} + HARBOR_HOST: ${{ inputs.harbor_host }} + run: | + set -eu + IMAGE_TAG="$(date -u +%Y%m%d-%H%M%S)" + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" + + test -n "$IMAGE_REPO" + test -n "$DOCKERFILE" + test -n "$BUILD_CONTEXT" + test -f "$DOCKERFILE" + test -d "$BUILD_CONTEXT" + + echo "Logging into $HARBOR_HOST..." + echo "$HARBOR_SECRET" | docker login "$HARBOR_HOST" -u "$HARBOR_USER" --password-stdin + + BUILD_ARG_FLAGS=() + if [ -n "$DOCKER_BUILD_ARGS" ]; then + while IFS= read -r build_arg_line; do + [ -n "$build_arg_line" ] || continue + BUILD_ARG_FLAGS+=("--build-arg" "$build_arg_line") + done <<< "$DOCKER_BUILD_ARGS" + fi + + echo "Building Image..." + if [ ${#BUILD_ARG_FLAGS[@]} -gt 0 ]; then + docker build "${BUILD_ARG_FLAGS[@]}" \ + -t "$IMAGE_REPO:$IMAGE_TAG" \ + -t "$IMAGE_REPO:$COMMIT_SHA" \ + -t "$IMAGE_REPO:latest" \ + -f "$DOCKERFILE" "$BUILD_CONTEXT" + else + docker build \ + -t "$IMAGE_REPO:$IMAGE_TAG" \ + -t "$IMAGE_REPO:$COMMIT_SHA" \ + -t "$IMAGE_REPO:latest" \ + -f "$DOCKERFILE" "$BUILD_CONTEXT" + fi + + echo "Pushing Image..." + docker push "$IMAGE_REPO:$IMAGE_TAG" + docker push "$IMAGE_REPO:$COMMIT_SHA" + docker push "$IMAGE_REPO:latest"