52 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
0ac2ca73ec Release v25.10.13
All checks were successful
CI / Checks (push) Successful in 3m58s
2025-10-13 21:46:18 +02:00
7b67a2f758 BTHLABS-62: Display progress in extension popup
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-13 18:48:00 +00:00
8b86145519 BTHLABS-61: Service layer refactoring
A journey to fix `ValidationError` in Pocket imports turned service
layer refactoring :D
2025-10-12 20:54:00 +02:00
ac7a8dd90e BTHLABS-0000: README.md fixes 2025-10-07 08:46:50 +02:00
6903b7f768 BTHLABS-0000: Nuked dotcom service
Moved to a separate repo
2025-10-07 08:45:10 +02:00
2e8b8d7330 BTHLABS-60: Appearance settings
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-07 04:42:58 +00:00
b4d5375954 BTHLABS-0000: Docker and CI tweaks
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-10-07 04:37:01 +00:00
3f3f90103c Release v25.10.4
All checks were successful
CI / Checks (push) Successful in 24m41s
2025-10-04 08:07:26 +02:00
8582c12ec7 BTHLABS-58: UI updates for Apple Apps 2025-10-04 08:07:00 +02:00
98b3798264 Release v25.10.3 2025-10-04 08:06:36 +02:00
6332a9cef9 BTHLABS-58: Tweaks and fixes
* Use explicit values to populate access token's platform in apps.
* Fix View Association layout.
* Web Extension popup layout rework.
2025-10-04 08:06:18 +02:00
efcce32b50 Release v25.10.2 2025-10-04 08:04:17 +02:00
0311a28571 BTHLABS-0000: Stop App Store Connect from nagging about encryption 2025-10-04 08:04:00 +02:00
cb001f7e91 BTHLABS-58: Fixing URL paths resolution to avoid double slashes 2025-10-04 08:03:42 +02:00
9ab2b304b8 Release v25.10.1 2025-10-04 08:03:07 +02:00
1fd4dd735d BTHLABS-58: Cleaning up and preparing Apple Apps for release 2025-10-04 08:02:49 +02:00
99e9226338 BTHLABS-58: Share Extension in Apple Apps 2025-10-04 08:02:13 +02:00
0c12f52569 Release v25.9.18
All checks were successful
CI / Checks (push) Successful in 18m22s
2025-09-18 20:43:05 +02:00
a6f01ba71e BTHLABS-0000: Fix bumping of the workspace 2025-09-18 20:42:49 +02:00
77526b1fae BTHLABS-0000: Allow bumping a single service from top-level tasks. 2025-09-18 20:41:04 +02:00
7c97445155 BTHLABS-0000: Fix a bug that prevented RPC-created saves from processing 2025-09-18 20:41:04 +02:00
a0f1a4ce80 Release v25.9.17
All checks were successful
CI / Checks (push) Successful in 16m16s
2025-09-17 20:38:59 +02:00
80fbbcddf3 BTHLABS-0000: bump-version task
Enough with manual version bumps :D
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-17 20:27:20 +02:00
0ab87e25a4 BTHLABS-0000: Add scope to PWA manifest so share sheet target. 2025-09-17 20:27:20 +02:00
495255206e BTHLABS-57: Pre-auth page in the extension 2025-09-17 20:27:20 +02:00
46254730bd BTHLABS-52: Firefox Desktop Extension 2025-09-17 20:27:08 +02:00
d1e60babf4 BTHLABS-56: _Copy share link_ button in view association page
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-15 06:28:38 +00:00
ab84f685c0 BTHLABS-51: Chrome Web Extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-14 06:34:43 +00:00
1a8c4bfebc BTHLABS-0000: eslint.config.js fixes and code cleanup 2025-09-13 09:05:29 +02:00
b15b48f702 BTHLABS-55: Inline create save form
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-13 06:56:44 +00:00
29c732faa0 Release v25.9.12
All checks were successful
CI / Checks (push) Successful in 15m7s
2025-09-11 20:50:18 +02:00
dcebccf947 BTHLABS-50: Safari Web Extension: Reloaded
Turns out, getting this thing out into the wild isn't as simple as I thought :D
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-11 15:57:11 +00:00
67138c7035 BTHLABS-0000: Use absolute URLs in ui.meta.manifest_json 2025-09-09 15:21:45 +02:00
6f848be1ee Release v25.9.8
All checks were successful
CI / Checks (push) Successful in 14m34s
2025-09-08 21:06:53 +02:00
b6d02dbe78 BTHLABS-50: Safari Web extension
Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
2025-09-08 18:11:36 +00:00
ffecf780ee BTHLABS-0000: Enable actions on development branch 2025-08-30 08:42:01 +02:00
fad9d2d26f BTHLABS-0000: Filter ci workflow's triggers. 2025-08-30 08:01:31 +02:00
f68e5b4573 Release v1.0.2
All checks were successful
CI / Checks (push) Successful in 16m8s
2025-08-29 14:15:16 +02:00
db39c38594 BTHLABS-54: Unprocessed save shows Untitled in the card title 2025-08-29 08:23:09 +02:00
38d768a584 BTHLABS-53: Processing task fails for newly created saves 2025-08-29 08:20:53 +02:00
fb39818be3 public -> development 2025-08-20 20:01:12 +00:00
415 changed files with 19329 additions and 1736 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,28 @@
#!/usr/bin/env bash
set -e
set +x
set -o pipefail
cat >"./docker-compose-ci-${COMPOSE_PROJECT}.yaml" <<EOF
services:
postgres:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-${COMPOSE_PROJECT}"
keycloak:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-${COMPOSE_PROJECT}"
rabbitmq:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-${COMPOSE_PROJECT}"
apple-ci:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-${COMPOSE_PROJECT}"
backend-ci:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-${COMPOSE_PROJECT}"
extension-ci:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-${COMPOSE_PROJECT}"
packages-ci:
image: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-${COMPOSE_PROJECT}"
EOF

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

