name: Continuous Deployment # Name of this workflow as shown in the CI/CD UI on: # Section defining which events trigger this workflow push: # Trigger when a push event occurs tags: # Limit triggers to pushes involving tags - 'v*' # Only run for version tags that start with 'v' (e.g., v0.0.1, v1.2.3) workflow_dispatch: # Allow this workflow to be triggered manually from the UI inputs: # Optional inputs for manual deployments version: # Input used to document which version is being deployed manually description: "Version to deploy when triggering manually (informational only)" # Help text for the version input required: false # This input is optional default: "manual-trigger" # Default value when no explicit version is supplied permissions: # Default permissions for the token used in this workflow contents: read # Allow reading repository contents (needed for checkout) packages: write # Allow pushing packages/images to registries (adjust or remove if not needed) id-token: write # Allow issuing OIDC tokens for cloud providers (remove if not used) jobs: # Collection of jobs defined in this workflow deploy: # Job responsible for deploying tagged releases name: Deploy tagged release # Human-readable name for this deployment job runs-on: ubuntu-latest # Use the latest Ubuntu Linux runner image timeout-minutes: 30 # Automatically fail the job if it runs longer than 30 minutes concurrency: # Prevent overlapping deployments group: deploy-main-tags # Group key: serialize all deployments that share this identifier cancel-in-progress: true # Cancel any running deployment in this group when a new one starts environment: # Associate this job with a deployment environment name: production # Label the environment as 'production' for visibility and protections steps: # Ordered list of steps in this job - name: Checkout Code # Step to check out the repository at the tagged commit uses: actions/checkout@v6 # Standard checkout action (Gitea-compatible) with: # Options for configuring the checkout behavior ref: ${{ gitea.ref }} # Check out the specific commit referenced by the pushed tag fetch-depth: 0 # Fetch full history so ancestry checks and branch analysis are reliable - name: Verify tag commit is on current or historical commit of 'remote/main' # Ensure the tag commit comes from the assumed main branch shell: bash # Explicitly use bash for this script run: | # Begin multi-line bash script set -euo pipefail # Enable strict mode: exit on error, unset var, or failed pipeline command REMOTE_NAME="remote" # Fixed assumption: the relevant remote is named "remote" MAIN_BRANCH_NAME="main" # Fixed assumption: the primary branch on the remote is named "main" echo "Assuming remote name: ${REMOTE_NAME}" # Log the assumed remote name echo "Assuming main branch name on remote: ${MAIN_BRANCH_NAME}" # Log the assumed main branch name if ! git remote | grep -qx "${REMOTE_NAME}"; then # Check that the assumed remote actually exists echo "❌ Expected remote '${REMOTE_NAME}' not found in repository remotes." # Explain missing remote git remote -v # Show the actual configured remotes for debugging exit 1 # Fail the job because we cannot safely validate without the expected remote fi # End of remote existence check TAG_COMMIT="$(git rev-parse HEAD)" # Determine the commit hash currently checked out (the tag target) echo "Tag points to commit: ${TAG_COMMIT}" # Log which commit the tag references echo "Fetching '${MAIN_BRANCH_NAME}' from remote '${REMOTE_NAME}'..." # Log the fetch action if ! git fetch "${REMOTE_NAME}" "${MAIN_BRANCH_NAME}" > /dev/null 2>&1; then # Fetch remote/main silently and detect failure echo "❌ Failed to fetch branch '${MAIN_BRANCH_NAME}' from remote '${REMOTE_NAME}'." # Explain fetch failure echo " Ensure '${REMOTE_NAME}/${MAIN_BRANCH_NAME}' exists and is accessible." # Suggest verifying remote branch presence exit 1 # Fail the job because we cannot validate against main fi # End of fetch error check MAIN_REF="${REMOTE_NAME}/${MAIN_BRANCH_NAME}" # Construct the fully qualified remote/main reference echo "Discovering remote branches that contain the tag commit ${TAG_COMMIT}..." # Log start of branch discovery BRANCHES_RAW="$(git branch -r --contains "${TAG_COMMIT}" || true)" # List remote branches whose history contains the tag commit (may be empty) echo "Raw remote branches containing tag commit:" # Introductory log for raw remote-branch list echo "${BRANCHES_RAW}" # Output the raw remote-tracking branches BRANCHES_CLEANED="$(echo "${BRANCHES_RAW}" | sed 's|^[[:space:]]*||;s|.*/||')" # Trim spaces and strip remote prefixes, leaving branch names only echo "Branch names containing tag commit (remote prefixes stripped):" # Introductory log for cleaned branch names echo "${BRANCHES_CLEANED}" # Output the cleaned list of branch names for human inspection if echo "${BRANCHES_RAW}" | sed 's|^[[:space:]]*||' | grep -qx "${MAIN_REF}"; then # Check if remote/main itself contains the tag commit echo "${MAIN_REF} is listed as containing the tag commit." # Log that remote/main is explicitly reported as containing the commit else # Branch taken when remote/main is not listed among the containing branches echo "Note: '${MAIN_REF}' is not explicitly listed among branches containing the tag commit;" # Note about absence in the listing echo " proceeding to verify via merge-base ancestry check as the final source of truth." # Explain that merge-base will be used to decide fi # End of explicit listing check echo "Verifying that tag commit ${TAG_COMMIT} is an ancestor of '${MAIN_REF}'..." # Log the start of ancestry verification if git merge-base --is-ancestor "${TAG_COMMIT}" "${MAIN_REF}"; then # Check if the tag commit is contained in the history of remote/main echo "✅ Tag commit is part of '${MAIN_REF}' history." # Success: tag commit is in remote/main history echo " This means the tag was created on the current or historical commit of the main branch on remote '${REMOTE_NAME}'." # Clarify the semantic meaning else # Branch taken when the tag commit is not reachable from remote/main echo "❌ Tag commit is NOT part of '${MAIN_REF}' history." # Failure: invalid tag source echo " Deployment is only allowed for tags created on the current or historical commit of '${MAIN_REF}'." # Explain the policy being enforced exit 1 # Fail the job to prevent deployment from a non-main branch fi # End of ancestry validation conditional - name: Build and Publish Release # Step that builds and deploys the validated tagged release shell: bash # Use bash for this deployment script env: # Environment variables used during build and deployment TAG_NAME: ${{ gitea.ref_name }} # Tag name (e.g., v0.0.1, v1.2.3) from the workflow context CI: "true" # Conventional flag signaling that commands run in a CI/CD environment run: | # Begin multi-line bash script for build and deploy set -euo pipefail # Enforce strict error handling during deployment echo "Proceeding with building and deploying version ${TAG_NAME}..." # Log which version is being deployed # --- Begin placeholder for production build and deployment logic --- # Marker for project-specific deployment implementation # Example for container-based deployments: # Example of a containerized deployment sequence # make test # Run tests one more time as a safeguard before deployment # make build-release # Build application artifacts for production # docker build -t myorg/myapp:${TAG_NAME} . # Build Docker image tagged with the version # docker push myorg/myapp:${TAG_NAME} # Push Docker image to the container registry # ./deploy_to_production.sh "${TAG_NAME}" # Run custom deployment script using the version tag # Example for non-container deployments: # Example of a file-based or script-based deployment sequence # ./scripts/package.sh "${TAG_NAME}" # Package application into production-ready artifacts # ./scripts/deploy.sh "${TAG_NAME}" # Deploy artifacts to servers or hosting platform # --- End placeholder for production build and deployment logic --- # End of deployment example section echo "Build and deployment steps completed for version ${TAG_NAME} (assuming real commands are configured above)." # Summary log for successful deployment step