11 Commits

Author SHA1 Message Date
ac9c7a81c3 Release v25.11.06
All checks were successful
CI / Checks (push) Successful in 4m30s
Production deployment / Build (release) Successful in 23s
Production deployment / Deploy (release) Successful in 1m50s
Staging deployment / Build (release) Successful in 1m9s
Staging deployment / Deploy (release) Successful in 1m1s
2025-11-06 22:02:59 +01:00
e800d0c16c BTHLABS-63: Production deployment workflow 2025-11-06 21:58:20 +01:00
d8bbe57b17 BTHLABS-64: Support for customized environments
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-27 19:04:48 +00:00
168657bd14 Release v25.10.21
All checks were successful
CI / Checks (push) Successful in 1m46s
2025-10-21 20:27:10 +02:00
6d49db5081 BTHLABS-0000: hotpocket.work.bthlabs.net vhosts for dotcom 2025-10-21 20:25:19 +02:00
9a6ade0d96 BTHLABS-0000: AIO settings fixes 2025-10-21 20:24:38 +02:00
a6e9b55837 Release v25.10.20 2025-10-20 20:30:50 +02:00
356f6ad76f BTHLABS-0000: Tweaking icons
Reviewed-on: hotpocket/hotpocket#20
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-20 18:14:22 +00:00
fbdebec6c8 BTHLABS-0000: sandstone and sketchy Bootswatch themes 2025-10-17 13:14:21 +02:00
10fccc17f7 BTHLABS-0000: development workflow
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-17 06:49:10 +00:00
0cf7b27f89 BTHLABS-0000: Deps update (Oct 2025)
Featuring Poetry bump to 2.2.1 :)
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-15 04:16:27 +00:00
135 changed files with 3548 additions and 1396 deletions

View File

@@ -0,0 +1,26 @@
name: "Get Build Options"
description: "Sanitizies and unifies the environment into build options"
outputs:
short-sha:
description: "Shortened hash if the current commit"
build-arch:
description: "Docker-compatible representation of build arch"
build-platform:
description: "Docker-compatible representation of build platform"
runs:
using: "composite"
steps:
- name: "Compute Build Options"
shell: "bash"
run: |
set -x
SHORT_SHA="${GITHUB_SHA::8}"
BUILD_ARCH="amd64"
BUILD_PLATFORM="linux/amd64"
if [ "${RUNNER_ARCH}" = "ARM64" ];then
BUILD_ARCH="arm64"
BUILD_PLATFORM="linux/arm64"
fi
echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "build-arch=$BUILD_ARCH" >> $GITHUB_OUTPUT
echo "build-platform=$BUILD_PLATFORM" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,17 @@
name: "Get Run Info"
description: "Sanitizies and unifies the environment into run info"
inputs:
compose-project-base:
description: "Base for the Compose project"
required: true
outputs:
compose-project:
description: "Compose project name"
runs:
using: "composite"
steps:
- name: "Compute Run Info"
shell: "bash"
run: |
set -x
echo "compose-project=${{ inputs.compose-project-base }}-${GITHUB_RUN_NUMBER}" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,27 @@
name: "Get Run Info"
description: "Sanitizies and unifies the environment into run info"
inputs:
service:
description: "The service to work on"
required: true
outputs:
version:
description: "Service version"
build-number:
description: "Build number"
runs:
using: "composite"
steps:
- name: "Compute Service Version"
shell: "bash"
run: |
set -x
if [[ ! -z "${GITHUB_HEAD_REF}" || "${GITHUB_REF_NAME}" = "development" ]]; then
VERSION="${GITHUB_SHA::8}"
BUILD="${GITHUB_RUN_NUMBER}"
else
VERSION="v$(grep -Po '(?<=^version\s=\s")[^"]+' services/${{ inputs.service }}/pyproject.toml)"
BUILD="01"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "build-number=$BUILD" >> $GITHUB_OUTPUT

View File

@@ -0,0 +1,19 @@
name: "Set up Ansible"
description: "Downloads and installs Ansible"
inputs:
version:
description: "Ansible version to install"
required: false
default: "10.2.0"
runs:
using: "composite"
steps:
- name: "Install Ansible"
shell: "bash"
env:
PIP_INDEX_URL: "https://nexus.bthlabs.pl/repository/pypi/simple/"
run: |
set -x
python3 -m venv /opt/ansible
/opt/ansible/bin/pip install ansible==${{ inputs.version }}

View File

@@ -0,0 +1,32 @@
name: "Set up k8s"
description: "Downloads and installs k8s tools"
inputs:
arch:
description: "Architecture"
required: true
kubectl-version:
description: "kubectl version to install"
required: false
default: "1.33.4"
kustomize-version:
description: "kustomize version to install"
required: false
default: "5.7.1"
runs:
using: "composite"
steps:
- name: "Install k8s tools"
shell: "bash"
run: |
set -x
mkdir -p /opt/k8s/bin /opt/k8s/etc /opt/k8s/src
wget -O /opt/k8s/src/kubectl "https://nexus.bthlabs.pl/repository/ops-tools/k8s/kubectl-${{ inputs.kubectl-version }}-linux-${{ inputs.arch }}"
chmod a+x /opt/k8s/src/kubectl
mv /opt/k8s/src/kubectl /opt/k8s/bin
wget -O /opt/k8s/src/kustomize "https://nexus.bthlabs.pl/repository/ops-tools/k8s/kustomize-${{ inputs.kustomize-version }}-linux-${{ inputs.arch }}"
chmod a+x /opt/k8s/src/kustomize
mv /opt/k8s/src/kustomize /opt/k8s/bin
rm -rf /opt/k8s/src/

View File

@@ -0,0 +1,81 @@
name: "Build deployment images"
on:
workflow_call:
inputs:
target:
required: true
type: "string"
registry:
required: false
type: "string"
default: "docker-hosted.nexus.bthlabs.pl"
platform:
required: false
type: "string"
default: "linux/amd64,linux/arm64"
secrets:
VAULT_ROLE_ID:
required: true
VAULT_SECRET_ID:
required: true
jobs:
build-deployment-images:
name: "Build deployment images"
runs-on: "ubuntu-latest"
steps:
- name: "Checkout the code"
uses: "actions/checkout@v2"
- name: "Get build options"
id: "get-build-options"
uses: "./.gitea/actions/get-build-options"
- name: "Get `backend` version"
id: "get-backend-version"
uses: "./.gitea/actions/get-service-version"
with:
service: "backend"
- name: "Import Secrets"
id: "import-secrets"
uses: "hashicorp/vault-action@v2"
with:
url: "https://vault.bthlabs.pl/"
method: "approle"
roleId: "${{ secrets.VAULT_ROLE_ID }}"
secretId: "${{ secrets.VAULT_SECRET_ID }}"
secrets: |
gitea/data/${{ inputs.registry }} username | DOCKER_USERNAME ;
gitea/data/${{ inputs.registry }} password | DOCKER_PASSWORD
- name: "Set up Docker Buildx"
id: "setup-docker-buildx"
uses: "docker/setup-buildx-action@v3"
with:
driver: "remote"
endpoint: "tcp://builder-01.bthlab:2375"
platforms: "linux/amd64"
append: |
- endpoint: "tcp://builder-mac-01.bthlab:2375"
platforms: "linux/arm64"
- name: "Login to Docker Registry"
uses: "docker/login-action@v3"
with:
registry: "${{ inputs.registry }}"
username: "${{ steps.import-secrets.outputs.DOCKER_USERNAME }}"
password: "${{ steps.import-secrets.outputs.DOCKER_PASSWORD }}"
- name: "Build `backend-aio` image"
env:
SHORT_SHA: "${{ steps.get-build-options.outputs.short-sha }}"
VERSION: "${{ steps.get-backend-version.outputs.version }}"
BUILD: "${{ steps.get-backend-version.outputs.build-number }}"
run: |
set -x
docker buildx build \
--cache-from "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket" \
--cache-to "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,target=max" \
--push \
--platform "${{ inputs.platform }}" \
--build-arg IMAGE_ID="${{ inputs.target }}.${SHORT_SHA}" \
-f services/backend/Dockerfile \
--target "${{ inputs.target }}" \
-t "${{ inputs.registry }}/hotpocket/backend:${{ inputs.target }}-${VERSION}-${BUILD}" \
services/

View File

@@ -19,23 +19,12 @@ jobs:
uses: "actions/checkout@v2"
- name: "Get run info"
id: "get-run-info"
run: |
set -x
echo "COMPOSE_PROJECT=${{ vars.COMPOSE_PROJECT_BASE }}-${GITHUB_RUN_NUMBER}" >> $GITHUB_OUTPUT
uses: "./.gitea/actions/get-run-info"
with:
compose-project-base: "${{ vars.COMPOSE_PROJECT_BASE }}"
- name: "Get build options"
id: "get-build-options"
run: |
set -x
SHORT_SHA="${GITHUB_SHA::8}"
BUILD_ARCH="amd64"
BUILD_PLATFORM="linux/amd64"
if [ "${RUNNER_ARCH}" = "ARM64" ];then
BUILD_ARCH="arm64"
BUILD_PLATFORM="linux/arm64"
fi
echo "SHORT_SHA=$SHORT_SHA" >> $GITHUB_OUTPUT
echo "BUILD_ARCH=$BUILD_ARCH" >> $GITHUB_OUTPUT
echo "BUILD_PLATFORM=$BUILD_PLATFORM" >> $GITHUB_OUTPUT
uses: "./.gitea/actions/get-build-options"
- name: "Set up Docker Buildx"
id: "setup-docker-buildx"
uses: "docker/setup-buildx-action@v3"
@@ -53,8 +42,8 @@ jobs:
context: "services/"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `keycloak` image"
@@ -64,8 +53,8 @@ jobs:
context: "services/"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `rabbitmq` image"
@@ -75,8 +64,8 @@ jobs:
context: "services/"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `backend-ci` image"
@@ -87,8 +76,8 @@ jobs:
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `packages-ci` image"
@@ -99,8 +88,8 @@ jobs:
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `extension-ci` image"
@@ -111,8 +100,8 @@ jobs:
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Build `apple-ci` image"
@@ -123,21 +112,21 @@ jobs:
target: "ci"
push: false
load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
platforms: "${{ steps.get-build-options.outputs.BUILD_PLATFORM }}"
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-${{ steps.get-run-info.outputs.compose-project }}"
platforms: "${{ steps.get-build-options.outputs.build-platform }}"
cache-from: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket"
cache-to: "type=registry,ref=nexus.bthlab.bthlabs.net:8001/hotpocket,mode=max"
- name: "Prepare the build"
id: "prepare"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
./.gitea/tools/render-docker-compose-ci.sh
- name: "Run `backend` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
@@ -150,7 +139,7 @@ jobs:
- name: "Run `packages` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
@@ -163,7 +152,7 @@ jobs:
- name: "Run `extension` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
@@ -176,7 +165,7 @@ jobs:
- name: "Run `apple` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
@@ -189,7 +178,7 @@ jobs:
- name: "Clean up"
if: always()
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.COMPOSE_PROJECT }}"
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \

View File

@@ -0,0 +1,91 @@
name: "Development deployment"
on:
push:
branches:
- "development"
jobs:
build-for-development:
name: "Build"
uses: "./.gitea/workflows/build-deployment-images.yaml"
with:
target: "deployment"
platform: "linux/amd64"
registry: "nexus.bthlab.bthlabs.net:8002"
secrets:
VAULT_ROLE_ID: "${{ secrets.VAULT_ROLE_ID }}"
VAULT_SECRET_ID: "${{ secrets.VAULT_SECRET_ID }}"
deploy-to-deployment:
name: "Deploy"
runs-on: "ubuntu-latest"
needs:
- "build-for-development"
env:
KUBERNETES_NAMESPACE: "hotpocket-development"
KUBERNETES_CLUSTER: "k8s.bthlab"
steps:
- name: "Checkout the code"
uses: "actions/checkout@v2"
- name: "Get build options"
id: "get-build-options"
uses: "./.gitea/actions/get-build-options"
- name: "Get `backend` version"
id: "get-backend-version"
uses: "./.gitea/actions/get-service-version"
with:
service: "backend"
- name: "Setup k8s"
uses: "./.gitea/actions/setup-k8s"
with:
arch: "${{ steps.get-build-options.outputs.build-arch }}"
- name: "Import Secrets"
id: "import-secrets"
uses: "hashicorp/vault-action@v2"
with:
url: "https://vault.bthlabs.pl/"
method: "approle"
roleId: "${{ secrets.VAULT_ROLE_ID }}"
secretId: "${{ secrets.VAULT_SECRET_ID }}"
secrets: |
gitea/data/k8s.bthlab config | KUBECONFIG_PAYLOAD
- name: "Set up kubeconfig"
env:
KUBECONFIG_PAYLOAD: "${{ steps.import-secrets.outputs.KUBECONFIG_PAYLOAD }}"
run: |
set -x
echo ${KUBECONFIG_PAYLOAD} | base64 -d >"/opt/k8s/etc/kubeconfig"
export KUBECONFIG="/opt/k8s/etc/kubeconfig"
/opt/k8s/bin/kubectl config use-context ${KUBERNETES_CLUSTER}
/opt/k8s/bin/kubectl get node
- name: "Run `backend` Django migrations"
env:
BACKEND_TAG: "deployment-${{ steps.get-backend-version.outputs.version }}-${{ steps.get-backend-version.outputs.build-number }}"
run: |
set -x
(
cd deployment/hotpocket.bthlab ;
export KUBECONFIG="/opt/k8s/etc/kubeconfig" ;
/opt/k8s/bin/kubectl config use-context ${KUBERNETES_CLUSTER} ;
/opt/k8s/bin/kubectl -n ${KUBERNETES_NAMESPACE} apply -f resources/backend/config-map-local-deps.yaml ;
/opt/k8s/bin/kubectl -n ${KUBERNETES_NAMESPACE} set image cronjobs/backend-job-migrations migrations=nexus.bthlab.bthlabs.net:8002/hotpocket/backend:${BACKEND_TAG} ;
/opt/k8s/bin/kubectl -n ${KUBERNETES_NAMESPACE} delete jobs --ignore-not-found=true backend-job-migrations ;
/opt/k8s/bin/kubectl -n ${KUBERNETES_NAMESPACE} create job backend-job-migrations --from=cronjob/backend-job-migrations ;
/opt/k8s/bin/kubectl -n ${KUBERNETES_NAMESPACE} wait --for=condition=complete --timeout=300s job/backend-job-migrations
)
- name: "Deploy"
env:
BACKEND_TAG: "deployment-${{ steps.get-backend-version.outputs.version }}-${{ steps.get-backend-version.outputs.build-number }}"
run: |
set -x
(
cd deployment/hotpocket.bthlab ;
export KUBECONFIG="/opt/k8s/etc/kubeconfig" ;
/opt/k8s/bin/kubectl config use-context ${KUBERNETES_CLUSTER} ;
/opt/k8s/bin/kustomize edit set image hotpocket-backend=nexus.bthlab.bthlabs.net:8002/hotpocket/backend:${BACKEND_TAG} ;
/opt/k8s/bin/kustomize build . | /opt/k8s/bin/kubectl apply -f -
)

View File

@@ -0,0 +1,76 @@
name: "Production deployment"
on:
release:
types: ["published"]
jobs:
build-for-production:
name: "Build"
uses: "./.gitea/workflows/build-deployment-images.yaml"
with:
target: "deployment"
platform: "linux/amd64"
secrets:
VAULT_ROLE_ID: "${{ secrets.VAULT_ROLE_ID }}"
VAULT_SECRET_ID: "${{ secrets.VAULT_SECRET_ID }}"
deploy-to-production:
name: "Deploy"
runs-on: "ubuntu-latest"
needs:
- "build-for-production"
steps:
- name: "Checkout the code"
uses: "actions/checkout@v2"
- name: "Get build options"
id: "get-build-options"
uses: "./.gitea/actions/get-build-options"
- name: "Get `backend` version"
id: "get-backend-version"
uses: "./.gitea/actions/get-service-version"
with:
service: "backend"
- name: "Import Secrets"
id: "import-secrets"
uses: "hashicorp/vault-action@v2"
with:
url: "https://vault.bthlabs.pl/"
method: "approle"
roleId: "${{ secrets.VAULT_ROLE_ID }}"
secretId: "${{ secrets.VAULT_SECRET_ID }}"
secrets: |
gitea/data/hotpocket.app ansible_vault_payload | ANSIBLE_VAULT_PAYLOAD ;
gitea/data/hotpocket.app ansible_vault_password | ANSIBLE_VAULT_PASSWORD ;
gitea/data/hotpocket.app ansible_inventory_payload | ANSIBLE_INVENTORY_PAYLOAD ;
gitea/data/hotpocket.app ssh_key_payload | SSH_KEY_PAYLOAD
- name: "Setup Ansible"
uses: "./.gitea/actions/setup-ansible"
- name: "Prepare Ansible secrets"
run: |
set -x
mkdir deployment/hotpocket_app/.ci
echo "${ANSIBLE_VAULT_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/env_vars/production/vault.yaml"
echo "${ANSIBLE_VAULT_PASSWORD}" >"deployment/hotpocket_app/.ci/vault_password"
echo "${ANSIBLE_INVENTORY_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/inventory_ci.yaml"
echo "${SSH_KEY_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/.ci/ssh_key"
chmod 600 deployment/hotpocket_app/.ci/ssh_key
- name: "Engage!"
env:
VERSION: "${{ steps.get-backend-version.outputs.version }}"
BUILD: "${{ steps.get-backend-version.outputs.build-number }}"
run: |
set -x
(
cd deployment/hotpocket_app ;
ANSIBLE_HOST_KEY_CHECKING="False" /opt/ansible/bin/ansible-playbook \
-i inventory_ci.yaml \
--vault-id hotpocket@.ci/vault_password \
-e @env_vars/production/vars.yaml \
-e @env_vars/production/vault.yaml \
-e hotpocket_app_image_tag="deployment-${VERSION}-${BUILD}" \
--limit "*.production.hotpocket.app" \
deploy.yaml
)

View File