@@ -2,7 +2,13 @@ name: "CI"
on: on:
push: push:
branches:
- "development"
- "public"
pull_request: pull_request:
branches:
- "development"
- "public"
jobs: jobs:
run-checks: run-checks:
@@ -11,8 +17,24 @@ jobs:
steps: steps:
- name: "Checkout the code" - name: "Checkout the code"
uses: "actions/checkout@v2" uses: "actions/checkout@v2"
- name: "Get run info"
id: "get-run-info"
uses: "./.gitea/actions/get-run-info"
with:
compose-project-base: "${{ vars.COMPOSE_PROJECT_BASE }}"
- name: "Get build options"
id: "get-build-options"
uses: "./.gitea/actions/get-build-options"
- name: "Set up Docker Buildx" - name: "Set up Docker Buildx"
id: "setup-docker-buildx"
uses: "docker/setup-buildx-action@v3" 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: "Build `postgres` image" - name: "Build `postgres` image"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -20,7 +42,10 @@ jobs:
context: "services/" context: "services/"
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/postgres:15.13-local" 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" - name: "Build `keycloak` image"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -28,7 +53,10 @@ jobs:
context: "services/" context: "services/"
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/keycloak:22.0.3-local" 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" - name: "Build `rabbitmq` image"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -36,7 +64,10 @@ jobs:
context: "services/" context: "services/"
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/rabbitmq:3.10.8-local" 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" - name: "Build `backend-ci` image"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -45,7 +76,10 @@ jobs:
target: "ci" target: "ci"
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/backend:ci-local" 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" - name: "Build `packages-ci` image"
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
@@ -54,17 +88,103 @@ jobs:
target: "ci" target: "ci"
push: false push: false
load: true load: true
tags: "docker-hosted.nexus.bthlabs.pl/hotpocket/packages:ci-local" 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"
uses: docker/build-push-action@v6
with:
file: "services/extension/Dockerfile"
context: "services/"
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 }}"
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"
uses: docker/build-push-action@v6
with:
file: "services/apple/Dockerfile"
context: "services/"
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 }}"
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 }}"
run: |
set -x
./.gitea/tools/render-docker-compose-ci.sh
- name: "Run `backend` checks" - name: "Run `backend` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: | run: |
set -x set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm backend-ci inv ci docker compose \
-p "${COMPOSE_PROJECT}" \
-f "docker-compose.yaml" \
-f "docker-compose-ci.yaml" \
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
run --rm \
backend-ci inv ci
- name: "Run `packages` checks" - name: "Run `packages` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: | run: |
set -x set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml run --rm packages-ci inv ci docker compose \
-p "${COMPOSE_PROJECT}" \
-f "docker-compose.yaml" \
-f "docker-compose-ci.yaml" \
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
run --rm \
packages-ci inv ci
- name: "Run `extension` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
-p "${COMPOSE_PROJECT}" \
-f "docker-compose.yaml" \
-f "docker-compose-ci.yaml" \
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
run --rm \
extension-ci inv ci
- name: "Run `apple` checks"
if: "steps.prepare.conclusion == 'success'"
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: |
set -x
docker compose \
-p "${COMPOSE_PROJECT}" \
-f "docker-compose.yaml" \
-f "docker-compose-ci.yaml" \
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
run --rm \
apple-ci inv ci
- name: "Clean up" - name: "Clean up"
if: always() if: always()
env:
COMPOSE_PROJECT: "${{ steps.get-run-info.outputs.compose-project }}"
run: | run: |
set -x set -x
docker compose -f docker-compose.yaml -f docker-compose-ci.yaml down --volumes docker compose \
-p "${COMPOSE_PROJECT}" \
-f "docker-compose.yaml" \
-f "docker-compose-ci.yaml" \
-f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" \
down --volumes --rmi all || true
rm -f "docker-compose-ci-${COMPOSE_PROJECT}.yaml" || true

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
)

