Compare commits
1 commit
main
...
feat-add-d
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c912de307 |
41 changed files with 568 additions and 1474 deletions
0
.envrc
Executable file → Normal file
0
.envrc
Executable file → Normal file
412
.github/workflows/build_deploy.yml
vendored
412
.github/workflows/build_deploy.yml
vendored
|
|
@ -8,37 +8,57 @@ on:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Job 1: Build the Docker image
|
build-deploy:
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
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.meta.outputs.prod }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout the repo
|
- name: Checkout the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
- name: 🏗 Set up yq
|
||||||
- name: Ensure scripts are executable
|
uses: frenck/action-setup-yq@v1
|
||||||
run: chmod +x scripts/*.sh
|
- name: Get full and partial repository name
|
||||||
|
|
||||||
- name: Setup build metadata and environment
|
|
||||||
id: meta
|
id: meta
|
||||||
|
run: |
|
||||||
|
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
|
||||||
|
repo_temp=""
|
||||||
|
|
||||||
|
if [[ "${{ github.repository }}" == git@* && "${{ github.repository }}" == *:* && "${{ github.repository }}" == *.git ]]; then
|
||||||
|
echo "Detected SSH style git remote (e.g. git@host:owner/repo.git)"
|
||||||
|
# Extracts 'owner/repo' from 'git@host:owner/repo.git'
|
||||||
|
repo_temp=$(echo "${{ github.repository }}" | sed 's/\.git$//' | awk -F: '{print $2}')
|
||||||
|
elif [[ "${{ github.repository }}" == "https://"* && "${{ github.repository }}" == *.git ]]; then
|
||||||
|
echo "Detected HTTPS URL ending in .git (e.g. https://host/owner/repo.git)"
|
||||||
|
# Extracts 'owner/repo' from 'https://host/owner/repo.git'
|
||||||
|
repo_temp=$(echo "${{ github.repository }}" | sed 's/\.git$//' | awk -F/ '{print $(NF-1)"/"$(NF)}')
|
||||||
|
elif [[ "${{ github.repository }}" == *.git ]]; then # Catches other cases ending in .git, like owner/repo.git or other path based .git repos
|
||||||
|
echo "Detected path-like git repo ending in .git (e.g. owner/repo.git)"
|
||||||
|
# Extracts 'owner/repo' from 'owner/repo.git'
|
||||||
|
repo_temp=$(echo "${{ github.repository }}" | sed 's/\.git$//')
|
||||||
|
else
|
||||||
|
echo "Assuming owner/repo format (e.g. owner/repo)"
|
||||||
|
repo_temp="${{ github.repository }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sanitize and lowercase for Docker image naming conventions
|
||||||
|
REPO=$(echo "$repo_temp" | tr '[:upper:]' '[:lower:]' | sed -e 's/[^a-z0-9\/-]/-/g' -e 's/--\+/-/g' -e 's/^-//g' -e 's/-$//g')
|
||||||
|
echo "REPO (image base name): $REPO"
|
||||||
|
echo "REPO=$REPO" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
REPO_NAME_ONLY=$(echo "$REPO" | cut -d'/' -f2)
|
||||||
|
echo "REPO_NAME_ONLY (project name): $REPO_NAME_ONLY"
|
||||||
|
echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
REPO_PROJECT_PATH=/srv/$(echo "$REPO_NAME_ONLY")
|
||||||
|
echo "REPO_PROJECT_PATH: $REPO_PROJECT_PATH"
|
||||||
|
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
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Exit script on any error"
|
echo "✅ Exit script on any error"
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# Parse repository name and set outputs
|
|
||||||
eval "$(./scripts/parse-repository-name.sh '${{ github.repository }}')"
|
|
||||||
echo "REPO=$REPO" >> $GITHUB_OUTPUT
|
|
||||||
echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
|
|
||||||
echo "REPO_PROJECT_PATH=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Determine PROD environment
|
|
||||||
prod_value=""
|
prod_value=""
|
||||||
|
|
||||||
echo "🔍 Check if PROD is set via vars; if not, determine from github.ref"
|
echo "🔍 Check if PROD is set via vars; if not, determine from github.ref"
|
||||||
if [ -z "${{ vars.PROD }}" ]; then
|
if [ -z "${{ vars.PROD }}" ]; then
|
||||||
prod_value="${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease') }}"
|
prod_value="${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease') }}"
|
||||||
|
|
@ -47,141 +67,188 @@ jobs:
|
||||||
prod_value="${{ vars.PROD }}"
|
prod_value="${{ vars.PROD }}"
|
||||||
echo "📦 PROD mode already set to: ${prod_value}"
|
echo "📦 PROD mode already set to: ${prod_value}"
|
||||||
fi
|
fi
|
||||||
echo "prod=${prod_value}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Set environment variables for subsequent steps
|
|
||||||
echo "🖊️ Writing determined values to GITHUB_ENV:"
|
echo "🖊️ Writing determined values to GITHUB_ENV:"
|
||||||
echo "PROD=${prod_value}" >> $GITHUB_ENV
|
echo "PROD=${prod_value}" >> $GITHUB_ENV
|
||||||
echo "PROD=${prod_value} -> GITHUB_ENV"
|
echo "PROD=${prod_value} -> GITHUB_ENV"
|
||||||
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar" >> $GITHUB_ENV
|
|
||||||
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar -> GITHUB_ENV"
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Generate tags
|
- name: Generate tags
|
||||||
id: generated_docker_tags
|
id: generated_docker_tags
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Exit script on any error"
|
echo "✅ Exit script on any error"
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
|
||||||
# Use the script to generate tags
|
# echo current shell
|
||||||
TAG_LIST=$(./scripts/generate-docker-tags.sh \
|
echo "Current shell: $SHELL"
|
||||||
"${{ steps.meta.outputs.REPO }}" \
|
|
||||||
"${{ github.sha }}" \
|
|
||||||
"${{ github.ref }}" \
|
|
||||||
"$PROD")
|
|
||||||
|
|
||||||
echo "Final list of generated tags:"
|
IMAGE_BASE_NAME="${{ steps.meta.outputs.REPO }}"
|
||||||
echo "$TAG_LIST"
|
GIT_SHA="${{ github.sha }}"
|
||||||
|
GIT_REF="${{ github.ref }}"
|
||||||
|
|
||||||
TAG_COUNT=$(echo "$TAG_LIST" | wc -l)
|
echo "Inputs for tagging:"
|
||||||
if [[ -z "${{ github.sha }}" || $TAG_COUNT -lt 4 ]]; then
|
echo "IMAGE_BASE_NAME: $IMAGE_BASE_NAME"
|
||||||
echo "⚠️ No tags (or too few) were generated based on the logic. Need at least 4 tags. Generated: $TAG_COUNT"
|
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
|
exit 1
|
||||||
fi
|
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<<EOF"
|
echo "tag<<EOF"
|
||||||
echo "$TAG_LIST"
|
printf "%s\n" "${TAG_LIST[@]}"
|
||||||
echo "EOF"
|
echo "EOF"
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Run prebuild tasks
|
- 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: |
|
run: |
|
||||||
pip install setuptools-scm
|
echo "🔄 Chdir to src/pkmntrade_club/theme/static_src"
|
||||||
VERSION=$(python -c "from setuptools_scm import get_version; print(get_version())")
|
cd src/pkmntrade_club/theme/static_src
|
||||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
|
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
|
- name: Build container
|
||||||
|
id: build_container
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
outputs: type=docker,dest=${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar
|
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 }}
|
tags: ${{ steps.generated_docker_tags.outputs.tag }}
|
||||||
build-args: |
|
build-args: CACHE_DIR=${{ runner.temp }}/.cache/dockerfile-cache
|
||||||
VERSION=${{ env.VERSION }}
|
|
||||||
context: .
|
context: .
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
#cache-from: type=local,src=${{ runner.temp }}/.cache/buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
#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
|
||||||
- name: Rotate cache
|
# 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: Update docker compose and "pin" just built image by adding image digest to tag
|
||||||
|
env:
|
||||||
|
IMAGE_DIGEST: ${{ steps.build_container.outputs.DIGEST }}
|
||||||
|
IMAGE_BASE_NAME: ${{ steps.meta.outputs.REPO }}
|
||||||
run: |
|
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: ${{ env.IMAGE_TAR_NAME }}
|
|
||||||
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/'))
|
|
||||||
# Determine environment based on ref
|
|
||||||
environment: ${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease')) && 'production' || 'staging' }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout the repo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Ensure scripts are executable
|
|
||||||
run: chmod +x scripts/*.sh
|
|
||||||
|
|
||||||
- name: Setup deployment metadata and environment
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
echo "✅ Exit script on any error"
|
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
|
echo "PROD_STATUS: ${PROD}"
|
||||||
|
echo "IMAGE_DIGEST: ${IMAGE_DIGEST}"
|
||||||
|
echo "IMAGE_BASE_NAME (for compose update): ${IMAGE_BASE_NAME}"
|
||||||
|
|
||||||
# Parse repository name and set outputs
|
TARGET_COMPOSE_FILE=""
|
||||||
eval "$(./scripts/parse-repository-name.sh '${{ github.repository }}')"
|
SERVICE_YQ_PATH=""
|
||||||
echo "REPO=$REPO" >> $GITHUB_OUTPUT
|
IMAGE_TAG=""
|
||||||
echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
|
|
||||||
echo "REPO_PROJECT_PATH=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Determine PROD environment
|
echo "Checking yq version..."
|
||||||
prod_value=""
|
yq --version
|
||||||
echo "🔍 Check if PROD is set via vars; if not, determine from github.ref"
|
|
||||||
if [ -z "${{ vars.PROD }}" ]; then
|
if [ "${PROD}" = "true" ]; then
|
||||||
prod_value="${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease') }}"
|
TARGET_COMPOSE_FILE="./server/docker-compose_web.yml"
|
||||||
echo "📦 PROD mode unset, determined from github.ref (starts with v and does not end with -prerelease?): ${prod_value}"
|
SERVICE1_YQ_PATH=".services.web"
|
||||||
|
SERVICE2_YQ_PATH=".services.celery"
|
||||||
|
IMAGE_TAG="stable"
|
||||||
|
|
||||||
|
echo "Updating PROD configuration in $TARGET_COMPOSE_FILE for service $SERVICE_YQ_PATH"
|
||||||
|
yq -i "${SERVICE1_YQ_PATH}.image = \"${IMAGE_BASE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}\"" "$TARGET_COMPOSE_FILE"
|
||||||
|
yq -i "${SERVICE2_YQ_PATH}.image = \"${IMAGE_BASE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}\"" "$TARGET_COMPOSE_FILE"
|
||||||
else
|
else
|
||||||
prod_value="${{ vars.PROD }}"
|
TARGET_COMPOSE_FILE="./server/docker-compose_staging.yml"
|
||||||
echo "📦 PROD mode already set to: ${prod_value}"
|
SERVICE1_YQ_PATH=".services.\"web-staging\""
|
||||||
|
SERVICE2_YQ_PATH=".services.\"celery-staging\""
|
||||||
|
IMAGE_TAG="staging"
|
||||||
|
|
||||||
|
echo "Updating STAGING configuration in $TARGET_COMPOSE_FILE for service $SERVICE_YQ_PATH"
|
||||||
|
yq -i "${SERVICE1_YQ_PATH}.image = \"${IMAGE_BASE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}\"" "$TARGET_COMPOSE_FILE"
|
||||||
|
yq -i "${SERVICE2_YQ_PATH}.image = \"${IMAGE_BASE_NAME}:${IMAGE_TAG}@${IMAGE_DIGEST}\"" "$TARGET_COMPOSE_FILE"
|
||||||
fi
|
fi
|
||||||
echo "prod=${prod_value}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
# Set all deployment environment variables
|
|
||||||
echo "📝 Setting deployment environment variables"
|
|
||||||
echo "REPO_PROJECT_PATH=${REPO_PROJECT_PATH}" >> $GITHUB_ENV
|
|
||||||
echo "REPO_NAME_ONLY=${REPO_NAME_ONLY}" >> $GITHUB_ENV
|
|
||||||
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar" >> $GITHUB_ENV
|
|
||||||
echo "PROD=${prod_value}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Download container artifact
|
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ env.IMAGE_TAR_NAME }}
|
|
||||||
path: ${{ runner.temp }}
|
|
||||||
|
|
||||||
|
echo "Successfully updated $TARGET_COMPOSE_FILE. Image pinned to:"
|
||||||
|
yq "${SERVICE_YQ_PATH}.image" "$TARGET_COMPOSE_FILE"
|
||||||
- name: Get Deploy Secrets
|
- name: Get Deploy Secrets
|
||||||
uses: bitwarden/sm-action@v2
|
uses: bitwarden/sm-action@v2
|
||||||
with:
|
with:
|
||||||
|
|
@ -192,7 +259,6 @@ jobs:
|
||||||
9aefe34e-c2cf-442e-973c-b2dd0032b6cf > ENV_FILE_BASE64
|
9aefe34e-c2cf-442e-973c-b2dd0032b6cf > ENV_FILE_BASE64
|
||||||
d3bb47f8-bfc0-4a61-9cee-b2df0147a02a > CF_PEM_CERT
|
d3bb47f8-bfc0-4a61-9cee-b2df0147a02a > CF_PEM_CERT
|
||||||
5f658ddf-aadd-4464-b501-b2df0147c338 > CF_PEM_CA
|
5f658ddf-aadd-4464-b501-b2df0147c338 > CF_PEM_CA
|
||||||
|
|
||||||
- name: Set up SSH
|
- name: Set up SSH
|
||||||
run: |
|
run: |
|
||||||
mkdir -p $HOME/.ssh
|
mkdir -p $HOME/.ssh
|
||||||
|
|
@ -212,40 +278,82 @@ jobs:
|
||||||
ControlPath $HOME/.ssh/control-%C
|
ControlPath $HOME/.ssh/control-%C
|
||||||
ControlPersist yes
|
ControlPersist yes
|
||||||
END
|
END
|
||||||
|
- name: Run Deploy Script
|
||||||
- name: Deploy to Server
|
|
||||||
env:
|
|
||||||
DOCKER_HOST: ssh://deploy
|
|
||||||
REPO_PROJECT_PATH: ${{ env.REPO_PROJECT_PATH }}
|
|
||||||
REPO_NAME_ONLY: ${{ env.REPO_NAME_ONLY }}
|
|
||||||
IMAGE_TAR: ${{ runner.temp }}/${{ env.IMAGE_TAR_NAME }}
|
|
||||||
PROD: ${{ env.PROD }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "✅ Exit script on any error"
|
echo "✅ Exit script on any error"
|
||||||
set -eu -o pipefail
|
set -eu -o pipefail
|
||||||
./scripts/deploy-to-server.sh
|
|
||||||
|
|
||||||
- name: Health Check and Rollback
|
echo "⚙️ Set docker host to ssh://deploy so that all docker commands are run on the remote server"
|
||||||
run: |
|
export DOCKER_HOST=ssh://deploy
|
||||||
# Determine the correct URL based on environment
|
|
||||||
if [ "${{ env.PROD }}" = "true" ]; then
|
echo "🚀 Enable and start docker service"
|
||||||
# Ensure PRODUCTION_DOMAIN is set
|
ssh deploy "sudo systemctl enable --now docker.service"
|
||||||
if [ -z "${{ vars.PRODUCTION_DOMAIN }}" ]; then
|
|
||||||
echo "Error: PRODUCTION_DOMAIN is not set"
|
echo "💾 Load the new docker image (${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar)"
|
||||||
exit 1
|
docker load -i "${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar"
|
||||||
fi
|
|
||||||
HEALTH_CHECK_URL="https://${{ vars.PRODUCTION_DOMAIN }}/health/"
|
echo "🧹 Remove the docker image artifact"
|
||||||
|
rm "${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar"
|
||||||
|
|
||||||
|
echo "🛑 Stop and remove containers before updating compose files"
|
||||||
|
#ssh deploy "cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}} && docker compose -f docker-compose_core.yml down"
|
||||||
|
if [ "${PROD}" = true ]; then
|
||||||
|
ssh deploy "cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}} && docker compose -f docker-compose_web.yml down"
|
||||||
else
|
else
|
||||||
# Ensure STAGING_DOMAIN is set
|
ssh deploy "cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}} && docker compose -f docker-compose_staging.yml down"
|
||||||
if [ -z "${{ vars.STAGING_DOMAIN }}" ]; then
|
|
||||||
echo "Error: STAGING_DOMAIN is not set"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
HEALTH_CHECK_URL="https://${{ vars.STAGING_DOMAIN }}/health/"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy script to remote and execute
|
echo "💾 Copy files to server"
|
||||||
scp scripts/health-check-and-rollback.sh deploy:/tmp/
|
ssh deploy "mkdir -p ${{ steps.meta.outputs.REPO_PROJECT_PATH}}"
|
||||||
ssh deploy "chmod +x /tmp/health-check-and-rollback.sh"
|
scp -pr ./server/* deploy:${{ steps.meta.outputs.REPO_PROJECT_PATH}}/
|
||||||
ssh deploy "/tmp/health-check-and-rollback.sh '${{ env.REPO_PROJECT_PATH }}' '${{ env.PROD }}' '$HEALTH_CHECK_URL' 30"
|
|
||||||
ssh deploy "rm -f /tmp/health-check-and-rollback.sh"
|
echo "📝 Create .env file"
|
||||||
|
printf "%s" "${ENV_FILE_BASE64}" | base64 -d | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/.env && chmod 600 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/.env"
|
||||||
|
|
||||||
|
echo "🔑 Set up certificates"
|
||||||
|
ssh deploy "mkdir -p ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs && chmod 550 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs"
|
||||||
|
printf "%s" "$CF_PEM_CERT" | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/crt.pem && chmod 440 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/crt.pem && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/crt.pem"
|
||||||
|
printf "%s" "$CF_PEM_CA" | ssh deploy "cat > ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/ca.pem && chmod 440 ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/ca.pem && chown 99:root ${{ steps.meta.outputs.REPO_PROJECT_PATH}}/certs/ca.pem"
|
||||||
|
|
||||||
|
echo "🚀 Start the new containers"
|
||||||
|
if [ "${PROD}" = true ]; then
|
||||||
|
ssh deploy "cd ${{ steps.meta.outputs.REPO_PROJECT_PATH }} && docker compose -f docker-compose_core.yml -f docker-compose_web.yml up -d --no-build"
|
||||||
|
else
|
||||||
|
ssh deploy "cd ${{ steps.meta.outputs.REPO_PROJECT_PATH }} && docker compose -f docker-compose_core.yml -f docker-compose_staging.yml up -d --no-build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# echo "🚀 Start the new containers, zero-downtime"
|
||||||
|
# if [ "${PROD}" = true ]; then
|
||||||
|
# ssh deploy <<<END
|
||||||
|
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
||||||
|
# old_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | tail -n1)
|
||||||
|
# docker compose -f docker-compose_web.yml up -d --no-build --no-recreate
|
||||||
|
# new_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | head -n1)
|
||||||
|
# # not needed, but might be useful at some point
|
||||||
|
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
||||||
|
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
||||||
|
# sleep 100 # change to wait for healthcheck in the future
|
||||||
|
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||||
|
# docker stop $old_container_id
|
||||||
|
# docker rm $old_container_id
|
||||||
|
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||||
|
# END
|
||||||
|
# else
|
||||||
|
# ssh deploy <<<END
|
||||||
|
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
||||||
|
# old_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | tail -n1)
|
||||||
|
# docker compose -f docker-compose_staging.yml up -d --no-build --no-recreate
|
||||||
|
# new_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | head -n1)
|
||||||
|
# # not needed, but might be useful at some point
|
||||||
|
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
||||||
|
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
||||||
|
# sleep 100 # change to wait for healthcheck in the future
|
||||||
|
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||||
|
# docker stop $old_container_id
|
||||||
|
# docker rm $old_container_id
|
||||||
|
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
||||||
|
# END
|
||||||
|
# fi
|
||||||
|
|
||||||
|
echo "🧹 Prune all unused images"
|
||||||
|
docker system prune -f
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
||||||
.env.*
|
.env.production
|
||||||
|
.env
|
||||||
src/pkmntrade_club/staticfiles/*
|
src/pkmntrade_club/staticfiles/*
|
||||||
!src/pkmntrade_club/staticfiles/.gitkeep
|
!src/pkmntrade_club/staticfiles/.gitkeep
|
||||||
src/pkmntrade_club/media/*
|
src/pkmntrade_club/media/*
|
||||||
|
|
|
||||||
8
.vscode/launch.json
vendored
8
.vscode/launch.json
vendored
|
|
@ -6,14 +6,10 @@
|
||||||
"type": "debugpy",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "${workspaceFolder}/manage.py",
|
"program": "${workspaceFolder}/manage.py",
|
||||||
"cwd": "${workspaceFolder}",
|
"args": ["runserver"],
|
||||||
"args": ["runserver", "0.0.0.0:8000"],
|
|
||||||
"django": true,
|
"django": true,
|
||||||
"justMyCode": true,
|
"justMyCode": true,
|
||||||
"preLaunchTask": "Run db standalone",
|
"preLaunchTask": "Run db standalone"
|
||||||
"env": {
|
|
||||||
"DEBUG": "True"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
10
Dockerfile
10
Dockerfile
|
|
@ -62,7 +62,6 @@ ENV PATH=/app/bin:$PATH
|
||||||
ENV PYTHONPATH=/app
|
ENV PYTHONPATH=/app
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
ENV HOME=/app
|
ENV HOME=/app
|
||||||
ENV DJANGO_SETTINGS_MODULE=pkmntrade_club.django_project.settings
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
@ -86,9 +85,8 @@ EOT
|
||||||
# See <https://hynek.me/articles/docker-signals/>.
|
# See <https://hynek.me/articles/docker-signals/>.
|
||||||
STOPSIGNAL SIGINT
|
STOPSIGNAL SIGINT
|
||||||
|
|
||||||
COPY --from=build --chown=app:app --chmod=u+rw /app /app
|
COPY --from=build --chown=app:app /app /app
|
||||||
|
|
||||||
COPY --from=ghcr.io/astral-sh/uv:0.7.2 /uv /app/bin/uv
|
|
||||||
COPY --chown=app:app --chmod=700 /scripts/entrypoint.sh /entrypoint.sh
|
COPY --chown=app:app --chmod=700 /scripts/entrypoint.sh /entrypoint.sh
|
||||||
COPY --chown=app:app --chmod=700 /scripts/deploy.sh /deploy.sh
|
COPY --chown=app:app --chmod=700 /scripts/deploy.sh /deploy.sh
|
||||||
COPY --chown=app:app --chmod=700 /manage.py /app/manage.py
|
COPY --chown=app:app --chmod=700 /manage.py /app/manage.py
|
||||||
|
|
@ -96,13 +94,11 @@ COPY --chown=app:app --chmod=700 /manage.py /app/manage.py
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
||||||
RUN --mount=type=cache,target=${CACHE_DIR} \
|
RUN --mount=type=cache,target=${CACHE_DIR} \
|
||||||
mkdir -p /app/.cursor-server && chmod 755 /app/.cursor-server && chown app:app /app /app/.cursor-server
|
mkdir -p /app/.cursor-server && chown app:app /app /app/.cursor-server
|
||||||
|
|
||||||
RUN --mount=type=cache,target=${CACHE_DIR} \
|
|
||||||
mkdir -p /flags && chmod 700 /flags && chown app:app /flags
|
|
||||||
|
|
||||||
USER app
|
USER app
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
CMD ["granian", "--interface", "wsgi", "pkmntrade_club.django_project.wsgi:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--backpressure", "16", "--workers-kill-timeout", "180", "--access-log"]
|
CMD ["granian", "--interface", "wsgi", "pkmntrade_club.django_project.wsgi:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--backpressure", "16", "--workers-kill-timeout", "180", "--access-log"]
|
||||||
|
#, "--static-path-mount", "./staticfiles"
|
||||||
|
|
@ -1,54 +1,54 @@
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
command: bash -c "cd /code && uv pip install --editable . --no-deps && python manage.py runserver 0.0.0.0:8000"
|
command: ["django-admin", "runserver", "0.0.0.0:8000"]
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- 8000:8000
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
environment:
|
|
||||||
- DEBUG=true
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./seed:/seed:ro
|
- ./seed:/seed:ro
|
||||||
- ./:/code
|
# DANGEROUS DUE TO DOCKERFILE PACKAGE BUILDING/INSTALLATION
|
||||||
depends_on:
|
#- ./src/pkmntrade_club:/app/lib/python3.12/site-packages/pkmntrade_club:ro
|
||||||
db:
|
env_file:
|
||||||
condition: service_healthy
|
- .env
|
||||||
redis:
|
environment:
|
||||||
condition: service_started
|
- DEBUG=true
|
||||||
|
- PUBLIC_HOST=localhost
|
||||||
|
- ALLOWED_HOSTS=127.0.0.1,localhost
|
||||||
|
- DISABLE_CACHE=false
|
||||||
celery:
|
celery:
|
||||||
build: .
|
build: .
|
||||||
command: bash -c "cd /code && uv pip install --editable . --no-deps && celery -A pkmntrade_club.django_project worker -l INFO -B -E"
|
command: ["celery", "-A", "pkmntrade_club.django_project", "worker", "-l", "INFO", "-B", "-E"]
|
||||||
restart: unless-stopped
|
restart: always
|
||||||
volumes:
|
env_file:
|
||||||
- ./:/code
|
- .env
|
||||||
depends_on:
|
environment:
|
||||||
db:
|
- DEBUG=true
|
||||||
condition: service_healthy
|
- PUBLIC_HOST=localhost
|
||||||
redis:
|
- ALLOWED_HOSTS=127.0.0.1,localhost
|
||||||
condition: service_started
|
- DISABLE_CACHE=false
|
||||||
redis:
|
redis:
|
||||||
image: redis:latest
|
image: redis:latest
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 6379:6379
|
- 6379:6379
|
||||||
healthcheck:
|
# depends_on:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
# db:
|
||||||
interval: 10s
|
# condition: service_healthy
|
||||||
timeout: 5s
|
# db:
|
||||||
retries: 5
|
# image: postgres:16
|
||||||
db:
|
# restart: always
|
||||||
image: postgres:16
|
# ports:
|
||||||
restart: always
|
# - 5432:5432
|
||||||
ports:
|
# volumes:
|
||||||
- 5432:5432
|
# - postgres_data:/var/lib/postgresql/data/
|
||||||
volumes:
|
# environment:
|
||||||
- postgres_data:/var/lib/postgresql/data/
|
# - "POSTGRES_HOST_AUTH_METHOD=trust"
|
||||||
environment:
|
# healthcheck:
|
||||||
- "POSTGRES_HOST_AUTH_METHOD=trust"
|
# test: ["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"]
|
||||||
healthcheck:
|
# interval: 10s
|
||||||
test: ["CMD", "pg_isready", "-U", "postgres", "-d", "postgres"]
|
# timeout: 5s
|
||||||
interval: 10s
|
# retries: 5
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
# volumes:
|
||||||
volumes:
|
# postgres_data:
|
||||||
postgres_data:
|
|
||||||
|
|
@ -6,8 +6,6 @@ import sys
|
||||||
def main():
|
def main():
|
||||||
"""Run administrative tasks."""
|
"""Run administrative tasks."""
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pkmntrade_club.django_project.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pkmntrade_club.django_project.settings")
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
except ImportError as exc:
|
except ImportError as exc:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pkmntrade-club"
|
name = "pkmntrade-club"
|
||||||
dynamic = ["version"]
|
version = "0.1.0"
|
||||||
description = "A django project for trading Pokémon TCG Pocket Cards"
|
description = "A django project for trading Pokémon TCG Pocket Cards"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|
@ -43,20 +43,18 @@ dependencies = [
|
||||||
"django-daisy==1.0.13",
|
"django-daisy==1.0.13",
|
||||||
"django-debug-toolbar==4.4.6",
|
"django-debug-toolbar==4.4.6",
|
||||||
"django-environ==0.12.0",
|
"django-environ==0.12.0",
|
||||||
"django-health-check>=3.18.3",
|
|
||||||
"django-linear-migrations>=2.17.0",
|
"django-linear-migrations>=2.17.0",
|
||||||
"django-meta==2.4.2",
|
"django-meta==2.4.2",
|
||||||
"django-tailwind-4[reload]==0.1.4",
|
"django-tailwind-4[reload]==0.1.4",
|
||||||
"django-widget-tweaks==1.5.0",
|
"django-widget-tweaks==1.5.0",
|
||||||
"gevent==25.4.1",
|
"gevent==25.4.1",
|
||||||
"granian==2.3.1",
|
"granian==2.2.5",
|
||||||
"gunicorn==23.0.0",
|
"gunicorn==23.0.0",
|
||||||
"idna==3.4",
|
"idna==3.4",
|
||||||
"oauthlib==3.2.2",
|
"oauthlib==3.2.2",
|
||||||
"packaging==23.1",
|
"packaging==23.1",
|
||||||
"pillow>=11.2.1",
|
"pillow>=11.2.1",
|
||||||
"playwright==1.52.0",
|
"playwright==1.52.0",
|
||||||
"psutil>=7.0.0",
|
|
||||||
"psycopg==3.2.3",
|
"psycopg==3.2.3",
|
||||||
"psycopg-binary==3.2.3",
|
"psycopg-binary==3.2.3",
|
||||||
"pycparser==2.21",
|
"pycparser==2.21",
|
||||||
|
|
@ -69,8 +67,6 @@ dependencies = [
|
||||||
"typing-extensions==4.9.0",
|
"typing-extensions==4.9.0",
|
||||||
"urllib3==1.26.14",
|
"urllib3==1.26.14",
|
||||||
"whitenoise==6.7.0",
|
"whitenoise==6.7.0",
|
||||||
"django-parler>=2.3",
|
|
||||||
"setuptools-scm>=8.3.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
|
|
@ -81,8 +77,3 @@ Homepage = "https://pkmntrade.club"
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["src"]
|
where = ["src"]
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
|
||||||
version_scheme = "no-guess-dev"
|
|
||||||
tag_regex = "^v(?P<version>[0-9]+(?:\\.[0-9]+)*(?:-.*)?)"
|
|
||||||
fallback_version = "0.0.0+unknown"
|
|
||||||
|
|
|
||||||
|
|
@ -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}"
|
|
||||||
: "${PROD:?Error: 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, error"
|
|
||||||
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}' '${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 '${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 <<EOF
|
|
||||||
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
|
||||||
# old_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | tail -n1)
|
|
||||||
# docker compose -f docker-compose_web.yml up -d --no-build --no-recreate
|
|
||||||
# new_container_id=$(docker compose -f docker-compose_web.yml ps -f name=web -q | head -n1)
|
|
||||||
# # not needed, but might be useful at some point
|
|
||||||
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
|
||||||
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
|
||||||
# sleep 100 # change to wait for healthcheck in the future
|
|
||||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
|
||||||
# docker stop $old_container_id
|
|
||||||
# docker rm $old_container_id
|
|
||||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
|
||||||
# EOF
|
|
||||||
# else
|
|
||||||
# ssh deploy <<EOF
|
|
||||||
# cd ${{ steps.meta.outputs.REPO_PROJECT_PATH}}
|
|
||||||
# old_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | tail -n1)
|
|
||||||
# docker compose -f docker-compose_staging.yml up -d --no-build --no-recreate
|
|
||||||
# new_container_id=$(docker compose -f docker-compose_staging.yml ps -f name=web-staging -q | head -n1)
|
|
||||||
# # not needed, but might be useful at some point
|
|
||||||
# #new_container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $new_container_id)
|
|
||||||
# #new_container_name=$(docker inspect -f '{{.Name}}' $new_container_id | cut -c2-)
|
|
||||||
# sleep 100 # change to wait for healthcheck in the future
|
|
||||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
|
||||||
# docker stop $old_container_id
|
|
||||||
# docker rm $old_container_id
|
|
||||||
# #docker compose -f docker-compose_core.yml kill -s SIGUSR2 loba
|
|
||||||
# EOF
|
|
||||||
# fi
|
|
||||||
|
|
||||||
echo "🚀 Start the new containers"
|
|
||||||
if [ "$PROD" = "true" ]; then
|
|
||||||
retry ssh deploy "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -f docker-compose_web.yml -p pkmntrade-club up -d --no-build"
|
|
||||||
else
|
|
||||||
retry ssh deploy "cd '${CURRENT_LINK_PATH}' && docker compose -f docker-compose_core.yml -f docker-compose_web.yml -f docker-compose_staging.yml -p pkmntrade-club up -d --no-build"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "🧹 Prune unused Docker resources"
|
|
||||||
ssh deploy "docker system prune -f"
|
|
||||||
|
|
||||||
echo "🗑️ Clean up old releases (keep last 5)"
|
|
||||||
ssh deploy "cd '${RELEASES_PATH}' && ls -dt */ 2>/dev/null | tail -n +6 | xargs -r rm -rf || true"
|
|
||||||
|
|
||||||
echo "✅ Deployment completed. Version: ${DEPLOYMENT_TIMESTAMP}"
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
if [[ -f /flags/.deployed && "$FORCE_DEPLOY" != "true" ]]; then
|
|
||||||
echo "*** Previously deployed successfully."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "*** Running makemigrations --check to make sure migrations are up to date..."
|
echo "*** Running makemigrations --check to make sure migrations are up to date..."
|
||||||
django-admin makemigrations --noinput --check 2>&1 || exit 1
|
django-admin makemigrations --noinput --check 2>&1 || exit 1
|
||||||
|
|
||||||
|
|
@ -17,7 +12,4 @@ django-admin clear_cache 2>&1
|
||||||
echo "*** Running collectstatic..."
|
echo "*** Running collectstatic..."
|
||||||
django-admin collectstatic -c --no-input 2>&1
|
django-admin collectstatic -c --no-input 2>&1
|
||||||
|
|
||||||
echo "*** Marking as deployed..."
|
|
||||||
touch /flags/.deployed
|
|
||||||
|
|
||||||
echo "*** Deployed successfully!"
|
echo "*** Deployed successfully!"
|
||||||
|
|
@ -6,14 +6,15 @@ if [ "$1" == "" ]; then
|
||||||
exit;
|
exit;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/deploy.sh
|
if [ "$DJANGO_SETTINGS_MODULE" == "" ]; then
|
||||||
|
echo "Environment variable 'DJANGO_SETTINGS_MODULE' not set. Exiting."
|
||||||
if [ "$1" == "granian" ]; then
|
exit;
|
||||||
granian --version
|
else
|
||||||
echo "Appending static files path to granian command (requires granian >= 2.3.0)"
|
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
|
||||||
STATIC_ROOT=$(python -c 'import os; import pkmntrade_club; from django.conf import settings; print(settings.STATIC_ROOT)')
|
|
||||||
set -- "$@" --static-path-mount "$STATIC_ROOT"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Environment is correct - executing command: '$@'"
|
echo "Running deploy.sh... (if you get a APP_REGISTRY_NOT_READY error, there's probably an error in settings.py)"
|
||||||
exec "$@"
|
/deploy.sh
|
||||||
|
|
||||||
|
echo "Environment is correct and deploy.sh has been run - executing command: '$@'"
|
||||||
|
exec "$@" && exit 0
|
||||||
|
|
@ -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 PROD
|
|
||||||
|
|
||||||
if [ $# -ne 4 ]; then
|
|
||||||
echo "Error: Invalid number of arguments" > /dev/stderr
|
|
||||||
echo "Usage: $0 IMAGE_BASE GIT_SHA GIT_REF PROD" > /dev/stderr
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
IMAGE_BASE="$1"
|
|
||||||
GIT_SHA="$2"
|
|
||||||
GIT_REF="$3"
|
|
||||||
PROD="$4"
|
|
||||||
|
|
||||||
# Validate inputs
|
|
||||||
if [ -z "$IMAGE_BASE" ] || [ -z "$GIT_SHA" ]; then
|
|
||||||
echo "Error: IMAGE_BASE and GIT_SHA cannot be empty" > /dev/stderr
|
|
||||||
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" ]] && [[ "$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 [[ "$PROD" == "false" ]]; then
|
|
||||||
echo "${IMAGE_BASE}:latest-staging"
|
|
||||||
echo "${IMAGE_BASE}:staging"
|
|
||||||
fi
|
|
||||||
|
|
@ -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 PROD HEALTH_CHECK_URL [MAX_ATTEMPTS]
|
|
||||||
|
|
||||||
if [ $# -lt 3 ]; then
|
|
||||||
echo "Error: Invalid number of arguments"
|
|
||||||
echo "Usage: $0 REPO_PROJECT_PATH PROD HEALTH_CHECK_URL [MAX_ATTEMPTS]"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
REPO_PROJECT_PATH="$1"
|
|
||||||
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 -p pkmntrade-club down || true
|
|
||||||
if [ "$PROD" = "false" ]; then
|
|
||||||
docker compose -f docker-compose_staging.yml -p pkmntrade-club down || true
|
|
||||||
fi
|
|
||||||
docker compose -f docker-compose_core.yml -p pkmntrade-club 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 -p pkmntrade-club up -d --no-build
|
|
||||||
if [ "$PROD" = "true" ]; then
|
|
||||||
docker compose -f docker-compose_web.yml -p pkmntrade-club up -d --no-build
|
|
||||||
else
|
|
||||||
docker compose -f docker-compose_web.yml -f docker-compose_staging.yml -p pkmntrade-club 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
|
|
||||||
|
|
@ -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
|
|
||||||
PROD=$(cat "${TARGET_PATH}/.deployment_env")
|
|
||||||
else
|
|
||||||
echo "Warning: Could not determine environment, assuming staging"
|
|
||||||
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
|
|
||||||
[ "$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 [ "$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
|
|
||||||
|
|
@ -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" > /dev/stderr
|
|
||||||
echo "Usage: $0 GITHUB_REPOSITORY" > /dev/stderr
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
GITHUB_REPOSITORY="$1"
|
|
||||||
|
|
||||||
echo "GITHUB_REPOSITORY: $GITHUB_REPOSITORY" > /dev/stderr
|
|
||||||
|
|
||||||
if [[ "$GITHUB_REPOSITORY" == *".git" ]]; then
|
|
||||||
if [[ "$GITHUB_REPOSITORY" == "https://"* ]]; then
|
|
||||||
echo "GITHUB_REPOSITORY ends in .git and is a URL" > /dev/stderr
|
|
||||||
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" > /dev/stderr
|
|
||||||
REPO=$(echo "$GITHUB_REPOSITORY" | sed 's/\.git$//' | sed 's/[^a-zA-Z0-9\/-]/-/g')
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "GITHUB_REPOSITORY is not a URL" > /dev/stderr
|
|
||||||
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"
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd src/pkmntrade_club/
|
|
||||||
|
|
||||||
# Remove all files in staticfiles except .gitkeep
|
# Remove all files in staticfiles except .gitkeep
|
||||||
if [ -d "staticfiles" ]; then
|
if [ -d "staticfiles" ]; then
|
||||||
find staticfiles -type f ! -name '.gitkeep' -delete
|
find staticfiles -type f ! -name '.gitkeep' -delete
|
||||||
|
find staticfiles -type d -empty -delete
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build the tailwind theme css
|
# Build the tailwind theme css
|
||||||
cd theme/static_src
|
cd src/pkmntrade_club/theme/static_src
|
||||||
npm install . && npm run build
|
npm install . && npm run build
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Prepare deployment by stopping containers
|
|
||||||
# Usage: ./prepare-deployment.sh REPO_PROJECT_PATH PROD CURRENT_LINK_PATH
|
|
||||||
|
|
||||||
if [ $# -ne 3 ]; then
|
|
||||||
echo "Error: Invalid number of arguments"
|
|
||||||
echo "Usage: $0 REPO_PROJECT_PATH PROD CURRENT_LINK_PATH"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
REPO_PROJECT_PATH="$1"
|
|
||||||
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 -p pkmntrade-club down || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$PROD" = "false" ] && [ -f "docker-compose_staging.yml" ]; then
|
|
||||||
docker compose -f docker-compose_staging.yml -p pkmntrade-club down || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "docker-compose_core.yml" ]; then
|
|
||||||
docker compose -f docker-compose_core.yml -p pkmntrade-club down || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✅ Containers stopped"
|
|
||||||
else
|
|
||||||
echo "ℹ️ No current deployment found (symlink doesn't exist or point to valid directory)"
|
|
||||||
fi
|
|
||||||
|
|
@ -4,23 +4,26 @@ set -e
|
||||||
|
|
||||||
echo "Remaking migrations..."
|
echo "Remaking migrations..."
|
||||||
find . -path "*/migrations/0*.py" -delete
|
find . -path "*/migrations/0*.py" -delete
|
||||||
|
set -a
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
uv run manage.py makemigrations --noinput
|
uv run manage.py makemigrations --noinput
|
||||||
|
|
||||||
echo "Resetting dev database... "
|
echo "Resetting database... "
|
||||||
docker compose down \
|
docker compose down \
|
||||||
&& docker volume rm -f pkmntradeclub_postgres_data \
|
&& docker volume rm -f pkmntradeclub_postgres_data \
|
||||||
&& docker compose up -d db
|
&& ./scripts/rebuild-and-run.sh
|
||||||
|
|
||||||
echo "Running prebuild..."
|
# Wait for the database to be ready.
|
||||||
./scripts/prebuild.sh
|
echo "Waiting 15 seconds for the database to be ready, and migrations to be autorun..."
|
||||||
|
sleep 15
|
||||||
|
|
||||||
echo "Running migrations..."
|
echo "Creating cache table..."
|
||||||
uv run manage.py migrate --noinput
|
docker compose exec -it web bash -c "django-admin createcachetable django_cache"
|
||||||
|
|
||||||
echo "Loading seed data..."
|
echo "Loading seed data..."
|
||||||
uv run manage.py loaddata ./seed/0*
|
docker compose exec -it web bash -c "django-admin loaddata /seed/0*"
|
||||||
|
|
||||||
echo "Running collectstatic..."
|
docker compose down
|
||||||
uv run manage.py collectstatic -c --no-input
|
|
||||||
|
|
||||||
echo "Done!"
|
echo "Done!"
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Retry function with exponential backoff
|
|
||||||
# Usage: source retry.sh && retry <command>
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
services:
|
services:
|
||||||
db-redis-healthcheck:
|
db-healthcheck:
|
||||||
image: stephenc/postgresql-cli:latest
|
image: stephenc/postgresql-cli:latest
|
||||||
command:
|
command:
|
||||||
- "sh"
|
- "sh"
|
||||||
|
|
@ -9,47 +9,26 @@ services:
|
||||||
sleep 30;
|
sleep 30;
|
||||||
while true; do
|
while true; do
|
||||||
pg_output=$$(pg_isready -d ${DJANGO_DATABASE_URL} 2>&1);
|
pg_output=$$(pg_isready -d ${DJANGO_DATABASE_URL} 2>&1);
|
||||||
pg_exit_code=$$?;
|
exit_code=$$?;
|
||||||
if [ $$pg_exit_code -eq 0 ]; then
|
if [ $$exit_code -eq 0 ]; then
|
||||||
pg_success="true";
|
success="true";
|
||||||
pg_error="";
|
error="";
|
||||||
else
|
else
|
||||||
pg_success="false";
|
success="false";
|
||||||
pg_error="$$pg_output";
|
error="$$pg_output";
|
||||||
fi;
|
fi;
|
||||||
curl -s -f -X POST \
|
curl -s -f -X POST \
|
||||||
--connect-timeout 10 \
|
--connect-timeout 10 \
|
||||||
--max-time 15 \
|
--max-time 15 \
|
||||||
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
||||||
http://health:8080/api/v1/endpoints/services_database/external?success=$$pg_success&error=$$pg_error || true
|
http://health:8080/api/v1/endpoints/db_pg-isready/external?success=$$success&error=$$error;
|
||||||
if [ "$$pg_success" = "true" ]; then
|
if [ "$$success" = "true" ]; then
|
||||||
echo " Database is OK";
|
echo " Database is OK";
|
||||||
|
sleep 60;
|
||||||
else
|
else
|
||||||
echo "Database is not OK: $$pg_output";
|
echo "Database is not OK: $$pg_output";
|
||||||
exit 1;
|
exit 1;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
redis_output=$$(echo -e "ping\nquit" | curl -v --max-time 10 --connect-timeout 10 telnet://redis:6379 2>&1 | grep -q "+PONG");
|
|
||||||
redis_exit_code=$$?;
|
|
||||||
if [ $$redis_exit_code -eq 0 ]; then
|
|
||||||
redis_success="true";
|
|
||||||
redis_error="";
|
|
||||||
else
|
|
||||||
redis_success="false";
|
|
||||||
redis_error="$$redis_output";
|
|
||||||
fi;
|
|
||||||
curl -s -f -X POST \
|
|
||||||
--connect-timeout 10 \
|
|
||||||
--max-time 15 \
|
|
||||||
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
|
||||||
http://health:8080/api/v1/endpoints/services_cache/external?success=$$redis_success&error=$$redis_error;
|
|
||||||
if [ "$$redis_success" = "true" ]; then
|
|
||||||
echo " Redis is OK";
|
|
||||||
else
|
|
||||||
echo "Redis is not OK: $$redis_output";
|
|
||||||
exit 1;
|
|
||||||
fi;
|
|
||||||
sleep 60;
|
|
||||||
done
|
done
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|
@ -67,193 +46,41 @@ services:
|
||||||
feedback:
|
feedback:
|
||||||
restart: always
|
restart: always
|
||||||
image: getfider/fider:stable
|
image: getfider/fider:stable
|
||||||
labels:
|
|
||||||
- "enable_gatekeeper=true"
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
# cadvisor:
|
cadvisor:
|
||||||
# volumes:
|
volumes:
|
||||||
# - /:/rootfs:ro
|
- /:/rootfs:ro
|
||||||
# - /var/run:/var/run:ro
|
- /var/run:/var/run:ro
|
||||||
# - /sys:/sys:ro
|
- /sys:/sys:ro
|
||||||
# - /var/lib/docker/:/var/lib/docker:ro
|
- /var/lib/docker/:/var/lib/docker:ro
|
||||||
# - /dev/disk/:/dev/disk:ro
|
- /dev/disk/:/dev/disk:ro
|
||||||
# privileged: true
|
privileged: true
|
||||||
# devices:
|
devices:
|
||||||
# - /dev/kmsg
|
- /dev/kmsg
|
||||||
# image: gcr.io/cadvisor/cadvisor:v0.52.1
|
image: gcr.io/cadvisor/cadvisor:v0.52.1
|
||||||
redis:
|
redis:
|
||||||
image: redis:latest
|
image: redis:latest
|
||||||
restart: always
|
restart: always
|
||||||
healthcheck:
|
ports:
|
||||||
test: ["CMD", "redis-cli", "ping"]
|
- 6379:6379
|
||||||
interval: 10s
|
# anubis:
|
||||||
timeout: 5s
|
# image: ghcr.io/techarohq/anubis:latest
|
||||||
retries: 5
|
# env_file:
|
||||||
start_period: 10s
|
# - .env
|
||||||
dockergen-health:
|
# dockergen:
|
||||||
image: nginxproxy/docker-gen:latest
|
# image: jwilder/docker-gen:latest
|
||||||
command: -wait 15s -watch /gatus/config.template.yaml /gatus/config.yaml
|
# container_name: dockergen_gatus_config
|
||||||
restart: unless-stopped
|
# command: -watch -notify-sighup gatus_service -only-exposed /app/config.template.yml /app/config.yaml
|
||||||
volumes:
|
# restart: unless-stopped
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
# volumes:
|
||||||
- ./gatus:/gatus
|
# - /var/run/docker.sock:/tmp/docker.sock:ro
|
||||||
dockergen-gatekeeper:
|
# - ./gatus:/app
|
||||||
image: nginxproxy/docker-gen:latest
|
# depends_on:
|
||||||
command: -wait 15s -watch /gatekeeper/gatekeepers.template.yml /gatekeeper/gatekeepers.yml -notify-sighup pkmntrade-club-gatekeeper-manager-1
|
# - health
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
|
||||||
- ./:/gatekeeper
|
|
||||||
gatekeeper-manager:
|
|
||||||
image: docker:latest
|
|
||||||
restart: always
|
|
||||||
stop_signal: SIGTERM
|
|
||||||
volumes:
|
|
||||||
- /srv:/srv:ro
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
environment:
|
|
||||||
- REFRESH_INTERVAL=60
|
|
||||||
entrypoint: ["/bin/sh", "-c"]
|
|
||||||
command:
|
|
||||||
- |
|
|
||||||
set -eu -o pipefail
|
|
||||||
apk add --no-cache curl
|
|
||||||
|
|
||||||
COMPOSE_FILE_PATH="/srv/pkmntrade-club/current/gatekeepers.yml"
|
|
||||||
PROJECT_DIR_PATH="/srv/pkmntrade-club/current"
|
|
||||||
PROJECT_NAME_TAG="gatekeepers"
|
|
||||||
TERMINATING="false"
|
|
||||||
RESTARTING="false"
|
|
||||||
STARTED="false"
|
|
||||||
|
|
||||||
gatekeeper_down() {
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Downing gatekeepers (Project: $$PROJECT_NAME_TAG)..."
|
|
||||||
cd "$$PROJECT_DIR_PATH"
|
|
||||||
if ! docker compose -p "$$PROJECT_NAME_TAG" -f "$$COMPOSE_FILE_PATH" down; then
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [WARN]: 'docker compose down' for $$PROJECT_NAME_TAG encountered an issue, but proceeding."
|
|
||||||
else
|
|
||||||
STARTED="false"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
gatekeeper_up() {
|
|
||||||
if [ "$$TERMINATING" = "true" ]; then return; fi
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Upping gatekeepers (Project: $$PROJECT_NAME_TAG, File: $$COMPOSE_FILE_PATH)..."
|
|
||||||
cd "$$PROJECT_DIR_PATH"
|
|
||||||
if ! docker compose -p "$$PROJECT_NAME_TAG" -f "$$COMPOSE_FILE_PATH" up -d --remove-orphans; then
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [ERROR]: 'docker compose up' for $$PROJECT_NAME_TAG failed. Will retry."
|
|
||||||
else
|
|
||||||
STARTED="true"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
restart_gatekeepers() {
|
|
||||||
if [ "$$TERMINATING" = "true" -o "$$RESTARTING" = "true" -o "$$STARTED" = "false" ]; then return; fi
|
|
||||||
RESTARTING="true"
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Restarting gatekeepers."
|
|
||||||
gatekeeper_down
|
|
||||||
gatekeeper_up
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Gatekeepers restarted."
|
|
||||||
RESTARTING="false"
|
|
||||||
}
|
|
||||||
|
|
||||||
gatekeeper_healthcheck() {
|
|
||||||
if [ "$$TERMINATING" = "true" -o "$$RESTARTING" = "true" -o "$$STARTED" = "false" ]; then
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Gatekeeper Manager is terminating/restarting/not started. Skipping healthcheck."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
ERROR_MSG=""
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Checking gatekeepers health..."
|
|
||||||
|
|
||||||
num_containers=$$(docker ps -q -a --filter "label=gatekeeper" | wc -l)
|
|
||||||
|
|
||||||
if [ "$$num_containers" -eq 0 ]; then
|
|
||||||
ERROR_MSG="No gatekeepers found. Healthcheck failed."
|
|
||||||
elif [ $(docker ps -q -a --filter "label=gatekeeper" --filter "status=running" | wc -l) -ne "$$num_containers" ]; then
|
|
||||||
ERROR_MSG="Gatekeeper containers are missing or not running. Healthcheck failed."
|
|
||||||
else
|
|
||||||
# check for 200 status code from each gatekeeper container
|
|
||||||
for container in $$(docker ps -q -a --filter "label=gatekeeper"); do
|
|
||||||
if [ $$(curl -s -o /dev/null -w "%{http_code}" -H "X-Real-Ip: 127.0.0.1" http://$$container:9090/metrics) -ne 200 ]; then
|
|
||||||
container_name=$$(docker ps -a --filter "label=gatekeeper" --filter "id=$$container" --format "{{.Names}}")
|
|
||||||
ERROR_MSG="Gatekeeper container $$container_name is unhealthy. Healthcheck failed."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$$ERROR_MSG" != "" ]; then
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [ERROR]: $$ERROR_MSG"
|
|
||||||
curl -s -f -X POST \
|
|
||||||
--connect-timeout 10 \
|
|
||||||
--max-time 15 \
|
|
||||||
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
|
||||||
"http://health:8080/api/v1/endpoints/services_gatekeeper/external?success=false&error=$$ERROR_MSG" || true
|
|
||||||
restart_gatekeepers
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: All gatekeepers are OK/HEALTHY."
|
|
||||||
curl -s -f -X POST \
|
|
||||||
--connect-timeout 10 \
|
|
||||||
--max-time 15 \
|
|
||||||
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
|
||||||
http://health:8080/api/v1/endpoints/services_gatekeeper/external?success=true&error=HEALTHY || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_sigterm() {
|
|
||||||
if [ "$$TERMINATING" = "true" ]; then return; fi
|
|
||||||
TERMINATING="true"
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: SIGTERM received. Initiating graceful shutdown for gatekeepers."
|
|
||||||
curl -s -f -X POST \
|
|
||||||
--connect-timeout 10 \
|
|
||||||
--max-time 15 \
|
|
||||||
--header "Authorization: Bearer ${GATUS_TOKEN}" \
|
|
||||||
http://health:8080/api/v1/endpoints/services_gatekeeper/external?success=false&error=SIGTERM%20received.%20Shutting%20down. || true
|
|
||||||
gatekeeper_down
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Gatekeepers shut down. Gatekeeper Manager exiting."
|
|
||||||
exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_sighup() {
|
|
||||||
if [ "$$TERMINATING" = "true" -o "$$RESTARTING" = "true" -o "$$STARTED" = "false" ]; then return; fi
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: SIGHUP received."
|
|
||||||
restart_gatekeepers
|
|
||||||
}
|
|
||||||
|
|
||||||
trap 'handle_sigterm' SIGTERM
|
|
||||||
trap 'handle_sighup' SIGHUP
|
|
||||||
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Gatekeeper Manager started."
|
|
||||||
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Periodic refresh enabled: $$REFRESH_INTERVAL seconds. Initial wait started."
|
|
||||||
|
|
||||||
while [ "$$TERMINATING" = "false" ]; do
|
|
||||||
# 'sleep x &' and 'wait $!' allows signals to interrupt the sleep.
|
|
||||||
# '|| true' ensures the loop continues if 'wait' is killed by a handled signal (SIGHUP/SIGTERM)
|
|
||||||
# SIGTERM handler exits completely, so loop won't continue. SIGHUP handler doesn't exit.
|
|
||||||
|
|
||||||
sleep $$REFRESH_INTERVAL &
|
|
||||||
wait $! || true
|
|
||||||
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [INFO]: Periodic healthcheck and refresh triggered."
|
|
||||||
|
|
||||||
if [ ! -f "$$COMPOSE_FILE_PATH" ]; then
|
|
||||||
echo "$(date +'%Y-%m-%d %H:%M:%S') [ERROR]: Gatekeepers.yml has not been generated after $$REFRESH_INTERVAL seconds. Please check dockergen-gatekeeper is running correctly. Exiting."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if gatekeeper_healthcheck && [ "$$RESTARTING" = "false" ]; then
|
|
||||||
gatekeeper_up
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
done
|
|
||||||
health:
|
health:
|
||||||
image: twinproduction/gatus:latest
|
image: twinproduction/gatus:latest
|
||||||
restart: always
|
restart: always
|
||||||
labels:
|
|
||||||
- "enable_gatekeeper=true"
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
||||||
|
|
@ -3,30 +3,24 @@ x-common: &common
|
||||||
restart: always
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
services:
|
|
||||||
web-staging:
|
|
||||||
<<: *common
|
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=False
|
- DEBUG=True
|
||||||
- DISABLE_SIGNUPS=True
|
- DISABLE_SIGNUPS=True
|
||||||
- PUBLIC_HOST=staging.pkmntrade.club
|
- PUBLIC_HOST=staging.pkmntrade.club
|
||||||
- ALLOWED_HOSTS=staging.pkmntrade.club,127.0.0.1
|
- ALLOWED_HOSTS=staging.pkmntrade.club,127.0.0.1
|
||||||
labels:
|
|
||||||
- "enable_gatekeeper=true"
|
services:
|
||||||
|
web-staging:
|
||||||
|
<<: *common
|
||||||
deploy:
|
deploy:
|
||||||
mode: replicated
|
mode: replicated
|
||||||
replicas: 2
|
replicas: 2
|
||||||
# healthcheck:
|
# healthcheck:
|
||||||
# test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/health"]
|
# test: ["CMD", "curl", "-f", "http://127.0.0.1:8000"]
|
||||||
# interval: 30s
|
# interval: 30s
|
||||||
# timeout: 10s
|
# timeout: 10s
|
||||||
# retries: 3
|
# retries: 3
|
||||||
# start_period: 30s
|
# start_period: 30s
|
||||||
celery-staging:
|
celery-staging:
|
||||||
<<: *common
|
<<: *common
|
||||||
environment:
|
|
||||||
- DEBUG=False
|
|
||||||
- DISABLE_SIGNUPS=True
|
|
||||||
- PUBLIC_HOST=staging.pkmntrade.club
|
|
||||||
- ALLOWED_HOSTS=staging.pkmntrade.club,127.0.0.1
|
|
||||||
command: ["celery", "-A", "pkmntrade_club.django_project", "worker", "-l", "INFO", "-B", "-E"]
|
command: ["celery", "-A", "pkmntrade_club.django_project", "worker", "-l", "INFO", "-B", "-E"]
|
||||||
|
|
@ -2,6 +2,11 @@ x-common: &common
|
||||||
restart: always
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
environment:
|
||||||
|
- DEBUG=False
|
||||||
|
- DISABLE_SIGNUPS=True
|
||||||
|
- PUBLIC_HOST=pkmntrade.club
|
||||||
|
- ALLOWED_HOSTS=pkmntrade.club,127.0.0.1
|
||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
|
|
@ -9,28 +14,17 @@ services:
|
||||||
image: ghcr.io/xe/x/httpdebug
|
image: ghcr.io/xe/x/httpdebug
|
||||||
entrypoint: ["/ko-app/httpdebug", "--bind", ":8000"]
|
entrypoint: ["/ko-app/httpdebug", "--bind", ":8000"]
|
||||||
#image: badbl0cks/pkmntrade-club:stable
|
#image: badbl0cks/pkmntrade-club:stable
|
||||||
environment:
|
#command: ["granian", "--interface", "wsgi", "pkmntrade_club.django_project.wsgi:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1", "--workers-kill-timeout", "180", "--access-log"]
|
||||||
- DEBUG=False
|
|
||||||
- DISABLE_SIGNUPS=True
|
|
||||||
- PUBLIC_HOST=pkmntrade.club
|
|
||||||
- ALLOWED_HOSTS=pkmntrade.club,127.0.0.1
|
|
||||||
labels:
|
|
||||||
- "enable_gatekeeper=true"
|
|
||||||
deploy:
|
deploy:
|
||||||
mode: replicated
|
mode: replicated
|
||||||
replicas: 4
|
replicas: 4
|
||||||
# healthcheck:
|
# healthcheck:
|
||||||
# test: ["CMD", "curl", "-f", "http://127.0.0.1:8000/health"]
|
# test: ["CMD", "curl", "-f", "http://127.0.0.1:8000"]
|
||||||
# interval: 30s
|
# interval: 30s
|
||||||
# timeout: 10s
|
# timeout: 10s
|
||||||
# retries: 3
|
# retries: 3
|
||||||
# start_period: 30s
|
# start_period: 30s
|
||||||
# celery:
|
celery:
|
||||||
# <<: *common
|
<<: *common
|
||||||
# image: badbl0cks/pkmntrade-club:stable
|
image: badbl0cks/pkmntrade-club:stable
|
||||||
# environment:
|
command: ["celery", "-A", "pkmntrade_club.django_project", "worker", "-l", "INFO", "-B", "-E"]
|
||||||
# - DEBUG=False
|
|
||||||
# - DISABLE_SIGNUPS=True
|
|
||||||
# - PUBLIC_HOST=pkmntrade.club
|
|
||||||
# - ALLOWED_HOSTS=pkmntrade.club,127.0.0.1
|
|
||||||
# command: ["celery", "-A", "pkmntrade_club.django_project", "worker", "-l", "INFO", "-B", "-E"]
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
services:
|
|
||||||
{{ $all_containers := whereLabelValueMatches . "enable_gatekeeper" "true" }}
|
|
||||||
{{ $all_containers = sortObjectsByKeysAsc $all_containers "Name" }}
|
|
||||||
|
|
||||||
{{ range $container := $all_containers }}
|
|
||||||
{{ $serviceLabel := index $container.Labels "com.docker.compose.service" }}
|
|
||||||
{{ $containerNumber := index $container.Labels "com.docker.compose.container-number" }}
|
|
||||||
{{ $port := "" }}
|
|
||||||
{{ if eq $serviceLabel "web" }}
|
|
||||||
{{ $port = ":8000" }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq $serviceLabel "web-staging" }}
|
|
||||||
{{ $port = ":8000" }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq $serviceLabel "feedback" }}
|
|
||||||
{{ $port = ":3000" }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq $serviceLabel "health" }}
|
|
||||||
{{ $port = ":8080" }}
|
|
||||||
{{ end }}
|
|
||||||
gatekeeper-{{ $serviceLabel }}-{{ $containerNumber }}:
|
|
||||||
image: ghcr.io/techarohq/anubis:latest
|
|
||||||
container_name: pkmntrade-club-gatekeeper-{{ $serviceLabel }}-{{ $containerNumber }}
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
- TARGET=http://{{ $container.Name }}{{ $port }}
|
|
||||||
{{ if eq $serviceLabel "web" }}
|
|
||||||
- TARGET_HOST=pkmntrade.club # pass this host to django, which checks it with ALLOWED_HOSTS
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq $serviceLabel "web-staging" }}
|
|
||||||
- TARGET_HOST=staging.pkmntrade.club # pass this host to django, which checks it with ALLOWED_HOSTS
|
|
||||||
{{ end }}
|
|
||||||
labels:
|
|
||||||
- gatekeeper=true
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
aliases:
|
|
||||||
- pkmntrade-club-gatekeeper-{{ $serviceLabel }}
|
|
||||||
- gatekeeper-{{ $serviceLabel }}
|
|
||||||
{{ end }}
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
name: pkmntrade-club_default
|
|
||||||
external: true
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
storage:
|
|
||||||
type: postgres
|
|
||||||
path: "${GATUS_DATABASE_URL}"
|
|
||||||
web:
|
|
||||||
read-buffer-size: 32768
|
|
||||||
connectivity:
|
|
||||||
checker:
|
|
||||||
target: 1.1.1.1:53
|
|
||||||
interval: 60s
|
|
||||||
external-endpoints:
|
|
||||||
- name: Database
|
|
||||||
group: Services
|
|
||||||
token: "${GATUS_TOKEN}"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Cache
|
|
||||||
group: Services
|
|
||||||
token: "${GATUS_TOKEN}"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Gatekeeper
|
|
||||||
group: Services
|
|
||||||
token: "${GATUS_TOKEN}"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
endpoints:
|
|
||||||
- name: Domain
|
|
||||||
group: Expirations
|
|
||||||
url: "https://pkmntrade.club"
|
|
||||||
interval: 1h
|
|
||||||
conditions:
|
|
||||||
- "[DOMAIN_EXPIRATION] > 720h"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Certificate
|
|
||||||
group: Expirations
|
|
||||||
url: "https://pkmntrade.club"
|
|
||||||
interval: 1h
|
|
||||||
conditions:
|
|
||||||
- "[CERTIFICATE_EXPIRATION] > 240h"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Cloudflare
|
|
||||||
group: DNS
|
|
||||||
url: "1.1.1.1"
|
|
||||||
interval: 60s
|
|
||||||
dns:
|
|
||||||
query-name: "pkmntrade.club"
|
|
||||||
query-type: "A"
|
|
||||||
conditions:
|
|
||||||
- "[DNS_RCODE] == NOERROR"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Google
|
|
||||||
group: DNS
|
|
||||||
url: "8.8.8.8"
|
|
||||||
interval: 60s
|
|
||||||
dns:
|
|
||||||
query-name: "pkmntrade.club"
|
|
||||||
query-type: "A"
|
|
||||||
conditions:
|
|
||||||
- "[DNS_RCODE] == NOERROR"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Quad9
|
|
||||||
group: DNS
|
|
||||||
url: "9.9.9.9"
|
|
||||||
interval: 60s
|
|
||||||
dns:
|
|
||||||
query-name: "pkmntrade.club"
|
|
||||||
query-type: "A"
|
|
||||||
conditions:
|
|
||||||
- "[DNS_RCODE] == NOERROR"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Load Balancer
|
|
||||||
group: Services
|
|
||||||
url: "http://loba/"
|
|
||||||
interval: 60s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
- "[BODY] == OK/HEALTHY"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Feedback
|
|
||||||
group: Main
|
|
||||||
url: "http://pkmntrade-club-feedback-1:3000/"
|
|
||||||
interval: 60s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
{{ $all_containers := . }}
|
|
||||||
{{ $web_containers := list }}
|
|
||||||
{{ $web_staging_containers := list }}
|
|
||||||
|
|
||||||
{{ range $container := $all_containers }}
|
|
||||||
{{ $serviceLabel := index $container.Labels "com.docker.compose.service" }}
|
|
||||||
{{ if eq $serviceLabel "web" }}
|
|
||||||
{{ $web_containers = append $web_containers $container }}
|
|
||||||
{{ end }}
|
|
||||||
{{ if eq $serviceLabel "web-staging" }}
|
|
||||||
{{ $web_staging_containers = append $web_staging_containers $container }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ $web_containers = sortObjectsByKeysAsc $web_containers "Name" }}
|
|
||||||
{{ $web_staging_containers = sortObjectsByKeysAsc $web_staging_containers "Name" }}
|
|
||||||
|
|
||||||
{{ range $container := $web_containers }}
|
|
||||||
{{ $containerNumber := index $container.Labels "com.docker.compose.container-number" }}
|
|
||||||
- name: "Web Worker {{ $containerNumber }}"
|
|
||||||
group: Main
|
|
||||||
url: "http://{{ $container.Name }}:8000/health/"
|
|
||||||
headers:
|
|
||||||
Host: "pkmntrade.club"
|
|
||||||
interval: 60s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
# - "[BODY] == OK/HEALTHY"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ range $container := $web_staging_containers }}
|
|
||||||
{{ $containerNumber := index $container.Labels "com.docker.compose.container-number" }}
|
|
||||||
- name: "Web Worker {{ $containerNumber }}"
|
|
||||||
group: Staging
|
|
||||||
url: "http://{{ $container.Name }}:8000/health/"
|
|
||||||
headers:
|
|
||||||
Host: "staging.pkmntrade.club"
|
|
||||||
interval: 60s
|
|
||||||
conditions:
|
|
||||||
- "[STATUS] == 200"
|
|
||||||
# - "[BODY] == OK/HEALTHY"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
alerting:
|
|
||||||
email:
|
|
||||||
from: "${GATUS_SMTP_FROM}"
|
|
||||||
username: "${GATUS_SMTP_USER}"
|
|
||||||
password: "${GATUS_SMTP_PASS}"
|
|
||||||
host: "${GATUS_SMTP_HOST}"
|
|
||||||
port: ${GATUS_SMTP_PORT}
|
|
||||||
to: "${GATUS_SMTP_TO}"
|
|
||||||
client:
|
|
||||||
insecure: false
|
|
||||||
default-alert:
|
|
||||||
enabled: true
|
|
||||||
failure-threshold: 3
|
|
||||||
success-threshold: 2
|
|
||||||
send-on-resolved: true
|
|
||||||
|
|
@ -8,19 +8,14 @@ connectivity:
|
||||||
target: 1.1.1.1:53
|
target: 1.1.1.1:53
|
||||||
interval: 60s
|
interval: 60s
|
||||||
external-endpoints:
|
external-endpoints:
|
||||||
- name: Database
|
- name: pg_isready
|
||||||
group: Services
|
group: db
|
||||||
token: "${GATUS_TOKEN}"
|
|
||||||
alerts:
|
|
||||||
- type: email
|
|
||||||
- name: Redis
|
|
||||||
group: Services
|
|
||||||
token: "${GATUS_TOKEN}"
|
token: "${GATUS_TOKEN}"
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
endpoints:
|
endpoints:
|
||||||
- name: Domain
|
- name: Domain
|
||||||
group: Expirations
|
group: expirations
|
||||||
url: "https://pkmntrade.club"
|
url: "https://pkmntrade.club"
|
||||||
interval: 1h
|
interval: 1h
|
||||||
conditions:
|
conditions:
|
||||||
|
|
@ -28,7 +23,7 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: Certificate
|
- name: Certificate
|
||||||
group: Expirations
|
group: expirations
|
||||||
url: "https://pkmntrade.club"
|
url: "https://pkmntrade.club"
|
||||||
interval: 1h
|
interval: 1h
|
||||||
conditions:
|
conditions:
|
||||||
|
|
@ -36,7 +31,7 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: Cloudflare
|
- name: Cloudflare
|
||||||
group: DNS
|
group: dns
|
||||||
url: "1.1.1.1"
|
url: "1.1.1.1"
|
||||||
interval: 60s
|
interval: 60s
|
||||||
dns:
|
dns:
|
||||||
|
|
@ -47,7 +42,7 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: Google
|
- name: Google
|
||||||
group: DNS
|
group: dns
|
||||||
url: "8.8.8.8"
|
url: "8.8.8.8"
|
||||||
interval: 60s
|
interval: 60s
|
||||||
dns:
|
dns:
|
||||||
|
|
@ -58,7 +53,7 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: Quad9
|
- name: Quad9
|
||||||
group: DNS
|
group: dns
|
||||||
url: "9.9.9.9"
|
url: "9.9.9.9"
|
||||||
interval: 60s
|
interval: 60s
|
||||||
dns:
|
dns:
|
||||||
|
|
@ -69,7 +64,7 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: HAProxy
|
- name: HAProxy
|
||||||
group: Load Balancer
|
group: loadbalancer
|
||||||
url: "http://loba/"
|
url: "http://loba/"
|
||||||
interval: 60s
|
interval: 60s
|
||||||
conditions:
|
conditions:
|
||||||
|
|
@ -78,22 +73,60 @@ endpoints:
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
- name: Feedback
|
- name: Feedback
|
||||||
group: Services
|
group: backends
|
||||||
url: "http://feedback:3000/"
|
url: "http://feedback:3000/"
|
||||||
interval: 60s
|
interval: 60s
|
||||||
conditions:
|
conditions:
|
||||||
- "[STATUS] == 200"
|
- "[STATUS] == 200"
|
||||||
alerts:
|
alerts:
|
||||||
- type: email
|
- type: email
|
||||||
|
- name: Web Worker 1
|
||||||
|
group: backends
|
||||||
|
url: "http://pkmntrade-club-web-1:8000/health/"
|
||||||
|
interval: 60s
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
#- "[BODY] == OK/HEALTHY"
|
||||||
|
#- [BODY].database == UP
|
||||||
|
# must return json like {"database": "UP"} first
|
||||||
|
alerts:
|
||||||
|
- type: email
|
||||||
|
- name: Web Worker 2
|
||||||
|
group: backends
|
||||||
|
url: "http://pkmntrade-club-web-2:8000/health/"
|
||||||
|
interval: 60s
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
#- "[BODY] == OK/HEALTHY"
|
||||||
|
alerts:
|
||||||
|
- type: email
|
||||||
|
- name: Web Worker 3
|
||||||
|
group: backends
|
||||||
|
url: "http://pkmntrade-club-web-3:8000/health/"
|
||||||
|
interval: 60s
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
#- "[BODY] == OK/HEALTHY"
|
||||||
|
alerts:
|
||||||
|
- type: email
|
||||||
|
- name: Web Worker 4
|
||||||
|
group: backends
|
||||||
|
url: "http://pkmntrade-club-web-4:8000/health/"
|
||||||
|
interval: 60s
|
||||||
|
conditions:
|
||||||
|
- "[STATUS] == 200"
|
||||||
|
#- "[BODY] == OK/HEALTHY"
|
||||||
|
alerts:
|
||||||
|
- type: email
|
||||||
|
# todo: add cadvisor checks via api https://github.com/google/cadvisor/blob/master/docs/api.md
|
||||||
alerting:
|
alerting:
|
||||||
email:
|
email:
|
||||||
from: "${GATUS_SMTP_FROM}"
|
from: noreply@pkmntrade.club
|
||||||
username: "${GATUS_SMTP_USER}"
|
username: dd2cd354-de6d-4fa4-bfe8-31c961cb4e90
|
||||||
password: "${GATUS_SMTP_PASS}"
|
password: 1622e8a1-9a45-4a7f-8071-cccca29d8675
|
||||||
host: "${GATUS_SMTP_HOST}"
|
host: smtp.tem.scaleway.com
|
||||||
port: ${GATUS_SMTP_PORT}
|
port: 465
|
||||||
to: "${GATUS_SMTP_TO}"
|
to: rob@badblocks.email
|
||||||
client:
|
client:
|
||||||
insecure: false
|
insecure: false
|
||||||
default-alert:
|
default-alert:
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ defaults
|
||||||
timeout http-request 120s
|
timeout http-request 120s
|
||||||
option httplog
|
option httplog
|
||||||
|
|
||||||
frontend haproxy_entrypoint
|
frontend web_frontend
|
||||||
bind :443 ssl crt /certs/crt.pem verify required ca-file /certs/ca.pem
|
bind :443 ssl crt /certs/crt.pem verify required ca-file /certs/ca.pem
|
||||||
use_backend %[req.hdr(host),lower,word(1,:)] # strip out port from host
|
use_backend %[req.hdr(host),lower,word(1,:)] # strip out port from host
|
||||||
|
|
||||||
|
|
@ -34,23 +34,17 @@ backend basic_check
|
||||||
|
|
||||||
backend pkmntrade.club
|
backend pkmntrade.club
|
||||||
balance leastconn
|
balance leastconn
|
||||||
http-request set-header Host pkmntrade.club
|
server-template web- 10 web:8000 check resolvers docker_resolver init-addr libc,none
|
||||||
server-template gatekeeper-web- 4 gatekeeper-web:8000 check resolvers docker_resolver init-addr libc,none
|
|
||||||
|
|
||||||
backend staging.pkmntrade.club
|
backend staging.pkmntrade.club
|
||||||
balance leastconn
|
balance leastconn
|
||||||
http-request set-header Host staging.pkmntrade.club
|
server-template web-staging- 10 web-staging:8000 check resolvers docker_resolver init-addr libc,none
|
||||||
server-template gatekeeper-web-staging- 4 gatekeeper-web-staging:8000 check resolvers docker_resolver init-addr libc,none
|
|
||||||
|
|
||||||
backend feedback.pkmntrade.club
|
backend feedback.pkmntrade.club
|
||||||
balance leastconn
|
server feedback-1 feedback:3000
|
||||||
http-request set-header Host feedback.pkmntrade.club
|
|
||||||
server-template gatekeeper-feedback- 4 gatekeeper-feedback:8000 check resolvers docker_resolver init-addr libc,none
|
|
||||||
|
|
||||||
backend health.pkmntrade.club
|
backend health.pkmntrade.club
|
||||||
balance leastconn
|
server health-1 health:8080
|
||||||
http-request set-header Host health.pkmntrade.club
|
|
||||||
server-template gatekeeper-health- 4 gatekeeper-health:8000 check resolvers docker_resolver init-addr libc,none
|
|
||||||
|
|
||||||
#EOF - trailing newline required
|
#EOF - trailing newline required
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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']
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,3 @@ def cache_settings(request):
|
||||||
return {
|
return {
|
||||||
'CACHE_TIMEOUT': settings.CACHE_TIMEOUT,
|
'CACHE_TIMEOUT': settings.CACHE_TIMEOUT,
|
||||||
}
|
}
|
||||||
|
|
||||||
def version_info(request):
|
|
||||||
return {
|
|
||||||
'VERSION': settings.VERSION,
|
|
||||||
'VERSION_INFO': settings.VERSION_INFO,
|
|
||||||
}
|
|
||||||
|
|
@ -4,29 +4,9 @@ import environ
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import sys
|
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(
|
env = environ.Env(
|
||||||
DEBUG=(bool, False), # MUST STAY FALSE FOR DEFAULT FOR SECURITY REASONS (e.g. if app can't access .env, prevent showing debug output)
|
DEBUG=(bool, False)
|
||||||
DISABLE_SIGNUPS=(bool, True),
|
|
||||||
DISABLE_CACHE=(bool, True),
|
|
||||||
DJANGO_DATABASE_URL=(str, 'postgresql://postgres@localhost:5432/postgres?sslmode=disable'),
|
|
||||||
DJANGO_EMAIL_HOST=(str, ''),
|
|
||||||
DJANGO_EMAIL_PORT=(int, 587),
|
|
||||||
DJANGO_EMAIL_USER=(str, ''),
|
|
||||||
DJANGO_EMAIL_PASSWORD=(str, ''),
|
|
||||||
DJANGO_EMAIL_USE_TLS=(bool, True),
|
|
||||||
DJANGO_DEFAULT_FROM_EMAIL=(str, ''),
|
|
||||||
SECRET_KEY=(str, '0000000000000000000000000000000000000000000000000000000000000000'),
|
|
||||||
ALLOWED_HOSTS=(str, 'localhost,127.0.0.1'),
|
|
||||||
PUBLIC_HOST=(str, 'localhost'),
|
|
||||||
ACCOUNT_EMAIL_VERIFICATION=(str, 'none'),
|
|
||||||
SCHEME=(str, 'http'),
|
|
||||||
REDIS_URL=(str, 'redis://localhost:6379'),
|
|
||||||
CACHE_TIMEOUT=(int, 604800),
|
|
||||||
TIME_ZONE=(str, 'America/Los_Angeles'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
|
@ -79,16 +59,6 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
# Take environment variables from .env file
|
# Take environment variables from .env file
|
||||||
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
||||||
|
|
||||||
SCHEME = env('SCHEME')
|
|
||||||
PUBLIC_HOST = env('PUBLIC_HOST')
|
|
||||||
REDIS_URL = env('REDIS_URL')
|
|
||||||
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
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
@ -96,6 +66,11 @@ VERSION_INFO = get_version_info()
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env('SECRET_KEY')
|
SECRET_KEY = env('SECRET_KEY')
|
||||||
|
|
||||||
|
# Scaleway Secret Key
|
||||||
|
SCW_SECRET_KEY = env('SCW_SECRET_KEY')
|
||||||
|
|
||||||
|
DISABLE_SIGNUPS = env('DISABLE_SIGNUPS', default=False)
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env('DEBUG')
|
DEBUG = env('DEBUG')
|
||||||
|
|
@ -110,7 +85,9 @@ try:
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.getLogger(__name__).info(f"Error determining server hostname for allowed hosts.")
|
logging.getLogger(__name__).info(f"Error determining server hostname for allowed hosts.")
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [f"{SCHEME}://{PUBLIC_HOST}"]
|
PUBLIC_HOST = env('PUBLIC_HOST')
|
||||||
|
|
||||||
|
CSRF_TRUSTED_ORIGINS = [f"https://{PUBLIC_HOST}"]
|
||||||
|
|
||||||
FIRST_PARTY_APPS = [
|
FIRST_PARTY_APPS = [
|
||||||
'pkmntrade_club.accounts',
|
'pkmntrade_club.accounts',
|
||||||
|
|
@ -141,15 +118,6 @@ INSTALLED_APPS = [
|
||||||
"crispy_tailwind",
|
"crispy_tailwind",
|
||||||
"tailwind",
|
"tailwind",
|
||||||
"django_linear_migrations",
|
"django_linear_migrations",
|
||||||
'health_check',
|
|
||||||
'health_check.db',
|
|
||||||
'health_check.cache',
|
|
||||||
'health_check.storage',
|
|
||||||
'health_check.contrib.migrations',
|
|
||||||
'health_check.contrib.celery',
|
|
||||||
'health_check.contrib.celery_ping',
|
|
||||||
'health_check.contrib.psutil',
|
|
||||||
'health_check.contrib.redis',
|
|
||||||
"meta",
|
"meta",
|
||||||
] + FIRST_PARTY_APPS
|
] + FIRST_PARTY_APPS
|
||||||
|
|
||||||
|
|
@ -163,9 +131,9 @@ if DEBUG:
|
||||||
TAILWIND_APP_NAME = 'theme'
|
TAILWIND_APP_NAME = 'theme'
|
||||||
|
|
||||||
META_SITE_NAME = 'PKMN Trade Club'
|
META_SITE_NAME = 'PKMN Trade Club'
|
||||||
META_SITE_PROTOCOL = SCHEME
|
META_SITE_PROTOCOL = 'https'
|
||||||
META_USE_SITES = True
|
META_USE_SITES = True
|
||||||
META_IMAGE_URL = f'{SCHEME}://{PUBLIC_HOST}/'
|
META_IMAGE_URL = f'https://{PUBLIC_HOST}/'
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
|
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
|
@ -187,11 +155,6 @@ if DEBUG:
|
||||||
"django_browser_reload.middleware.BrowserReloadMiddleware",
|
"django_browser_reload.middleware.BrowserReloadMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
HEALTH_CHECK = {
|
|
||||||
'DISK_USAGE_MAX': 90, # percent
|
|
||||||
'MEMORY_MIN': 100, # in MB
|
|
||||||
}
|
|
||||||
|
|
||||||
DAISY_SETTINGS = {
|
DAISY_SETTINGS = {
|
||||||
'SITE_TITLE': 'PKMN Trade Club Admin',
|
'SITE_TITLE': 'PKMN Trade Club Admin',
|
||||||
'DONT_SUPPORT_ME': True,
|
'DONT_SUPPORT_ME': True,
|
||||||
|
|
@ -218,7 +181,6 @@ TEMPLATES = [
|
||||||
"django.contrib.auth.context_processors.auth",
|
"django.contrib.auth.context_processors.auth",
|
||||||
"django.contrib.messages.context_processors.messages",
|
"django.contrib.messages.context_processors.messages",
|
||||||
"pkmntrade_club.common.context_processors.cache_settings",
|
"pkmntrade_club.common.context_processors.cache_settings",
|
||||||
"pkmntrade_club.common.context_processors.version_info",
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -246,13 +208,14 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/dev/topics/i18n/
|
# https://docs.djangoproject.com/en/dev/topics/i18n/
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
|
# https://docs.djangoproject.com/en/dev/ref/settings/#time-zone
|
||||||
TIME_ZONE = env('TIME_ZONE')
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-USE_I18N
|
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-USE_I18N
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
@ -305,14 +268,19 @@ CRISPY_TEMPLATE_PACK = "tailwind"
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||||
EMAIL_HOST = env('DJANGO_EMAIL_HOST')
|
# EMAIL_HOST = "smtp.resend.com"
|
||||||
EMAIL_PORT = env('DJANGO_EMAIL_PORT')
|
# EMAIL_PORT = 587
|
||||||
EMAIL_HOST_USER = env('DJANGO_EMAIL_USER')
|
# EMAIL_HOST_USER = "resend"
|
||||||
EMAIL_HOST_PASSWORD = env('DJANGO_EMAIL_PASSWORD')
|
# EMAIL_HOST_PASSWORD = RESEND_API_KEY
|
||||||
EMAIL_USE_TLS = env('DJANGO_EMAIL_USE_TLS')
|
# EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST = "smtp.tem.scaleway.com"
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_HOST_USER = "dd2cd354-de6d-4fa4-bfe8-31c961cb4e90"
|
||||||
|
EMAIL_HOST_PASSWORD = SCW_SECRET_KEY
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
||||||
DEFAULT_FROM_EMAIL = env('DJANGO_DEFAULT_FROM_EMAIL')
|
DEFAULT_FROM_EMAIL = "noreply@pkmntrade.club"
|
||||||
|
|
||||||
# django-debug-toolbar
|
# django-debug-toolbar
|
||||||
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html
|
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html
|
||||||
|
|
@ -321,7 +289,7 @@ INTERNAL_IPS = [
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
# for docker
|
# for docker + debug toolbar
|
||||||
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
INTERNAL_IPS.append(ip)
|
INTERNAL_IPS.append(ip)
|
||||||
|
|
@ -356,7 +324,7 @@ ACCOUNT_EMAIL_REQUIRED = True
|
||||||
ACCOUNT_EMAIL_VERIFICATION = env('ACCOUNT_EMAIL_VERIFICATION')
|
ACCOUNT_EMAIL_VERIFICATION = env('ACCOUNT_EMAIL_VERIFICATION')
|
||||||
ACCOUNT_EMAIL_NOTIFICATIONS = True
|
ACCOUNT_EMAIL_NOTIFICATIONS = True
|
||||||
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
|
ACCOUNT_EMAIL_UNKNOWN_ACCOUNTS = False
|
||||||
ACCOUNT_DEFAULT_HTTP_PROTOCOL = SCHEME
|
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
|
||||||
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
||||||
ACCOUNT_USERNAME_MIN_LENGTH = 2
|
ACCOUNT_USERNAME_MIN_LENGTH = 2
|
||||||
ACCOUNT_CHANGE_EMAIL = True
|
ACCOUNT_CHANGE_EMAIL = True
|
||||||
|
|
@ -372,12 +340,12 @@ SOCIALACCOUNT_EMAIL_AUTHENTICATION = False
|
||||||
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = False
|
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = False
|
||||||
SOCIALACCOUNT_ONLY = False
|
SOCIALACCOUNT_ONLY = False
|
||||||
|
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
|
CACHE_TIMEOUT = 604800 # 1 week
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
|
||||||
|
|
||||||
# auto-detection doesn't work properly sometimes, so we'll just use the DEBUG setting
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG}
|
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: DEBUG}
|
||||||
|
|
||||||
|
DISABLE_CACHE = env('DISABLE_CACHE', default=DEBUG)
|
||||||
|
|
||||||
if DISABLE_CACHE:
|
if DISABLE_CACHE:
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
@ -388,12 +356,12 @@ else:
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
||||||
"LOCATION": REDIS_URL,
|
"LOCATION": "redis://redis:6379",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CELERY_BROKER_URL = REDIS_URL
|
CELERY_BROKER_URL = "redis://redis:6379"
|
||||||
CELERY_RESULT_BACKEND = REDIS_URL
|
CELERY_RESULT_BACKEND = "redis://redis:6379"
|
||||||
CELERY_TIMEZONE = TIME_ZONE
|
CELERY_TIMEZONE = "America/Los_Angeles"
|
||||||
CELERY_ENABLE_UTC = True
|
CELERY_ENABLE_UTC = True
|
||||||
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ from debug_toolbar.toolbar import debug_toolbar_urls
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path('account/', include('pkmntrade_club.accounts.urls')),
|
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
path("", include("pkmntrade_club.home.urls")),
|
path("", include("pkmntrade_club.home.urls")),
|
||||||
path("cards/", include("pkmntrade_club.cards.urls")),
|
path("cards/", include("pkmntrade_club.cards.urls")),
|
||||||
path("health/", include('health_check.urls')),
|
path('account/', include('pkmntrade_club.accounts.urls')),
|
||||||
path("trades/", include("pkmntrade_club.trades.urls")),
|
path("trades/", include("pkmntrade_club.trades.urls")),
|
||||||
path("__reload__/", include("django_browser_reload.urls")),
|
path("__reload__/", include("django_browser_reload.urls")),
|
||||||
] + debug_toolbar_urls()
|
] + debug_toolbar_urls()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import HomePageView
|
from .views import HomePageView, HealthCheckView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", HomePageView.as_view(), name="home"),
|
path("", HomePageView.as_view(), name="home"),
|
||||||
|
path("health", HealthCheckView.as_view(), name="health"),
|
||||||
|
path("health/", HealthCheckView.as_view(), name="health"),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -139,3 +139,29 @@ class HomePageView(TemplateView):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Override get method to add caching"""
|
"""Override get method to add caching"""
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
class HealthCheckView(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.db import connection
|
||||||
|
connection.cursor().execute("SELECT 1")
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponse("Database connection failed", status=500)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pkmntrade_club.trades.models import TradeOffer
|
||||||
|
with contextlib.redirect_stdout(None):
|
||||||
|
print(TradeOffer.objects.count())
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponse("DB models not reachable, but db is reachable", status=500)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.core.cache import cache
|
||||||
|
cache.set("test", "test")
|
||||||
|
with contextlib.redirect_stdout(None):
|
||||||
|
print(cache.get("test"))
|
||||||
|
except Exception as e:
|
||||||
|
return HttpResponse("Cache not reachable", status=500)
|
||||||
|
|
||||||
|
return HttpResponse("OK/HEALTHY")
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@
|
||||||
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
<link rel="stylesheet" href="{% static 'css/base.css' %}">
|
||||||
|
|
||||||
<!-- Floating UI -->
|
<!-- Floating UI -->
|
||||||
<script src="{% static 'js/floating-ui_core-1.6.9.9.min.js' %}"></script>
|
<script src="{% static 'js/floating-ui_core@1.6.9.9.min.js' %}"></script>
|
||||||
<script src="{% static 'js/floating-ui_dom-1.6.13.13.min.js' %}"></script>
|
<script src="{% static 'js/floating-ui_dom@1.6.13.13.min.js' %}"></script>
|
||||||
|
|
||||||
<script defer src="{% static 'js/card-multiselect.js' %}"></script>
|
<script defer src="{% static 'js/card-multiselect.js' %}"></script>
|
||||||
<link rel="stylesheet" href="{% static 'css/card-multiselect.css' %}">
|
<link rel="stylesheet" href="{% static 'css/card-multiselect.css' %}">
|
||||||
|
|
@ -130,13 +130,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Alpine Plugins -->
|
<!-- Alpine Plugins -->
|
||||||
<script defer src="{% static 'js/alpinejs.collapse-3.14.8.min.js' %}"></script>
|
<script defer src="{% static 'js/alpinejs.collapse@3.14.8.min.js' %}"></script>
|
||||||
|
|
||||||
<!-- Alpine Core -->
|
<!-- Alpine Core -->
|
||||||
<script defer src="{% static 'js/alpinejs-3.14.8.min.js' %}"></script>
|
<script defer src="{% static 'js/alpinejs@3.14.8.min.js' %}"></script>
|
||||||
|
|
||||||
<!-- Goatcounter: 100% privacy-first, no tracking analytics -->
|
<!-- 100% privacy-first, no tracking analytics -->
|
||||||
<script data-goatcounter="https://stats.pkmntrade.club/count" async src="{% static 'js/count-v4.js' %}"></script>
|
<script data-goatcounter="https://stats.pkmntrade.club/count" async src="/static/js/count@v4.js"></script>
|
||||||
|
|
||||||
{% block javascript %}{% endblock %}
|
{% block javascript %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
{% load gravatar card_badge cache %}
|
{% load gravatar card_badge %}
|
||||||
|
|
||||||
{% cache CACHE_TIMEOUT trade_acceptance cache_key %}
|
{% cache CACHE_TIMEOUT trade_acceptance cache_key %}
|
||||||
<div class="card card-border bg-base-100 shadow-lg max-w-90 mx-auto">
|
<div class="card card-border bg-base-100 shadow-lg max-w-90 mx-auto">
|
||||||
|
|
|
||||||
135
uv.lock
generated
135
uv.lock
generated
|
|
@ -104,14 +104,14 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.2.1"
|
version = "8.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ 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 }
|
sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 },
|
{ url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -313,18 +313,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
|
{ url = "https://files.pythonhosted.org/packages/83/b3/0a3bec4ecbfee960f39b1842c2f91e4754251e0a6ed443db9fe3f666ba8f/django_environ-0.12.0-py2.py3-none-any.whl", hash = "sha256:92fb346a158abda07ffe6eb23135ce92843af06ecf8753f43adf9d2366dcc0ca", size = 19957 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-health-check"
|
|
||||||
version = "3.18.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "django" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/66/e9/0699ea3debfda75e5960ff99f56974136380e6f8202d453de7357e1f67fc/django_health_check-3.18.3.tar.gz", hash = "sha256:18b75daca4551c69a43f804f9e41e23f5f5fb9efd06cf6a313b3d5031bb87bd0", size = 20919 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/1e/3b23b580762cca7456427731de9b90718d15eec02ebe096437469d767dfe/django_health_check-3.18.3-py2.py3-none-any.whl", hash = "sha256:f5f58762b80bdf7b12fad724761993d6e83540f97e2c95c42978f187e452fa07", size = 30331 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-linear-migrations"
|
name = "django-linear-migrations"
|
||||||
version = "2.17.0"
|
version = "2.17.0"
|
||||||
|
|
@ -346,18 +334,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 },
|
{ 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]]
|
[[package]]
|
||||||
name = "django-tailwind-4"
|
name = "django-tailwind-4"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
|
@ -429,41 +405,41 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "granian"
|
name = "granian"
|
||||||
version = "2.3.1"
|
version = "2.2.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/82/0f/04aacf7ec30ba04018c7be761e5a6964d73cf82da5969b35e912e8e4e662/granian-2.3.1.tar.gz", hash = "sha256:5e9bddf3580e8ffccfaa97196672a6351630c959c37eb2498772504759a9f1ba", size = 100302 }
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/59/064df25d63fbfc27c7ec48c1d0efe3fffe6b70b8d3d03c59f136f390cad7/granian-2.2.5.tar.gz", hash = "sha256:90b832270b6b03a41b1706051113a3ffcca307860d5c864dc1f47ea290fc4b58", size = 94178 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/26/cd/30917d357be84957b3e8a1a82ac45e14fdfeb8e1afcd82ffe50a94f759f1/granian-2.3.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9067454777e14b8d3d5ad2d3e5f11ee2cc1ae18c09455908d44e6b5a0d018207", size = 3047974 },
|
{ url = "https://files.pythonhosted.org/packages/7c/90/84dd92375dfb33876c82a05e1942c8800931b7c439299d5e1485ef7216c8/granian-2.2.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:2ff8916ba37490fef696a461c2a43498b8865b3dcfa73e3dbff9d72ea2f6fbb9", size = 2848961 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/27/a5/c3752565733da327441e602e6dddafa79219e752c25ee70416b48df30321/granian-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4851094be97758f9d72a7d304021edeaf902818a5635b13ea1784092090098d8", size = 2722452 },
|
{ url = "https://files.pythonhosted.org/packages/2e/0d/e62d645ec01ac0b6dd3860949eda41de4e2ec1b014dc50b11a34989d4c4d/granian-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb56eccde7fe1c94ffb9ae60d516221c57b2e29224b6c6c2484ded044852320", size = 2592306 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/77/0a/38d6eb581c43cffb5b4a87a2bbd8b3f39e0874785d95db5d94c01f259809/granian-2.3.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c6f805468151a756142d59fab8414c986bdbdeea26983178d5e3c00705aaba6", size = 3365275 },
|
{ url = "https://files.pythonhosted.org/packages/b8/94/f955777a6d75c79198d8ca34132d04698dd0bf9b833833646e77c4fb164f/granian-2.2.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1883c7e3e1b1154ba49d1317e42a660b2c12a7bda8e4bc79b9279904db01d48b", size = 3114729 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/33/44/5fa5aab9a1cf27295bee81e22ecf758adef68c12244c0fd8d9d82da350e2/granian-2.3.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:300954af2a155d8827c6c7af45d8bff8c080303c23fac502e21c34cfb3f92de1", size = 3001384 },
|
{ url = "https://files.pythonhosted.org/packages/a4/9c/814f88c8bf4eb1b9758bacf38838c8d3de3deb9c51b8d7ecdf5dd524988a/granian-2.2.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84ce4b171e3dd10a8d1b0ddf6a09665faae257ca5e35394af0784d1682252903", size = 2877067 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/21/25/df592394d957933dbe905510dc4ad35141ea3e49fd4e562bc529727a8f44/granian-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73659a68149d159cfadcf44fcf6cdb91b027aa4ebb7ad561a9febbfaaecc903b", size = 3215845 },
|
{ url = "https://files.pythonhosted.org/packages/3b/e9/aa321896f8ce46e370a3b52dbd4104d3a145e47884cb885da1492bb20486/granian-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cdded34f6a0f9f4bdbb26535c4b16312b38b7edb799b39e2282f57b605919ea", size = 3049879 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ef/67/9213bf996d0e687939924468615762d106fd38f8c098f34266648f465d2b/granian-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3fa062db9d6fe7e57aa9c0043e30d50e1ee9fcf4226768b1b13a4fddef81d761", size = 3163131 },
|
{ url = "https://files.pythonhosted.org/packages/e4/b0/14f73043a7762138681a07d7bf18125e7a7d7ba5e2b96406ccf281ad3251/granian-2.2.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:41f90d5123c1d772c24dfa552551831cd96742f72be26d550d3ac0bae733e870", size = 2960461 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/25/de/1829c71fd0cba459a9bfc998138ca3ff18f8b913c9ae3c3a3c8c675ceb0c/granian-2.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4d00d26d7d02436ca2d1a26b6390710bea6d98cd519927835b04991043777852", size = 3139498 },
|
{ url = "https://files.pythonhosted.org/packages/f5/7d/c004df81422bfe1857f768a98550c8f1017140f00a6d9179e37ce29086dc/granian-2.2.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fadb34659db0e4eaba19e3d3547eaa4f75a64a63f222a57df7badcc17d3842d9", size = 2865627 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/8a/ce0adbeefcd7a78981d6a4f99709d82344a5c76d163d741547e9cc6f864e/granian-2.3.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:bd35d898c7be876924e73b13004ccee20b6bc071bf851c3d7eb029f01be22306", size = 3460123 },
|
{ url = "https://files.pythonhosted.org/packages/64/31/70bbfef65e5612f790d2b7140309ccde8736744203b78ef281288b6f201a/granian-2.2.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:bbeeeb96e7148cc36afa56f652da24f65098bd5e64a528ce894f60ab2db85ff7", size = 3128615 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/f1/1918ff96843125ae12480eb7430692fa3243e42206b263918b087c34852c/granian-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5979218d189e8da8de50a690c7399e1f0b052419c0b10dd20210ec73dfe29f83", size = 3273879 },
|
{ url = "https://files.pythonhosted.org/packages/5e/5c/179a7f3f0c1c46ccaee8ef9b78dd679db304461c2538907e826c20a0025d/granian-2.2.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ae5587061a95b936ecaaf337e1aff35d19e27c1872d09e1a86debf1010841f8", size = 2997155 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3b/14/bc6f049852d26045839dc6bddfd06ca6efa8966b939fa4c747ad7c9ab9bf/granian-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:878dea8b920a52d7ab36ee8110f960a8a2dde1cb0d43331bf71e1815f1051628", size = 2766821 },
|
{ url = "https://files.pythonhosted.org/packages/a6/99/331354780b32f1fc626e510856e43d094fe68e6ac101805cef9351e3078f/granian-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:fc1fa600bf0be3e8a3e2a49fb013aa9edf740dbf1ab14a19cad75088bd44dae4", size = 2586303 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/c1/71c4a64ca6e65e390ba82270c967318956ea67ed1467f68fc1bd236cc338/granian-2.3.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5e97505ba05f73d76669f732221d01c1c69b0ce01384db872d0b0c240cc422e4", size = 3047647 },
|
{ url = "https://files.pythonhosted.org/packages/b7/61/652d9817f6dff310950ab835b8838c44a370fa5c3ac8f997f4ec2738a403/granian-2.2.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:00d0d1797df1e0c3c9e438670e0f1f27500efef543ced42415d821e6162f884e", size = 2848540 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/f7/e1135ee1f9b6188438ca0b406f081b040ebf5c8bcd290b8a2086c4e1cdf3/granian-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8f0add56a7188830e82ac76bc8c11ab758edaec889f6f744023d4cd0ac45a908", size = 2721628 },
|
{ url = "https://files.pythonhosted.org/packages/f3/50/c63b8b7d4951be43ba5f1c9d3e67f9fde1ddddaca61164ab7ae70f3405c3/granian-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:05d0852938a40948ce48a14a0186c9757b738e2715bd817a7931cb5b65aff4cb", size = 2591960 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ad/d8/73d953e94c7d62c60db7c0ae8448bed578fdbd85b6aa6d939f52541da266/granian-2.3.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:006fd310d23e904802ea293abdc909c1871762f955deeb4c32232f7ddec37a3f", size = 3364877 },
|
{ url = "https://files.pythonhosted.org/packages/23/c5/631c10134ced73dfcf03f3ba1157aa02dffa1d30cd5ec3b85a5d469c7090/granian-2.2.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:19b6817f25f1c29858e06c2ded96d16734ebb5c7d0f2d29f71c0e6e3da240906", size = 3113616 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/5e/a81c96fb365cee2395092250d97f600b6bc4b477702f98e1befbef27f937/granian-2.3.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:172ed6e87b588eb54dfaf3a39a486b06c82e0accbe3b86427333ea3a57c9b2c9", size = 3001067 },
|
{ url = "https://files.pythonhosted.org/packages/64/d2/7015aa7b6faedccb1498acd0b2f838c1cf15b13faa4052077b3a82d7035c/granian-2.2.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6a79710035d5c5964e3f4a528e0d9b74de5d73a69b1ea48142804469a4c635f", size = 2876933 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/40/b0/9270bf7d1b612923d14d9783dde0d065afec62146753a2d64f17ef49e955/granian-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:413617122d940bbcf793bd1a9ba6a0fabadd5ba75b03acf99c101f735030dc0e", size = 3215182 },
|
{ url = "https://files.pythonhosted.org/packages/6b/fb/284b5fee9630f512c1ba9f54992f321a9f3b29e1d9c71199fb3cd700eb1a/granian-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a8b508b785e0a68593b111e598a199567c6fb98841cbd9cd1b5a10baa4cc13d", size = 3049641 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/27/a1166372c0f40fde0ea3778e2ddacbf752d4e1ce3a2ecb49b5e100c7fbaf/granian-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a7a9b93e7dd2f63a1762343c6d396eef52163fb2cea044569102ae41fa3fd607", size = 3163178 },
|
{ url = "https://files.pythonhosted.org/packages/71/c5/6e92f8d897017b53ac2e9608f268eccfa268433179dda5f7f6c6e87d71b6/granian-2.2.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9c6f1f4351ccf9783db6d6da022f1ba83ef4c83c2d26f52ca5d30acf5fbac2df", size = 2960064 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/cc/53d257346b9274001141421fca0436df67662634dfdd9f6ac5a595737804/granian-2.3.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:b6eea65f77527aeb5deb75186c90063e4368a94f7076aa9495067804f06d0345", size = 3138821 },
|
{ url = "https://files.pythonhosted.org/packages/43/c7/86422d387da46eb956660d9a1fd12da07c165bd1033fc32badee854e4797/granian-2.2.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ac26b7933e3302b526d481b7c61f017288e06eb56bf9168133f649097b2ce5ab", size = 2865506 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/79/a1f50daf41dc1a50384a359dcd1f02a844c0a9b4f009a5d5c399c1893a9a/granian-2.3.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:d1ae09ae5424ca5e57dbea660088878aca858f176b2ddf26dc5bf685b939b567", size = 3460024 },
|
{ url = "https://files.pythonhosted.org/packages/e8/68/f6e5f9b087e1ede11fcd4dbb8d70bff8eed4f9b5ea691863035292ec9d39/granian-2.2.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:4950e77678378995df3b2be5544179ae5757a3ab6079272f14a161e14f0fe1eb", size = 3128304 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/3a/78038ab237eda59707d0f0e0fae750ff21db543e7175dfb7ac3b87a87b7f/granian-2.3.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:8c10fdee7afee4d8b69a92f937552c94b979521e6f0169bb300c9612a85b9989", size = 3273042 },
|
{ url = "https://files.pythonhosted.org/packages/40/31/65595f29a42fb7b6639ca4566d547219655796c45ad372cba7168dff2689/granian-2.2.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:928389b89ffe88af96fbd8120fc3cb64afe9a62797000f6e7e6ff88ff5612ccc", size = 2996875 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/2c/ab47958d0d808fda5659535a30214ed24622d89190f37fa00d2200e88fb5/granian-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:e58d2945ab1f99a5f2f804b9d552b337cccf77620dd17ddda94b0baaff0d78ef", size = 2766257 },
|
{ url = "https://files.pythonhosted.org/packages/bd/76/6435e413702cc80963044627f96e03c49110bdf86e11a571e76560df5edc/granian-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:db1f3c2ae4496e82803014587b822c702d5ea263c35a8edf1a2b098ee9459c9a", size = 2586059 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b2/58/5ef889557401cd01d9f4380dc4a23ee679d835b51f84956d06b97b4bcb8d/granian-2.3.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f662331ff69802ffdc02160feadb1a347c652afe8192739d4adf34da6cd1bbff", size = 2999112 },
|
{ url = "https://files.pythonhosted.org/packages/a1/3e/fa02abd294ddf5e0e432c01727cc76a931d030e4f24141cfdcdfb078357a/granian-2.2.5-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f0b688402347e646a2d2b95114eef2eb786ec4c9cb747157e7e892f809e0bb3f", size = 2697330 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/13/70/4363eb6ac164063af7d322691be221477d127c6c6986a339688c32dbd1d1/granian-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3941e11bcb018cab31ed9a597c67458d278db1469242775e28711d5a3c0be481", size = 2667844 },
|
{ url = "https://files.pythonhosted.org/packages/e9/c8/a1dfaec4b6308e47a90c3e1920f681db36449829be445fef653e8ef7d3fa/granian-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e8fd8688c79fd7d9dec162b5217869288c1da64ce26518d9fbb69d8f8e97ac9", size = 2466298 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/53/dc/bdcd9f18f7e070d17236c685cd56ec5834a088e8f885de671bd13c608176/granian-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d53338bb064586e929cece3611d1a49b398ac328f87604e56eda7754c2d0c55", size = 3076031 },
|
{ url = "https://files.pythonhosted.org/packages/c6/f8/ea86317f6582be1b3ac6b29781631ae69c5d4693e5da9467fd9fb18abe02/granian-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12fe0a5f37affa90d2d576b9a7c4e1bbe18ff4cce59f6cd05d33375e6d5b4b5a", size = 2816442 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/74/18/9dbc3c4b7a14c3eeecac702a8d351e4c1220c89e99ffe7f0211e856f3c54/granian-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:16941818186789804da8b0837130117ca310f88d419856d8df2655ccae27f670", size = 3028926 },
|
{ url = "https://files.pythonhosted.org/packages/dd/26/fd6d5d19ce5a4a149cc93c05d7522ce90ee6728c56b13035a2d5259404bc/granian-2.2.5-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:48a43bf791ec7ec8c75cf538f5957875aedc5b4653688c9292887911738d3f51", size = 2735825 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/b5/7b72ada8a04203c4e9165a3ba7bf266568bf0507ea40c170f96566e0b390/granian-2.3.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:8265480fc80cf0546af509f9f9d27519af26038fbd586081cdd3641d4dd3f44e", size = 3130077 },
|
{ url = "https://files.pythonhosted.org/packages/06/34/148a6f3918dbb71824845edbe2a6d8512a52ae2a8c323a9071002a68d6d1/granian-2.2.5-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:bb44abd371bf054626aa750ad25dfe6b17216a0dbf10faa4f6a61a2fea57eaf6", size = 2857911 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/79/34/2395be45fea5e818a5ba0b8871c0fb5776e23f664fdd05a9b00849757314/granian-2.3.1-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:0bf2ace1107950110e7da5cca88ecca1f97bb5ef7398c6bc9c95bd0787f0edac", size = 3450662 },
|
{ url = "https://files.pythonhosted.org/packages/28/1c/6c0c5aeae2a090ac046065944863fb76608c6b09c5249fda46148391b128/granian-2.2.5-cp313-cp313t-musllinux_1_1_armv7l.whl", hash = "sha256:4cdccad590be2183eed4e11f4aef7e62c5326df777e4aaefceecb23edea474ad", size = 3118748 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/c3/9a037020e26ede18b8570f559254911722875ae129293337dc0170ec7c0e/granian-2.3.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:4a08c567a2472fdabd49c1564c560ffe1734e8acd1e0fc3027907296f36434fc", size = 3264479 },
|
{ url = "https://files.pythonhosted.org/packages/1a/34/22ada66b585c9a3076c63777491dc6daf1773a86cb262a613cd9af3cb24f/granian-2.2.5-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0ea5be0b02b78a024fc730cf2a271e58ec553aa39b0b43bdb11492c4c82024ba", size = 2989738 },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/32/660601986c5b5815d13399e9c3b6e84a119be010c974d32d505eb1ef4c7e/granian-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d828917559a53ff9581ac4a475a508d7b6de7abebb256a27543af8363eb7c844", size = 2792073 },
|
{ url = "https://files.pythonhosted.org/packages/7f/6e/6063f3a44e20dcfa838467b5a3358b907e367edf3596056f86abed532085/granian-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:3e83de670fd6c75b405f28f62053d3477433650f102cb88e6582df6acced0a6c", size = 2510704 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -596,6 +572,7 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkmntrade-club"
|
name = "pkmntrade-club"
|
||||||
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { editable = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "asgiref" },
|
{ name = "asgiref" },
|
||||||
|
|
@ -614,10 +591,8 @@ dependencies = [
|
||||||
{ name = "django-daisy" },
|
{ name = "django-daisy" },
|
||||||
{ name = "django-debug-toolbar" },
|
{ name = "django-debug-toolbar" },
|
||||||
{ name = "django-environ" },
|
{ name = "django-environ" },
|
||||||
{ name = "django-health-check" },
|
|
||||||
{ name = "django-linear-migrations" },
|
{ name = "django-linear-migrations" },
|
||||||
{ name = "django-meta" },
|
{ name = "django-meta" },
|
||||||
{ name = "django-parler" },
|
|
||||||
{ name = "django-tailwind-4", extra = ["reload"] },
|
{ name = "django-tailwind-4", extra = ["reload"] },
|
||||||
{ name = "django-widget-tweaks" },
|
{ name = "django-widget-tweaks" },
|
||||||
{ name = "gevent" },
|
{ name = "gevent" },
|
||||||
|
|
@ -628,7 +603,6 @@ dependencies = [
|
||||||
{ name = "packaging" },
|
{ name = "packaging" },
|
||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "playwright" },
|
{ name = "playwright" },
|
||||||
{ name = "psutil" },
|
|
||||||
{ name = "psycopg" },
|
{ name = "psycopg" },
|
||||||
{ name = "psycopg-binary" },
|
{ name = "psycopg-binary" },
|
||||||
{ name = "pycparser" },
|
{ name = "pycparser" },
|
||||||
|
|
@ -637,7 +611,6 @@ dependencies = [
|
||||||
{ name = "redis" },
|
{ name = "redis" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
{ name = "requests-oauthlib" },
|
{ name = "requests-oauthlib" },
|
||||||
{ name = "setuptools-scm" },
|
|
||||||
{ name = "sqlparse" },
|
{ name = "sqlparse" },
|
||||||
{ name = "typing-extensions" },
|
{ name = "typing-extensions" },
|
||||||
{ name = "urllib3" },
|
{ name = "urllib3" },
|
||||||
|
|
@ -662,21 +635,18 @@ requires-dist = [
|
||||||
{ name = "django-daisy", specifier = "==1.0.13" },
|
{ name = "django-daisy", specifier = "==1.0.13" },
|
||||||
{ name = "django-debug-toolbar", specifier = "==4.4.6" },
|
{ name = "django-debug-toolbar", specifier = "==4.4.6" },
|
||||||
{ name = "django-environ", specifier = "==0.12.0" },
|
{ name = "django-environ", specifier = "==0.12.0" },
|
||||||
{ name = "django-health-check", specifier = ">=3.18.3" },
|
|
||||||
{ name = "django-linear-migrations", specifier = ">=2.17.0" },
|
{ name = "django-linear-migrations", specifier = ">=2.17.0" },
|
||||||
{ name = "django-meta", specifier = "==2.4.2" },
|
{ 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-tailwind-4", extras = ["reload"], specifier = "==0.1.4" },
|
||||||
{ name = "django-widget-tweaks", specifier = "==1.5.0" },
|
{ name = "django-widget-tweaks", specifier = "==1.5.0" },
|
||||||
{ name = "gevent", specifier = "==25.4.1" },
|
{ name = "gevent", specifier = "==25.4.1" },
|
||||||
{ name = "granian", specifier = "==2.3.1" },
|
{ name = "granian", specifier = "==2.2.5" },
|
||||||
{ name = "gunicorn", specifier = "==23.0.0" },
|
{ name = "gunicorn", specifier = "==23.0.0" },
|
||||||
{ name = "idna", specifier = "==3.4" },
|
{ name = "idna", specifier = "==3.4" },
|
||||||
{ name = "oauthlib", specifier = "==3.2.2" },
|
{ name = "oauthlib", specifier = "==3.2.2" },
|
||||||
{ name = "packaging", specifier = "==23.1" },
|
{ name = "packaging", specifier = "==23.1" },
|
||||||
{ name = "pillow", specifier = ">=11.2.1" },
|
{ name = "pillow", specifier = ">=11.2.1" },
|
||||||
{ name = "playwright", specifier = "==1.52.0" },
|
{ name = "playwright", specifier = "==1.52.0" },
|
||||||
{ name = "psutil", specifier = ">=7.0.0" },
|
|
||||||
{ name = "psycopg", specifier = "==3.2.3" },
|
{ name = "psycopg", specifier = "==3.2.3" },
|
||||||
{ name = "psycopg-binary", specifier = "==3.2.3" },
|
{ name = "psycopg-binary", specifier = "==3.2.3" },
|
||||||
{ name = "pycparser", specifier = "==2.21" },
|
{ name = "pycparser", specifier = "==2.21" },
|
||||||
|
|
@ -685,7 +655,6 @@ requires-dist = [
|
||||||
{ name = "redis", specifier = ">=6.1.0" },
|
{ name = "redis", specifier = ">=6.1.0" },
|
||||||
{ name = "requests", specifier = "==2.28.2" },
|
{ name = "requests", specifier = "==2.28.2" },
|
||||||
{ name = "requests-oauthlib", specifier = "==1.3.1" },
|
{ name = "requests-oauthlib", specifier = "==1.3.1" },
|
||||||
{ name = "setuptools-scm", specifier = ">=8.3.1" },
|
|
||||||
{ name = "sqlparse", specifier = "==0.4.3" },
|
{ name = "sqlparse", specifier = "==0.4.3" },
|
||||||
{ name = "typing-extensions", specifier = "==4.9.0" },
|
{ name = "typing-extensions", specifier = "==4.9.0" },
|
||||||
{ name = "urllib3", specifier = "==1.26.14" },
|
{ name = "urllib3", specifier = "==1.26.14" },
|
||||||
|
|
@ -723,21 +692,6 @@ wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
|
{ url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "psutil"
|
|
||||||
version = "7.0.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "psycopg"
|
name = "psycopg"
|
||||||
version = "3.2.3"
|
version = "3.2.3"
|
||||||
|
|
@ -885,24 +839,11 @@ wheels = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "80.8.0"
|
version = "80.7.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/d2/ec1acaaff45caed5c2dedb33b67055ba9d4e96b091094df90762e60135fe/setuptools-80.8.0.tar.gz", hash = "sha256:49f7af965996f26d43c8ae34539c8d99c5042fbff34302ea151eaa9c207cd257", size = 1319720 }
|
sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188 }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/29/93c53c098d301132196c3238c312825324740851d77a8500a2462c0fd888/setuptools-80.8.0-py3-none-any.whl", hash = "sha256:95a60484590d24103af13b686121328cc2736bee85de8936383111e421b9edc0", size = 1201470 },
|
{ url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776 },
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "setuptools-scm"
|
|
||||||
version = "8.3.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "setuptools" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/19/7ae64b70b2429c48c3a7a4ed36f50f94687d3bfcd0ae2f152367b6410dff/setuptools_scm-8.3.1.tar.gz", hash = "sha256:3d555e92b75dacd037d32bafdf94f97af51ea29ae8c7b234cf94b7a5bd242a63", size = 78088 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ab/ac/8f96ba9b4cfe3e4ea201f23f4f97165862395e9331a424ed325ae37024a8/setuptools_scm-8.3.1-py3-none-any.whl", hash = "sha256:332ca0d43791b818b841213e76b1971b7711a960761c5bea5fc5cdb5196fbce3", size = 43935 },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue