diff --git a/.github/workflows/build_deploy.yml b/.github/workflows/build_deploy.yml index d9cddf8..bf61f71 100644 --- a/.github/workflows/build_deploy.yml +++ b/.github/workflows/build_deploy.yml @@ -8,37 +8,37 @@ on: branches: [main] jobs: - # Job 1: Build the Docker image - build: + build-deploy: runs-on: ubuntu-latest - outputs: - repo: ${{ steps.meta.outputs.REPO }} - repo-name: ${{ steps.meta.outputs.REPO_NAME_ONLY }} - repo-path: ${{ steps.meta.outputs.REPO_PROJECT_PATH }} - image-tar: ${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar - tags: ${{ steps.generated_docker_tags.outputs.tag }} - prod: ${{ steps.env.outputs.prod }} steps: - name: Checkout the repo uses: actions/checkout@v4 - - - name: Ensure scripts are executable - run: chmod +x scripts/*.sh - - name: Get full and partial repository name id: meta run: | - # Parse repository name and set outputs - eval "$(./scripts/parse-repository-name.sh '${{ github.repository }}')" + echo "GITHUB_REPOSITORY: ${{ github.repository }}" + if [[ "${{ github.repository }}" == *".git" ]]; then + if [[ "${{ github.repository }}" == "https://"* ]]; then + echo "GITHUB_REPOSITORY ends in .git and is a URL" + REPO=$(echo "${{ github.repository }}" | sed 's/\.git$//' | cut -d'/' -f4-5 | sed 's/[^a-zA-Z0-9\/-]/-/g') + else + echo "GITHUB_REPOSITORY ends in .git and is not a URL" + REPO=$(echo "${{ github.repository }}" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g') + fi + else + echo "GITHUB_REPOSITORY is not a URL" + REPO=$(echo "${{ github.repository }}" | sed 's/[^a-zA-Z0-9\/-]/-/g') + fi echo "REPO=$REPO" >> $GITHUB_OUTPUT + + REPO_NAME_ONLY=$(echo "$REPO" | cut -d'/' -f2) echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT + + REPO_PROJECT_PATH=/srv/$(echo "$REPO_NAME_ONLY") echo "REPO_PROJECT_PATH=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set PROD environment variable - id: env run: | echo "βœ… Exit script on any error" set -eu -o pipefail @@ -57,99 +57,144 @@ jobs: echo "πŸ–ŠοΈ Writing determined values to GITHUB_ENV:" echo "PROD=${prod_value}" >> $GITHUB_ENV echo "PROD=${prod_value} -> GITHUB_ENV" - echo "prod=${prod_value}" >> $GITHUB_OUTPUT - - name: Generate tags id: generated_docker_tags run: | echo "βœ… Exit script on any error" set -eu -o pipefail - # Use the script to generate tags - TAG_LIST=$(./scripts/generate-docker-tags.sh \ - "${{ steps.meta.outputs.REPO }}" \ - "${{ github.sha }}" \ - "${{ github.ref }}" \ - "$PROD") - - echo "Final list of generated tags:" - echo "$TAG_LIST" - - TAG_COUNT=$(echo "$TAG_LIST" | wc -l) - if [[ -z "${{ github.sha }}" || $TAG_COUNT -lt 4 ]]; then - echo "⚠️ No tags (or too few) were generated based on the logic. Need at least 4 tags. Generated: $TAG_COUNT" + # echo current shell + echo "Current shell: $SHELL" + + IMAGE_BASE_NAME="${{ steps.meta.outputs.REPO }}" + GIT_SHA="${{ github.sha }}" + GIT_REF="${{ github.ref }}" + + echo "Inputs for tagging:" + echo "IMAGE_BASE_NAME: $IMAGE_BASE_NAME" + echo "GIT_SHA: $GIT_SHA" + echo "GIT_REF: $GIT_REF" + echo "PROD status for tagging: ${PROD}" + + TAG_LIST=() + VERSION_TAG_LIST=() + + if [[ -n "$GIT_SHA" ]]; then + SHORT_SHA=$(echo "$GIT_SHA" | cut -c1-7) + TAG_LIST+=("${IMAGE_BASE_NAME}:sha-${SHORT_SHA}") + TAG_LIST+=("${IMAGE_BASE_NAME}:sha-${GIT_SHA}") + else + echo "πŸ”΄ No Git SHA found, cannot generate tags. Aborting." exit 1 fi - # Output the tags for the docker build action + GIT_TAG_VERSION="" + # Extract version only if GIT_REF is a tag like refs/tags/vX.Y.Z or refs/tags/vX.Y.Z-prerelease + if [[ "$GIT_REF" == refs/tags/v* ]]; then + GIT_TAG_VERSION=$(echo "$GIT_REF" | sed 's%refs/tags/v%%' | sed 's%-prerelease$%%') + + if [[ "$GIT_TAG_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "Detected Git tag: v$GIT_TAG_VERSION" + MAJOR=${BASH_REMATCH[1]} + MINOR=${BASH_REMATCH[2]} + PATCH=${BASH_REMATCH[3]} + echo "Parsed version: Major=$MAJOR, Minor=$MINOR, Patch=$PATCH from v$GIT_TAG_VERSION" + + if [ "$MAJOR" -gt 0 ]; then + VERSION_TAG_LIST+=("${IMAGE_BASE_NAME}:v${MAJOR}") + else + echo "ℹ️ Major version is 0 (v$GIT_TAG_VERSION). Skipping MAJOR-only tag v0. Please reference by MAJOR.MINOR or MAJOR.MINOR.PATCH." + fi + + VERSION_TAG_LIST+=("${IMAGE_BASE_NAME}:v${MAJOR}.${MINOR}") + VERSION_TAG_LIST+=("${IMAGE_BASE_NAME}:v${MAJOR}.${MINOR}.${PATCH}") + else + echo "⚠️ Git tag 'v$GIT_TAG_VERSION' is not a valid semantic version (x.y.z) but should be. Aborting." + exit 1 + fi + fi + + if [ "$PROD" = "true" ]; then + echo "πŸ“¦ Generating PROD tags." + TAG_LIST+=("${IMAGE_BASE_NAME}:stable") + TAG_LIST+=("${IMAGE_BASE_NAME}:latest") + + if [ ${#VERSION_TAG_LIST[@]} -gt 0 ]; then + TAG_LIST+=("${VERSION_TAG_LIST[@]}") + else + echo "πŸ”΄ PROD mode is true, but Git ref ($GIT_REF) is not a valid version tag. This is unexpected, aborting." + exit 1 + fi + else # Non-PROD + echo "πŸ”¨ Generating STAGING tags." + TAG_LIST+=("${IMAGE_BASE_NAME}:staging") + TAG_LIST+=("${IMAGE_BASE_NAME}:latest-staging") + + if [ ${#VERSION_TAG_LIST[@]} -gt 0 ]; then + echo "πŸ”¨ This is also a prerelease version, generating version tags with '-prerelease' suffix." + VERSION_TAG_LIST=("${VERSION_TAG_LIST[@]/%/-prerelease}") + + TAG_LIST+=("${VERSION_TAG_LIST[@]}") + else + echo "ℹ️ Git ref ($GIT_REF) is not a valid version tag. Skipping versioned -prerelease tag generation." + fi + fi + + echo "Final list of generated tags:" + printf "%s\n" "${TAG_LIST[@]}" + + if [[ -z "$GIT_SHA" || ${#TAG_LIST[@]} -lt 4 ]]; then + echo "⚠️ No tags (or too few) were generated based on the logic. Need at least 4 tags: Git commit short and full length SHA tags, a latest/latest-staging tag, and a stable/staging tag. This is unexpected, aborting." + exit 1 + fi + + # Output the tags for the docker build action (output name is 'tag') { echo "tag<> "$GITHUB_OUTPUT" - - name: Run prebuild tasks - run: ./scripts/prebuild.sh - - - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx- - - - name: Extract version for Docker build - id: extract_version run: | - pip install setuptools-scm - VERSION=$(python -c "from setuptools_scm import get_version; print(get_version())") - echo "VERSION=${VERSION}" >> $GITHUB_ENV - + echo "πŸ”„ Chdir to src/pkmntrade_club/theme/static_src" + cd src/pkmntrade_club/theme/static_src + + echo "πŸ“¦ Install npm dependencies" + npm install . + + echo "πŸ”¨ Build the tailwind theme css" + npm run build + # - name: Cache Docker layers + # uses: actions/cache@v4 + # with: + # path: ${{ runner.temp }}/.cache + # key: ${{ runner.os }}-${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}-${{ github.sha }} + # restore-keys: | + # ${{ runner.os }}-${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}- + # - name: Inject buildx-cache + # uses: reproducible-containers/buildkit-cache-dance@4b2444fec0c0fb9dbf175a96c094720a692ef810 # v2.1.4 + # with: + # cache-source: ${{ runner.temp }}/.cache/buildx-cache - name: Build container uses: docker/build-push-action@v6 with: outputs: type=docker,dest=${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar tags: ${{ steps.generated_docker_tags.outputs.tag }} - build-args: | - VERSION=${{ env.VERSION }} + build-args: CACHE_DIR=${{ runner.temp }}/.cache/dockerfile-cache context: . - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - - - name: Rotate cache - run: | - rm -rf /tmp/.buildx-cache - mv /tmp/.buildx-cache-new /tmp/.buildx-cache - - - name: Upload container as artifact - uses: actions/upload-artifact@v4 - with: - name: docker-image - path: ${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar - if-no-files-found: error - retention-days: 1 - - # Job 2: Deploy (only runs on main branch or tags) - deploy: - needs: build - runs-on: ubuntu-latest - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) - environment: ${{ needs.build.outputs.prod == 'true' && 'production' || 'staging' }} - steps: - - name: Checkout the repo - uses: actions/checkout@v4 - - - name: Ensure scripts are executable - run: chmod +x scripts/*.sh - - - name: Download container artifact - uses: actions/download-artifact@v4 - with: - name: docker-image - path: ${{ runner.temp }} - + #cache-from: type=local,src=${{ runner.temp }}/.cache/buildx-cache + #cache-to: type=local,src=${{ runner.temp }}/.cache/buildx-cache-new,mode=max + # - name: Rotate cache # along with cache-from & cache-to: prevents cache from growing indefinitely + # run: | + # rm -rf ${{ runner.temp }}/.cache/buildx-cache + # mv ${{ runner.temp }}/.cache/buildx-cache-new ${{ runner.temp }}/.cache/buildx-cache + # - name: Upload container as artifact + # uses: actions/upload-artifact@v4 + # with: + # name: ${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar + # path: ${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar + # if-no-files-found: error + # compression-level: 0 - name: Get Deploy Secrets uses: bitwarden/sm-action@v2 with: @@ -160,7 +205,6 @@ jobs: 9aefe34e-c2cf-442e-973c-b2dd0032b6cf > ENV_FILE_BASE64 d3bb47f8-bfc0-4a61-9cee-b2df0147a02a > CF_PEM_CERT 5f658ddf-aadd-4464-b501-b2df0147c338 > CF_PEM_CA - - name: Set up SSH run: | mkdir -p $HOME/.ssh @@ -180,40 +224,105 @@ jobs: ControlPath $HOME/.ssh/control-%C ControlPersist yes END - - - name: Deploy to Server - env: - DOCKER_HOST: ssh://deploy - REPO_PROJECT_PATH: ${{ needs.build.outputs.repo-path }} - REPO_NAME_ONLY: ${{ needs.build.outputs.repo-name }} - IMAGE_TAR: ${{ runner.temp }}/${{ needs.build.outputs.image-tar }} - IS_PROD: ${{ needs.build.outputs.prod }} + - name: Run Deploy Script run: | echo "βœ… Exit script on any error" set -eu -o pipefail - ./scripts/deploy-to-server.sh - - - name: Health Check and Rollback - run: | - # Determine the correct URL based on environment - if [ "${{ needs.build.outputs.prod }}" = "true" ]; then - # Ensure PRODUCTION_DOMAIN is set - if [ -z "${{ vars.PRODUCTION_DOMAIN }}" ]; then - echo "Error: PRODUCTION_DOMAIN is not set" - exit 1 + + echo "βš™οΈ Set docker host to ssh://deploy so that all docker commands are run on the remote server" + export DOCKER_HOST=ssh://deploy + + echo "πŸš€ Enable and start docker service" + ssh deploy "sudo systemctl enable --now docker.service" + + echo "πŸ’Ύ Load the new docker image (${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar)" + docker load -i "${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar" + + echo "🧹 Remove the docker image artifact" + rm "${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar" + + echo "πŸ’Ύ Copy new files to server" + ssh deploy "mkdir -p ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new" + scp -pr ./server/* deploy:${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/ + + echo "πŸ“ Create new .env file" + printf "%s" "${ENV_FILE_BASE64}" | base64 -d | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/.env && chmod 600 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/.env" + + echo "πŸ”‘ Set up certs" + ssh deploy "mkdir -p ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs && chmod 550 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs" + printf "%s" "$CF_PEM_CERT" | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/crt.pem && chmod 440 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/crt.pem && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/crt.pem" + printf "%s" "$CF_PEM_CA" | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/ca.pem && chmod 440 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/ca.pem && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/new/certs/ca.pem" + + ssh -T deploy <=2.3", ] [project.scripts] @@ -80,8 +79,3 @@ Homepage = "https://pkmntrade.club" [tool.setuptools.packages.find] where = ["src"] - -[tool.setuptools_scm] -version_scheme = "no-guess-dev" -tag_regex = "^v(?P[0-9]+(?:\\.[0-9]+)*(?:-.*)?)" -fallback_version = "0.0.0+unknown" diff --git a/scripts/deploy-to-server.sh b/scripts/deploy-to-server.sh deleted file mode 100644 index 15fd44b..0000000 --- a/scripts/deploy-to-server.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Main deployment script with versioned releases -# Usage: ./deploy-to-server.sh - -# Source retry function -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/retry.sh" - -# Required environment variables (should be set by GitHub Actions) -: "${DOCKER_HOST:?Error: DOCKER_HOST not set}" -: "${REPO_PROJECT_PATH:?Error: REPO_PROJECT_PATH not set}" -: "${REPO_NAME_ONLY:?Error: REPO_NAME_ONLY not set}" -: "${IMAGE_TAR:?Error: IMAGE_TAR not set}" -: "${ENV_FILE_BASE64:?Error: ENV_FILE_BASE64 not set}" -: "${CF_PEM_CERT:?Error: CF_PEM_CERT not set}" -: "${CF_PEM_CA:?Error: CF_PEM_CA not set}" -: "${IS_PROD:?Error: IS_PROD not set}" - -echo "βš™οΈ Docker host: $DOCKER_HOST" - -# Generate deployment timestamp -DEPLOYMENT_TIMESTAMP=$(date +%Y%m%d_%H%M%S) -RELEASES_PATH="${REPO_PROJECT_PATH}/releases" -NEW_RELEASE_PATH="${RELEASES_PATH}/${DEPLOYMENT_TIMESTAMP}" -CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current" - -echo "πŸ“… Deployment version: ${DEPLOYMENT_TIMESTAMP}" - -echo "πŸš€ Enable and start docker service" -retry ssh deploy "sudo systemctl enable --now docker.service" - -echo "πŸ’Ύ Load the new docker image ($IMAGE_TAR)" -if [ ! -f "$IMAGE_TAR" ]; then - echo "Error: Docker image tar file not found: $IMAGE_TAR" - exit 1 -fi -retry docker load -i "$IMAGE_TAR" - -echo "πŸ“ Create versioned release directory" -ssh deploy "mkdir -p '${NEW_RELEASE_PATH}'" - -echo "πŸ’Ύ Copy new files to server" -# Check if server directory exists before copying -if [ -d "./server" ]; then - retry scp -pr ./server/* "deploy:${NEW_RELEASE_PATH}/" -else - echo "⚠️ No server directory found, erroring out" - exit 1 -fi - -echo "πŸ“ Create new .env file" -printf "%s" "${ENV_FILE_BASE64}" | base64 -d | ssh deploy "cat > '${NEW_RELEASE_PATH}/.env' && chmod 600 '${NEW_RELEASE_PATH}/.env'" - -echo "πŸ”‘ Set up certs" -ssh deploy "mkdir -p '${NEW_RELEASE_PATH}/certs' && chmod 550 '${NEW_RELEASE_PATH}/certs' && chown 99:root '${NEW_RELEASE_PATH}/certs'" -printf "%s" "$CF_PEM_CERT" | ssh deploy "cat > '${NEW_RELEASE_PATH}/certs/crt.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/crt.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/crt.pem'" -printf "%s" "$CF_PEM_CA" | ssh deploy "cat > '${NEW_RELEASE_PATH}/certs/ca.pem' && chmod 440 '${NEW_RELEASE_PATH}/certs/ca.pem' && chown 99:root '${NEW_RELEASE_PATH}/certs/ca.pem'" - -echo "πŸ”„ Prepare deployment (stop current containers)" -# Copy script to remote and execute with parameters -scp "${SCRIPT_DIR}/prepare-deployment.sh" deploy:/tmp/ -ssh deploy "chmod +x /tmp/prepare-deployment.sh && /tmp/prepare-deployment.sh '${REPO_PROJECT_PATH}' '${IS_PROD}' '${CURRENT_LINK_PATH}'" -ssh deploy "rm -f /tmp/prepare-deployment.sh" - -echo "πŸ“ Save deployment metadata" -ssh deploy "echo '${DEPLOYMENT_TIMESTAMP}' > '${NEW_RELEASE_PATH}/.deployment_version'" -ssh deploy "echo '${IS_PROD}' > '${NEW_RELEASE_PATH}/.deployment_env'" - -# Save previous version info for potential rollback -ssh deploy "if [ -L '${CURRENT_LINK_PATH}' ]; then readlink -f '${CURRENT_LINK_PATH}' > '${NEW_RELEASE_PATH}/.previous_version'; fi" - -echo "πŸ”— Update current symlink to new release" -ssh deploy "ln -sfn '${NEW_RELEASE_PATH}' '${CURRENT_LINK_PATH}'" - -# TODO: implement zero-downtime deployment -# echo "πŸš€ Start the new containers, zero-downtime" -# if [ "${PROD}" = true ]; then -# ssh deploy </dev/null | tail -n +6 | xargs -r rm -rf || true" - -echo "βœ… Deployment completed. Version: ${DEPLOYMENT_TIMESTAMP}" \ No newline at end of file diff --git a/scripts/generate-docker-tags.sh b/scripts/generate-docker-tags.sh deleted file mode 100644 index b22dc5b..0000000 --- a/scripts/generate-docker-tags.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Generate Docker tags based on git ref and environment -# Usage: ./generate-docker-tags.sh IMAGE_BASE GIT_SHA GIT_REF IS_PROD - -if [ $# -ne 4 ]; then - echo "Error: Invalid number of arguments" - echo "Usage: $0 IMAGE_BASE GIT_SHA GIT_REF IS_PROD" - exit 1 -fi - -IMAGE_BASE="$1" -GIT_SHA="$2" -GIT_REF="$3" -IS_PROD="$4" - -# Validate inputs -if [ -z "$IMAGE_BASE" ] || [ -z "$GIT_SHA" ]; then - echo "Error: IMAGE_BASE and GIT_SHA cannot be empty" - exit 1 -fi - -# Always include SHA tags -echo "${IMAGE_BASE}:sha-${GIT_SHA:0:7}" -echo "${IMAGE_BASE}:sha-${GIT_SHA}" - -# Handle version tags -if [[ "$GIT_REF" =~ ^refs/tags/v([0-9]+)\.([0-9]+)\.([0-9]+)(-.*)?$ ]]; then - MAJOR="${BASH_REMATCH[1]}" - MINOR="${BASH_REMATCH[2]}" - PATCH="${BASH_REMATCH[3]}" - PRERELEASE="${BASH_REMATCH[4]}" - - if [[ -z "$PRERELEASE" ]] && [[ "$IS_PROD" == "true" ]]; then - echo "${IMAGE_BASE}:latest" - echo "${IMAGE_BASE}:stable" - [[ "$MAJOR" -gt 0 ]] && echo "${IMAGE_BASE}:v${MAJOR}" - echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}" - echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}" - else - echo "${IMAGE_BASE}:latest-staging" - echo "${IMAGE_BASE}:staging" - echo "${IMAGE_BASE}:v${MAJOR}.${MINOR}.${PATCH}-prerelease" - fi -elif [[ "$IS_PROD" == "false" ]]; then - echo "${IMAGE_BASE}:latest-staging" - echo "${IMAGE_BASE}:staging" -fi \ No newline at end of file diff --git a/scripts/health-check-and-rollback.sh b/scripts/health-check-and-rollback.sh deleted file mode 100644 index 446f1f5..0000000 --- a/scripts/health-check-and-rollback.sh +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Perform health check and rollback if necessary -# Usage: ./health-check-and-rollback.sh REPO_PROJECT_PATH IS_PROD HEALTH_CHECK_URL [MAX_ATTEMPTS] - -if [ $# -lt 3 ]; then - echo "Error: Invalid number of arguments" - echo "Usage: $0 REPO_PROJECT_PATH IS_PROD HEALTH_CHECK_URL [MAX_ATTEMPTS]" - exit 1 -fi - -REPO_PROJECT_PATH="$1" -IS_PROD="$2" -HEALTH_CHECK_URL="$3" -MAX_ATTEMPTS="${4:-30}" - -CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current" -RELEASES_PATH="${REPO_PROJECT_PATH}/releases" - -echo "πŸ₯ Performing health check..." -echo "Health check URL: $HEALTH_CHECK_URL" - -get_current_version() { - if [ -L "$CURRENT_LINK_PATH" ]; then - basename "$(readlink -f "$CURRENT_LINK_PATH")" - else - echo "unknown" - fi -} - -ATTEMPT=0 -while [ "$ATTEMPT" -lt "$MAX_ATTEMPTS" ]; do - # Check if the service is responding with 200 OK - HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' -m 10 "$HEALTH_CHECK_URL" || echo '000') - - if [ "$HTTP_CODE" = "200" ]; then - echo "βœ… Health check passed! (HTTP $HTTP_CODE)" - CURRENT_VERSION=$(get_current_version) - echo "πŸ“Œ Current version: ${CURRENT_VERSION}" - exit 0 - fi - - ATTEMPT=$((ATTEMPT + 1)) - if [ "$ATTEMPT" -eq "$MAX_ATTEMPTS" ]; then - echo "❌ Health check failed after $MAX_ATTEMPTS attempts (Last HTTP code: $HTTP_CODE)" - echo "πŸ”„ Rolling back deployment..." - - FAILED_VERSION=$(get_current_version) - echo "❌ Failed version: ${FAILED_VERSION}" - - # Check if we have a previous version to roll back to - if [ -f "${CURRENT_LINK_PATH}/.previous_version" ]; then - PREVIOUS_VERSION_PATH=$(cat "${CURRENT_LINK_PATH}/.previous_version") - PREVIOUS_VERSION=$(basename "$PREVIOUS_VERSION_PATH") - - if [ -d "$PREVIOUS_VERSION_PATH" ]; then - echo "πŸ”„ Rolling back to version: ${PREVIOUS_VERSION}" - - # Stop failed deployment containers - cd "$CURRENT_LINK_PATH" - echo "Stopping failed deployment containers..." - docker compose -f docker-compose_web.yml down || true - if [ "$IS_PROD" = "false" ]; then - docker compose -f docker-compose_staging.yml down || true - fi - docker compose -f docker-compose_core.yml down || true - - # Switch symlink back to previous version - ln -sfn "$PREVIOUS_VERSION_PATH" "$CURRENT_LINK_PATH" - - # Start previous version containers - cd "$CURRENT_LINK_PATH" - docker compose -f docker-compose_core.yml up -d --no-build - if [ "$IS_PROD" = "true" ]; then - docker compose -f docker-compose_web.yml up -d --no-build - else - docker compose -f docker-compose_web.yml -f docker-compose_staging.yml up -d --no-build - fi - - echo "βœ… Rollback completed to version: ${PREVIOUS_VERSION}" - - # Mark failed version - if [ -d "${RELEASES_PATH}/${FAILED_VERSION}" ]; then - touch "${RELEASES_PATH}/${FAILED_VERSION}/.failed" - echo "$(date): Health check failed, rolled back to ${PREVIOUS_VERSION}" > "${RELEASES_PATH}/${FAILED_VERSION}/.failure_reason" - fi - else - echo "❌ Previous version directory not found: $PREVIOUS_VERSION_PATH" - exit 1 - fi - else - echo "❌ No previous version information found. Cannot rollback!" - echo "πŸ’‘ This might be the first deployment or the previous version info is missing." - exit 1 - fi - exit 1 - fi - - echo "⏳ Waiting for service to be healthy... (attempt $ATTEMPT/$MAX_ATTEMPTS, HTTP code: $HTTP_CODE)" - sleep 10 -done \ No newline at end of file diff --git a/scripts/manage-releases.sh b/scripts/manage-releases.sh deleted file mode 100644 index 255c35c..0000000 --- a/scripts/manage-releases.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Manage deployment releases -# Usage: ./manage-releases.sh REPO_PROJECT_PATH COMMAND [ARGS] - -if [ $# -lt 2 ]; then - echo "Error: Invalid number of arguments" - echo "Usage: $0 REPO_PROJECT_PATH COMMAND [ARGS]" - echo "Commands:" - echo " list - List all releases" - echo " current - Show current release" - echo " rollback VERSION - Rollback to specific version" - echo " cleanup [KEEP] - Clean up old releases (default: keep 5)" - exit 1 -fi - -REPO_PROJECT_PATH="$1" -COMMAND="$2" -CURRENT_LINK_PATH="${REPO_PROJECT_PATH}/current" -RELEASES_PATH="${REPO_PROJECT_PATH}/releases" - -case "$COMMAND" in - list) - echo "πŸ“‹ Available releases:" - if [ -d "$RELEASES_PATH" ]; then - for release in $(ls -dt "${RELEASES_PATH}"/*/); do - version=$(basename "$release") - status="" - - # Check if it's current - if [ -L "$CURRENT_LINK_PATH" ] && [ "$(readlink -f "$CURRENT_LINK_PATH")" = "$(realpath "$release")" ]; then - status=" [CURRENT]" - fi - - # Check if it failed - if [ -f "${release}/.failed" ]; then - status="${status} [FAILED]" - fi - - echo " - ${version}${status}" - done - else - echo "No releases found" - fi - ;; - - current) - if [ -L "$CURRENT_LINK_PATH" ]; then - current_version=$(basename "$(readlink -f "$CURRENT_LINK_PATH")") - echo "πŸ“Œ Current version: ${current_version}" - else - echo "❌ No current deployment found" - fi - ;; - - rollback) - if [ $# -lt 3 ]; then - echo "Error: VERSION required for rollback" - exit 1 - fi - TARGET_VERSION="$3" - TARGET_PATH="${RELEASES_PATH}/${TARGET_VERSION}" - - if [ ! -d "$TARGET_PATH" ]; then - echo "Error: Version ${TARGET_VERSION} not found" - exit 1 - fi - - echo "πŸ”„ Rolling back to version: ${TARGET_VERSION}" - - # Read environment from target version - if [ -f "${TARGET_PATH}/.deployment_env" ]; then - IS_PROD=$(cat "${TARGET_PATH}/.deployment_env") - else - echo "Warning: Could not determine environment, assuming staging" - IS_PROD="false" - fi - - # Stop current containers - if [ -L "$CURRENT_LINK_PATH" ] && [ -d "$CURRENT_LINK_PATH" ]; then - cd "$CURRENT_LINK_PATH" - docker compose -f docker-compose_web.yml down || true - [ "$IS_PROD" = "false" ] && docker compose -f docker-compose_staging.yml down || true - docker compose -f docker-compose_core.yml down || true - fi - - # Update symlink - ln -sfn "$TARGET_PATH" "$CURRENT_LINK_PATH" - - # Start containers - cd "$CURRENT_LINK_PATH" - docker compose -f docker-compose_core.yml up -d --no-build - if [ "$IS_PROD" = "true" ]; then - docker compose -f docker-compose_web.yml up -d --no-build - else - docker compose -f docker-compose_web.yml -f docker-compose_staging.yml up -d --no-build - fi - - echo "βœ… Rollback completed" - ;; - - cleanup) - KEEP_COUNT="${3:-5}" - echo "πŸ—‘οΈ Cleaning up old releases (keeping last ${KEEP_COUNT})" - - if [ -d "$RELEASES_PATH" ]; then - cd "$RELEASES_PATH" - ls -dt */ 2>/dev/null | tail -n +$((KEEP_COUNT + 1)) | xargs -r rm -rf || true - echo "βœ… Cleanup completed" - else - echo "No releases directory found" - fi - ;; - - *) - echo "Error: Unknown command: $COMMAND" - exit 1 - ;; -esac \ No newline at end of file diff --git a/scripts/parse-repository-name.sh b/scripts/parse-repository-name.sh deleted file mode 100644 index 2e3aa80..0000000 --- a/scripts/parse-repository-name.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Parse repository name and generate project paths -# Usage: ./parse-repository-name.sh GITHUB_REPOSITORY - -if [ $# -eq 0 ]; then - echo "Error: No repository name provided" - echo "Usage: $0 GITHUB_REPOSITORY" - exit 1 -fi - -GITHUB_REPOSITORY="$1" - -echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" - -if [[ "$GITHUB_REPOSITORY" == *".git" ]]; then - if [[ "$GITHUB_REPOSITORY" == "https://"* ]]; then - echo "GITHUB_REPOSITORY ends in .git and is a URL" - REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | cut -d'/' -f4-5 | sed 's/[^a-zA-Z0-9\/-]/-/g') - else - echo "GITHUB_REPOSITORY ends in .git and is not a URL" - REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g') - fi -else - echo "GITHUB_REPOSITORY is not a URL" - REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/[^a-zA-Z0-9\/-]/-/g') -fi - -REPO_NAME_ONLY=$(echo "$REPO" | cut -d'/' -f2) -REPO_PROJECT_PATH="/srv/${REPO_NAME_ONLY}" - -# Output in format that can be sourced - using printf %q for proper escaping -printf "export REPO=%q\n" "$REPO" -printf "export REPO_NAME_ONLY=%q\n" "$REPO_NAME_ONLY" -printf "export REPO_PROJECT_PATH=%q\n" "$REPO_PROJECT_PATH" \ No newline at end of file diff --git a/scripts/prepare-deployment.sh b/scripts/prepare-deployment.sh deleted file mode 100644 index ae6ac29..0000000 --- a/scripts/prepare-deployment.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Prepare deployment by stopping containers -# Usage: ./prepare-deployment.sh REPO_PROJECT_PATH IS_PROD CURRENT_LINK_PATH - -if [ $# -ne 3 ]; then - echo "Error: Invalid number of arguments" - echo "Usage: $0 REPO_PROJECT_PATH IS_PROD CURRENT_LINK_PATH" - exit 1 -fi - -REPO_PROJECT_PATH="$1" -IS_PROD="$2" -CURRENT_LINK_PATH="$3" - -# Ensure base directory exists -if [ ! -d "$REPO_PROJECT_PATH" ]; then - echo "⚠️ Directory $REPO_PROJECT_PATH does not exist, creating it..." - mkdir -p "$REPO_PROJECT_PATH" -fi - -# If current symlink exists, stop containers in that directory -if [ -L "$CURRENT_LINK_PATH" ] && [ -d "$CURRENT_LINK_PATH" ]; then - echo "πŸ›‘ Stopping containers in current deployment..." - cd "$CURRENT_LINK_PATH" - - # Stop containers - if [ -f "docker-compose_web.yml" ]; then - docker compose -f docker-compose_web.yml down || true - fi - - if [ "$IS_PROD" = "false" ] && [ -f "docker-compose_staging.yml" ]; then - docker compose -f docker-compose_staging.yml down || true - fi - - if [ -f "docker-compose_core.yml" ]; then - docker compose -f docker-compose_core.yml down || true - fi - - echo "βœ… Containers stopped" -else - echo "ℹ️ No current deployment found (symlink doesn't exist or point to valid directory)" -fi \ No newline at end of file diff --git a/scripts/retry.sh b/scripts/retry.sh deleted file mode 100644 index 42ee35c..0000000 --- a/scripts/retry.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# Retry function with exponential backoff -# Usage: source retry.sh && retry - -retry() { - local max_attempts=3 - local delay=5 - local attempt=1 - - until "$@"; do - if [ "$attempt" -ge "$max_attempts" ]; then - echo "Command failed after $max_attempts attempts: $*" - return 1 - fi - - echo "Command failed (attempt $attempt/$max_attempts): $*" - echo "Retrying in $delay seconds..." - sleep "$delay" - attempt=$((attempt + 1)) - delay=$((delay * 2)) # Exponential backoff - done -} \ No newline at end of file diff --git a/src/pkmntrade_club/__init__.py b/src/pkmntrade_club/__init__.py index 8d1f1f7..e69de29 100644 --- a/src/pkmntrade_club/__init__.py +++ b/src/pkmntrade_club/__init__.py @@ -1,5 +0,0 @@ -"""pkmntrade.club - A django project for trading PokΓ©mon TCG Pocket Cards""" - -from pkmntrade_club._version import __version__, get_version, get_version_info - -__all__ = ['__version__', 'get_version', 'get_version_info'] diff --git a/src/pkmntrade_club/_version.py b/src/pkmntrade_club/_version.py deleted file mode 100644 index 6f90c71..0000000 --- a/src/pkmntrade_club/_version.py +++ /dev/null @@ -1,61 +0,0 @@ -from importlib.metadata import version, PackageNotFoundError -from setuptools_scm import get_version -""" -Version module for pkmntrade.club - -This module provides version information from git tags via setuptools-scm. -""" - -try: - __version__ = version("pkmntrade-club") -except PackageNotFoundError: - # Package is not installed, try to get version from setuptools_scm - try: - __version__ = get_version(root='../../..', relative_to=__file__) - except (ImportError, LookupError): - __version__ = "0.0.0+unknown" - -def get_version(): - """Return the current version.""" - return __version__ - -def get_version_info(): - """Return detailed version information.""" - import re - - # Parse version string (e.g., "1.2.3", "1.2.3.dev4+gabc1234", "1.2.3-prerelease") - match = re.match( - r'^(\d+)\.(\d+)\.(\d+)' - r'(?:\.dev(\d+))?' - r'(?:\+g([a-f0-9]+))?' - r'(?:-(.+))?$', - __version__ - ) - - if match: - major, minor, patch, dev, git_sha, prerelease = match.groups() - return { - 'version': __version__, - 'major': int(major), - 'minor': int(minor), - 'patch': int(patch), - 'dev': int(dev) if dev else None, - 'git_sha': git_sha, - 'prerelease': prerelease, - 'is_release': dev is None and not prerelease, - 'is_prerelease': bool(prerelease), - 'is_dev': dev is not None - } - - return { - 'version': __version__, - 'major': 0, - 'minor': 0, - 'patch': 0, - 'dev': None, - 'git_sha': None, - 'prerelease': None, - 'is_release': False, - 'is_prerelease': False, - 'is_dev': True -} \ No newline at end of file diff --git a/src/pkmntrade_club/common/context_processors.py b/src/pkmntrade_club/common/context_processors.py index 7950ded..22b6b57 100644 --- a/src/pkmntrade_club/common/context_processors.py +++ b/src/pkmntrade_club/common/context_processors.py @@ -3,10 +3,4 @@ from django.conf import settings def cache_settings(request): return { 'CACHE_TIMEOUT': settings.CACHE_TIMEOUT, - } - -def version_info(request): - return { - 'VERSION': settings.VERSION, - 'VERSION_INFO': settings.VERSION_INFO, } \ No newline at end of file diff --git a/src/pkmntrade_club/django_project/settings.py b/src/pkmntrade_club/django_project/settings.py index 550b184..270ecdc 100644 --- a/src/pkmntrade_club/django_project/settings.py +++ b/src/pkmntrade_club/django_project/settings.py @@ -4,8 +4,6 @@ import environ import os import logging import sys -from django.utils.translation import gettext_lazy as _ -from pkmntrade_club._version import __version__, get_version_info # set default values to local dev values env = environ.Env( @@ -86,9 +84,6 @@ CACHE_TIMEOUT = env('CACHE_TIMEOUT') DISABLE_SIGNUPS = env('DISABLE_SIGNUPS') DISABLE_CACHE = env('DISABLE_CACHE') -VERSION = __version__ -VERSION_INFO = get_version_info() - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ @@ -218,7 +213,6 @@ TEMPLATES = [ "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "pkmntrade_club.common.context_processors.cache_settings", - "pkmntrade_club.common.context_processors.version_info", ], }, }, diff --git a/uv.lock b/uv.lock index 2c2eefa..19bc266 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 1 requires-python = ">=3.12" [[package]] @@ -106,7 +107,7 @@ name = "click" version = "8.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } wheels = [ @@ -345,18 +346,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/78/2fb6ff7df06fe4ad31f3f9b9b80e682317b6d22188148dca52e0ec87bf4a/django_meta-2.4.2-py2.py3-none-any.whl", hash = "sha256:afc6b77c3885db0cd97883d1dc3df47f91a9c7951b2f4928fee91ca60a7d0ff2", size = 27792 }, ] -[[package]] -name = "django-parler" -version = "2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "django" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8c/2b/2423d31620efe8ab0d0390e60afab4f9cc2e62d4bf39fe0e05df0eef1b93/django-parler-2.3.tar.gz", hash = "sha256:2c8f5012ceb5e49af93b16ea3fe4d0c83d70b91b2d0f470c05d7d742b6f3083d", size = 69167 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/38/11f1a7e3d56f3a6b74cf99e307f2554b741cadebc9b1c45b05e2ec1f35a2/django_parler-2.3-py3-none-any.whl", hash = "sha256:8f6c8061e4b5690f1ee2d8e5760940ef06bf78a5bfa033d11178377559c749cf", size = 83288 }, -] - [[package]] name = "django-tailwind-4" version = "0.1.4" @@ -617,7 +606,6 @@ dependencies = [ { name = "django-health-check" }, { name = "django-linear-migrations" }, { name = "django-meta" }, - { name = "django-parler" }, { name = "django-tailwind-4", extra = ["reload"] }, { name = "django-widget-tweaks" }, { name = "gevent" }, @@ -664,7 +652,6 @@ requires-dist = [ { name = "django-health-check", specifier = ">=3.18.3" }, { name = "django-linear-migrations", specifier = ">=2.17.0" }, { name = "django-meta", specifier = "==2.4.2" }, - { name = "django-parler", specifier = ">=2.3" }, { name = "django-tailwind-4", extras = ["reload"], specifier = "==0.1.4" }, { name = "django-widget-tweaks", specifier = "==1.5.0" }, { name = "gevent", specifier = "==25.4.1" },