3
.gitignore vendored
View File

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

View File

@@ -86,3 +86,9 @@ Licensed under terms of the MIT License
Pepper Hot Solid icon Pepper Hot Solid icon
Copyright (c) Icons8 Copyright (c) Icons8
Licensed under terms of the MIT License 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_USERNAME=hotpocket \
-e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \ -e HOTPOCKET_BACKEND_INITIAL_ACCOUNT_PASSWORD=hotpocketm4st3r \
-p 8000:8000 \ -p 8000:8000 \
docker-hosted.nexus.bthlabs.pl/hotpocket/backend:aio-v1.0.1-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 The command above will set up and start the application. The SQLite file will
@@ -76,8 +76,7 @@ credentials. The Web app will be reachable at `http://127.0.0.1:8000/`.
The admin will be reachable at `http://127.0.0.1:8000/admin/`. The admin will be reachable at `http://127.0.0.1:8000/admin/`.
The `DJANGO_SETTINGS_MODULE` environment variable defaults to The `DJANGO_SETTINGS_MODULE` environment variable defaults to
`hotpocket_backend.settings.deployment.webapp`. This should be set to `hotpocket_backend.settings.deployment.aio`.
`hotpocket_backend.settings.deployment.admin` in the Admin container.
**NOTE:** The command above specifies wildly insecure `SECRET_KEY` which is **NOTE:** The command above specifies wildly insecure `SECRET_KEY` which is
used among other things to secure the session cookie. Please *please* used among other things to secure the session cookie. Please *please*
@@ -94,7 +93,8 @@ backend etc. The final deployment will require services for at least the Web
app, the Celery worker and Celery Beat. Admin is optional. app, the Celery worker and Celery Beat. Admin is optional.
The `DJANGO_SETTINGS_MODULE` environment variable defaults to The `DJANGO_SETTINGS_MODULE` environment variable defaults to
`hotpocket_backend.settings.deployment.aio`. `hotpocket_backend.settings.deployment.webapp`. This should be set to
`hotpocket_backend.settings.deployment.admin` in the Admin container.
The `deployment/fullstack/docker-compose.yaml` file can be used as a The `deployment/fullstack/docker-compose.yaml` file can be used as a
starting point for full-stack deployments. starting point for full-stack deployments.

View File

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

View File

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

View File