@@ -0,0 +1,76 @@
name: "Staging deployment"
on:
release:
types: ["published"]
jobs:
build-for-staging:
name: "Build"
uses: "./.gitea/workflows/build-deployment-images.yaml"
with:
target: "aio"
platform: "linux/amd64"
secrets:
VAULT_ROLE_ID: "${{ secrets.VAULT_ROLE_ID }}"
VAULT_SECRET_ID: "${{ secrets.VAULT_SECRET_ID }}"
deploy-to-staging:
name: "Deploy"
runs-on: "ubuntu-latest"
needs:
- "build-for-staging"
steps:
- name: "Checkout the code"
uses: "actions/checkout@v2"
- name: "Get build options"
id: "get-build-options"
uses: "./.gitea/actions/get-build-options"
- name: "Get `backend` version"
id: "get-backend-version"
uses: "./.gitea/actions/get-service-version"
with:
service: "backend"
- name: "Import Secrets"
id: "import-secrets"
uses: "hashicorp/vault-action@v2"
with:
url: "https://vault.bthlabs.pl/"
method: "approle"
roleId: "${{ secrets.VAULT_ROLE_ID }}"
secretId: "${{ secrets.VAULT_SECRET_ID }}"
secrets: |
gitea/data/staging.hotpocket.app ansible_vault_payload | ANSIBLE_VAULT_PAYLOAD ;
gitea/data/staging.hotpocket.app ansible_vault_password | ANSIBLE_VAULT_PASSWORD ;
gitea/data/staging.hotpocket.app ansible_inventory_payload | ANSIBLE_INVENTORY_PAYLOAD ;
gitea/data/staging.hotpocket.app ssh_key_payload | SSH_KEY_PAYLOAD
- name: "Setup Ansible"
uses: "./.gitea/actions/setup-ansible"
- name: "Prepare Ansible secrets"
run: |
set -x
mkdir deployment/hotpocket_app/.ci
echo "${ANSIBLE_VAULT_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/env_vars/staging/vault.yaml"
echo "${ANSIBLE_VAULT_PASSWORD}" >"deployment/hotpocket_app/.ci/vault_password"
echo "${ANSIBLE_INVENTORY_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/inventory_ci.yaml"
echo "${SSH_KEY_PAYLOAD}" | base64 -d >"deployment/hotpocket_app/.ci/ssh_key"
chmod 600 deployment/hotpocket_app/.ci/ssh_key
- name: "Engage!"
env:
VERSION: "${{ steps.get-backend-version.outputs.version }}"
BUILD: "${{ steps.get-backend-version.outputs.build-number }}"
run: |
set -x
(
cd deployment/hotpocket_app ;
ANSIBLE_HOST_KEY_CHECKING="False" /opt/ansible/bin/ansible-playbook \
-i inventory_ci.yaml \
--vault-id hotpocket@.ci/vault_password \
-e @env_vars/staging/vars.yaml \
-e @env_vars/staging/vault.yaml \
-e hotpocket_app_image_tag="aio-${VERSION}-${BUILD}" \
--limit "*.staging.hotpocket.app" \
deploy.yaml
)

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.ci/
.envrc*
.ipythonhome/
services/vendor/
/docker-compose-ci-*.yaml

View File

@@ -88,3 +88,7 @@ Copyright (c) Icons8
Licensed under terms of the MIT License
Spinner Loader CSS from https://css-loaders.com/
cosmo, sandstone, sketchy and solar Bootswatch Themes
Copyright 2012-2025 Thomas Park
Licensed under terms of the MIT License

View File