@@ -2,8 +2,10 @@
"group": { "group": {
"default": { "default": {
"targets": [ "targets": [
"apple-management",
"backend-management", "backend-management",
"caddy", "caddy",
"extension-management",
"keycloak", "keycloak",
"packages-management", "packages-management",
"postgres", "postgres",
@@ -12,6 +14,28 @@
} }
}, },
"target": { "target": {
"apple-management": {
"context": "services/",
"dockerfile": "apple/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/apple:local"
],
"target": "development",
"output": [
"type=docker,load=true,push=false"
]
},
"apple-ci": {
"context": "services/",
"dockerfile": "apple/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/apple:ci-local"
],
"target": "ci",
"output": [
"type=docker,load=true,push=false"
]
},
"backend-management": { "backend-management": {
"context": "services/", "context": "services/",
"dockerfile": "backend/Dockerfile", "dockerfile": "backend/Dockerfile",
@@ -67,6 +91,28 @@
"type=docker,load=true,push=false" "type=docker,load=true,push=false"
] ]
}, },
"extension-management": {
"context": "services/",
"dockerfile": "extension/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:local"
],
"target": "development",
"output": [
"type=docker,load=true,push=false"
]
},
"extension-ci": {
"context": "services/",
"dockerfile": "extension/Dockerfile",
"tags": [
"docker-hosted.nexus.bthlabs.pl/hotpocket/extension:ci-local"
],
"target": "ci",
"output": [
"type=docker,load=true,push=false"
]
},
"caddy": { "caddy": {
"context": "services/", "context": "services/",
"dockerfile": "caddy/Dockerfile", "dockerfile": "caddy/Dockerfile",

View File

@@ -1,15 +1,17 @@
services: services:
postgres: postgres:
ports: [] ports: !override []
keycloak: keycloak:
command: "echo 'NOOP'" command: "echo 'NOOP'"
ports: [] ports: !override []
restart: "no" restart: "no"
rabbitmq: rabbitmq:
ports: [] ports: !override []
include: include:
- path: "./services/backend/docker-compose-ci.yaml" - path: "./services/backend/docker-compose-ci.yaml"
- path: "./services/packages/docker-compose-ci.yaml" - path: "./services/packages/docker-compose-ci.yaml"
- path: "./services/extension/docker-compose-ci.yaml"
- path: "./services/apple/docker-compose-ci.yaml"

View File

@@ -5,6 +5,8 @@ include:
- path: "./docker-compose-cloud.yaml" - path: "./docker-compose-cloud.yaml"
- path: "./services/backend/docker-compose.yaml" - path: "./services/backend/docker-compose.yaml"
- path: "./services/packages/docker-compose.yaml" - path: "./services/packages/docker-compose.yaml"
- path: "./services/extension/docker-compose.yaml"
- path: "./services/apple/docker-compose.yaml"
volumes: {} volumes: {}

View File

@@ -1,6 +0,0 @@
{
"run": {
"echo": true,
"pty": true
}
}

8
invoke.yaml Normal file
View File

@@ -0,0 +1,8 @@
run:
echo: true
pty: true
files_to_version:
- "deployment/aio/docker-compose.yaml"
- "deployment/fullstack/docker-compose.yaml"
- "pyproject.toml"
- "README.md"

17
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]] [[package]]
name = "hotpocket-workspace-tools" name = "hotpocket-workspace-tools"
@@ -6,25 +6,30 @@ version = "1.0.0.dev0"
description = "HotPocket Workspace Tools" description = "HotPocket Workspace Tools"
optional = false optional = false
python-versions = "^3.12" python-versions = "^3.12"
groups = ["main"]
files = [] files = []
develop = true develop = true
[package.dependencies]
invoke = "2.2.1"
[package.source] [package.source]
type = "directory" type = "directory"
url = "services/packages/workspace_tools" url = "services/packages/workspace_tools"
[[package]] [[package]]
name = "invoke" name = "invoke"
version = "2.2.0" version = "2.2.1"
description = "Pythonic task execution" description = "Pythonic task execution"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "invoke-2.2.0-py3-none-any.whl", hash = "sha256:6ea924cc53d4f78e3d98bc436b08069a03077e6f85ad1ddaa8a116d7dad15820"}, {file = "invoke-2.2.1-py3-none-any.whl", hash = "sha256:2413bc441b376e5cd3f55bb5d364f973ad8bdd7bf87e53c79de3c11bf3feecc8"},
{file = "invoke-2.2.0.tar.gz", hash = "sha256:ee6cbb101af1a859c7fe84f2a264c059020b0cb7fe3535f9424300ab568f6bd5"}, {file = "invoke-2.2.1.tar.gz", hash = "sha256:515bf49b4a48932b79b024590348da22f39c4942dff991ad1fb8b8baea1be707"},
] ]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.1"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "ec33c3b3ec0f988e333872bdd134c1adce0782e98512dd2484cb85009b3da6cb" content-hash = "a7028d4a0260c82012077d9cc4b324b0ef5ab8ed24aa283a51cf941ba09685a9"

View File

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

View File

@@ -1,4 +1,8 @@
.mypy_cache/
.pytest_cache/
_tmp/ _tmp/
apple/build/
apple/DerivedData/
backend/node_modules/ backend/node_modules/
backend/ops/metal/ backend/ops/metal/
backend/hotpocket_backend/playground.py backend/hotpocket_backend/playground.py
@@ -7,4 +11,7 @@ backend/hotpocket_backend/settings/docker/
backend/hotpocket_backend/secrets/metal/ backend/hotpocket_backend/secrets/metal/
backend/hotpocket_backend/settings/metal/ backend/hotpocket_backend/settings/metal/
backend/hotpocket_backend/static/ backend/hotpocket_backend/static/
extension/node_modules/
extension/dist/
vendor/
.envrc* .envrc*

94
services/apple/.gitignore vendored Normal file
View File

@@ -0,0 +1,94 @@
# Xcode
## Build generated
build/
DerivedData/
## Various settings
ExportOptions.plist
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## We don't want any memgrap's to leak ;)
**.memgraph
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build
# Accio dependency management
Dependencies/
.accio/
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
fastlane/*.env
# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
# Extension stuff
Shared (Extension)/Resources/_locales/
Shared (Extension)/Resources/images/
Shared (Extension)/Resources/background-bundle.js
Shared (Extension)/Resources/content-bundle.js
Shared (Extension)/Resources/manifest.json
Shared (Extension)/Resources/preauth.html
Shared (Extension)/Resources/preauth.js

19
services/apple/Dockerfile Normal file
View File

@@ -0,0 +1,19 @@
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-20251014-01 AS development
ARG APP_USER_UID
ARG APP_USER_GID
ARG IMAGE_ID
# COPY --chown=$APP_USER_UID:$APP_USER_GID apple/ops/bin/*.sh /srv/bin/
VOLUME ["/srv/node_modules", "/srv/venv"]
FROM development AS ci
COPY --chown=$APP_USER_UID:$APP_USER_GID apple/ /srv/app/
COPY --chown=$APP_USER_UID:$APP_USER_GID packages/ /srv/packages/
COPY --chown=$APP_USER_UID:$APP_USER_GID tls/ /srv/tls/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAD42E56F0C900D8A354"
BuildableName = "HotPocket Extension.appex"
BlueprintName = "HotPocket Extension (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAAF2E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (iOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCADE2E56F0C900D8A354"
BuildableName = "HotPocket Extension.appex"
BlueprintName = "HotPocket Extension (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CBCEA4E2E81CB9500722009"
BuildableName = "Save to HotPocket.appex"
BlueprintName = "macOS (Share Extension)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4CABCAC52E56F0C900D8A354"
BuildableName = "HotPocket.app"
BlueprintName = "HotPocket (macOS)"
ReferencedContainer = "container:HotPocket.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

3
services/apple/README.md Normal file
View File

@@ -0,0 +1,3 @@
# HotPocket by BTHLabs
This repository contains the _HotPocket Apple Integrations_ project.

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xED",
"green" : "0xBA",
"red" : "0x1C"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,93 @@
{
"images" : [
{
"filename" : "icon-ios-light.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "icon-ios-dark.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"filename" : "icon-ios-tinted.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"filename" : "icon-mac-16.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"filename" : "icon-mac-32.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"filename" : "icon-mac-32 1.png",
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"filename" : "icon-mac-64.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"filename" : "icon-mac-1024.png",
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

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.

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

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x29",
"green" : "0x25",
"red" : "0x21"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x76",
"green" : "0x64",
"red" : "0xEE"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"filename" : "icon-large-128.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "icon-large-128@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "icon-large-128@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xDD",
"green" : "0x82",
"red" : "0x00"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x67",
"green" : "0xA7",
"red" : "0x0E"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x2E",
"green" : "0x96",
"red" : "0xF1"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,32 @@
//
// HPAPI.h
// HotPocket
//
// Created by Tomek Wójcik on 23/09/2025.
//
#import <Foundation/Foundation.h>
#import "HPRPCClient.h"
NS_ASSUME_NONNULL_BEGIN
typedef void (^HPAPICheckAuthCompletionHandler)(BOOL authValid, NSError * _Nullable error, NSString * _Nullable callId);
@interface HPAPI : NSObject
@property (nullable) HPRPCClient *rpcClient;
@property NSMutableSet *callIds;
-(id)initWithRPCClientDelegate:(id<HPRPCClientDelegate>)delegate;
+(NSDictionary *)getAccessTokenMeta;
-(NSString *)checkAuth;
-(void)checkAuth:(HPAPICheckAuthCompletionHandler)completionHandler;
-(NSString *)save:(NSURL *)url;
-(BOOL)save:(NSURL *)url completionHandler:(HPRPCClientCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,145 @@
//
// HPAPI.m
// HotPocket
//
// Created by Tomek Wójcik on 23/09/2025.
//
#import "HPAPI.h"
#import "HPCredentialsHelper.h"
#import "HPRPCClient.h"
@implementation HPAPI (HPAPIPrivate)
#pragma mark - Private interface
-(void)updateRPCClientCredentials {
HPCredentials *credentials = [[HPCredentialsHelper sharedHelper] getCredentials];
self.rpcClient.baseURL = credentials.rpcURL;
self.rpcClient.accessToken = credentials.accessToken;
}
@end
@implementation HPAPI
#pragma mark - Initialization
-(id)init {
if (self = [super init]) {
self.rpcClient = [[HPRPCClient alloc] initWithBaseURL:nil accessToken:nil];
[self updateRPCClientCredentials];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onCredentialsChanged:)
name:@"HPCredentialsChanged"
object:nil];
}
return self;
}
-(id)initWithRPCClientDelegate:(id<HPRPCClientDelegate>)rpcClientDelegate {
if (self = [self init]) {
self.rpcClient.delegate = rpcClientDelegate;
}
return self;
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public interface
+(NSDictionary *)getAccessTokenMeta {
NSBundle *mainBundle = [NSBundle mainBundle];
return @{
@"version": [mainBundle.infoDictionary valueForKey:@"CFBundleShortVersionString"],
@"platform": [mainBundle.infoDictionary valueForKey:@"HPAPIAccessTokenPlatform"],
};
}
-(NSString *)checkAuth {
if (self.rpcClient.hasCredentials == NO) {
return nil;
}
NSString *callId = [[NSUUID UUID] UUIDString];
BOOL callResult = [self.rpcClient call:callId
method:@"accounts.auth.check_access_token"
params:@[self.rpcClient.accessToken, [HPAPI getAccessTokenMeta]]
endopoint:@"/accounts/rpc/"];
if (callResult == NO) {
return nil;
}
return callId;
}
-(void)checkAuth:(HPAPICheckAuthCompletionHandler)completionHandler {
if (self.rpcClient.hasCredentials == NO) {
completionHandler(NO, nil, nil);
} else {
NSString *callId = [[NSUUID UUID] UUIDString];
BOOL callResult = [self.rpcClient call:callId
method:@"accounts.auth.check_access_token"
params:@[self.rpcClient.accessToken, [HPAPI getAccessTokenMeta]]
endopoint:@"/accounts/rpc/" completionHandler:^(NSString *finalCallId, HPRPCCallResult *result) {
BOOL authValid = YES;
if (result.error != nil) {
authValid = NO;
} else if ([(NSNumber *)result.result boolValue] == NO) {
authValid = NO;
}
completionHandler(authValid, result.error, finalCallId);
}];
}
}
-(NSString *)save:(NSURL *)url {
if (self.rpcClient.hasCredentials == NO) {
return nil;
}
if (url == nil) {
return nil;
}
NSString *callId = [[NSUUID UUID] UUIDString];
BOOL callResult = [self.rpcClient call:callId method:@"saves.create" params:@[[url absoluteString]]];
if (callResult == NO) {
return nil;
}
return callId;
}
-(BOOL)save:(NSURL *)url completionHandler:(HPRPCClientCompletionHandler)completionHandler {
if (self.rpcClient.hasCredentials == NO) {
return NO;
}
if (url == nil) {
return NO;
}
NSString *callId = [[NSUUID UUID] UUIDString];
return [self.rpcClient call:callId
method:@"saves.create"
params:@[[url absoluteString]]
completionHandler:completionHandler];
}
#pragma mark - Notification handlers
-(void)onCredentialsChanged:(NSNotification *)notification {
[self updateRPCClientCredentials];
}
@end

View File

@@ -0,0 +1,30 @@
//
// HPAuthFlow.h
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 21/09/2025.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HPAuthParams : NSObject
@property (copy) NSString *authKey;
@property (copy) NSString *sessionToken;
@end
@interface HPAuthFlow : NSObject
@property (nullable) NSURL *baseURL;
@property (nullable) NSString *sessionToken;
-(NSURL *)start;
-(HPAuthParams *)handlePostAuthenticateURL:(NSURL *)url;
-(BOOL)handleAuthParams:(HPAuthParams *)authParams;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,140 @@
//
// HPAuthFlow.m
// HotPocket (macOS)
//
// Created by Tomek Wójcik on 21/09/2025.
//
#import "HPAuthFlow.h"
#import "HPAPI.h"
#import "HPCredentialsHelper.h"
#import "HPRPCClient.h"
#import "NSURL+HotPocketExtensions.h"
@implementation HPAuthParams
#pragma mark - HPAuthParams implementation
@end
@implementation HPAuthFlow (HPAuthFlowPrivate)
#pragma mark - HPAuthFlow private interface
-(NSURL *)resolveAuthenticateURL {
if (self.baseURL == nil) {
return nil;
}
if (self.baseURL.isUsableInHotPocket == NO) {
return nil;
}
NSBundle *mainBundle = [NSBundle mainBundle];
NSURLComponents *authURLComponents = [NSURLComponents componentsWithURL:self.baseURL resolvingAgainstBaseURL:NO];
authURLComponents.path = @"/integrations/extension/authenticate/";
authURLComponents.queryItems = @[
[NSURLQueryItem queryItemWithName:@"source" value:[[mainBundle infoDictionary] valueForKey:@"HPAuthFlowSource"]],
[NSURLQueryItem queryItemWithName:@"session_token" value:self.sessionToken],
];
return authURLComponents.URL;
}
@end
@implementation HPAuthFlow
#pragma mark - Initialization
-(id)init {
if (self = [super init]) {
self.baseURL = nil;
self.sessionToken = nil;
}
return self;
}
#pragma mark - Public interface
-(NSURL *)start {
if (self.baseURL == nil) {
return nil;
}
if (self.sessionToken == nil) {
self.sessionToken = [[NSUUID UUID] UUIDString];
}
return [self resolveAuthenticateURL];
}
-(HPAuthParams *)handlePostAuthenticateURL:(NSURL *)url {
if (url == nil) {
return nil;
}
NSDictionary *postAuthenticateURLParams = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"HPAuthFlowPostAuthenticateURLParts"];
if (postAuthenticateURLParams == nil) {
return nil;
}
NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
if ([urlComponents.scheme isEqualToString:[postAuthenticateURLParams valueForKey:@"scheme"]] == NO) {
return nil;
}
if ([urlComponents.host isEqualToString:[postAuthenticateURLParams valueForKey:@"host"]] == NO) {
return nil;
}
HPAuthParams *result = [[HPAuthParams alloc] init];
for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
if ([queryItem.name isEqualToString:@"auth_key"] == YES) {
result.authKey = queryItem.value;
} else if ([queryItem.name isEqualToString:@"session_token"] == YES) {
result.sessionToken = queryItem.value;
}
}
if ([self.sessionToken isEqualToString:result.sessionToken] == NO) {
return nil;
}
return result;
}
-(BOOL)handleAuthParams:(HPAuthParams *)authParams {
HPRPCClient *rpcClient = [[HPRPCClient alloc] initWithBaseURL:self.baseURL accessToken:nil];
NSArray *callParams = @[
authParams.authKey,
[HPAPI getAccessTokenMeta],
];
BOOL callResult = [rpcClient call:self.sessionToken
method:@"accounts.access_tokens.create"
params:callParams endopoint:@"/accounts/rpc/"
completionHandler:^(NSString *callId, HPRPCCallResult *result) {
dispatch_async(dispatch_get_main_queue(), ^{
if (result.error != nil) {
NSLog(@"-[HPAuthFlow handleAuthParams:] error=`%@`", result.error);
} else {
HPCredentialsHelper *credentialsHelper = [HPCredentialsHelper sharedHelper];
[credentialsHelper saveCredentials:[self.baseURL absoluteString] accessToken:(NSString *)result.result];
}
self.sessionToken = nil;
[[NSNotificationCenter defaultCenter] postNotificationName:@"AuthFlowDidFinish" object:self];
});
}];
return callResult;
}
@end

View File

@@ -0,0 +1,32 @@
//
// HPCredentialsHelper.h
// HotPocket
//
// Created by Tomek Wójcik on 19/09/2025.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HPCredentials : NSObject
@property (nullable) NSString *baseURL;
@property (nullable) NSString *accessToken;
@property (readonly) BOOL usable;
@property (readonly) NSURL *rpcURL;
@end
@interface HPCredentialsHelper : NSObject
+(instancetype)sharedHelper;
-(HPCredentials *)getCredentials;
-(BOOL)saveCredentials:(NSString *)baseURL accessToken:(NSString *)accessToken;
-(BOOL)clearCredentials;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,218 @@
//
// HPCredentialsHelper.m
// HotPocket
//
// Created by Tomek Wójcik on 19/09/2025.
//
#import <Security/Security.h>
#import "HPCredentialsHelper.h"
@implementation HPCredentials
#pragma mark - HPCredentials implementation
-(id)init {
if (self = [super init]) {
self.baseURL = nil;
self.accessToken = nil;
}
return self;
}
-(BOOL)usable {
return (self.baseURL != nil && self.accessToken != nil);
}
-(NSURL *)rpcURL {
return [NSURL URLWithString:self.baseURL];
}
-(NSString *)description {
NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithCapacity:2];
if (self.baseURL == nil) {
[attributes setValue:@"(null)" forKey:@"baseURL"];
} else {
[attributes setValue:self.baseURL forKey:@"baseURL"];
}
if (self.accessToken == nil) {
[attributes setValue:@"(null)" forKey:@"accessToken"];
} else {
[attributes setValue:@"***" forKey:@"accessToken"];
}
return [NSString stringWithFormat:@"<%@: %p; %@>", NSStringFromClass([self class]), self, attributes];
}
@end
@implementation HPCredentialsHelper (HPCredentialsHelperPrivate)
#pragma mark - Private interface
-(NSString *)getService {
#ifdef DEBUG
return @"pl.bthlabs.HotPocket.Debug";
#else
return @"pl.bthlabs.HotPocket";
#endif
}
-(NSData *)getKeychainItem:(NSString *)service account:(NSString *)account {
if (service == nil || account == nil) {
return nil;
}
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
(__bridge id)kSecUseDataProtectionKeychain: @YES,
};
CFTypeRef resultData = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &resultData);
if (status == errSecSuccess && resultData != NULL) {
NSData *result = (__bridge_transfer NSData *)resultData;
return result;
} else {
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
NSString *statusString = (__bridge NSString *)statusStringRef;
NSLog(@"-[HPCredentialsHelper getKeychainItem:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
return nil;
}
}
-(BOOL)createKeychainItemWithValue:(NSData *)value service:(NSString *)service account:(NSString *)account {
if (value == nil || service == nil || account == nil) {
return NO;
}
NSDictionary *attributes = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecValueData: value,
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
(__bridge id)kSecUseDataProtectionKeychain: @YES,
};
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
if (status != errSecSuccess) {
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
NSString *statusString = (__bridge NSString *)statusStringRef;
NSLog(@"-[HPCredentialsHelper createKeychainItemWithValue:service:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
return NO;
}
return YES;
}
-(BOOL)deleteKeychainItem:(NSString *)service account:(NSString *)account {
if (service == nil || account == nil) {
return NO;
}
NSDictionary *query = @{
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: service,
(__bridge id)kSecAttrAccount: account,
(__bridge id)kSecAttrAccessGroup: @"648728X64K.pl.bthlabs.HotPocketShared",
(__bridge id)kSecUseDataProtectionKeychain: @YES,
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status != errSecSuccess) {
CFStringRef statusStringRef = SecCopyErrorMessageString(status, NULL);
NSString *statusString = (__bridge NSString *)statusStringRef;
NSLog(@"-[HPCredentialsHelper deleteKeychainItem:account:] service=`%@` account=`%@` status=%@", service, account, statusString);
return NO;
}
return YES;
}
@end
@implementation HPCredentialsHelper
#pragma mark - Initialization
+(instancetype)sharedHelper {
static HPCredentialsHelper *sharedInstance = nil;
static dispatch_once_t initToken;
dispatch_once(&initToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
#pragma mark - Public interface
-(HPCredentials *)getCredentials {
HPCredentials *result = [[HPCredentials alloc] init];
NSData *itemData = [self getKeychainItem:[self getService] account:@"RPC"];
if (itemData != nil) {
NSError *error;
NSDictionary *itemPayload = [NSJSONSerialization JSONObjectWithData:itemData
options:NSJSONReadingTopLevelDictionaryAssumed
error:&error];
if (error != nil) {
NSLog(@"-[HPCredentialsHalper getCredentials] error=`%@`", error);
} else if (itemPayload != nil) {
result.baseURL = [itemPayload valueForKey:@"baseURL"];
result.accessToken = [itemPayload valueForKey:@"accessToken"];
}
}
return result;
}
-(BOOL)saveCredentials:(NSString *)baseURL accessToken:(NSString *)accessToken {
NSMutableDictionary *itemPayload = [NSMutableDictionary dictionaryWithCapacity:2];
if (baseURL != nil) {
[itemPayload setValue:baseURL forKey:@"baseURL"];
}
if (accessToken != nil) {
[itemPayload setValue:accessToken forKey:@"accessToken"];
}
NSError *error;
NSData *itemData = [NSJSONSerialization dataWithJSONObject:itemPayload options:0 error:&error];
if (error != nil) {
NSLog(@"-[HPCredentialsHalper saveCredentials:accessToken:] error=`%@`", error);
return NO;
}
BOOL saveResult = [self createKeychainItemWithValue:itemData service:[self getService] account:@"RPC"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"HPCredentialsChanged" object:self];
return saveResult;
}
-(BOOL)clearCredentials {
BOOL deleteResult = [self deleteKeychainItem:[self getService] account:@"RPC"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"HPCredentialsChanged" object:self];
return deleteResult;
}
@end

View File

@@ -0,0 +1,44 @@
//
// HPRPCClient.h
// HotPocket
//
// Created by Tomek Wójcik on 19/09/2025.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface HPRPCCallResult : NSObject
@property (nullable) NSError *error;
@property (nullable) id result;
@end
@protocol HPRPCClientDelegate <NSObject>
-(void)rpcClientDidReceiveResult:(HPRPCCallResult *)result callId:(NSString *)callId;
@end
typedef void (^HPRPCClientCompletionHandler)(NSString * _Nullable callId, HPRPCCallResult * _Nullable result);
@interface HPRPCClient : NSObject
@property (nonatomic, weak) id<HPRPCClientDelegate> delegate;
@property NSURL *baseURL;
@property NSString *accessToken;
@property NSURLSession *session;
-(id)initWithBaseURL:(nullable NSURL *)baseURL accessToken:(nullable NSString *)accessToken;
-(BOOL)hasCredentials;
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint completionHandler:(HPRPCClientCompletionHandler)completionHandler;
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params endopoint:(nullable NSString *)endpoint;
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params;
-(BOOL)call:(nullable NSString *)callId method:(NSString *)method params:(NSArray *)params completionHandler:(HPRPCClientCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END

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