@@ -66,7 +66,7 @@ $ docker run --rm -it \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME=hotpocket \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
-p 8000:8000 \
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.10.13-01
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.11.06-01
```
The command above will set up and start the application. The SQLite file will

View File

@@ -1,6 +1,6 @@
services:
backend:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.10.13-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v25.11.06-01"
environment:
HOTPOCKET_BACKEND_SECRET_KEY: "thisisntright"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "hotpocket"

View File

@@ -8,7 +8,7 @@ x-backend-environment: &x-backend-environment
services:
webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.11.06-01"
environment:
<<: *x-backend-environment
HOTPOCKET_BACKEND_ALLOWED_HOSTS: "app.staging.hotpocket.bthlab.bthlabs.net"
@@ -21,7 +21,7 @@ services:
restart: "unless-stopped"
admin:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.11.06-01"
environment:
<<: *x-backend-environment
HOTPOCKET_BACKEND_APP: "admin"
@@ -35,7 +35,7 @@ services:
restart: "unless-stopped"
celery-worker:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.11.06-01"
command:
- "/srv/venv/bin/celery"
- "-A"
@@ -57,7 +57,7 @@ services:
restart: "unless-stopped"
celery-beat:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.10.13-01"
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:deployment-v25.11.06-01"
command:
- "/srv/venv/bin/celery"
- "-A"

View File

@@ -0,0 +1,7 @@
DJANGO_SETTINGS_MODULE=hotpocket_bthlabs.settings.admin
HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets
HOTPOCKET_BACKEND_ENV=development
HOTPOCKET_BACKEND_APP=admin
HOTPOCKET_BACKEND_SECRET_KEY=thisissecret
HOTPOCKET_BACKEND_ALLOWED_HOSTS=admin.hotpocket.bthlab.bthlabs.net

View File

@@ -0,0 +1,8 @@
HOTPOCKET_BACKEND_ENV=deployment
HOTPOCKET_BACKEND_DATABASE_NAME=hotpocket_development_backend
HOTPOCKET_BACKEND_DATABASE_USER=thisissecret
HOTPOCKET_BACKEND_DATABASE_PASSWORD=thisissecret
HOTPOCKET_BACKEND_DATABASE_HOST=databases.bthlab
HOTPOCKET_BACKEND_CELERY_BROKER_URL=thisissecret
HOTPOCKET_BACKEND_CELERY_RESULT_BACKEND=thisissecret
HOTPOCKET_BACKEND_MODEL_AUTH_IS_DISABLED=false

View File

@@ -0,0 +1,9 @@
DJANGO_SETTINGS_MODULE=hotpocket_bthlabs.settings.webapp
HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets
HOTPOCKET_BACKEND_ENV=development
HOTPOCKET_BACKEND_APP=webapp
HOTPOCKET_BACKEND_SECRET_KEY=thisissecret
HOTPOCKET_BACKEND_ALLOWED_HOSTS=app.hotpocket.bthlab.bthlabs.net
HOTPOCKET_BACKEND_SAVES_SAVE_ADAPTER=hotpocket_backend.apps.saves.adapters.postgres:PostgresSaveAdapter
HOTPOCKET_BACKEND_SAVES_ASSOCIATION_ADAPTER=hotpocket_backend.apps.saves.adapters.postgres:PostgresAssociationAdapter

View File

@@ -0,0 +1,40 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources/namespace.yaml
- resources/volumes.yaml
- resources/backend/config-map-local-deps.yaml
- resources/backend/job-migrations.yaml
- resources/backend/webapp.yaml
- resources/backend/webapp-service.yaml
- resources/backend/webapp-ingress.yaml
- resources/backend/admin.yaml
- resources/backend/admin-service.yaml
- resources/backend/admin-ingress.yaml
- resources/backend/celery-worker.yaml
- resources/backend/celery-beat.yaml
configMapGenerator:
- behavior: create
namespace: hotpocket-development
envs:
- configs/backend/base
name: backend-base-config
- behavior: create
namespace: hotpocket-development
envs:
- configs/backend/webapp
name: backend-webapp-config
- behavior: create
namespace: hotpocket-development
envs:
- configs/backend/admin
name: backend-admin-config
patches: []
images:
- name: hotpocket-backend
newName: nexus.bthlab.bthlabs.net:8002/hotpocket/backend
newTag: deployment-8e09ae51-01

View File

@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backend-admin-ingress
namespace: hotpocket-development
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: "web"
spec:
rules:
- host: admin.hotpocket.bthlab.bthlabs.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-admin-service
port:
name: http

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: backend-admin-service
namespace: hotpocket-development
spec:
type: ClusterIP
selector:
app.kubernetes.io/app: backend-admin
ports:
- name: http
protocol: TCP
port: 8000
targetPort: http

View File

@@ -0,0 +1,99 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-admin
namespace: hotpocket-development
labels:
app.kubernetes.io/app: backend-admin
spec:
minReadySeconds: 30
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/app: backend-admin
template:
metadata:
labels:
app.kubernetes.io/app: backend-admin
spec:
containers:
- name: app
image: hotpocket-backend:latest
args:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "hotpocket_backend.wsgi:application"
envFrom:
- configMapRef:
name: backend-base-config
- configMapRef:
name: backend-admin-config
env:
- name: VAULT_URL
valueFrom:
secretKeyRef:
name: backend-vault
key: url
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: role_id
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: secret_id
ports:
- containerPort: 8000
name: http
protocol: TCP
- containerPort: 8001
name: healthcheck
protocol: TCP
livenessProbe:
httpGet:
path: "/"
port: 8001
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: "/"
port: 8001
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- mountPath: /dev/shm
name: shm
- mountPath: /srv/run
name: backend-admin-srv-run
- name: backend-admin-local-deps
mountPath: "/srv/lib/requirements.txt"
subPath: "requirements.txt"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/01-install-extra-deps.sh"
subPath: "01-install-extra-deps.sh"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/99-collectstatic.sh"
subPath: "99-collectstatic.sh"
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: shm
emptyDir:
medium: Memory
- name: backend-admin-srv-run
emptyDir: {}
- name: backend-admin-local-deps
configMap:
name: "backend-local-deps"
defaultMode: 0755

View File

@@ -0,0 +1,80 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: backend-celery-beat
namespace: hotpocket-development
labels:
app.kubernetes.io/app: backend-celery-beat
spec:
minReadySeconds: 30
replicas: 1
revisionHistoryLimit: 1
selector:
matchLabels:
app.kubernetes.io/app: backend-celery-beat
template:
metadata:
labels:
app.kubernetes.io/app: backend-celery-beat
spec:
containers:
- name: app
image: hotpocket-backend:latest
args:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "beat"
- "-l"
- "INFO"
- "-s"
- "/srv/run/celery-beat-schedule"
envFrom:
- configMapRef:
name: backend-base-config
- configMapRef:
name: backend-webapp-config
env:
- name: VAULT_URL
valueFrom:
secretKeyRef:
name: backend-vault
key: url
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: role_id
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: secret_id
volumeMounts:
- mountPath: /dev/shm
name: shm
- mountPath: /srv/run
name: backend-celery-beat-srv-run
- mountPath: /srv/uploads
name: backend-celery-beat-srv-uploads
- name: backend-admin-local-deps
mountPath: "/srv/lib/requirements.txt"
subPath: "requirements.txt"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/01-install-extra-deps.sh"
subPath: "01-install-extra-deps.sh"
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: shm
emptyDir:
medium: Memory
- name: backend-celery-beat-srv-run
persistentVolumeClaim:
claimName: backend-celery-beat-run
- name: backend-celery-beat-srv-uploads
emptyDir: {}
- name: backend-admin-local-deps
configMap:
name: "backend-local-deps"
defaultMode: 0755

View File

@@ -0,0 +1,88 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-celery-worker
namespace: hotpocket-development
labels:
app.kubernetes.io/app: backend-celery-worker
spec:
minReadySeconds: 30
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/app: backend-celery-worker
template:
metadata:
labels:
app.kubernetes.io/app: backend-celery-worker
spec:
containers:
- name: app
image: hotpocket-backend:latest
args:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "worker"
- "-l"
- "INFO"
- "-Q"
- "celery,webapp"
- "-c"
- "2"
envFrom:
- configMapRef:
name: backend-base-config
- configMapRef:
name: backend-webapp-config
env:
- name: VAULT_URL
valueFrom:
secretKeyRef:
name: backend-vault
key: url
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: role_id
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: secret_id
volumeMounts:
- mountPath: /dev/shm
name: shm
- mountPath: /srv/run
name: backend-celery-worker-srv-run
- mountPath: /srv/uploads
name: backend-celery-worker-srv-uploads
- name: backend-admin-local-deps
mountPath: "/srv/lib/requirements.txt"
subPath: "requirements.txt"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/01-install-extra-deps.sh"
subPath: "01-install-extra-deps.sh"
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: shm
emptyDir:
medium: Memory
- name: backend-celery-worker-srv-run
emptyDir: {}
- name: backend-celery-worker-srv-uploads
persistentVolumeClaim:
claimName: backend-uploads
- name: backend-admin-local-deps
configMap:
name: "backend-local-deps"
defaultMode: 0755

View File

@@ -0,0 +1,18 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: backend-local-deps
namespace: hotpocket-development
data:
01-install-extra-deps.sh: |
#!/usr/bin/env bash
export PIP_INDEX_URL="https://nexus.bthlabs.pl/repository/pypi/simple/"
/srv/venv/bin/pip install -r /srv/lib/requirements.txt
99-collectstatic.sh: |
#!/usr/bin/env bash
(
cd /srv/app;
./manage.py collectstatic --no-input
)
requirements.txt: |
hotpocket_bthlabs>=25.10.28

View File

@@ -0,0 +1,75 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: backend-job-migrations
namespace: hotpocket-development
labels:
app.kubernetes.io/app: backend-job-migrations
spec:
concurrencyPolicy: "Forbid"
successfulJobsHistoryLimit: 1
failedJobsHistoryLimit: 1
startingDeadlineSeconds: 180
schedule: "* * * * *"
suspend: true
jobTemplate:
spec:
backoffLimit: 1
completions: 1
parallelism: 1
template:
spec:
containers:
- name: migrations
image: hotpocket-backend:latest
args:
- "./manage.py"
- "migrate"
envFrom:
- configMapRef:
name: backend-base-config
- configMapRef:
name: backend-webapp-config
env:
- name: VAULT_URL
valueFrom:
secretKeyRef:
name: backend-vault
key: url
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: role_id
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: secret_id
volumeMounts:
- mountPath: /dev/shm
name: shm
- mountPath: /srv/run
name: backend-webapp-srv-run
- mountPath: /srv/uploads
name: backend-webapp-srv-uploads
- name: backend-admin-local-deps
mountPath: "/srv/lib/requirements.txt"
subPath: "requirements.txt"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/01-install-extra-deps.sh"
subPath: "01-install-extra-deps.sh"
dnsPolicy: ClusterFirst
restartPolicy: Never
volumes:
- name: shm
emptyDir:
medium: Memory
- name: backend-webapp-srv-run
emptyDir: {}
- name: backend-webapp-srv-uploads
emptyDir: {}
- name: backend-admin-local-deps
configMap:
name: "backend-local-deps"
defaultMode: 0755

View File

@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backend-webapp-ingress
namespace: hotpocket-development
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: "web"
spec:
rules:
- host: app.hotpocket.bthlab.bthlabs.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend-webapp-service
port:
name: http

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: backend-webapp-service
namespace: hotpocket-development
spec:
type: ClusterIP
selector:
app.kubernetes.io/app: backend-webapp
ports:
- name: http
protocol: TCP
port: 8000
targetPort: http

View File

@@ -0,0 +1,106 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-webapp
namespace: hotpocket-development
labels:
app.kubernetes.io/app: backend-webapp
spec:
minReadySeconds: 30
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 1
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
type: RollingUpdate
selector:
matchLabels:
app.kubernetes.io/app: backend-webapp
template:
metadata:
labels:
app.kubernetes.io/app: backend-webapp
spec:
containers:
- name: app
image: hotpocket-backend:latest
args:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "hotpocket_backend.wsgi:application"
envFrom:
- configMapRef:
name: backend-base-config
- configMapRef:
name: backend-webapp-config
env:
- name: VAULT_URL
valueFrom:
secretKeyRef:
name: backend-vault
key: url
- name: VAULT_ROLE_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: role_id
- name: VAULT_SECRET_ID
valueFrom:
secretKeyRef:
name: backend-vault
key: secret_id
- name: HOTPOCKET_BACKEND_CREATE_INITIAL_ACCOUNT
value: "true"
ports:
- containerPort: 8000
name: http
protocol: TCP
- containerPort: 8001
name: healthcheck
protocol: TCP
livenessProbe:
httpGet:
path: "/"
port: 8001
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: "/"
port: 8001
initialDelaySeconds: 2
periodSeconds: 5
volumeMounts:
- mountPath: /dev/shm
name: shm
- mountPath: /srv/run
name: backend-webapp-srv-run
- mountPath: /srv/uploads
name: backend-webapp-srv-uploads
- name: backend-admin-local-deps
mountPath: "/srv/lib/requirements.txt"
subPath: "requirements.txt"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/01-install-extra-deps.sh"
subPath: "01-install-extra-deps.sh"
- name: backend-admin-local-deps
mountPath: "/srv/etc/entrypoint.d/99-collectstatic.sh"
subPath: "99-collectstatic.sh"
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: shm
emptyDir:
medium: Memory
- name: backend-webapp-srv-run
emptyDir: {}
- name: backend-webapp-srv-uploads
persistentVolumeClaim:
claimName: backend-uploads
- name: backend-admin-local-deps
configMap:
name: "backend-local-deps"
defaultMode: 0755

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: hotpocket-development

View File

@@ -0,0 +1,26 @@
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backend-uploads
namespace: hotpocket-development
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: "1Gi"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backend-celery-beat-run
namespace: hotpocket-development
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: "1Gi"

3
deployment/hotpocket_app/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.ci/
inventory_ci.yaml
vault.yaml

View File

@@ -0,0 +1,5 @@
- name: "Deploy HotPocket"
hosts: "hotpocket_app"
roles:
- role: "hotpocket_app"
tags: ["hotpocket-app"]

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
export PIP_INDEX_URL="https://nexus.bthlabs.pl/repository/pypi/simple/"
/srv/venv/bin/pip install -r /srv/lib/backend/requirements.txt

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
(
cd /srv/app;
./manage.py collectstatic --no-input
)

View File

@@ -0,0 +1 @@
hotpocket-bthlabs>=25.10.28

View File

@@ -0,0 +1,60 @@
hotpocket_app:
deployment_directory: "/srv/hotpocket"
owner: "hotpocket"
group: "hotpocket"
mode: "fullstack"
loki:
url: "http://monitoring.vm.snakeweb.net.bthlabs.net:3100/loki/api/v1/push"
node: "home.vm.snakeweb.net"
docker:
extra_hosts:
- "home.vm:10.0.1.2"
backend:
image_tag: "{{ hotpocket_app_image_tag|default('deployment-v25.10.21-01') }}"
database:
name: "thisissecret"
user: "thisissecret"
host: "thisissecret"
rabbitmq:
vhost: "thisissecret"
user: "thisissecret"
host: "thisissecret"
model_auth_is_disabled: true
env: "production"
extra_env:
- "HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets"
- "VAULT_URL={{ hotpocket_app_secrets.backend.vault.url }}"
- "VAULT_ROLE_ID={{ hotpocket_app_secrets.backend.vault.role_id }}"
- "VAULT_SECRET_ID={{ hotpocket_app_secrets.backend.vault.secret_id }}"
oidc:
enabled: true
endpoint: "thisissecret"
display_name: "thisissecret"
webapp:
settings_module: "hotpocket_bthlabs.settings.webapp"
loki:
external_labels: "job=hotpocket,service=backend-webapp,environment=production"
allowed_hosts:
- "my.hotpocket.app"
admin:
settings_module: "hotpocket_bthlabs.settings.admin"
loki:
external_labels: "job=hotpocket,service=backend-admin,environment=production"
allowed_hosts:
- "admin.hotpocket.app"
celery_worker:
concurrency: 2
loki:
external_labels: "job=hotpocket,service=backend-celery-worker,environment=production"
celery_beat:
loki:
external_labels: "job=hotpocket,service=backend-celery-beat,environment=production"
customization:
- src: "{{ inventory_dir }}/env_vars/production/etc/backend/entrypoint.d/01-install-customized-deps.sh"
dest: "etc/backend/entrypoint.d/01-install-customized-deps.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/production/etc/backend/entrypoint.d/99-collectstatic.sh"
dest: "etc/backend/entrypoint.d/99-collectstatic.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/production/lib/backend/requirements.txt"
dest: "lib/backend/requirements.txt"

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
export PIP_INDEX_URL="https://nexus.bthlabs.pl/repository/pypi/simple/"
/srv/venv/bin/pip install -r /srv/lib/backend/requirements.txt

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
(
cd /srv/app;
./manage.py collectstatic --no-input
)

View File

@@ -0,0 +1 @@
hotpocket-bthlabs>=25.10.28

View File

@@ -0,0 +1,37 @@
hotpocket_app:
deployment_directory: "/srv/hotpocket_staging"
owner: "hotpocket_staging"
group: "hotpocket_staging"
mode: "aio"
loki:
url: "http://monitoring.vm.snakeweb.net.bthlabs.net:3100/loki/api/v1/push"
node: "home.vm.snakeweb.net"
docker:
extra_hosts:
- "home.vm:10.0.1.2"
backend:
image_tag: "{{ hotpocket_app_image_tag|default('aio-v25.10.29-rc1-01') }}"
model_auth_is_disabled: false
env: "staging"
extra_env:
- "HOTPOCKET_BACKEND_SECRETS_PACKAGE=hotpocket_bthlabs.secrets"
- "VAULT_URL={{ hotpocket_app_secrets.backend.vault.url }}"
- "VAULT_ROLE_ID={{ hotpocket_app_secrets.backend.vault.role_id }}"
- "VAULT_SECRET_ID={{ hotpocket_app_secrets.backend.vault.secret_id }}"
oidc:
enabled: false
webapp:
settings_module: "hotpocket_bthlabs.settings.webapp"
loki:
external_labels: "job=hotpocket,service=backend-webapp,environment=staging"
allowed_hosts:
- "staging.hotpocket.app"
customization:
- src: "{{ inventory_dir }}/env_vars/staging/etc/backend/entrypoint.d/01-install-customized-deps.sh"
dest: "etc/backend/entrypoint.d/01-install-customized-deps.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/staging/etc/backend/entrypoint.d/99-collectstatic.sh"
dest: "etc/backend/entrypoint.d/99-collectstatic.sh"
mode: "755"
- src: "{{ inventory_dir }}/env_vars/staging/lib/backend/requirements.txt"
dest: "lib/backend/requirements.txt"

View File

@@ -0,0 +1,10 @@
hotpocket_app:
hosts:
web1.staging.hotpocket.app:
ansible_host: vm-125.homelab01.bthlab
ansible_port: 22
ansible_user: hotpocket_staging
web1.production.hotpocket.app:
ansible_host: vm-125.homelab01.bthlab
ansible_port: 22
ansible_user: hotpocket

View File

@@ -0,0 +1,73 @@
- name: "Create workspace directories"
ansible.builtin.file:
path: "{{ hotpocket_app.deployment_directory }}/{{ item }}"
state: "directory"
loop:
- "etc"
- "etc/backend"
- "etc/backend/entrypoint.d"
- "lib"
- "lib/backend"
- "log"
- "run"
- "run/backend-admin"
- "run/backend-celery-beat"
- "run/backend-celery-worker"
- "run/backend-webapp"
- "run/uploads"
- name: "Install docker-compose.yml"
ansible.builtin.template:
src: "templates/{{ hotpocket_app.mode }}/docker-compose.yaml.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/docker-compose.yaml"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
- name: "Install env files"
ansible.builtin.template:
src: "templates/{{ hotpocket_app.mode }}/{{ item }}.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/etc/{{ item }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
loop: "{{ hotpocket_app_role.env_files[hotpocket_app.mode] }}"
- name: "Upload customization files"
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ hotpocket_app.deployment_directory }}/{{ item.dest }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
mode: "{{ item.mode|default('644') }}"
loop: "{{ hotpocket_app.customization }}"
when: "hotpocket_app.customization is defined"
- name: "Install hotpocket_app.service unit"
ansible.builtin.template:
src: "templates/{{ hotpocket_app_role.services[hotpocket_app.mode].src }}.jinja2"
dest: "{{ hotpocket_app.deployment_directory }}/etc/{{ hotpocket_app_role.services[hotpocket_app.mode].dest }}"
owner: "{{ hotpocket_app.owner }}"
group: "{{ hotpocket_app.group }}"
- name: "Stop the stack"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "down"
chdir: "{{ hotpocket_app.deployment_directory }}"
- name: "Run backend migrations"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "run"
- "--rm"
- "backend-webapp"
- "./manage.py"
- "migrate"
chdir: "{{ hotpocket_app.deployment_directory }}"
when: "hotpocket_app.mode == 'fullstack' and is_manual_run is not defined"
- name: "Start the stack"
ansible.builtin.command:
argv:
- "docker"
- "compose"
- "up"
- "-d"
chdir: "{{ hotpocket_app.deployment_directory }}"
when: "is_manual_run is not defined"

View File

@@ -0,0 +1,9 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.webapp.settings_module|default('hotpocket_backend.settings.aio')}}"
HOTPOCKET_BACKEND_ENV="{{ hotpocket_app.backend.env|default('aio') }}"
HOTPOCKET_BACKEND_MODEL_AUTH_IS_DISABLED="{% if hotpocket_app.backend.model_auth_is_disabled %}true{% else %}false{% endif %}"
{% if hotpocket_app.backend.oidc.enabled %}HOTPOCKET_BACKEND_OIDC_PAYLOAD='{"endpoint":"{{ hotpocket_app.backend.oidc.endpoint }}","key":"{{ hotpocket_app_secrets.backend.oidc.key }}","secret":"{{ hotpocket_app_secrets.backend.oidc.secret }}","display_name":"{{ hotpocket_app.backend.oidc.display_name }}"}'{% else %}#noop{% endif %}
{% for extra_env in hotpocket_app.backend.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,7 @@
HOTPOCKET_BACKEND_SECRET_KEY: "{{ hotpocket_app_secrets.backend.webapp.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.webapp.allowed_hosts|join(',') }}"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME: "{{ hotpocket_app_secrets.backend.webapp.initial_account.username }}"
HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD: "{{ hotpocket_app_secrets.backend.webapp.initial_account.password }}"
{% for extra_env in hotpocket_app.backend.webapp.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,28 @@
services:
backend-webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.webapp.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts|default([]) %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-webapp:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"

View File

@@ -0,0 +1,8 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.admin.settings_module|default('hotpocket_backend.settings.deployment.admin')}}"
HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
HOTPOCKET_BACKEND_APP="admin"
HOTPOCKET_BACKEND_SECRET_KEY="{{ hotpocket_app_secrets.backend.admin.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.admin.allowed_hosts|join(',') }}"
{% for extra_env in hotpocket_app.backend.admin.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,15 @@
HOTPOCKET_BACKEND_ENV="{{ hotpocket_app.backend.env|default('deployment') }}"
HOTPOCKET_BACKEND_DATABASE_NAME="{{ hotpocket_app.backend.database.name }}"
HOTPOCKET_BACKEND_DATABASE_USER="{{ hotpocket_app.backend.database.user }}"
HOTPOCKET_BACKEND_DATABASE_PASSWORD="{{ hotpocket_app_secrets.backend.database.password }}"
HOTPOCKET_BACKEND_DATABASE_HOST="{{ hotpocket_app.backend.database.host }}"
HOTPOCKET_BACKEND_CELERY_BROKER_URL="amqp://{{ hotpocket_app.backend.rabbitmq.user }}:{{ hotpocket_app_secrets.backend.rabbitmq.password }}@{{ hotpocket_app.backend.rabbitmq.host }}/{{ hotpocket_app.backend.rabbitmq.vhost }}"
HOTPOCKET_BACKEND_CELERY_RESULT_BACKEND="db+postgresql+psycopg://{{ hotpocket_app.backend.database.user }}:{{ hotpocket_app_secrets.backend.database.password }}@{{ hotpocket_app.backend.database.host }}/{{ hotpocket_app.backend.database.name }}"
HOTPOCKET_BACKEND_MODEL_AUTH_IS_DISABLED="{% if hotpocket_app.backend.model_auth_is_disabled %}true{% else %}false{% endif %}"
{% if hotpocket_app.backend.oidc.enabled %}HOTPOCKET_BACKEND_OIDC_PAYLOAD='{"endpoint":"{{ hotpocket_app.backend.oidc.endpoint }}","key":"{{ hotpocket_app_secrets.backend.oidc.key }}","secret":"{{ hotpocket_app_secrets.backend.oidc.secret }}","display_name":"{{ hotpocket_app.backend.oidc.display_name }}"}'{% else %}#noop{% endif %}
{% for extra_env in hotpocket_app.backend.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,9 @@
DJANGO_SETTINGS_MODULE="{{ hotpocket_app.backend.webapp.settings_module|default('hotpocket_backend.settings.deployment.webapp')}}"
HOTPOCKET_BACKEND_APP="webapp"
HOTPOCKET_BACKEND_SECRET_KEY="{{ hotpocket_app_secrets.backend.webapp.secret_key }}"
HOTPOCKET_BACKEND_ALLOWED_HOSTS="{{ hotpocket_app.backend.webapp.allowed_hosts|join(',') }}"
HOTPOCKET_BACKEND_SAVES_SAVE_ADAPTER="hotpocket_backend.apps.saves.adapters.postgres:PostgresSaveAdapter"
HOTPOCKET_BACKEND_SAVES_ASSOCIATION_ADAPTER="hotpocket_backend.apps.saves.adapters.postgres:PostgresAssociationAdapter"
{% for extra_env in hotpocket_app.backend.webapp.extra_env|default([]) %}
{{ extra_env }}
{% endfor %}

View File

@@ -0,0 +1,118 @@
services:
backend-webapp:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.webapp.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-webapp:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-admin:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/gunicorn"
- "-c"
- "/srv/lib/gunicorn.conf.py"
- "-b"
- "unix:///srv/run/gunicorn.sock"
- "hotpocket_backend.wsgi:application"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.admin.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_admin.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-admin:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-celery-worker:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "worker"
- "-l"
- "INFO"
- "-Q"
- "celery,webapp"
- "-c"
- "{{ hotpocket_app.backend.celery_worker.concurrency }}"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.celery_worker.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-celery-worker:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"
backend-celery-beat:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:{{ hotpocket_app.backend.image_tag }}"
command:
- "/srv/venv/bin/celery"
- "-A"
- "hotpocket_backend.celery:app"
- "beat"
- "-l"
- "INFO"
- "-s"
- "/srv/run/celery-beat-schedule"
logging:
driver: "loki"
options:
loki-url: "{{ hotpocket_app.loki.url }}"
loki-external-labels: "{{ hotpocket_app.backend.celery_beat.loki.external_labels }}"
labels: "node"
labels:
node: "{{ hotpocket_app.loki.node }}"
env_file:
- "etc/backend_base.env"
- "etc/backend_webapp.env"
extra_hosts: [{% for extra_host in hotpocket_app.docker.extra_hosts %}"{{ extra_host }}"{% endfor %}]
restart: "unless-stopped"
volumes:
- "{{ hotpocket_app.deployment_directory }}/etc/backend:/srv/etc"
- "{{ hotpocket_app.deployment_directory }}/lib/backend:/srv/lib/backend"
- "{{ hotpocket_app.deployment_directory }}/run/backend-celery-beat:/srv/run"
- "{{ hotpocket_app.deployment_directory }}/run/uploads:/srv/uploads"

View File

@@ -0,0 +1,15 @@
[Unit]
Description=hotpocket_backend.webapp
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory={{ hotpocket_app.deployment_directory }}
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,16 @@
hotpocket_app_role:
env_files:
fullstack:
- "backend_admin.env"
- "backend_base.env"
- "backend_webapp.env"
aio:
- "backend_base.env"
- "backend_webapp.env"
services:
fullstack:
src: "hotpocket_app.service"
dest: "hotpocket_app.service"
aio:
src: "hotpocket_app.service"
dest: "staging_hotpocket_app.service"

16
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "hotpocket-workspace-tools"
@@ -6,11 +6,12 @@ version = "1.0.0.dev0"
description = "HotPocket Workspace Tools"
optional = false
python-versions = "^3.12"
groups = ["main"]
files = []
develop = true
[package.dependencies]
invoke = "2.2.0"
invoke = "2.2.1"
[package.source]
type = "directory"
@@ -18,16 +19,17 @@ url = "services/packages/workspace_tools"
[[package]]
name = "invoke"
version = "2.2.0"
version = "2.2.1"
description = "Pythonic task execution"
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"},
{file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"},
{file = "invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"},
{file = "invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"},
]
[metadata]
lock-version = "2.0"
lock-version = "2.1"
python-versions = "^3.12"
content-hash = "ec33c3b3ec0f988e333872bdd134c1adce0782e98512dd2484cb85009b3da6cb"
content-hash = "a7028d4a0260c82012077d9cc4b324b0ef5ab8ed24aa283a51cf941ba09685a9"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "hotpocket-workspace"
version = "25.10.13"
version = "25.11.06"
description = "HotPocket Workspace"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0"
@@ -9,7 +9,6 @@ package-mode = false
[tool.poetry.dependencies]
python = "^3.12"
hotpocket-workspace-tools = {path = "services/packages/workspace_tools", develop = true}
invoke = "2.2.0"
[build-system]
requires = ["poetry-core"]

View File

@@ -13,4 +13,5 @@ backend/hotpocket_backend/settings/metal/
backend/hotpocket_backend/static/
extension/node_modules/
extension/dist/
vendor/
.envrc*

View File

@@ -2,7 +2,7 @@ ARG APP_USER_UID=1000
ARG APP_USER_GID=1000
ARG IMAGE_ID=development.00000000
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-node-20250819-01 AS development
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-node-20251014-01 AS development
ARG APP_USER_UID
ARG APP_USER_GID

View File

@@ -713,7 +713,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
@@ -726,7 +726,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
SDKROOT = iphoneos;
@@ -746,7 +746,7 @@
buildSettings = {
CODE_SIGN_ENTITLEMENTS = "iOS (Share Extension)/iOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Share Extension)/Info.plist";
@@ -759,7 +759,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
SDKROOT = iphoneos;
@@ -779,7 +779,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
@@ -792,7 +792,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -814,7 +814,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (Extension)/Info.plist";
@@ -827,7 +827,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -853,7 +853,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
@@ -873,7 +873,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -899,7 +899,7 @@
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "iOS (App)/HotPocket (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "iOS (App)/Info.plist";
@@ -919,7 +919,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -945,7 +945,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -960,7 +960,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -980,7 +980,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (Extension)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
@@ -995,7 +995,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -1017,7 +1017,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -1033,7 +1033,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -1056,7 +1056,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (App)/HotPocket.entitlements";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -1072,7 +1072,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
OTHER_LDFLAGS = (
"-framework",
SafariServices,
@@ -1206,7 +1206,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -1220,7 +1220,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
REGISTER_APP_GROUPS = YES;
@@ -1236,7 +1236,7 @@
CODE_SIGN_ENTITLEMENTS = "macOS (Share Extension)/macOS (Share Extension).entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2025101302;
CURRENT_PROJECT_VERSION = 2025110601;
DEVELOPMENT_TEAM = 648728X64K;
ENABLE_HARDENED_RUNTIME = YES;
GENERATE_INFOPLIST_FILE = YES;
@@ -1250,7 +1250,7 @@
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 25.10.13;
MARKETING_VERSION = 25.11.06;
PRODUCT_BUNDLE_IDENTIFIER = pl.bthlabs.HotPocket.ShareExtension;
PRODUCT_NAME = "Save to HotPocket";
REGISTER_APP_GROUPS = YES;

View File

@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "icon-1024.png",
"filename" : "icon-ios-light.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
@@ -13,7 +13,7 @@
"value" : "dark"
}
],
"filename" : "icon-1024 1.png",
"filename" : "icon-ios-dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
@@ -25,31 +25,31 @@
"value" : "tinted"
}
],
"filename" : "icon-1024 2.png",
"filename" : "icon-ios-tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "icon-16.png",
"filename" : "icon-mac-16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon-32.png",
"filename" : "icon-mac-32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon-32 1.png",
"filename" : "icon-mac-32 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon-64.png",
"filename" : "icon-mac-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "asttokens"
@@ -6,6 +6,7 @@ version = "3.0.0"
description = "Annotate AST trees with source code positions"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
@@ -21,6 +22,8 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["dev"]
markers = "sys_platform == \"win32\""
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -32,6 +35,7 @@ version = "5.2.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
@@ -43,13 +47,14 @@ version = "2.2.1"
description = "Get the currently executing AST node of a frame, and other information"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"},
{file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"},
]
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""]
[[package]]
name = "factory-boy"
@@ -57,6 +62,7 @@ version = "3.3.3"
description = "A versatile test fixtures replacement based on thoughtbot's factory_bot for Ruby."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "factory_boy-3.3.3-py2.py3-none-any.whl", hash = "sha256:1c39e3289f7e667c4285433f305f8d506efc2fe9c73aaea4151ebd5cdea394fc"},
{file = "factory_boy-3.3.3.tar.gz", hash = "sha256:866862d226128dfac7f2b4160287e899daf54f2612778327dd03d0e2cb1e3d03"},
@@ -71,13 +77,14 @@ doc = ["Sphinx", "sphinx-rtd-theme", "sphinxcontrib-spelling"]
[[package]]
name = "faker"
version = "37.6.0"
version = "37.11.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "faker-37.6.0-py3-none-any.whl", hash = "sha256:3c5209b23d7049d596a51db5d76403a0ccfea6fc294ffa2ecfef6a8843b1e6a7"},
{file = "faker-37.6.0.tar.gz", hash = "sha256:0f8cc34f30095184adf87c3c24c45b38b33ad81c35ef6eb0a3118f301143012c"},
{file = "faker-37.11.0-py3-none-any.whl", hash = "sha256:1508d2da94dfd1e0087b36f386126d84f8583b3de19ac18e392a2831a6676c57"},
{file = "faker-37.11.0.tar.gz", hash = "sha256:22969803849ba0618be8eee2dd01d0d9e2cd3b75e6ff1a291fa9abcdb34da5e6"},
]
[package.dependencies]
@@ -89,6 +96,7 @@ version = "7.3.0"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e"},
{file = "flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872"},
@@ -105,6 +113,7 @@ version = "4.0.0"
description = "Flake8 lint for trailing commas."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "flake8_commas-4.0.0-py3-none-any.whl", hash = "sha256:cad476d71ba72e8b941a8508d5b9ffb6b03e50f7102982474f085ad0d674b685"},
{file = "flake8_commas-4.0.0.tar.gz", hash = "sha256:a68834b42a9a31c94ca790efe557a932c0eae21a3479c6b9a23c4dc077e3ea96"},
@@ -119,11 +128,12 @@ version = "1.0.0.dev0"
description = "HotPocket Workspace Tools"
optional = false
python-versions = "^3.12"
groups = ["dev"]
files = []
develop = true
[package.dependencies]
invoke = "2.2.0"
invoke = "2.2.1"
[package.source]
type = "directory"
@@ -131,13 +141,14 @@ url = "../packages/workspace_tools"
[[package]]
name = "invoke"
version = "2.2.0"
version = "2.2.1"
description = "Pythonic task execution"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"},
{file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"},
{file = "invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"},
{file = "invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"},
]
[[package]]
@@ -146,6 +157,7 @@ version = "0.13.13"
description = "IPython-enabled pdb"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
groups = ["dev"]
files = [
{file = "ipdb-0.13.13-py3-none-any.whl", hash = "sha256:45529994741c4ab6d2388bfa5d7b725c2cf7fe9deffabdb8a6113aa5ed449ed4"},
{file = "ipdb-0.13.13.tar.gz", hash = "sha256:e3ac6018ef05126d442af680aad863006ec19d02290561ac88b8b1c0b0cfc726"},
@@ -157,13 +169,14 @@ ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""}
[[package]]
name = "ipython"
version = "9.3.0"
version = "9.6.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.11"
groups = ["dev"]
files = [
{file = "ipython-9.3.0-py3-none-any.whl", hash = "sha256:1a0b6dd9221a1f5dddf725b57ac0cb6fddc7b5f470576231ae9162b9b3455a04"},
{file = "ipython-9.3.0.tar.gz", hash = "sha256:79eb896f9f23f50ad16c3bc205f686f6e030ad246cc309c6279a242b14afe9d8"},
{file = "ipython-9.6.0-py3-none-any.whl", hash = "sha256:5f77efafc886d2f023442479b8149e7d86547ad0a979e9da9f045d252f648196"},
{file = "ipython-9.6.0.tar.gz", hash = "sha256:5603d6d5d356378be5043e69441a072b50a5b33b4503428c77b04cb8ce7bc731"},
]
[package.dependencies]
@@ -181,10 +194,10 @@ traitlets = ">=5.13.0"
[package.extras]
all = ["ipython[doc,matplotlib,test,test-extra]"]
black = ["black"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"]
matplotlib = ["matplotlib"]
test = ["packaging", "pytest", "pytest-asyncio (<0.22)", "testpath"]
test-extra = ["curio", "ipykernel", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbclient", "nbformat", "numpy (>=1.23)", "pandas", "trio"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[matplotlib,test]", "setuptools (>=61.2)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinx_toml (==0.0.4)", "typing_extensions"]
matplotlib = ["matplotlib (>3.7)"]
test = ["packaging", "pytest", "pytest-asyncio", "testpath"]
test-extra = ["curio", "ipykernel", "ipython[matplotlib]", "ipython[test]", "jupyter_ai", "nbclient", "nbformat", "numpy (>=1.25)", "pandas (>2.0)", "trio"]
[[package]]
name = "ipython-pygments-lexers"
@@ -192,6 +205,7 @@ version = "1.1.1"
description = "Defines a variety of Pygments lexers for highlighting IPython code."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"},
{file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"},
@@ -202,13 +216,14 @@ pygments = "*"
[[package]]
name = "isort"
version = "6.0.1"
version = "7.0.0"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.9.0"
python-versions = ">=3.10.0"
groups = ["dev"]
files = [
{file = "isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615"},
{file = "isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450"},
{file = "isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1"},
{file = "isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187"},
]
[package.extras]
@@ -221,6 +236,7 @@ version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
@@ -240,6 +256,7 @@ version = "0.1.7"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
@@ -254,6 +271,7 @@ version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
@@ -261,43 +279,50 @@ files = [
[[package]]
name = "mypy"
version = "1.16.1"
version = "1.18.2"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"},
{file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"},
{file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"},
{file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"},
{file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"},
{file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"},
{file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"},
{file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"},
{file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"},
{file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"},
{file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"},
{file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"},
{file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"},
{file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"},
{file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"},
{file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"},
{file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"},
{file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"},
{file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"},
{file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"},
{file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"},
{file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"},
{file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"},
{file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"},
{file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"},
{file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"},
{file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"},
{file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"},
{file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"},
{file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"},
{file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"},
{file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"},
{file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"},
{file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"},
{file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"},
{file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"},
{file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"},
{file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"},
{file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"},
{file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"},
{file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"},
{file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"},
{file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"},
{file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"},
{file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"},
{file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"},
{file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"},
{file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"},
{file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"},
{file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"},
{file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"},
{file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"},
{file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"},
{file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"},
{file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"},
{file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"},
{file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"},
{file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"},
{file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"},
{file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"},
{file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"},
{file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"},
{file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"},
{file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"},
{file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"},
]
[package.dependencies]
@@ -318,6 +343,7 @@ version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
@@ -329,6 +355,7 @@ version = "0.8.5"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"},
{file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"},
@@ -344,6 +371,7 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@@ -355,6 +383,8 @@ version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications."
optional = false
python-versions = "*"
groups = ["dev"]
markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
@@ -369,6 +399,7 @@ version = "3.0.52"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"},
{file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"},
@@ -383,6 +414,8 @@ version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
groups = ["dev"]
markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
@@ -394,6 +427,7 @@ version = "0.2.3"
description = "Safely evaluate AST nodes without side effects"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
@@ -408,6 +442,7 @@ version = "2.14.0"
description = "Python style guide checker"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d"},
{file = "pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783"},
@@ -419,6 +454,7 @@ version = "3.4.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f"},
{file = "pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58"},
@@ -430,6 +466,7 @@ version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
@@ -444,6 +481,7 @@ version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false
python-versions = "*"
groups = ["dev"]
files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
@@ -463,6 +501,7 @@ version = "5.14.3"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
@@ -478,6 +517,7 @@ version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
@@ -489,6 +529,7 @@ version = "2025.2"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
groups = ["dev"]
files = [
{file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
{file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
@@ -496,16 +537,17 @@ files = [
[[package]]
name = "wcwidth"
version = "0.2.13"
version = "0.2.14"
description = "Measures the displayed width of unicode strings in a terminal"
optional = false
python-versions = "*"
python-versions = ">=3.6"
groups = ["dev"]
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
{file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"},
{file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"},
]
[metadata]
lock-version = "2.0"
lock-version = "2.1"
python-versions = "^3.12"
content-hash = "1c74b6d5928a0b1bde41962f4233af2eba2242324c3155b21093b2f46baf5cd0"
content-hash = "b03ef0369277d183d033049206a4cfd5f450473179995a4a79165fbb84d81fa0"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "hotpocket-apple"
version = "25.10.13"
version = "25.11.06"
description = "HotPocket Apple Integrations"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0"
@@ -15,11 +15,10 @@ factory-boy = "3.3.3"
flake8 = "7.3.0"
flake8-commas = "4.0.0"
hotpocket-workspace-tools = {path = "../packages/workspace_tools", develop = true}
invoke = "2.2.0"
ipdb = "0.13.13"
ipython = "9.3.0"
isort = "6.0.1"
mypy = "1.16.1"
ipython = "9.6.0"
isort = "7.0.0"
mypy = "1.18.2"
[build-system]
requires = ["poetry-core"]

View File

@@ -2,7 +2,7 @@ ARG APP_USER_UID=1000
ARG APP_USER_GID=1000
ARG IMAGE_ID=development.00000000
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-node-20250819-01 AS development
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-node-20251014-01 AS development
ARG APP_USER_UID
ARG APP_USER_GID
@@ -12,7 +12,7 @@ COPY --chown=$APP_USER_UID:$APP_USER_GID backend/ops/bin/*.sh /srv/bin/
VOLUME ["/srv/node_modules", "/srv/venv"]
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-python-20250819-01 AS deployment-build
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:build-python-20251014-01 AS deployment-build
ARG APP_USER_UID
ARG APP_USER_GID
@@ -31,7 +31,7 @@ RUN poetry install --only main,deployment && \
rm -f hotpocket_backend/settings/deployment/build.py && \
rm -rf node_modules/
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:base-20250819-01 AS deployment-base
FROM docker-hosted.nexus.bthlabs.pl/hotpocket/base:base-20251014-01 AS deployment-base
ARG APP_USER_UID
ARG APP_USER_GID
@@ -45,11 +45,12 @@ COPY --from=deployment-build /srv/packages /srv/packages
COPY --from=deployment-build /srv/venv /srv/venv
COPY --chown=$APP_USER_UID:$APP_USER_GID backend/ops/bin/*.sh /srv/bin/
COPY --chown=$APP_USER_UID:$APP_USER_GID backend/ops/deployment/gunicorn.conf.py backend/ops/deployment/gunicorn.logging.conf /srv/lib/
COPY --chown=root:root backend/ops/etc/sudoers_app /etc/sudoers.d/app
USER root
RUN apt-get update && \
apt-get install -y libpq5 dumb-init && \
apt-get install -y libpq5 dumb-init sudo && \
apt-get clean autoclean && \
apt-get autoremove --yes && \
rm -rf /var/lib/apt /var/lib/dpkg && \
@@ -67,6 +68,8 @@ ARG APP_USER_GID
ARG IMAGE_ID
ENV DJANGO_SETTINGS_MODULE=hotpocket_backend.settings.deployment.webapp
ENV HOTPOCKET_BACKEND_APP_USER_UID=${APP_USER_UID}
ENV HOTPOCKET_BACKEND_APP_USER_GID=${APP_USER_GID}
ENV HOTPOCKET_BACKEND_ENV=deployment
ENV HOTPOCKET_BACKEND_APP=webapp
@@ -79,6 +82,8 @@ ARG APP_USER_GID
ARG IMAGE_ID
ENV DJANGO_SETTINGS_MODULE=hotpocket_backend.settings.aio
ENV HOTPOCKET_BACKEND_APP_USER_UID=${APP_USER_UID}
ENV HOTPOCKET_BACKEND_APP_USER_GID=${APP_USER_GID}
ENV HOTPOCKET_BACKEND_ENV=aio
ENV HOTPOCKET_BACKEND_APP=webapp
ENV HOTPOCKET_BACKEND_DEBUG=false
@@ -92,6 +97,7 @@ ENV HOTPOCKET_BACKEND_CELERY_IGNORE_RESULT=true
ENV HOTPOCKET_BACKEND_CELERY_ALWAYS_EAGER=true
ENV HOTPOCKET_BACKEND_GUNICORN_WORKERS=2
ENV HOTPOCKET_BACKEND_RUN_MIGRATIONS=true
ENV HOTPOCKET_BACKEND_CREATE_INITIAL_ACCOUNT=true
ENV HOTPOCKET_BACKEND_UPLOADS_PATH=/srv/run/uploads
VOLUME ["/srv/run"]

View File

@@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
version = '25.10.13'
version = '25.11.06'

View File

@@ -8,6 +8,7 @@ from django.core.management import BaseCommand
import django.db
from hotpocket_backend.apps.accounts.models import Account
from hotpocket_backend.apps.core.conf import settings
LOGGER = logging.getLogger(__name__)
@@ -17,12 +18,12 @@ class Command(BaseCommand):
def add_arguments(self, parser: ArgumentParser):
parser.add_argument(
'username',
help='Username for the Account',
'-u', '--username', default=None,
help='Override username for the Account',
)
parser.add_argument(
'password',
help='Password for the Account',
'-p', '--password', default=None,
help='Override Password for the Account',
)
parser.add_argument(
'-d', '--dry-run', action='store_true', default=False,
@@ -31,10 +32,22 @@ class Command(BaseCommand):
def handle(self, *args, **options):
LOGGER.debug('args=`%s` options=`%s`', args, options)
username = options.get('username')
password = options.get('password')
dry_run = options.get('dry_run', False)
username = options.get('username') or settings.SECRETS.INITIAL_ACCOUNT.username
if not username:
LOGGER.info('Not creating initial Account: empty `username`')
return
password = options.get('password') or settings.SECRETS.INITIAL_ACCOUNT.password
assert password, 'Unable to proceed: empty `password`'
LOGGER.debug(
'Creating initial Account: username=`%s` password=`%s`',
username,
password,
)
with django.db.transaction.atomic():
current_account = Account.objects.filter(username=username).first()
if current_account is not None:

View File

@@ -1,12 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import logging
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
from hotpocket_backend.apps.core.conf import settings
LOGGER = logging.getLogger(__name__)
class CoreConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
label = 'core'
name = 'hotpocket_backend.apps.core'
verbose_name = _('Core')
def ready(self):
LOGGER.info(
'HotPocket Backend ready: env=`%s` app=`%s`',
settings.ENV.name,
settings.APP.name,
)

View File

@@ -6,7 +6,7 @@ import typing
from hotpocket_backend.secrets.admin import AdminSecrets
from hotpocket_backend.secrets.webapp import WebAppSecrets
from hotpocket_common.constants import App, Env
from hotpocket_common.constants import App, Environment
class PSettings(typing.Protocol):
@@ -16,7 +16,7 @@ class PSettings(typing.Protocol):
SECRET_KEY: str
APP: App
ENV: Env
ENV: Environment
SECRETS: AdminSecrets | WebAppSecrets
@@ -32,3 +32,9 @@ class PSettings(typing.Protocol):
UPLOADS_PATH: pathlib.Path
AUTH_KEY_TTL: int
UI_BASE_HEAD_INCLUDES: list
UI_BASE_SCRIPT_INCLUDES: list
UI_PAGE_HEAD_INCLUDES: list
UI_PAGE_SCRIPT_INCLUDES: list

View File

@@ -73,6 +73,30 @@ def appearance_settings(request: HttpRequest) -> dict:
case 'solar':
result['theme_css'] = 'ui/css/solar.min.css'
case 'sandstone':
result['theme_css'] = 'ui/css/sandstone.min.css'
case 'sketchy':
result['theme_css'] = 'ui/css/sketchy.min.css'
return {
'APPEARANCE_SETTINGS': result,
}
def base_includes(request: HttpRequest) -> dict:
return {
'BASE_INCLUDES': {
'head': settings.UI_BASE_HEAD_INCLUDES,
'script': settings.UI_BASE_SCRIPT_INCLUDES,
},
}
def page_includes(request: HttpRequest) -> dict:
return {
'PAGE_INCLUDES': {
'head': settings.UI_PAGE_HEAD_INCLUDES,
'script': settings.UI_PAGE_SCRIPT_INCLUDES,
},
}

View File

@@ -150,6 +150,8 @@ class SettingsForm(Form):
('bootstrap', _('Bootstrap')),
('cosmo', _('Cosmo')),
('solar', _('Solar')),
('sandstone', _('Sandstone')),
('sketchy', _('Sketchy')),
],
)
light_mode = forms.BooleanField(

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 874 B

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View File

@@ -31,11 +31,17 @@
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'ui/img/icon-32.png' %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'ui/img/icon-16.png' %}">
<link rel="manifest" href="{% url 'ui.meta.manifest_json' %}">
{% for head_include in BASE_INCLUDES.head %}
{% include head_include %}
{% endfor %}
{% block page_head %}{% endblock %}
</head>
<body class="{% block body_class %}{% endblock %}" hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'>
{% block body %}
{% endblock %}
{% block scripts %}{% endblock %}
{% for script_include in BASE_INCLUDES.script %}
{% include script_include %}
{% endfor %}
</body>
</html>

View File

@@ -2,6 +2,12 @@
{% load i18n static ui %}
{% block page_head %}
{% for head_include in PAGE_INCLUDES.head %}
{% include head_include %}
{% endfor %}
{% endblock %}
{% block body %}
<nav id="navbar" class="navbar navbar-expand-sm bg-body-tertiary fixed-top">
<div class="container">
@@ -153,7 +159,11 @@
<script src="{% static 'ui/js/hotpocket-backend.ui.BrowseAccountAppsView.js' %}" type="text/javascript"></script>
<script src="{% static 'ui/js/hotpocket-backend.ui.InlineCreateSaveForm.js' %}" type="text/javascript"></script>
<script src="{% static 'ui/js/hotpocket-backend.ui.PasteboardLink.js' %}" type="text/javascript"></script>
{% block page_scripts %}{% endblock %}
{% for script_include in PAGE_INCLUDES.script %}
{% include script_include %}
{% endfor %}
{% block page_scripts %}
{% endblock %}
<script type="text/javascript">
(() => {
window.HotPocket.run({

View File

@@ -3,7 +3,13 @@ from __future__ import annotations
import json
from keep_it_secret import AbstractField, LiteralField, Secrets, SecretsField
from keep_it_secret import (
AbstractField,
EnvField,
LiteralField,
Secrets,
SecretsField,
)
class DatabaseSecrets(Secrets):
@@ -84,6 +90,19 @@ class CelerySecrets(Secrets):
result_backend: str = AbstractField.new()
class InitialAccountSecrets(Secrets):
username: str = EnvField.new(
'HOTPOCKET_BACKEND_INITIAL_ACCOUNT_USERNAME',
default=None,
required=False,
)
password: str = EnvField.new(
'HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD',
default=None,
required=False,
)
class BaseSecrets(Secrets):
SECRET_KEY: str = AbstractField.new()
@@ -91,3 +110,4 @@ class BaseSecrets(Secrets):
CELERY: CelerySecrets = SecretsField.new(CelerySecrets)
OIDC: OIDCSecrets = SecretsField.new(OIDCSecrets)
INITIAL_ACCOUNT: InitialAccountSecrets = SecretsField.new(InitialAccountSecrets)

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import os
from .base import * # noqa: F403
from .webapp import * # noqa: F403
DEBUG = (
os.environ.get('HOTPOCKET_BACKEND_DEBUG', 'false').lower() == 'true'
@@ -12,26 +12,14 @@ DEBUG = (
ALLOWED_HOSTS = os.environ.get('HOTPOCKET_BACKEND_ALLOWED_HOSTS', '*').split(',')
INSTALLED_APPS += [ # noqa: F405
'crispy_forms',
'crispy_bootstrap5',
'django_htmx',
]
MIDDLEWARE = [
'hotpocket_backend.apps.core.middleware.RequestIDMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'social_django.middleware.SocialAuthExceptionMiddleware',
'django_htmx.middleware.HtmxMiddleware',
*MIDDLEWARE, # noqa: F405
'whitenoise.middleware.WhiteNoiseMiddleware',
]
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = False
STORAGES['staticfiles'] = { # noqa: F405
'BACKEND': 'whitenoise.storage.CompressedManifestStaticFilesStorage',
}

Some files were not shown because too many files have changed in this diff Show More