mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
⚡️(devx) improve "make bootstrap" setup time and overall DevX
Notably, we try to reduce disk usage by standardizind on common base Docker images. We also improve node_modules by reducing duplicate dependencies and install speed.
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@ venv
|
||||
|
||||
# Docker
|
||||
compose.*
|
||||
env.d
|
||||
deploy/env
|
||||
|
||||
# Docs
|
||||
docs
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
name: Download translations from Crowdin
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
|
||||
synchronize-with-crowdin:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Create env files
|
||||
run: make create-env-files
|
||||
# crowdin workflow
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v2
|
||||
with:
|
||||
config: crowdin/config.yml
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
create_pull_request: false
|
||||
push_translations: false
|
||||
push_sources: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# A numeric ID, found at https://crowdin.com/project/<projectName>/tools/api
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
|
||||
# Visit https://crowdin.com/settings#api-key to create this token
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
CROWDIN_BASE_PATH: "../src/"
|
||||
# Create a new PR
|
||||
- name: Create a new Pull Request with new translated strings
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
commit-message: |
|
||||
🌐(i18n) update translated strings
|
||||
|
||||
Update translated files with new translations
|
||||
title: 🌐(i18n) update translated strings
|
||||
body: |
|
||||
## Purpose
|
||||
|
||||
update translated strings
|
||||
|
||||
## Proposal
|
||||
|
||||
- [x] update translated strings
|
||||
branch: i18n/update-translations
|
||||
labels: i18n
|
||||
@@ -26,11 +26,27 @@ name: Build and Push Container Image
|
||||
required: false
|
||||
default: ""
|
||||
description: "Build arg name to pass first amd64 tag to arm64 build (skips arch-independent build steps)"
|
||||
build_args:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: "Newline-separated KEY=value build args passed to both platform builds (e.g. PYTHON_UV_IMAGE=...)."
|
||||
dockerfile:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
description: "Path to the Dockerfile (defaults to <context>/Dockerfile). E.g. src/mta-in/Dockerfile.pymta."
|
||||
outputs:
|
||||
image_ref:
|
||||
description: "registry/name@sha256:digest of the published multi-arch image (for downstream `FROM`)."
|
||||
value: ${{ jobs.docker-build-push.outputs.image_ref }}
|
||||
|
||||
# see https://docs.github.com/en/enterprise-cloud@latest/actions/how-tos/use-cases-and-examples/publishing-packages/publishing-docker-images#publishing-images-to-github-packages
|
||||
jobs:
|
||||
docker-build-push:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
image_ref: "${{ inputs.registry }}/${{ github.repository }}-${{ inputs.image_name }}@${{ steps.create-manifest.outputs.digest }}"
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
@@ -77,16 +93,19 @@ jobs:
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
file: ${{ inputs.dockerfile || format('{0}/Dockerfile', inputs.context) }}
|
||||
target: ${{ inputs.target }}
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
provenance: false
|
||||
tags: ${{ steps.platform-tags.outputs.amd64 }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: ${{ inputs.build_args }}
|
||||
- name: Build and push (arm64)
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ${{ inputs.context }}
|
||||
file: ${{ inputs.dockerfile || format('{0}/Dockerfile', inputs.context) }}
|
||||
target: ${{ inputs.target }}
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
@@ -94,6 +113,7 @@ jobs:
|
||||
tags: ${{ steps.platform-tags.outputs.arm64 }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
${{ inputs.build_args }}
|
||||
${{ inputs.arm64_reuse_amd64_build_arg && format('{0}={1}', inputs.arm64_reuse_amd64_build_arg, steps.platform-tags.outputs.amd64_first) || '' }}
|
||||
- name: Create multi-arch manifests
|
||||
id: create-manifest
|
||||
|
||||
@@ -9,7 +9,23 @@ name: Build and publish OCI images
|
||||
|
||||
jobs:
|
||||
|
||||
# Shared base (debian-trixie + uv + managed CPython 3.14.6), built + pushed once
|
||||
# from deploy/python-uv/Dockerfile, then referenced by digest via
|
||||
# PYTHON_UV_IMAGE in the backend and MTA builds below.
|
||||
docker-publish-python-uv:
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: "python-uv"
|
||||
context: "deploy/python-uv"
|
||||
|
||||
docker-publish-mta-in:
|
||||
needs: docker-publish-python-uv
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -21,8 +37,30 @@ jobs:
|
||||
image_name: "mta-in"
|
||||
context: "src/mta-in"
|
||||
target: runtime-prod
|
||||
build_args: |
|
||||
PYTHON_UV_IMAGE=${{ needs.docker-publish-python-uv.outputs.image_ref }}
|
||||
|
||||
# Pure-Python (aiosmtpd) inbound MTA — built from Dockerfile.pymta, published
|
||||
# alongside the Postfix mta-in image while the two run side-by-side.
|
||||
docker-publish-mta-in-py:
|
||||
needs: docker-publish-python-uv
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
secrets: inherit
|
||||
with:
|
||||
image_name: "mta-in-py"
|
||||
context: "src/mta-in"
|
||||
dockerfile: "src/mta-in/Dockerfile.pymta"
|
||||
target: runtime-prod
|
||||
build_args: |
|
||||
PYTHON_UV_IMAGE=${{ needs.docker-publish-python-uv.outputs.image_ref }}
|
||||
|
||||
docker-publish-mta-out:
|
||||
needs: docker-publish-python-uv
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -34,6 +72,8 @@ jobs:
|
||||
image_name: "mta-out"
|
||||
context: "src/mta-out"
|
||||
target: runtime-prod
|
||||
build_args: |
|
||||
PYTHON_UV_IMAGE=${{ needs.docker-publish-python-uv.outputs.image_ref }}
|
||||
|
||||
docker-publish-socks-proxy:
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
@@ -62,6 +102,7 @@ jobs:
|
||||
arm64_reuse_amd64_build_arg: "FRONTEND_IMAGE"
|
||||
|
||||
docker-publish-backend:
|
||||
needs: docker-publish-python-uv
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -73,6 +114,8 @@ jobs:
|
||||
image_name: "backend"
|
||||
context: "src/backend"
|
||||
target: runtime-prod
|
||||
build_args: |
|
||||
PYTHON_UV_IMAGE=${{ needs.docker-publish-python-uv.outputs.image_ref }}
|
||||
|
||||
docker-publish-keycloak:
|
||||
uses: ./.github/workflows/docker-publish.yml
|
||||
|
||||
@@ -85,6 +85,36 @@ jobs:
|
||||
- name: Run E2E tests only for Chromium browser to speed up the tests
|
||||
run: make test-e2e-ci args="--project chromium"
|
||||
|
||||
test-mta-in:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create env files
|
||||
run: make create-env-files
|
||||
- name: Run mta-in (Postfix milter) tests
|
||||
run: make test-mta-in
|
||||
|
||||
test-mta-in-py:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create env files
|
||||
run: make create-env-files
|
||||
- name: Run mta-in pymta (aiosmtpd) tests
|
||||
run: make test-mta-in-py
|
||||
|
||||
# NOTE: no test-mta-out job — the mta-out suite is an integration test that
|
||||
# needs a live Postfix + SASL relay (its entrypoint runs `saslpasswd2`, which
|
||||
# fails in the ephemeral CI container: "saslpasswd2: generic failure"). It
|
||||
# stays a local/manual target (`make test-mta-out`) until it can be run
|
||||
# hermetically.
|
||||
|
||||
lint-front:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
+3
-3
@@ -38,9 +38,9 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
env.d/development/*
|
||||
!env.d/development/*.defaults
|
||||
!env.d/development/*.e2e
|
||||
deploy/env/*
|
||||
!deploy/env/*.defaults
|
||||
!deploy/env/*.e2e
|
||||
env.d/terraform
|
||||
|
||||
# npm
|
||||
|
||||
@@ -25,6 +25,12 @@ DOCKER_UID = $(shell id -u)
|
||||
DOCKER_GID = $(shell id -g)
|
||||
DOCKER_USER = $(DOCKER_UID):$(DOCKER_GID)
|
||||
COMPOSE = DOCKER_USER=$(DOCKER_USER) DOCKER_UID=$(DOCKER_UID) docker compose
|
||||
# Local tag for the shared base image (deploy/python-uv). `build-python-base`
|
||||
# builds it, and the Dockerfiles default `FROM ${PYTHON_UV_IMAGE}` to this same
|
||||
# tag, so compose builds resolve it. (CI publishes/overrides the base via the
|
||||
# docker-publish workflow build-args, not this variable — overriding it here
|
||||
# would only retag the local build, leaving the compose `FROM` unchanged.)
|
||||
PYTHON_UV_IMAGE ?= messages-python-uv:local
|
||||
COMPOSE_E2E = DOCKER_USER=$(DOCKER_USER) docker compose -f src/e2e/compose.yaml
|
||||
COMPOSE_EXEC = $(COMPOSE) exec
|
||||
COMPOSE_EXEC_APP = $(COMPOSE_EXEC) backend-dev
|
||||
@@ -54,15 +60,15 @@ data/static:
|
||||
|
||||
create-env-files: ## Create empty .local env files for local development
|
||||
create-env-files: \
|
||||
env.d/development/crowdin.local \
|
||||
env.d/development/postgresql.local \
|
||||
env.d/development/keycloak.local \
|
||||
env.d/development/backend.local \
|
||||
env.d/development/frontend.local \
|
||||
env.d/development/mta-in.local \
|
||||
env.d/development/mta-in-py.local \
|
||||
env.d/development/mta-out.local \
|
||||
env.d/development/socks-proxy.local
|
||||
deploy/env/crowdin.local \
|
||||
deploy/env/postgresql.local \
|
||||
deploy/env/keycloak.local \
|
||||
deploy/env/backend.local \
|
||||
deploy/env/frontend.local \
|
||||
deploy/env/mta-in.local \
|
||||
deploy/env/mta-in-py.local \
|
||||
deploy/env/mta-out.local \
|
||||
deploy/env/socks-proxy.local
|
||||
.PHONY: create-env-files
|
||||
|
||||
bootstrap: ## Prepare the project for local development
|
||||
@@ -86,6 +92,8 @@ bootstrap: ## Prepare the project for local development
|
||||
@echo "$(RESET)"
|
||||
@echo "$(GREEN)Starting bootstrap process...$(RESET)"
|
||||
@echo ""
|
||||
@$(MAKE) create-env-files
|
||||
@$(MAKE) start-deps
|
||||
@$(MAKE) update
|
||||
@$(MAKE) superuser
|
||||
@$(MAKE) start
|
||||
@@ -95,26 +103,44 @@ bootstrap: ## Prepare the project for local development
|
||||
@echo "$(BOLD)Next steps:$(RESET)"
|
||||
@echo " • Visit http://localhost:8900 to access the application"
|
||||
@echo " • Run 'make help' to see all available commands"
|
||||
@echo " • Need search, object storage or the MTAs? Run 'make bootstrap-full'"
|
||||
@echo ""
|
||||
.PHONY: bootstrap
|
||||
|
||||
update: ## Update the project with latest changes
|
||||
bootstrap-full: ## Prepare the project for local development with the full stack
|
||||
@echo "$(GREEN)Starting full bootstrap process...$(RESET)"
|
||||
@echo ""
|
||||
@$(MAKE) create-env-files
|
||||
@$(MAKE) start-deps
|
||||
@$(MAKE) update-full
|
||||
@$(MAKE) superuser
|
||||
@$(MAKE) start-full
|
||||
@echo ""
|
||||
@echo "$(GREEN)🎉 Full bootstrap completed successfully!$(RESET)"
|
||||
@echo ""
|
||||
.PHONY: bootstrap-full
|
||||
|
||||
update: ## Update the project with latest changes (light stack; run this when pulling code)
|
||||
@$(MAKE) data/media
|
||||
@$(MAKE) data/static
|
||||
@$(MAKE) create-env-files
|
||||
@$(MAKE) create-buckets
|
||||
@$(MAKE) build
|
||||
@$(MAKE) collectstatic
|
||||
@$(MAKE) migrate
|
||||
@$(MAKE) install-frozen-front
|
||||
.PHONY: update
|
||||
|
||||
update-full: ## Update the project with latest changes incl. object-storage buckets (full stack)
|
||||
update-full: \
|
||||
update \
|
||||
create-buckets
|
||||
.PHONY: update-full
|
||||
|
||||
# -- Docker/compose
|
||||
build: ## build the project containers
|
||||
build: build-python-base ## build the project containers
|
||||
@$(COMPOSE) build
|
||||
.PHONY: build
|
||||
|
||||
build-back-distroless: ## build the distroless production image
|
||||
build-back-distroless: build-python-base ## build the distroless production image
|
||||
@docker buildx build --load --target runtime-distroless-prod -t messages-distroless \
|
||||
-f src/backend/Dockerfile \
|
||||
src/backend/
|
||||
@@ -128,7 +154,7 @@ test-back-distroless: build-back-distroless ## build and smoke-test the distrole
|
||||
print(f'OK: Python {sys.version.split()[0]}, {ssl.OPENSSL_VERSION}')"
|
||||
.PHONY: test-back-distroless
|
||||
|
||||
build-pymta-distroless: ## build the pymta distroless production image
|
||||
build-pymta-distroless: build-python-base ## build the pymta distroless production image
|
||||
@docker build --target runtime-distroless-prod -t messages-pymta-distroless -f src/mta-in/Dockerfile.pymta src/mta-in/
|
||||
.PHONY: build-pymta-distroless
|
||||
|
||||
@@ -147,13 +173,51 @@ logs: ## display all services logs (follow mode)
|
||||
@$(COMPOSE) logs -f
|
||||
.PHONY: logs
|
||||
|
||||
start: ## start all development services
|
||||
@$(COMPOSE) up --force-recreate --build -d frontend-dev backend-dev worker-dev mta-in --wait
|
||||
build-python-base: ## build the shared python+uv base image (deploy/python-uv) that the backend and MTA images inherit from
|
||||
@docker build -t $(PYTHON_UV_IMAGE) deploy/python-uv
|
||||
.PHONY: build-python-base
|
||||
|
||||
start-deps: ## start the slow infra deps (postgres, redis, keycloak) in the background so they warm up while the rest of bootstrap runs
|
||||
@$(COMPOSE) up -d --no-recreate postgresql redis keycloak
|
||||
.PHONY: start-deps
|
||||
|
||||
# Fail fast (before booting a broken stack) when the project has not been
|
||||
# bootstrapped: `make bootstrap` creates the gitignored env files and the
|
||||
# frontend node_modules volume. start/start-full depend on this.
|
||||
check-bootstrapped:
|
||||
@test -f deploy/env/backend.local || { \
|
||||
printf "\n$(BOLD)✗ Not bootstrapped$(RESET): env files are missing.\n Run $(BOLD)make bootstrap$(RESET) first.\n\n" >&2; exit 1; }
|
||||
@docker volume inspect st-messages_frontend-node-modules >/dev/null 2>&1 || { \
|
||||
printf "\n$(BOLD)✗ Not bootstrapped$(RESET): frontend dependencies are not installed.\n Run $(BOLD)make bootstrap$(RESET) first.\n\n" >&2; exit 1; }
|
||||
.PHONY: check-bootstrapped
|
||||
|
||||
start: check-bootstrapped build-python-base ## start the light dev stack (backend, worker, frontend, keycloak, postgresql, redis)
|
||||
@$(COMPOSE) stop backend-dev worker-dev worker-ui opensearch objectstorage mailcatcher mta-in-py mpa >/dev/null 2>&1 || true
|
||||
@$(COMPOSE) up --build -d --wait \
|
||||
postgresql \
|
||||
redis \
|
||||
keycloak \
|
||||
frontend-dev \
|
||||
backend-dev-light \
|
||||
worker-dev-light
|
||||
.PHONY: start
|
||||
|
||||
start-minimal: ## start minimal services (backend, frontend, keycloak and DB)
|
||||
@$(COMPOSE) up --force-recreate --build -d backend-db frontend-dev keycloak --wait
|
||||
.PHONY: start-minimal
|
||||
start-full: check-bootstrapped build-python-base ## start the full dev stack (adds OpenSearch, object storage, mailcatcher and the MTAs)
|
||||
@$(COMPOSE) stop backend-dev-light worker-dev-light >/dev/null 2>&1 || true
|
||||
@$(COMPOSE) up --build -d --wait \
|
||||
postgresql \
|
||||
redis \
|
||||
opensearch \
|
||||
objectstorage \
|
||||
mailcatcher \
|
||||
keycloak \
|
||||
frontend-dev \
|
||||
backend-dev \
|
||||
worker-dev \
|
||||
worker-ui \
|
||||
mta-in-py \
|
||||
mpa
|
||||
.PHONY: start-full
|
||||
|
||||
status: ## an alias for "docker compose ps"
|
||||
@$(COMPOSE) ps
|
||||
@@ -163,21 +227,21 @@ stop: ## stop all development services
|
||||
@$(COMPOSE) --profile "*" stop
|
||||
.PHONY: stop
|
||||
|
||||
restart: ## restart all development services
|
||||
restart: ## restart the light dev stack
|
||||
restart: \
|
||||
stop \
|
||||
start
|
||||
.PHONY: restart
|
||||
|
||||
restart-minimal: ## restart minimal services
|
||||
restart-minimal: \
|
||||
restart-full: ## restart the full dev stack
|
||||
restart-full: \
|
||||
stop \
|
||||
start-minimal
|
||||
.PHONY: restart-minimal
|
||||
start-full
|
||||
.PHONY: restart-full
|
||||
|
||||
create-buckets: ## create the message imports & blobs buckets in objectstorage
|
||||
@$(COMPOSE) up -d objectstorage --wait
|
||||
@$(MANAGE_DB) create_bucket --storage message-imports --expire-days 1
|
||||
@$(MANAGE_DB) create_bucket --storage message-imports --expire-days 7
|
||||
@$(MANAGE_DB) create_bucket --storage message-blobs
|
||||
.PHONY: create-buckets
|
||||
|
||||
@@ -229,7 +293,7 @@ lint-check: \
|
||||
lint-front
|
||||
.PHONY: lint-check
|
||||
|
||||
lint-back: ## run back-end linters (with auto-fix)
|
||||
lint-back: build-python-base ## run back-end linters (with auto-fix)
|
||||
lint-back: \
|
||||
format-back \
|
||||
check-back \
|
||||
@@ -294,22 +358,22 @@ test: \
|
||||
test-socks-proxy
|
||||
.PHONY: test
|
||||
|
||||
test-back: ## run back-end tests
|
||||
test-back: build-python-base ## run back-end tests
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
bin/pytest $${args:-${1}}
|
||||
.PHONY: test-back
|
||||
|
||||
test-back-parallel: ## run all back-end tests in parallel
|
||||
test-back-parallel: build-python-base ## run all back-end tests in parallel
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
bin/pytest -n auto $${args:-${1}}
|
||||
.PHONY: test-back-parallel
|
||||
|
||||
fuzz-back: ## run back-end fuzz tests
|
||||
fuzz-back: build-python-base ## run back-end fuzz tests
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
bin/pytest -m fuzz $${args:-${1}}
|
||||
.PHONY: fuzz-back
|
||||
|
||||
fuzz-back-intensive: ## run back-end fuzz tests with 10x more examples (~20-30 min)
|
||||
fuzz-back-intensive: build-python-base ## run back-end fuzz tests with 10x more examples (~20-30 min)
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
rm -rf src/backend/.hypothesis/examples && \
|
||||
FUZZ_EXAMPLES=20000 bin/pytest -m fuzz $${args:-${1}}
|
||||
@@ -329,37 +393,37 @@ test-front-amd64: ## run the frontend tests in amd64
|
||||
$(COMPOSE) run --rm frontend-tools-amd64 npm run test -- $${args:-${1}}
|
||||
.PHONY: test-front-amd64
|
||||
|
||||
test-mta-in: ## run the mta-in tests against the Postfix milter implementation
|
||||
test-mta-in: build-python-base ## run the mta-in tests against the Postfix milter implementation
|
||||
@$(COMPOSE) run --build --rm mta-in-test
|
||||
.PHONY: test-mta-in
|
||||
|
||||
test-mta-in-py: ## run the mta-in tests against the pure-Python (aiosmtpd) implementation
|
||||
test-mta-in-py: build-python-base ## run the mta-in tests against the pure-Python (aiosmtpd) implementation
|
||||
@$(COMPOSE) run --build --rm mta-in-py-test
|
||||
.PHONY: test-mta-in-py
|
||||
|
||||
test-mta-out: ## run the mta-out tests
|
||||
test-mta-out: build-python-base ## run the mta-out tests
|
||||
@$(COMPOSE) run --build --rm mta-out-test
|
||||
.PHONY: test-mta-out
|
||||
|
||||
test-mpa: ## run the mpa tests
|
||||
test-mpa: build-python-base ## run the mpa tests
|
||||
@$(COMPOSE) run --build --rm mpa-test
|
||||
.PHONY: test-mpa
|
||||
|
||||
test-jmap-email: ## run the jmap-email package tests (zero infrastructure deps)
|
||||
test-jmap-email: build-python-base ## run the jmap-email package tests (zero infrastructure deps)
|
||||
@$(COMPOSE) run --build --rm jmap-email-test
|
||||
.PHONY: test-jmap-email
|
||||
|
||||
fuzz-jmap-email: ## run the jmap-email Hypothesis fuzz suite
|
||||
fuzz-jmap-email: build-python-base ## run the jmap-email Hypothesis fuzz suite
|
||||
@$(COMPOSE) run --build --rm jmap-email-test pytest -m fuzz tests/
|
||||
.PHONY: fuzz-jmap-email
|
||||
|
||||
lint-jmap-email: ## lint the jmap-email library (ruff check + format check + pylint)
|
||||
lint-jmap-email: build-python-base ## lint the jmap-email library (ruff check + format check + pylint)
|
||||
@$(COMPOSE) run --build --rm --entrypoint ruff jmap-email-test check jmap_email tests
|
||||
@$(COMPOSE) run --build --rm --entrypoint ruff jmap-email-test format --check jmap_email tests
|
||||
@$(COMPOSE) run --build --rm --entrypoint pylint jmap-email-test jmap_email tests
|
||||
.PHONY: lint-jmap-email
|
||||
|
||||
typecheck-jmap-email: ## type-check the jmap-email library with ty (Astral, Rust)
|
||||
typecheck-jmap-email: build-python-base ## type-check the jmap-email library with ty (Astral, Rust)
|
||||
@$(COMPOSE) run --build --rm --entrypoint ty jmap-email-test check
|
||||
.PHONY: typecheck-jmap-email
|
||||
|
||||
@@ -367,7 +431,7 @@ release-jmap-email: ## publish jmap-email to PyPI (interactive: TestPyPI → smo
|
||||
@bin/release-jmap-email.sh
|
||||
.PHONY: release-jmap-email
|
||||
|
||||
test-socks-proxy: ## run the socks-proxy tests
|
||||
test-socks-proxy: build-python-base ## run the socks-proxy tests
|
||||
@$(COMPOSE) run --build --rm socks-proxy-test
|
||||
.PHONY: test-socks-proxy
|
||||
|
||||
@@ -392,7 +456,7 @@ test-e2e-dev: ## Setup, run and teardown e2e tests in UI mode with dev frontend
|
||||
@$(MAKE) stop-e2e
|
||||
.PHONY: test-e2e-dev
|
||||
|
||||
test-e2e-ci: ## Setup and run e2e tests in CI mode
|
||||
test-e2e-ci: build-python-base ## Setup and run e2e tests in CI mode
|
||||
@$(MAKE) start-e2e
|
||||
@$(MAKE) test-e2e-bare args="$(args)"
|
||||
.PHONY: test-e2e-ci
|
||||
@@ -468,7 +532,7 @@ migrations-check: ## check that all model changes have corresponding migrations
|
||||
@$(COMPOSE_RUN_APP_TOOLS) python manage.py makemigrations --check --dry-run
|
||||
.PHONY: migrations-check
|
||||
|
||||
migrate: ## run django migrations for the messages project.
|
||||
migrate: build-python-base ## run django migrations for the messages project.
|
||||
@echo "$(BOLD)Running migrations$(RESET)"
|
||||
@$(MANAGE_DB) migrate
|
||||
.PHONY: migrate
|
||||
@@ -477,7 +541,7 @@ showmigrations: ## show all migrations for the messages project.
|
||||
@$(MANAGE_DB) showmigrations
|
||||
.PHONY: showmigrations
|
||||
|
||||
superuser: ## Create an admin superuser with password "admin" and promote user1 as superuser
|
||||
superuser: build-python-base ## Create an admin superuser with password "admin" and promote user1 as superuser
|
||||
@echo "$(BOLD)Creating a Django superuser$(RESET)"
|
||||
@$(MANAGE_DB) createsuperuser --email admin@admin.local --password admin
|
||||
@$(MANAGE_DB) createsuperuser --email user1@example.local --password user1
|
||||
@@ -495,7 +559,7 @@ exec-back: ## open a shell in the running backend-dev container
|
||||
@$(COMPOSE) exec backend-dev /bin/bash
|
||||
.PHONY: exec-back
|
||||
|
||||
deps-lock-back: ## lock the dependencies
|
||||
deps-lock-back: build-python-base ## lock the dependencies
|
||||
@$(COMPOSE) run --rm --build backend-uv uv lock
|
||||
@$(MAKE) deps-audit
|
||||
.PHONY: deps-lock-back
|
||||
@@ -520,7 +584,7 @@ deps-audit-back: ## audit back-end dependencies for vulnerabilities
|
||||
deps-audit: deps-audit-back ## alias for deps-audit-back
|
||||
.PHONY: deps-audit
|
||||
|
||||
collectstatic: ## collect static files
|
||||
collectstatic: build-python-base ## collect static files
|
||||
@$(MANAGE_DB) collectstatic --noinput
|
||||
.PHONY: collectstatic
|
||||
|
||||
@@ -550,7 +614,7 @@ reset-db-full: build ## flush database, including schema
|
||||
$(MANAGE_DB) migrate
|
||||
.PHONY: reset-db-full
|
||||
|
||||
env.d/development/%.local:
|
||||
deploy/env/%.local:
|
||||
@echo "# Local development overrides for $(notdir $*)" > $@
|
||||
@echo "# Add your local-specific environment variables below:" >> $@
|
||||
@echo "# Example: DJANGO_DEBUG=True" >> $@
|
||||
@@ -616,9 +680,10 @@ shell-front: ## open a shell in the frontend container
|
||||
.PHONY: shell-front
|
||||
|
||||
# Front
|
||||
install-front: ## install the frontend locally
|
||||
install-front: ## install the frontend locally (freezes the lockfile, then runs the dependency guardrail)
|
||||
@args="$(filter-out $@,$(MAKECMDGOALS))" && \
|
||||
$(COMPOSE) run --rm --build frontend-tools npm install $${args:-${1}}
|
||||
@$(COMPOSE) run --rm frontend-tools npm run check:deps
|
||||
.PHONY: install-front
|
||||
|
||||
install-frozen-front: ## install the frontend locally, following the frozen lockfile
|
||||
@@ -697,7 +762,7 @@ i18n-generate-front: ## Extract the frontend translation inside a json to be use
|
||||
@$(COMPOSE) run --rm --build frontend-tools npm run i18n:extract
|
||||
.PHONY: i18n-generate-front
|
||||
|
||||
api-update-back: ## Update the OpenAPI schema
|
||||
api-update-back: build-python-base ## Update the OpenAPI schema
|
||||
bin/update_openapi_schema
|
||||
.PHONY: api-update-back
|
||||
|
||||
@@ -737,10 +802,10 @@ test-keycloak: ## run all Keycloak provider tests (builds JARs, brings up Keyclo
|
||||
@bin/test-keycloak
|
||||
.PHONY: test-keycloak
|
||||
|
||||
deps-lock-mta-in: ## lock the dependencies for mta-in (shared between both implementations)
|
||||
deps-lock-mta-in: build-python-base ## lock the dependencies for mta-in (shared between both implementations)
|
||||
@$(COMPOSE) run --rm --build mta-in-uv uv lock
|
||||
.PHONY: deps-lock-mta-in
|
||||
|
||||
deps-lock-mta-out: ## lock the dependencies
|
||||
deps-lock-mta-out: build-python-base ## lock the dependencies
|
||||
@$(COMPOSE) run --rm --build mta-out-uv uv lock
|
||||
.PHONY: deps-lock-mta-out
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
web: bin/scalingo_run_web
|
||||
web: deploy/paas/scalingo_run_web
|
||||
workerall: python worker.py
|
||||
workerimports: python worker.py --concurrency=1 --queues=imports --disable-scheduler
|
||||
workerreindex: python worker.py --concurrency=2 --queues=reindex --disable-scheduler
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#
|
||||
# Interactive PyPI release for the jmap-email package.
|
||||
#
|
||||
# Hermetic: every step runs inside the python:3.14.5-slim image, so a
|
||||
# Hermetic: every step runs inside the python:3.14.6-slim image, so a
|
||||
# clean VM only needs Docker. The host never touches pip or twine.
|
||||
#
|
||||
# Flow:
|
||||
@@ -21,7 +21,7 @@ set -eo pipefail
|
||||
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PKG_DIR="${REPO_DIR}/src/jmap-email"
|
||||
PYTHON_IMAGE="python:3.14.5-slim"
|
||||
PYTHON_IMAGE="python:3.14.6-slim"
|
||||
|
||||
BOLD=$'\033[1m'
|
||||
GREEN=$'\033[1;32m'
|
||||
|
||||
Regular → Executable
+5
-10
@@ -1,11 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit # always exit on error
|
||||
set -o pipefail # don't ignore exit codes when piping output
|
||||
|
||||
echo "-----> Running post-compile script"
|
||||
|
||||
# Remove all the files we don't need
|
||||
rm -rf src docker env.d .cursor .github compose.yaml README.md .cache
|
||||
|
||||
chmod +x bin/scalingo_run_web
|
||||
# DEPRECATED shim — the Scalingo scripts moved to deploy/paas/. This passthrough
|
||||
# keeps external Scalingo hooks that still reference bin/scalingo_postcompile working; update the
|
||||
# hook to deploy/paas/scalingo_postcompile and delete this shim.
|
||||
echo "⚠️ bin/scalingo_postcompile is DEPRECATED → use deploy/paas/scalingo_postcompile (update your Scalingo hook)" >&2
|
||||
exec bash "$(dirname "$0")/../deploy/paas/scalingo_postcompile" "$@"
|
||||
|
||||
Regular → Executable
+5
-22
@@ -1,23 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit # always exit on error
|
||||
set -o pipefail # don't ignore exit codes when piping output
|
||||
|
||||
echo "-----> Running post-frontend script"
|
||||
|
||||
# Move the frontend build to the app root and clean up
|
||||
mkdir -p build/
|
||||
mv src/frontend/dist build/frontend-out
|
||||
|
||||
mv src/backend/* ./
|
||||
mkdir -p messages_backend && touch messages_backend/__init__.py
|
||||
|
||||
# Download Caddy binary
|
||||
CADDY_VERSION="2.9.1"
|
||||
curl -fsSL "https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_amd64.tar.gz" | tar -xz -C bin/ caddy
|
||||
chmod +x bin/caddy
|
||||
|
||||
# Copy Caddyfile (uses {$ENV} vars natively, no ERB needed)
|
||||
cp src/frontend/caddy/Caddyfile ./Caddyfile
|
||||
|
||||
echo "3.14" > .python-version
|
||||
# DEPRECATED shim — the Scalingo scripts moved to deploy/paas/. This passthrough
|
||||
# keeps external Scalingo hooks that still reference bin/scalingo_postfrontend working; update the
|
||||
# hook to deploy/paas/scalingo_postfrontend and delete this shim.
|
||||
echo "⚠️ bin/scalingo_postfrontend is DEPRECATED → use deploy/paas/scalingo_postfrontend (update your Scalingo hook)" >&2
|
||||
exec bash "$(dirname "$0")/../deploy/paas/scalingo_postfrontend" "$@"
|
||||
|
||||
+192
-87
@@ -1,8 +1,39 @@
|
||||
name: st-messages
|
||||
|
||||
# Raise the open-file limit for the Django dev server + import workers. The
|
||||
# Docker default (1024) is too low: the dev ``runserver`` keeps inbound HTTP
|
||||
# keep-alive connections open with no idle timeout, so a long UI session polling
|
||||
# the API (plus large imports opening S3 connections) exhausts FDs and wedges
|
||||
# the backend with "Too many open files".
|
||||
x-nofile-ulimits: &nofile-ulimits
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
|
||||
# Shared config for the Celery workers. worker-dev (full stack) and
|
||||
# worker-dev-light (light stack) differ only in their profiles / depends_on /
|
||||
# environment; everything else lives here. Note that ``environment`` is a list,
|
||||
# so the ``<<`` merge does NOT combine it — each service repeats the full list.
|
||||
x-worker-base: &worker-base
|
||||
build:
|
||||
context: src/backend
|
||||
target: runtime-dev
|
||||
args:
|
||||
DOCKER_USER: ${DOCKER_USER:-1000}
|
||||
user: ${DOCKER_USER:-1000}
|
||||
ulimits: *nofile-ulimits
|
||||
command: ["python", "worker.py", "--loglevel=DEBUG"]
|
||||
env_file:
|
||||
- deploy/env/backend.defaults
|
||||
- deploy/env/backend.local
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
mem_limit: 2G
|
||||
|
||||
services:
|
||||
postgresql:
|
||||
image: postgres:16.6
|
||||
image: postgres:16.6@sha256:557fea37a744d5f4c8faab304b0a90858b53ab119735a88c131fd19dab802f36
|
||||
ports:
|
||||
- "8912:5432"
|
||||
healthcheck:
|
||||
@@ -11,8 +42,8 @@ services:
|
||||
timeout: 2s
|
||||
retries: 300
|
||||
env_file:
|
||||
- env.d/development/postgresql.defaults
|
||||
- env.d/development/postgresql.local
|
||||
- deploy/env/postgresql.defaults
|
||||
- deploy/env/postgresql.local
|
||||
|
||||
redis:
|
||||
image: redis:5
|
||||
@@ -20,7 +51,7 @@ services:
|
||||
- "8913:6379"
|
||||
|
||||
opensearch:
|
||||
image: opensearchproject/opensearch:2.19.2
|
||||
image: opensearchproject/opensearch:2.19.2@sha256:69588c664014fa3d3260ed5dd337f8538fec94790dad00bfbe92301eaaaac240
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- bootstrap.memory_lock=true
|
||||
@@ -46,14 +77,14 @@ services:
|
||||
hard: 65536
|
||||
|
||||
mailcatcher:
|
||||
image: maildev/maildev:2.2.1
|
||||
image: maildev/maildev:2.2.1@sha256:180ef51f65eefebb0e7122d8308813c1fd7bff164bc440ce5a3c2feee167a810
|
||||
ports:
|
||||
- "8904:1080"
|
||||
- "8917:1025"
|
||||
|
||||
objectstorage:
|
||||
# user: ${DOCKER_USER:-1000}
|
||||
image: rustfs/rustfs:1.0.0-alpha.83
|
||||
image: rustfs/rustfs:1.0.0-alpha.83@sha256:1cfa82fb394a7c6a4ffc24f5333b52afee608d258503f94b37fcce5cc965c896
|
||||
environment:
|
||||
- RUSTFS_ACCESS_KEY=st-messages
|
||||
- RUSTFS_SECRET_KEY=password
|
||||
@@ -80,6 +111,56 @@ services:
|
||||
volumes:
|
||||
- objectstorage-data:/data
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:26.6.4@sha256:0aae0de7fca85525f727d3354df17896092de8bb26ae4c12d89c77e5df8cbce4
|
||||
volumes:
|
||||
- ./src/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro
|
||||
- ./src/keycloak/themes/dsfr-2.3.4.jar:/opt/keycloak/providers/keycloak-theme.jar:ro
|
||||
- ./src/keycloak/bulk-role-membership/bulk-role-membership.jar:/opt/keycloak/providers/bulk-role-membership.jar:ro
|
||||
environment:
|
||||
- HOST=http://localhost:8902
|
||||
- ADMIN_HOST=http://localhost:8902
|
||||
command:
|
||||
- start-dev
|
||||
- --features=preview
|
||||
- --import-realm
|
||||
- --proxy-headers=xforwarded
|
||||
- --http-enabled=true
|
||||
- --hostname=$${HOST}
|
||||
- --hostname-admin=$${ADMIN_HOST}
|
||||
- --http-port=8802
|
||||
# Expose the management health endpoints (:9000/health/*) so the
|
||||
# healthcheck below can tell when Keycloak is actually serving.
|
||||
- --health-enabled=true
|
||||
env_file:
|
||||
- deploy/env/keycloak.defaults
|
||||
- deploy/env/keycloak.local
|
||||
ports:
|
||||
- "8902:8802"
|
||||
# Without a healthcheck, `--wait` / `depends_on: service_started` consider
|
||||
# Keycloak ready the instant the container process launches — but the JVM is
|
||||
# still booting + importing the realm for several seconds, so :8902 500s.
|
||||
# The image ships no curl/wget, so probe the management readiness endpoint
|
||||
# (:9000/health/ready, 200 only once the realm import + DB are up) with
|
||||
# bash's /dev/tcp.
|
||||
healthcheck:
|
||||
test:
|
||||
- CMD
|
||||
- bash
|
||||
- -c
|
||||
- "exec 3<>/dev/tcp/localhost/9000; printf 'GET /health/ready HTTP/1.1\\r\\nHost: localhost\\r\\nConnection: close\\r\\n\\r\\n' >&3; head -n1 <&3 | grep -q ' 200'"
|
||||
interval: 3s
|
||||
timeout: 5s
|
||||
retries: 40
|
||||
# During start-up, probe every second (instead of `interval`) so Keycloak
|
||||
# is detected healthy within ~1s of actually serving. start_period is set
|
||||
# to comfortably cover a cold JVM boot + realm import so those early
|
||||
# failures stay in the grace window.
|
||||
start_period: 25s
|
||||
start_interval: 1s
|
||||
depends_on:
|
||||
- postgresql
|
||||
|
||||
backend-base:
|
||||
build:
|
||||
context: src/backend
|
||||
@@ -98,6 +179,7 @@ services:
|
||||
# environment when the backend's Python floor moves. Comment this mount
|
||||
# out to run exactly what CI/prod install from PyPI.
|
||||
- ./src/jmap-email/jmap_email:/venv/lib/${PYTHON_VERSION:-python3.14}/site-packages/jmap_email
|
||||
ulimits: *nofile-ulimits
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request as u; u.urlopen('http://localhost:8000/__heartbeat__/', timeout=1)"]
|
||||
interval: 3s
|
||||
@@ -110,8 +192,8 @@ services:
|
||||
- PYLINTHOME=/app/.pylint.d
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
env_file:
|
||||
- env.d/development/backend.defaults
|
||||
- env.d/development/backend.local
|
||||
- deploy/env/backend.defaults
|
||||
- deploy/env/backend.local
|
||||
ports:
|
||||
- "8901:8000"
|
||||
depends_on:
|
||||
@@ -125,10 +207,41 @@ services:
|
||||
opensearch:
|
||||
condition: service_healthy
|
||||
keycloak:
|
||||
condition: service_started
|
||||
condition: service_healthy
|
||||
mailcatcher:
|
||||
condition: service_started
|
||||
|
||||
# Lean backend for the default `make start`: same Development config (Redis
|
||||
# cache + Celery broker, real worker) but WITHOUT OpenSearch, object storage
|
||||
# or the MTAs — so a plain dev loop boots a handful of containers. Under the
|
||||
# `light` profile so a bare `docker compose up` (make start-full) skips it;
|
||||
# `make start` names it explicitly. `backend-dev` is left untouched because
|
||||
# `make test-back` (docker compose run backend-dev) relies on its OpenSearch /
|
||||
# object-storage deps to provision those for the search/storage test suites.
|
||||
backend-dev-light:
|
||||
extends: backend-base
|
||||
profiles:
|
||||
- light
|
||||
environment:
|
||||
- PYLINTHOME=/app/.pylint.d
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
# No OpenSearch container in this stack: skip the per-delivery reindex
|
||||
# enqueue so the worker isn't logging connection errors (search is a
|
||||
# start-full feature).
|
||||
- OPENSEARCH_INDEX_THREADS=False
|
||||
env_file:
|
||||
- deploy/env/backend.defaults
|
||||
- deploy/env/backend.local
|
||||
ports:
|
||||
- "8901:8000"
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
keycloak:
|
||||
condition: service_healthy
|
||||
|
||||
backend-db:
|
||||
extends: backend-base
|
||||
profiles:
|
||||
@@ -136,8 +249,8 @@ services:
|
||||
environment:
|
||||
- DJANGO_CONFIGURATION=DevelopmentMinimal
|
||||
env_file:
|
||||
- env.d/development/backend.defaults
|
||||
- env.d/development/backend.local
|
||||
- deploy/env/backend.defaults
|
||||
- deploy/env/backend.local
|
||||
ports:
|
||||
- "8901:8000"
|
||||
depends_on:
|
||||
@@ -156,25 +269,30 @@ services:
|
||||
pull_policy: build
|
||||
|
||||
worker-dev:
|
||||
build:
|
||||
context: src/backend
|
||||
target: runtime-dev
|
||||
args:
|
||||
DOCKER_USER: ${DOCKER_USER:-1000}
|
||||
user: ${DOCKER_USER:-1000}
|
||||
command: ["python", "worker.py", "--loglevel=DEBUG"]
|
||||
<<: *worker-base
|
||||
environment:
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
env_file:
|
||||
- env.d/development/backend.defaults
|
||||
- env.d/development/backend.local
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
- ./data/static:/data/static
|
||||
mem_limit: 2G
|
||||
depends_on:
|
||||
- backend-dev
|
||||
|
||||
# Worker for the light stack (see backend-dev-light). Needs only the
|
||||
# broker (Redis) + DB — not the web backend, not OpenSearch. Defined inline
|
||||
# rather than via ``extends: worker-dev`` because ``extends`` drags in that
|
||||
# service's ``depends_on: [backend-dev]``, which would pull the full backend
|
||||
# (and its OpenSearch / object-storage deps) back into the light stack.
|
||||
worker-dev-light:
|
||||
<<: *worker-base
|
||||
profiles:
|
||||
- light
|
||||
environment:
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
- OPENSEARCH_INDEX_THREADS=False
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_started
|
||||
|
||||
worker-ui:
|
||||
build:
|
||||
context: src/backend
|
||||
@@ -188,8 +306,8 @@ services:
|
||||
- FLOWER_UNAUTHENTICATED_API=true
|
||||
- DJANGO_CONFIGURATION=Development
|
||||
env_file:
|
||||
- env.d/development/backend.defaults
|
||||
- env.d/development/backend.local
|
||||
- deploy/env/backend.defaults
|
||||
- deploy/env/backend.local
|
||||
volumes:
|
||||
- ./src/backend:/app
|
||||
ports:
|
||||
@@ -201,18 +319,26 @@ services:
|
||||
build:
|
||||
context: ./src/frontend
|
||||
dockerfile: Dockerfile
|
||||
target: frontend-deps
|
||||
# Lean image with no node_modules baked in — deps live in the
|
||||
# frontend-node-modules volume, installed by `make install-frozen-front`.
|
||||
target: frontend-dev-base
|
||||
args:
|
||||
DOCKER_USER: ${DOCKER_USER:-1000}
|
||||
|
||||
frontend-dev:
|
||||
extends: frontend-base
|
||||
env_file:
|
||||
- env.d/development/frontend.defaults
|
||||
- env.d/development/frontend.local
|
||||
- deploy/env/frontend.defaults
|
||||
- deploy/env/frontend.local
|
||||
command: ["npm", "run", "dev"]
|
||||
volumes:
|
||||
- ./src/frontend/:/home/frontend/
|
||||
# Keep node_modules on a fast Docker volume instead of the (slow,
|
||||
# virtualized) host bind mount above. Writing ~thousands of files to a
|
||||
# bind mount is what made installs take minutes; a named volume lives on
|
||||
# the container filesystem, so `npm ci` runs in ~20s. Populated by
|
||||
# `make install-frozen-front`.
|
||||
- frontend-node-modules:/home/frontend/node_modules
|
||||
ports:
|
||||
- "8900:3000"
|
||||
|
||||
@@ -223,6 +349,10 @@ services:
|
||||
volumes:
|
||||
- ./src/backend/core/api/openapi.json:/home/backend/core/api/openapi.json
|
||||
- ./src/frontend/:/home/frontend/
|
||||
# Share the same fast node_modules volume as frontend-dev so that
|
||||
# `make install-frozen-front` (which runs here) installs into the volume
|
||||
# that frontend-dev consumes.
|
||||
- frontend-node-modules:/home/frontend/node_modules
|
||||
|
||||
frontend-tools-amd64:
|
||||
extends: frontend-tools
|
||||
@@ -261,12 +391,12 @@ services:
|
||||
- ./src/frontend/:/home/frontend/
|
||||
|
||||
crowdin:
|
||||
image: crowdin/cli:4.11.0
|
||||
image: crowdin/cli:4.11.0@sha256:9b32eafcfd8ba4b5183bd601bfeb8a65b0d338f2d2fc36df155f4845ee244eff
|
||||
volumes:
|
||||
- ".:/app"
|
||||
env_file:
|
||||
- env.d/development/crowdin.defaults
|
||||
- env.d/development/crowdin.local
|
||||
- deploy/env/crowdin.defaults
|
||||
- deploy/env/crowdin.local
|
||||
user: "${DOCKER_USER:-1000}"
|
||||
working_dir: /app
|
||||
|
||||
@@ -275,8 +405,8 @@ services:
|
||||
context: src/mta-in
|
||||
target: runtime-prod
|
||||
env_file:
|
||||
- env.d/development/mta-in.defaults
|
||||
- env.d/development/mta-in.local
|
||||
- deploy/env/mta-in.defaults
|
||||
- deploy/env/mta-in.local
|
||||
ports:
|
||||
- "8910:25"
|
||||
depends_on:
|
||||
@@ -289,8 +419,8 @@ services:
|
||||
context: src/mta-in
|
||||
target: runtime-dev
|
||||
env_file:
|
||||
- env.d/development/mta-in.defaults
|
||||
- env.d/development/mta-in.local
|
||||
- deploy/env/mta-in.defaults
|
||||
- deploy/env/mta-in.local
|
||||
environment:
|
||||
- EXEC_CMD=true
|
||||
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
||||
@@ -326,10 +456,10 @@ services:
|
||||
DOCKER_USER: ${DOCKER_USER:-65532}
|
||||
user: ${DOCKER_USER:-65532}
|
||||
env_file:
|
||||
- env.d/development/mta-in.defaults
|
||||
- env.d/development/mta-in.local
|
||||
- env.d/development/mta-in-py.defaults
|
||||
- env.d/development/mta-in-py.local
|
||||
- deploy/env/mta-in.defaults
|
||||
- deploy/env/mta-in.local
|
||||
- deploy/env/mta-in-py.defaults
|
||||
- deploy/env/mta-in-py.local
|
||||
ports:
|
||||
- "8920:25"
|
||||
- "9120:9100" # Prometheus metrics
|
||||
@@ -361,10 +491,10 @@ services:
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
env_file:
|
||||
- env.d/development/mta-in.defaults
|
||||
- env.d/development/mta-in.local
|
||||
- env.d/development/mta-in-py.defaults
|
||||
- env.d/development/mta-in-py.local
|
||||
- deploy/env/mta-in.defaults
|
||||
- deploy/env/mta-in.local
|
||||
- deploy/env/mta-in-py.defaults
|
||||
- deploy/env/mta-in-py.local
|
||||
environment:
|
||||
- EXEC_CMD=true
|
||||
- MDA_API_BASE_URL=http://localhost:8000/api/mail/
|
||||
@@ -381,8 +511,8 @@ services:
|
||||
context: src/mta-out
|
||||
target: runtime-prod
|
||||
env_file:
|
||||
- env.d/development/mta-out.defaults
|
||||
- env.d/development/mta-out.local
|
||||
- deploy/env/mta-out.defaults
|
||||
- deploy/env/mta-out.local
|
||||
ports:
|
||||
- "8911:587"
|
||||
depends_on:
|
||||
@@ -396,8 +526,8 @@ services:
|
||||
context: src/mta-out
|
||||
target: runtime-dev
|
||||
env_file:
|
||||
- env.d/development/mta-out.defaults
|
||||
- env.d/development/mta-out.local
|
||||
- deploy/env/mta-out.defaults
|
||||
- deploy/env/mta-out.local
|
||||
environment:
|
||||
- EXEC_CMD=true
|
||||
- MTA_OUT_SMTP_HOST=localhost:587
|
||||
@@ -408,13 +538,23 @@ services:
|
||||
volumes:
|
||||
- ./src/mta-out:/app
|
||||
|
||||
mta-out-uv:
|
||||
profiles:
|
||||
- tools
|
||||
volumes:
|
||||
- ./src/mta-out:/app
|
||||
build:
|
||||
context: src/mta-out
|
||||
target: uv
|
||||
pull_policy: build
|
||||
|
||||
socks-proxy:
|
||||
build:
|
||||
context: src/socks-proxy
|
||||
target: runtime
|
||||
env_file:
|
||||
- env.d/development/socks-proxy.defaults
|
||||
- env.d/development/socks-proxy.local
|
||||
- deploy/env/socks-proxy.defaults
|
||||
- deploy/env/socks-proxy.local
|
||||
ports:
|
||||
- "8916:1080"
|
||||
|
||||
@@ -430,42 +570,6 @@ services:
|
||||
socks-proxy:
|
||||
condition: service_started
|
||||
|
||||
mta-out-uv:
|
||||
profiles:
|
||||
- tools
|
||||
volumes:
|
||||
- ./src/mta-out:/app
|
||||
build:
|
||||
context: src/mta-out
|
||||
target: uv
|
||||
pull_policy: build
|
||||
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:26.6.4
|
||||
volumes:
|
||||
- ./src/keycloak/realm.json:/opt/keycloak/data/import/realm.json:ro
|
||||
- ./src/keycloak/themes/dsfr-2.3.4.jar:/opt/keycloak/providers/keycloak-theme.jar:ro
|
||||
- ./src/keycloak/bulk-role-membership/bulk-role-membership.jar:/opt/keycloak/providers/bulk-role-membership.jar:ro
|
||||
environment:
|
||||
- HOST=http://localhost:8902
|
||||
- ADMIN_HOST=http://localhost:8902
|
||||
command:
|
||||
- start-dev
|
||||
- --features=preview
|
||||
- --import-realm
|
||||
- --proxy-headers=xforwarded
|
||||
- --http-enabled=true
|
||||
- --hostname=$${HOST}
|
||||
- --hostname-admin=$${ADMIN_HOST}
|
||||
- --http-port=8802
|
||||
env_file:
|
||||
- env.d/development/keycloak.defaults
|
||||
- env.d/development/keycloak.local
|
||||
ports:
|
||||
- "8902:8802"
|
||||
depends_on:
|
||||
- postgresql
|
||||
|
||||
mpa:
|
||||
build:
|
||||
context: src/mpa/rspamd
|
||||
@@ -512,3 +616,4 @@ services:
|
||||
|
||||
volumes:
|
||||
objectstorage-data:
|
||||
frontend-node-modules:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit # always exit on error
|
||||
set -o pipefail # don't ignore exit codes when piping output
|
||||
|
||||
echo "-----> Running post-compile script"
|
||||
|
||||
# Remove all the files we don't need
|
||||
rm -rf src deploy/python-uv deploy/env .cursor .github compose.yaml README.md .cache
|
||||
|
||||
chmod +x deploy/paas/scalingo_run_web
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit # always exit on error
|
||||
set -o pipefail # don't ignore exit codes when piping output
|
||||
|
||||
echo "-----> Running post-frontend script"
|
||||
|
||||
# Move the frontend build to the app root and clean up
|
||||
mkdir -p build/
|
||||
mv src/frontend/dist build/frontend-out
|
||||
|
||||
mv src/backend/* ./
|
||||
mkdir -p messages_backend && touch messages_backend/__init__.py
|
||||
|
||||
# Download Caddy binary
|
||||
CADDY_VERSION="2.9.1"
|
||||
curl -fsSL "https://github.com/caddyserver/caddy/releases/download/v${CADDY_VERSION}/caddy_${CADDY_VERSION}_linux_amd64.tar.gz" | tar -xz -C bin/ caddy
|
||||
chmod +x bin/caddy
|
||||
|
||||
# Copy Caddyfile (uses {$ENV} vars natively, no ERB needed)
|
||||
cp src/frontend/caddy/Caddyfile ./Caddyfile
|
||||
|
||||
echo "3.14" > .python-version
|
||||
@@ -0,0 +1,52 @@
|
||||
# Shared Python base image — the single source of truth for the Debian base, the
|
||||
# uv version, and the uv-managed CPython version used by every uv-based Python
|
||||
# service (backend, mta-in, mta-out, and the mta-in pymta variant).
|
||||
#
|
||||
# Built + published by `make build-python-base` (locally, tagged
|
||||
# `messages-python-uv:local`) and by the `python-uv` job in messages-ghcr.yml
|
||||
# (multi-arch, pushed to ghcr). Services consume it via the build arg
|
||||
# PYTHON_UV_IMAGE and do `FROM ${PYTHON_UV_IMAGE} AS uv` — see compose.yaml
|
||||
# `build.args` and the CI `build-args` input.
|
||||
#
|
||||
# Deliberately MINIMAL: no build-essential here, because the MTA runtimes inherit
|
||||
# this image directly and we don't want compilers in production images. Each
|
||||
# service adds its own build deps in a build-only stage.
|
||||
|
||||
FROM debian:trixie-slim@sha256:28de0877c2189802884ccd20f15ee41c203573bd87bb6b883f5f46362d24c5c2 AS base
|
||||
|
||||
# Bump to force an apt refresh + security upgrade of the base OS.
|
||||
ENV MIN_UPDATE_DATE="2026-07-08"
|
||||
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get upgrade -y
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ---- uv + managed Python ----
|
||||
FROM base AS uv
|
||||
|
||||
# Pin uv by SHA256 digest for supply chain security.
|
||||
# Verify with: gh attestation verify --owner astral-sh oci://ghcr.io/astral-sh/uv:0.11.28
|
||||
COPY --from=ghcr.io/astral-sh/uv@sha256:0f36cb9361a3346885ca3677e3767016687b5a170c1a6b88465ec14aefec90aa /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_PYTHON_PREFERENCE=only-managed
|
||||
ENV UV_PYTHON_INSTALL_DIR=/opt/python
|
||||
ENV UV_PROJECT_ENVIRONMENT=/venv
|
||||
|
||||
# Install Python via uv — integrity verified against SHA256 checksums embedded in
|
||||
# the uv binary. python-build-standalone statically links most C deps (ssl, ffi,
|
||||
# sqlite, zlib, lzma, bz2).
|
||||
RUN uv python install 3.14.6
|
||||
|
||||
# Prune list for the distroless images, shared by the backend and pymta builds
|
||||
# (they run `strip-python` in a stage off this image). Single source of truth.
|
||||
COPY --chmod=0755 strip-python.sh /usr/local/bin/strip-python
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# Trim the uv-managed runtime for production images: remove tooling the app never
|
||||
# uses at runtime — pip (scripts AND package), idle, pydoc, C headers,
|
||||
# pkg-config, tkinter/tcl, tests — plus the uv/uvx build binaries themselves.
|
||||
# We build and manage everything with uv, so none of this is needed to *run*.
|
||||
#
|
||||
# Baked into the shared python-uv image and invoked as `RUN strip-python` by the
|
||||
# backend/pymta distroless builds and the slim runtime-prod stages, so the list
|
||||
# lives in exactly one place.
|
||||
#
|
||||
# NOTE: this shrinks the running-container attack surface (files are whited-out),
|
||||
# but it only shrinks image *size* for the distroless targets, which COPY an
|
||||
# already-trimmed tree into a clean base. For the slim runtime-prod images the
|
||||
# Python/uv sit in an inherited layer, so the bytes remain in the pull.
|
||||
#
|
||||
# Version-agnostic: the interpreter path comes from `uv python find`; stdlib
|
||||
# paths glob python3.*. uv/uvx are removed LAST, after uv is used above.
|
||||
set -eu
|
||||
|
||||
PYDIR=$(dirname "$(dirname "$(uv python find)")")
|
||||
|
||||
# shellcheck disable=SC2086 # deliberate globbing of the paths below
|
||||
rm -rf \
|
||||
"$PYDIR"/bin/idle* "$PYDIR"/bin/pip* "$PYDIR"/bin/pydoc* "$PYDIR"/bin/*-config \
|
||||
"$PYDIR"/include "$PYDIR"/share \
|
||||
"$PYDIR"/lib/pkgconfig "$PYDIR"/lib/itcl* "$PYDIR"/lib/libtcl* \
|
||||
"$PYDIR"/lib/tcl* "$PYDIR"/lib/tk* "$PYDIR"/lib/thread* \
|
||||
"$PYDIR"/lib/python3.*/idlelib \
|
||||
"$PYDIR"/lib/python3.*/ensurepip \
|
||||
"$PYDIR"/lib/python3.*/tkinter \
|
||||
"$PYDIR"/lib/python3.*/turtledemo \
|
||||
"$PYDIR"/lib/python3.*/lib-dynload/_tkinter* \
|
||||
"$PYDIR"/lib/python3.*/lib-dynload/_ctypes_test* \
|
||||
"$PYDIR"/lib/python3.*/site-packages/pip "$PYDIR"/lib/python3.*/site-packages/pip-*.dist-info \
|
||||
"${UV_PYTHON_INSTALL_DIR:-/opt/python}"/.gitignore \
|
||||
"${UV_PYTHON_INSTALL_DIR:-/opt/python}"/.lock \
|
||||
"${UV_PYTHON_INSTALL_DIR:-/opt/python}"/.temp
|
||||
|
||||
# uv/uvx are build tools — never needed to run the app. Remove after the
|
||||
# `uv python find` above.
|
||||
rm -f /bin/uv /bin/uvx
|
||||
+1
-1
@@ -597,7 +597,7 @@ _Set only to receive a startup deprecation warning; otherwise ignored._
|
||||
|
||||
## Environment Files
|
||||
|
||||
The application uses environment files located in `env.d/development/` for different services:
|
||||
The application uses environment files located in `deploy/env/` for different services:
|
||||
|
||||
- `backend.defaults` - Main Django application settings
|
||||
- `common.defaults` - Shared settings across services
|
||||
|
||||
+12
-12
@@ -27,7 +27,7 @@ Messages supports multiple deployment strategies depending on your infrastructur
|
||||
|
||||
**Process**:
|
||||
1. Start from the `compose.yaml` in the repository
|
||||
2. Create production environment files (`env.d/production/*.defaults`)
|
||||
2. Create production environment files (`deploy/env/production/*.defaults`)
|
||||
3. Deploy to any environment where Docker Compose runs
|
||||
4. Configure DNS and SSL certificates
|
||||
|
||||
@@ -114,24 +114,24 @@ The DNS records for each customer domains are available either via API at http:/
|
||||
Messages uses environment variables as the primary configuration method:
|
||||
|
||||
**Environment File Structure:**
|
||||
- `env.d/production/backend.defaults` - Main Django application settings
|
||||
- `env.d/production/frontend.defaults` - Frontend configuration
|
||||
- `env.d/production/mta-in.defaults` - Inbound mail server settings
|
||||
- `env.d/production/mta-out.defaults` - Outbound mail server settings
|
||||
- `env.d/production/postgresql.defaults` - Database configuration
|
||||
- `env.d/production/keycloak.defaults` - Identity provider settings
|
||||
- `deploy/env/production/backend.defaults` - Main Django application settings
|
||||
- `deploy/env/production/frontend.defaults` - Frontend configuration
|
||||
- `deploy/env/production/mta-in.defaults` - Inbound mail server settings
|
||||
- `deploy/env/production/mta-out.defaults` - Outbound mail server settings
|
||||
- `deploy/env/production/postgresql.defaults` - Database configuration
|
||||
- `deploy/env/production/keycloak.defaults` - Identity provider settings
|
||||
|
||||
**For detailed environment variable documentation, see [Environment Variables](./env.md).**
|
||||
|
||||
### 3. MTA Configuration
|
||||
|
||||
#### MTA-in (Inbound Email)
|
||||
- Configured via `env.d/production/mta-in.defaults`
|
||||
- Configured via `deploy/env/production/mta-in.defaults`
|
||||
- Uses custom milter for synchronous delivery during SMTP sessions
|
||||
- Validates recipients via REST API before accepting messages
|
||||
|
||||
#### MTA-out (Outbound Email)
|
||||
- Configured via `env.d/production/mta-out.defaults`
|
||||
- Configured via `deploy/env/production/mta-out.defaults`
|
||||
- Supports relay configuration for external SMTP providers
|
||||
- Requires TLS certificates for production
|
||||
|
||||
@@ -180,12 +180,12 @@ Messages uses OpenID Connect (OIDC) for user authentication. This is the only au
|
||||
- Keycloak is included in the default Docker Compose setup
|
||||
- Pre-configured with Messages realm and users
|
||||
- Suitable for organizations wanting a self-hosted identity provider
|
||||
- Configure via `env.d/production/keycloak.defaults`
|
||||
- Configure via `deploy/env/production/keycloak.defaults`
|
||||
|
||||
2. **External OIDC Provider**
|
||||
- Use any OIDC-compliant identity provider
|
||||
- Examples: Auth0, Okta, Azure AD, Google Workspace
|
||||
- Configure via `env.d/production/backend.defaults`
|
||||
- Configure via `deploy/env/production/backend.defaults`
|
||||
- Requires proper OIDC endpoint configuration
|
||||
|
||||
**User Management:**
|
||||
@@ -197,7 +197,7 @@ Messages uses OpenID Connect (OIDC) for user authentication. This is the only au
|
||||
For production deployment, create your own Docker Compose configuration based on `compose.yaml`:
|
||||
|
||||
**Key Considerations:**
|
||||
- Use production environment files (`env.d/production/*.defaults`)
|
||||
- Use production environment files (`deploy/env/production/*.defaults`)
|
||||
- Configure SSL/TLS certificates
|
||||
- Set up persistent volumes for databases
|
||||
- Implement proper restart policies
|
||||
|
||||
+33
-57
@@ -1,30 +1,17 @@
|
||||
# ---- Base OS ----
|
||||
FROM debian:trixie-slim AS base
|
||||
|
||||
# Bump this to force an update of the apt repositories
|
||||
ENV MIN_UPDATE_DATE="2026-02-20"
|
||||
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get upgrade -y
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
WORKDIR /app
|
||||
# Shared base image (deploy/python-uv/Dockerfile): debian-trixie + apt upgrade +
|
||||
# ca-certificates + uv 0.11.28 (digest-pinned) + uv-managed CPython 3.14.6 at
|
||||
# /opt/python. Global ARG so it can be used in `FROM` below. Built by
|
||||
# `make build-python-base`; overridden in CI.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
|
||||
# ---- uv + managed Python + build system deps ----
|
||||
FROM base AS uv
|
||||
|
||||
# Pin uv by SHA256 digest for supply chain security.
|
||||
# Verify with: gh attestation verify --owner astral-sh oci://ghcr.io/astral-sh/uv:0.11.19
|
||||
COPY --from=ghcr.io/astral-sh/uv@sha256:b46b03ddfcfbf8f547af7e9eaefdf8a39c8cebcba7c98858d3162bd28cf536f6 /uv /uvx /bin/
|
||||
FROM ${PYTHON_UV_IMAGE} AS uv
|
||||
|
||||
# Backend-specific build/collectstatic deps (libmagic1 is needed at build time
|
||||
# because collectstatic imports the app, which imports python-magic).
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
rdfind \
|
||||
@@ -34,16 +21,6 @@ DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_PYTHON_PREFERENCE=only-managed
|
||||
ENV UV_PYTHON_INSTALL_DIR=/opt/python
|
||||
ENV UV_PROJECT_ENVIRONMENT=/venv
|
||||
|
||||
# Install Python via uv — integrity verified against SHA256 checksums embedded in the uv binary.
|
||||
# Uses python-build-standalone: most C deps (ssl, ffi, sqlite, zlib, lzma, bz2) are statically linked.
|
||||
RUN uv python install 3.14.5
|
||||
|
||||
# ---- Production dependencies ----
|
||||
FROM uv AS base-with-deps
|
||||
|
||||
@@ -74,27 +51,9 @@ DJANGO_CONFIGURATION=Build python manage.py collectstatic --noinput
|
||||
rdfind -makesymlinks true -followsymlinks true -makeresultsfile false ${MESSAGES_STATIC_ROOT}
|
||||
EOR
|
||||
|
||||
# ---- Strip Python for production (remove pip, idle, tkinter, tcl, headers, tests) ----
|
||||
FROM uv AS python-runtime
|
||||
RUN <<EOR
|
||||
set -e
|
||||
PYDIR=$(dirname $(dirname $(uv python find 3.14.5)))
|
||||
rm -rf \
|
||||
$PYDIR/bin/idle* $PYDIR/bin/pip* $PYDIR/bin/pydoc* $PYDIR/bin/*-config \
|
||||
$PYDIR/include $PYDIR/share \
|
||||
$PYDIR/lib/pkgconfig $PYDIR/lib/itcl* $PYDIR/lib/libtcl* \
|
||||
$PYDIR/lib/tcl* $PYDIR/lib/tk* $PYDIR/lib/thread* \
|
||||
$PYDIR/lib/python3.14/idlelib \
|
||||
$PYDIR/lib/python3.14/ensurepip \
|
||||
$PYDIR/lib/python3.14/tkinter \
|
||||
$PYDIR/lib/python3.14/turtledemo \
|
||||
$PYDIR/lib/python3.14/lib-dynload/_tkinter* \
|
||||
$PYDIR/lib/python3.14/lib-dynload/_ctypes_test* \
|
||||
/opt/python/.gitignore /opt/python/.lock /opt/python/.temp
|
||||
EOR
|
||||
|
||||
# ---- Base runtime image ----
|
||||
FROM base AS runtime-base
|
||||
# Inherits the shared base directly (full managed Python at /opt/python).
|
||||
FROM ${PYTHON_UV_IMAGE} AS runtime-base
|
||||
|
||||
# Give the "root" group the same permissions as the "root" user on /etc/passwd
|
||||
# to allow a user belonging to the root group to add new users; typically the
|
||||
@@ -120,15 +79,14 @@ ENV VIRTUAL_ENV_PROMPT=venv
|
||||
# We wrap commands run in this container by the following entrypoint that
|
||||
# creates a user on-the-fly with the container user ID (see USER) and root group
|
||||
# ID.
|
||||
COPY ./entrypoint /usr/local/bin/entrypoint
|
||||
COPY --chmod=755 ./entrypoint /usr/local/bin/entrypoint
|
||||
ENTRYPOINT [ "/usr/local/bin/entrypoint" ]
|
||||
|
||||
|
||||
# ---- Development runtime ----
|
||||
FROM runtime-base AS runtime-dev
|
||||
|
||||
# Full Python installation (with headers, pip — useful for debugging)
|
||||
COPY --from=uv /opt/python /opt/python
|
||||
# Full managed Python (with headers, pip) is already present from the shared base.
|
||||
COPY --from=base-with-deps-dev /venv /venv
|
||||
|
||||
# /app will be mounted as a volume in the development container
|
||||
@@ -138,7 +96,7 @@ CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
|
||||
|
||||
# ---- Production application source (strip tests, dev tooling, build files) ----
|
||||
FROM base AS app-prod
|
||||
FROM ${PYTHON_UV_IMAGE} AS app-prod
|
||||
COPY . /app/
|
||||
RUN rm -rf \
|
||||
/app/core/tests \
|
||||
@@ -154,9 +112,20 @@ RUN rm -rf \
|
||||
|
||||
# ---- Production runtime ----
|
||||
FROM runtime-base AS runtime-prod
|
||||
ARG DOCKER_USER
|
||||
ARG MESSAGES_STATIC_ROOT=/data/static
|
||||
|
||||
COPY --from=python-runtime /opt/python /opt/python
|
||||
# Hardening: whiteout pip/tkinter/tests/headers from the runtime (see
|
||||
# strip-python). This does NOT shrink the image — the full Python sits in an
|
||||
# inherited layer — but keeps those tools out of the running container. Strip as
|
||||
# root so /opt/python stays root-owned (not writable by the unprivileged app
|
||||
# user), then restore that user.
|
||||
USER root
|
||||
RUN strip-python
|
||||
USER ${DOCKER_USER}
|
||||
|
||||
# Full managed Python is inherited from the shared base (runtime-base). The
|
||||
# distroless target below copies in an already-stripped Python instead.
|
||||
COPY --from=base-with-deps /venv /venv
|
||||
COPY --from=link-collector ${MESSAGES_STATIC_ROOT} ${MESSAGES_STATIC_ROOT}
|
||||
COPY --from=app-prod /app/ /app/
|
||||
@@ -179,6 +148,13 @@ RUN mkdir -p /shared-libs/usr/lib /shared-libs/usr/share/misc && \
|
||||
cp /usr/share/misc/magic.mgc /shared-libs/usr/share/misc/
|
||||
|
||||
|
||||
# ---- Strip Python for the distroless image (see deploy/python-uv/strip-python.sh) ----
|
||||
# Only runtime-distroless-prod (the real production target) uses this. The slim
|
||||
# runtime-prod inherits the full managed Python from the shared base instead.
|
||||
FROM ${PYTHON_UV_IMAGE} AS python-stripped-runtime
|
||||
RUN strip-python
|
||||
|
||||
|
||||
# ---- Distroless production runtime ----
|
||||
# Uses cc-debian13 (C runtime only) + python-build-standalone from uv.
|
||||
# Debug with: docker run --entrypoint='' gcr.io/distroless/cc-debian13:debug-nonroot sh
|
||||
@@ -188,7 +164,7 @@ ARG MESSAGES_STATIC_ROOT=/data/static
|
||||
WORKDIR /app
|
||||
|
||||
# Stripped Python installation (python-build-standalone via uv)
|
||||
COPY --from=python-runtime /opt/python /opt/python
|
||||
COPY --from=python-stripped-runtime /opt/python /opt/python
|
||||
# Python dependencies
|
||||
COPY --from=base-with-deps /venv /venv
|
||||
# libmagic shared library + magic database
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
#
|
||||
# or define new variables in an environment file to use with docker or docker compose:
|
||||
#
|
||||
# # env.d/production
|
||||
# # deploy/env
|
||||
# USER_NAME=foo
|
||||
# HOME=/home/foo
|
||||
#
|
||||
# docker run --rm --env-file env.d/production st-messages-backend:latest python manage.py migrate
|
||||
# docker run --rm --env-file deploy/env st-messages-backend:latest python manage.py migrate
|
||||
#
|
||||
|
||||
echo "🐳(entrypoint) creating user running in the container..."
|
||||
|
||||
@@ -383,7 +383,7 @@ class Base(Configuration):
|
||||
# No default on purpose: the MTA-to-MDA channel is authenticated solely by
|
||||
# an HS256 JWT signed with this shared secret, so a hardcoded fallback would
|
||||
# be internet-spoofable in production. The development value is supplied via
|
||||
# env.d/development/backend.defaults (and the matching mta-in.defaults), the
|
||||
# deploy/env/backend.defaults (and the matching mta-in.defaults), the
|
||||
# same way DJANGO_SECRET_KEY is handled. Unset → None → all MTA auth fails
|
||||
# closed.
|
||||
MDA_API_SECRET = values.Value(
|
||||
|
||||
@@ -20,17 +20,17 @@ description = "A Django MDA"
|
||||
keywords = ["Django", "Contacts", "Templates", "RBAC"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14.5,<4.0"
|
||||
requires-python = ">=3.14.6,<4.0"
|
||||
|
||||
# Note: after changing this list you must re-run `make deps-lock-back`
|
||||
dependencies = [
|
||||
"boto3==1.42.53",
|
||||
"botocore==1.42.53",
|
||||
"celery[redis]==5.6.2",
|
||||
"cryptography==46.0.5",
|
||||
"cryptography==48.0.1",
|
||||
"defusedxml==0.7.1",
|
||||
"dj-database-url==3.1.2",
|
||||
"django==5.2.11",
|
||||
"django==5.2.15",
|
||||
"django-celery-beat==2.8.1",
|
||||
"django-celery-results==2.6.0",
|
||||
"django-configurations==2.5.1",
|
||||
@@ -56,13 +56,13 @@ dependencies = [
|
||||
"nested-multipart-parser==1.6.0",
|
||||
"openai==2.21.0",
|
||||
"psycopg[binary]==3.3.3",
|
||||
"PyJWT==2.11.0",
|
||||
"PyJWT==2.13.0",
|
||||
"PySocks==1.7.1",
|
||||
"python-keycloak==5.5.1",
|
||||
"python-magic==0.4.27",
|
||||
"pyzstd==0.19.1",
|
||||
"redis==6.4.0",
|
||||
"requests==2.32.5",
|
||||
"requests==2.34.2",
|
||||
"sentry-sdk[django]==2.53.0",
|
||||
"libpff-python==20231205",
|
||||
"url-normalize==2.2.1",
|
||||
@@ -88,7 +88,7 @@ dev = [
|
||||
"pylint==4.0.4",
|
||||
"pytest-cov==7.0.0",
|
||||
"pytest-django==4.12.0",
|
||||
"pytest==9.0.2",
|
||||
"pytest==9.0.3",
|
||||
"pytest-icdiff==0.9",
|
||||
"pytest-repeat==0.9.4",
|
||||
"pytest-xdist==3.8.0",
|
||||
|
||||
Generated
+115
-113
@@ -1,6 +1,6 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14.5, <4.0"
|
||||
requires-python = ">=3.14.6, <4.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
@@ -347,55 +347,55 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "46.0.5"
|
||||
version = "48.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/12/45/870e7f4bef50e5f53b9f51d4428aee5290eedf58ba443f16b1ebb7ab8e66/cryptography-48.0.1.tar.gz", hash = "sha256:266f4ee051abb2f725b74ef8072b521ce1feacf685a3364fa6a6b45548db791a", size = 832989, upload-time = "2026-06-09T22:32:31.8Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/bc/ee4137cbbe105652c0ee4252792b78fc8e7afa4b8e61d9d5dc05a7f45731/cryptography-48.0.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:3e4a1a3232eef2e6c732827d5722db29a0cc8b27af2a4d865b094cf954be9ca1", size = 8008324, upload-time = "2026-06-09T22:31:00.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/85/6379d42181bfc713094f081360fc5784d6c816b599d45e7f082502d173ce/cryptography-48.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:32143b24adb918f078134e1e230f1eb8cc04886b92c28b5f0041aaf3e5699225", size = 4696243, upload-time = "2026-06-09T22:32:33.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/87/c85d147b53323c7eb4d850920c8901377323c2a0ff8d79c262d4fee89aa2/cryptography-48.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0d27a5696721ef7a672b8c810f6aded391058e0b9486e63e6d93baf765da691", size = 4713235, upload-time = "2026-06-09T22:31:40.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/58/67cbf8cf1ee7c54b439ca07bbecf8362c07afc11a3724fea70f745784add/cryptography-48.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb86ce1af36fe65041b6db9a8bb064ee621a7e5fded0f80d475ec243477cd242", size = 4702323, upload-time = "2026-06-09T22:31:42.191Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/c6/24266ac10c47f6cd2a865f4446062b466da1d1f10b27189eac00e61bf0c9/cryptography-48.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:b024e784ad6c077ee0147b35ea9cbfc1e34e1fd4c1dcca214c2794d73a12df08", size = 5300085, upload-time = "2026-06-09T22:31:58.703Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/bb/cc4b78784f97efc8c5874c2a9743708d172be6663024b34a0467885ae0c8/cryptography-48.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3752f2dbc8f07a30aad2932c986cea495b03bb554887828225da104f732852b6", size = 4746137, upload-time = "2026-06-09T22:31:31.01Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/52/0c44de3f5267f8fbe8e835138017522a333436166e406f0db9b9e6e3033f/cryptography-48.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:bd81490cd5801d755cf97bb68ac191f14b708470b1c7cf4580f669b9c9264cd8", size = 4333867, upload-time = "2026-06-09T22:32:28.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/2e/772d7adbfa931537bc401640b7cac9976bff689bda187833e5d63b428e49/cryptography-48.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:66fd0771e7b9c6dcd44cf1120690d2338d16d72795cf40cae2786a39eba65429", size = 4701805, upload-time = "2026-06-09T22:31:38.284Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/a3/b06844f303873493c963caf581c04df31c7035e0c1b0f02c4814d319ec80/cryptography-48.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:3fd2ca57062b241c856670b073487d2e86c4637937ca5601e48f97bf8e11fc8f", size = 5258461, upload-time = "2026-06-09T22:31:04.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/13/8b765e2e12b07c74941caadb9d1c8fdc006c4dfbf2b8f2d610519758954d/cryptography-48.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:0ee6ea481db1ab889cba043ec1eda17bb9c1ea79db6722f779c3667f9f70322f", size = 4745488, upload-time = "2026-06-09T22:32:30.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/aa/48972bce55049b32a94f4907eda4d75fa385aad8a39506cc2fc72196ecf0/cryptography-48.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f2ceef93cb096aa3c4cc4b5c94ca6131f9196d28c64d6111533402a9b2054d41", size = 4830256, upload-time = "2026-06-09T22:31:43.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/a2/e5079a032fb85cf6005046ca92bbd78b0c82dad2b5751ab8c311659da06f/cryptography-48.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bd3f92d76217892b15df84ca256c2c113d386fdda7a7d8691aeeced976507c6", size = 4979117, upload-time = "2026-06-09T22:31:05.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/a0/8f50cae9c74e718ed769d63ed5c74bd0ea830c9550a74629cebd1b9c7bc7/cryptography-48.0.1-cp311-abi3-win32.whl", hash = "sha256:b9a32b876490d66c8bcc9963ef220199569748434ab01a9d6aaeabf88e7f5158", size = 3304154, upload-time = "2026-06-09T22:32:16.845Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/69/0572c77dbace6fef72f33755bd52ea399c71367250d366237f8691826b9e/cryptography-48.0.1-cp311-abi3-win_amd64.whl", hash = "sha256:39489bfca54c7a1f6b297efcd8bc608ab92d16c4ca631b0cad4da46724588b24", size = 3817138, upload-time = "2026-06-09T22:32:00.388Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/06/3e768b4c3bc78201583fa35a0e18f640dd782ff41afba88f8545481a8874/cryptography-48.0.1-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:f817adc181390bd54f2f700107a7419040fb7c1bdf2fc26f36551a06a68c3345", size = 7989830, upload-time = "2026-06-09T22:31:07.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/13/6476736484b94041110c8340a3eb63962fea4975baea8cb4a512adb44d4d/cryptography-48.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d5d30989c6917b478b5817902e85fddaea2261efa8648383d965381ccb9e1ac4", size = 4689201, upload-time = "2026-06-09T22:31:09.745Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/62/65a87f34d2a431546e2509b85d55e8c90df86d668f6731da64d538512ac2/cryptography-48.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:df637c05205ea7c1d7fbcbe54bbfea648a52951155f997af13d895d0ecc96991", size = 4702822, upload-time = "2026-06-09T22:32:24.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/59/810b5204b0a9b10f4b6bc06bd551a8b609803cd931806bc3b71884b225e5/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:869c3b8a53bfe27147832df48b32adadf558249d50e76cb3769d40e986b13265", size = 4694875, upload-time = "2026-06-09T22:32:08.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/dc/d8ca05ffea724eec6d232ea6f18e74c269eb6bdfdcc9bfba689790d1325f/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:e361afba8918070d376df76f408a4f67fec0ee9cff81a99e48fe9a233ef59e17", size = 5290385, upload-time = "2026-06-09T22:31:15.212Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/8c/3be6cb4da181f5bb6c19cf560c2359d60644a6b5fc5b57854e528f47b296/cryptography-48.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d069066deead00ac7f090be101be875a06855908f7ec004c27b8fefb4acfb411", size = 4737082, upload-time = "2026-06-09T22:32:22.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/f6/d5f60a5a1434dbfd949e227fd0065d194c7e6b6ac526b17f5c06152b8231/cryptography-48.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:09f73a725d582cef64b91281a322cd798d14a33b2b6f2b7ad9531dc336d84c02", size = 4325328, upload-time = "2026-06-09T22:32:10.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/b7/ba75dd947a14b6ad907b01ae8f6b5b348cdd1b48142f0063dee9e20c1d9d/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:15254441469dd6bf027039453288e2072124f8b6603563f5d759e1c9b69273fa", size = 4694530, upload-time = "2026-06-09T22:31:53.105Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/29/50d6b9e8aff12d8b67afaeb3569335e32dc83a5723e3bbded24fdac9f809/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:8ace4507d1e6533c125f4fac754f8bb8b6a74c08e92179dabd7e16571a3efbf3", size = 5245046, upload-time = "2026-06-09T22:31:25.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/04/618f4115cfc0add0838c82507aa18a346089428da8653ad38b3ff36f5cb3/cryptography-48.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b4e391975f038e66432328639620a4aff2d307513b004f1ca06d6225bced815c", size = 4736660, upload-time = "2026-06-09T22:32:12.676Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/9c/06e062462a0de28a3b3911322eded4c16deb9f441b1b7575d3dc59488ab5/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42fcd8e26fe555d9b3577a135f5091fefa0aa4e99129c23fb56787a1bd4ada72", size = 4822229, upload-time = "2026-06-09T22:31:17.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/be/0561971eaaee4b8a0e7d5113c536921063ab91aaf23278ac374eaf881e11/cryptography-48.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1400da5e32a43253392277eac7490a60e497d810a63dd5608d71bbd7af507c9", size = 4966364, upload-time = "2026-06-09T22:31:32.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/27/728c77876f12b000820b69ae490f3c4083775e79e07827e9e60be07ad209/cryptography-48.0.1-cp314-cp314t-win32.whl", hash = "sha256:0df56b056bc17c1b7d6821dfa65216e62bd232d8ab05eb3db44e71d235651471", size = 3278498, upload-time = "2026-06-09T22:31:29.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/e3/79a612c6d7b1e6ee0edd43633d53035bec2cfb78c82b76f7864f39e36f34/cryptography-48.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:9de21387aa95e2a895823d0745b430bed4f33503ba9ab5e0b5311f33e37d66d2", size = 3798790, upload-time = "2026-06-09T22:31:56.697Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/6c/00fa2a95997164c8b2072ce327c23d4ab20809ccc323ea5fab91e53a4bba/cryptography-48.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:4fdc69f8e4316bcf0c8c8ec1f26f285d12e8142d88d96c876a59a03be3f6ae67", size = 7987408, upload-time = "2026-06-09T22:32:20.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/d9/45f309a7e4e5f3f8f121d6d3be9e94024a7726ec598d6e08ae04edb2f04d/cryptography-48.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48fe40804d4caa2288f24e70ca8c64c42dd826da0ad7e4f1b41b2128d679e6c8", size = 4690196, upload-time = "2026-06-09T22:31:54.74Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9f/a1bc8bcc798811b8527eb374bbccf30a3f3e806829d967118222bf1125eb/cryptography-48.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:86be3b1b0b6bf09482fb50a979c508d2950ed95f5621ec77f4e385962006b83a", size = 4696782, upload-time = "2026-06-09T22:31:45.615Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/c2/81a4fb4e4373c500bb526bc337ac5719dd31dd15b970b84a238168c6aa08/cryptography-48.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4ab0a343c807bbcd90c971cd1ecf072937cd01847a9e002bef88fb47ac6be577", size = 4696618, upload-time = "2026-06-09T22:31:11.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/0b/aa68b221dde92d09cb29a024ede17550ee21e77a404e59fc093c82bb51e1/cryptography-48.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9621de99d2da096006b629979efd8ae7eb2d8b822488d0c89ee4000c306c59b1", size = 5289970, upload-time = "2026-06-09T22:31:20.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/13/fba657f958d2af66ea959a4ba01212632089249d34af1ae48054136344d7/cryptography-48.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:88c852a0ae366e262e5a1744b685e6a433dc8788dd2a277e418bf4904203609d", size = 4731873, upload-time = "2026-06-09T22:31:22.253Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/4c/9a964756d24a26b3e34dfcb16f961b89838786e6700b635b0d1e3adff4b6/cryptography-48.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:43c5835e2cb98c8733d86f57d6fc879b613f5c3478607281c3e36daffc6dd8a6", size = 4330804, upload-time = "2026-06-09T22:31:36.56Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/0f/a10f3a6eb12950a10e3a874070283aa2dd5875b2bfd15fad8a3e17b3f13e/cryptography-48.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:fe0180af5bf9236518a087e35bf2d9a347d5f5f51e63c579d683ddff424e3d46", size = 4696217, upload-time = "2026-06-09T22:31:13.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/6f/5cd12f951165ea73ef85266775d97e4c763b2474ccfd816dd69d3a18d6f8/cryptography-48.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:b7a2d1a937a738a881737cec135a38bb61470589b17515b9f73f571d0ae10401", size = 5245252, upload-time = "2026-06-09T22:32:02.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/ab/8aaa12e4516ec4464033ab79b6f3b592bd5a92102467c4ace8a0d970203f/cryptography-48.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b74ca3b8e5ecdd833bf6a002ca41b4793bb27fb8f1c06ffaf2643c9e9140e31b", size = 4731388, upload-time = "2026-06-09T22:32:04.019Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/24/50027ea4dca85ec1f40688f3c24fb32ccacd520583c9592c3cc95628e6fb/cryptography-48.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c37f2461406063b417837f5f3daab668652acd82423efcd7f0a9f04be972de1", size = 4824186, upload-time = "2026-06-09T22:32:18.707Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/41/04cb5eb17085ade6f50cc611fb657df6a0f5885350de8764ece89c050197/cryptography-48.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:86fe77abb1bd87afb251d4d02ada7ecf53a32cee9b67d976abb2e45a13297475", size = 4964539, upload-time = "2026-06-09T22:31:18.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/bf/ed70785c496e89d7e73b7cda2d21f2447fd6d4e821714b8d04ff217fed92/cryptography-48.0.1-cp39-abi3-win32.whl", hash = "sha256:6b2c0c3e6ccf3ade7750f836ef3ee36eea250cc467d45c256895573ac08cc6f1", size = 3282307, upload-time = "2026-06-09T22:30:53.162Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/ff/371ea7d252656ee1eb6d83eeeef3d1d0c6baf1d6497687d081ea03814670/cryptography-48.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:9a49ca6c81417f6a5edb50375a60cccdd70fa0a91a5211829dbea74eba94d2ac", size = 3793408, upload-time = "2026-06-09T22:32:15.191Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -466,16 +466,16 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "5.2.11"
|
||||
version = "5.2.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "sqlparse" },
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/17/f2/3e57ef696b95067e05ae206171e47a8e53b9c84eec56198671ef9eaa51a6/django-5.2.11.tar.gz", hash = "sha256:7f2d292ad8b9ee35e405d965fbbad293758b858c34bbf7f3df551aeeac6f02d3", size = 10885017, upload-time = "2026-02-03T13:52:50.554Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2b/e3/31722f7284c9f43333daff9aee9184678e4487adcb5506af0db8cea09ce1/django-5.2.15.tar.gz", hash = "sha256:5154a9bf84ac01dde011e367f355c07dbb329532e06810dcf3ef2af269e236e7", size = 10873669, upload-time = "2026-06-03T13:03:35.892Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/a7/2b112ab430575bf3135b8304ac372248500d99c352f777485f53fdb9537e/django-5.2.11-py3-none-any.whl", hash = "sha256:e7130df33ada9ab5e5e929bc19346a20fe383f5454acb2cc004508f242ee92c0", size = 8291375, upload-time = "2026-02-03T13:52:42.47Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/b5/38140b1643c00d5c46ce69c78e6980fd285aee223100319631bedee4f5e7/django-5.2.15-py3-none-any.whl", hash = "sha256:0eb4a9bb1853a35b0286dbc6d916bd352c8c2687195a7f2d6f80cefd840e4970", size = 8311957, upload-time = "2026-06-03T13:03:31.329Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -876,11 +876,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -963,14 +963,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "joserfc"
|
||||
version = "1.6.2"
|
||||
version = "1.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/03/7a/c5bfcb971e5d330e21ae26935cc798dcf928a68c4e42a6990d0d6f1e83d6/joserfc-1.6.2.tar.gz", hash = "sha256:fd666ee075dbc4889b8b0353437847644777c1045c394838feeadb5aaecd72ab", size = 228888, upload-time = "2026-02-16T03:37:16.039Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f1/26/abe1ad855eb334b5ebc9c6495d4798e12bee70e5e8e815d54570710b8312/joserfc-1.7.2.tar.gz", hash = "sha256:537ffb8888b2df039cb5b6d017d7cff6f09d521ce65d89cc9b8ab752b1cff947", size = 233183, upload-time = "2026-06-29T09:03:10.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/e7/921d11bc89086a0b22a72e9f3ab8423855e1aab01a0da17bc83b702ceb3c/joserfc-1.6.2-py3-none-any.whl", hash = "sha256:2570a69cca95619fae4ffad9d051f0eb57a7741a5a9bd1afc996e74534e31a3d", size = 70380, upload-time = "2026-02-16T03:37:14.353Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/80/d1b30336582cced4dce0dae776508a6011723e32f907bc7a702c0b25890a/joserfc-1.7.2-py3-none-any.whl", hash = "sha256:ddd818c0ca9b4f17bbc2d72cb3966e6ded7502be089316c62c3cc64ae86132b5", size = 70426, upload-time = "2026-06-29T09:03:09.393Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1002,15 +1002,15 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "jwcrypto"
|
||||
version = "1.5.6"
|
||||
version = "1.5.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/db/870e5d5fb311b0bcf049630b5ba3abca2d339fd5e13ba175b4c13b456d08/jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039", size = 87168, upload-time = "2024-03-06T19:58:31.831Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/de/7e/53c14813521693e931e420d9db00efe317419ff194495903bce03402275e/jwcrypto-1.5.8.tar.gz", hash = "sha256:c3d7114b6f6e65b52f6b7da817eb8cb8423e1da31e1ef13508447c81ecbdcc34", size = 90772, upload-time = "2026-06-24T19:36:50.782Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/58/4a1880ea64032185e9ae9f63940c9327c6952d5584ea544a8f66972f2fda/jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789", size = 92520, upload-time = "2024-03-06T19:58:29.765Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/7c/f87c5db042c4f7b4476346fc0d22f1025a777fee08eebde3720d3b9c4eb5/jwcrypto-1.5.8-py3-none-any.whl", hash = "sha256:85aeb475f808d56bbc2f2ed1f6f73e6a317c4011a4321505f02f0aed695a3742", size = 96133, upload-time = "2026-06-24T19:36:49.519Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1167,10 +1167,10 @@ requires-dist = [
|
||||
{ name = "boto3", specifier = "==1.42.53" },
|
||||
{ name = "botocore", specifier = "==1.42.53" },
|
||||
{ name = "celery", extras = ["redis"], specifier = "==5.6.2" },
|
||||
{ name = "cryptography", specifier = "==46.0.5" },
|
||||
{ name = "cryptography", specifier = "==48.0.1" },
|
||||
{ name = "defusedxml", specifier = "==0.7.1" },
|
||||
{ name = "dj-database-url", specifier = "==3.1.2" },
|
||||
{ name = "django", specifier = "==5.2.11" },
|
||||
{ name = "django", specifier = "==5.2.15" },
|
||||
{ name = "django-celery-beat", specifier = "==2.8.1" },
|
||||
{ name = "django-celery-results", specifier = "==2.6.0" },
|
||||
{ name = "django-configurations", specifier = "==2.5.1" },
|
||||
@@ -1204,11 +1204,11 @@ requires-dist = [
|
||||
{ name = "pipdeptree", marker = "extra == 'dev'", specifier = "==2.31.0" },
|
||||
{ name = "prometheus-client", specifier = "==0.24.1" },
|
||||
{ name = "psycopg", extras = ["binary"], specifier = "==3.3.3" },
|
||||
{ name = "pyjwt", specifier = "==2.11.0" },
|
||||
{ name = "pyjwt", specifier = "==2.13.0" },
|
||||
{ name = "pylint", marker = "extra == 'dev'", specifier = "==4.0.4" },
|
||||
{ name = "pylint-django", marker = "extra == 'dev'", specifier = "==2.7.0" },
|
||||
{ name = "pysocks", specifier = "==1.7.1" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==9.0.2" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==9.0.3" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = "==7.0.0" },
|
||||
{ name = "pytest-django", marker = "extra == 'dev'", specifier = "==4.12.0" },
|
||||
{ name = "pytest-icdiff", marker = "extra == 'dev'", specifier = "==0.9" },
|
||||
@@ -1219,7 +1219,7 @@ requires-dist = [
|
||||
{ name = "pyzstd", specifier = "==0.19.1" },
|
||||
{ name = "radicale", marker = "extra == 'dev'", specifier = "==3.6.1" },
|
||||
{ name = "redis", specifier = "==6.4.0" },
|
||||
{ name = "requests", specifier = "==2.32.5" },
|
||||
{ name = "requests", specifier = "==2.34.2" },
|
||||
{ name = "responses", marker = "extra == 'dev'", specifier = "==0.26.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = "==0.15.2" },
|
||||
{ name = "sentry-sdk", extras = ["django"], specifier = "==2.53.0" },
|
||||
@@ -1245,28 +1245,32 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "msgpack"
|
||||
version = "1.1.2"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/31/f9/c0a1c127f9049db9155afc316952ea571720dd01833ff5e4d7e8e6352dbb/msgpack-1.2.1.tar.gz", hash = "sha256:04c721c2c7448767e9e3f2520a475663d8ee0f09c31890f6d2bd70fd636a9647", size = 183960, upload-time = "2026-06-18T16:13:52.594Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/71/201105712d0a2ff07b7873ed3c220292fb2ea5120603c00c4b634bcdafb3/msgpack-1.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e23ce8d5f7aa6ea6d2a2b326b4ba46c985dbb204523759984430db7114f8aa00", size = 81127, upload-time = "2025-10-08T09:15:24.408Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/9f/38ff9e57a2eade7bf9dfee5eae17f39fc0e998658050279cbb14d97d36d9/msgpack-1.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6c15b7d74c939ebe620dd8e559384be806204d73b4f9356320632d783d1f7939", size = 84981, upload-time = "2025-10-08T09:15:25.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/a9/3536e385167b88c2cc8f4424c49e28d49a6fc35206d4a8060f136e71f94c/msgpack-1.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99e2cb7b9031568a2a5c73aa077180f93dd2e95b4f8d3b8e14a73ae94a9e667e", size = 411885, upload-time = "2025-10-08T09:15:27.22Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/40/dc34d1a8d5f1e51fc64640b62b191684da52ca469da9cd74e84936ffa4a6/msgpack-1.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:180759d89a057eab503cf62eeec0aa61c4ea1200dee709f3a8e9397dbb3b6931", size = 419658, upload-time = "2025-10-08T09:15:28.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ef/2b92e286366500a09a67e03496ee8b8ba00562797a52f3c117aa2b29514b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:04fb995247a6e83830b62f0b07bf36540c213f6eac8e851166d8d86d83cbd014", size = 403290, upload-time = "2025-10-08T09:15:29.764Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/90/e0ea7990abea5764e4655b8177aa7c63cdfa89945b6e7641055800f6c16b/msgpack-1.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8e22ab046fa7ede9e36eeb4cfad44d46450f37bb05d5ec482b02868f451c95e2", size = 415234, upload-time = "2025-10-08T09:15:31.022Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/4e/9390aed5db983a2310818cd7d3ec0aecad45e1f7007e0cda79c79507bb0d/msgpack-1.1.2-cp314-cp314-win32.whl", hash = "sha256:80a0ff7d4abf5fecb995fcf235d4064b9a9a8a40a3ab80999e6ac1e30b702717", size = 66391, upload-time = "2025-10-08T09:15:32.265Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/f1/abd09c2ae91228c5f3998dbd7f41353def9eac64253de3c8105efa2082f7/msgpack-1.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:9ade919fac6a3e7260b7f64cea89df6bec59104987cbea34d34a2fa15d74310b", size = 73787, upload-time = "2025-10-08T09:15:33.219Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/b0/9d9f667ab48b16ad4115c1935d94023b82b3198064cb84a123e97f7466c1/msgpack-1.1.2-cp314-cp314-win_arm64.whl", hash = "sha256:59415c6076b1e30e563eb732e23b994a61c159cec44deaf584e5cc1dd662f2af", size = 66453, upload-time = "2025-10-08T09:15:34.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/67/93f80545eb1792b61a217fa7f06d5e5cb9e0055bed867f43e2b8e012e137/msgpack-1.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:897c478140877e5307760b0ea66e0932738879e7aa68144d9b78ea4c8302a84a", size = 85264, upload-time = "2025-10-08T09:15:35.61Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/1c/33c8a24959cf193966ef11a6f6a2995a65eb066bd681fd085afd519a57ce/msgpack-1.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a668204fa43e6d02f89dbe79a30b0d67238d9ec4c5bd8a940fc3a004a47b721b", size = 89076, upload-time = "2025-10-08T09:15:36.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/6b/62e85ff7193663fbea5c0254ef32f0c77134b4059f8da89b958beb7696f3/msgpack-1.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5559d03930d3aa0f3aacb4c42c776af1a2ace2611871c84a75afe436695e6245", size = 435242, upload-time = "2025-10-08T09:15:37.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/47/5c74ecb4cc277cf09f64e913947871682ffa82b3b93c8dad68083112f412/msgpack-1.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70c5a7a9fea7f036b716191c29047374c10721c389c21e9ffafad04df8c52c90", size = 432509, upload-time = "2025-10-08T09:15:38.794Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/a4/e98ccdb56dc4e98c929a3f150de1799831c0a800583cde9fa022fa90602d/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2cb069d8b981abc72b41aea1c580ce92d57c673ec61af4c500153a626cb9e20", size = 415957, upload-time = "2025-10-08T09:15:40.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/28/6951f7fb67bc0a4e184a6b38ab71a92d9ba58080b27a77d3e2fb0be5998f/msgpack-1.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d62ce1f483f355f61adb5433ebfd8868c5f078d1a52d042b0a998682b4fa8c27", size = 422910, upload-time = "2025-10-08T09:15:41.505Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/03/42106dcded51f0a0b5284d3ce30a671e7bd3f7318d122b2ead66ad289fed/msgpack-1.1.2-cp314-cp314t-win32.whl", hash = "sha256:1d1418482b1ee984625d88aa9585db570180c286d942da463533b238b98b812b", size = 75197, upload-time = "2025-10-08T09:15:42.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/86/d0071e94987f8db59d4eeb386ddc64d0bb9b10820a8d82bcd3e53eeb2da6/msgpack-1.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5a46bf7e831d09470ad92dff02b8b1ac92175ca36b087f904a0519857c6be3ff", size = 85772, upload-time = "2025-10-08T09:15:43.954Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/f2/08ace4142eb281c12701fc3b93a10795e4d4dc7f753911d836675050f886/msgpack-1.1.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d99ef64f349d5ec3293688e91486c5fdb925ed03807f64d98d205d2713c60b46", size = 70868, upload-time = "2025-10-08T09:15:44.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/58/cce442852c6b9e1639c7c8ac8fd9143121cb32dab0f308df4d1426a8eb9c/msgpack-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:05f340e47e7e47d2da8db9b53e1bb1d294369e9ef45a747441309f6650b8351d", size = 83610, upload-time = "2026-06-18T16:13:25.724Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/5c/15b4c7a0182f75ffa90751958ba36a9c01cafee367d49a3edc10ed140b01/msgpack-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:810b916696c86ef0deb3b74588480224df4c1b071136c34183e4a2a4284d7ac7", size = 83138, upload-time = "2026-06-18T16:13:26.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a6/99e58722feaffc5f2fbcc0c8c0d1451ab9f84097f7af87291b46af2390f4/msgpack-1.2.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca0dacff965c47afdc3749a8469d7302a8f801d6a28758d55120d75e66ce6889", size = 406090, upload-time = "2026-06-18T16:13:28.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/03/8c63e8cf52958534ef688625965ab04c269a6cadd8caef16758b380a821a/msgpack-1.2.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e2bf9280bceb5efca998435904b5d3e9fdbcc11d90dc9df30aec7973252b720", size = 412106, upload-time = "2026-06-18T16:13:29.427Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/d2/155d9e71b40e41fd934bc0c48b9b2770f22263e1ac20aad8e29fdca7be3f/msgpack-1.2.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aa6c4be5d1c02a42b066ca6ddb71adf36432868fdcdb6ee87e634e86e0674190", size = 374851, upload-time = "2026-06-18T16:13:30.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/48/deaf2326262a8d5ea3295ce9649912ecd3f551ba7ec8e33c665d2ba583f3/msgpack-1.2.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec0e675d59150a6269ddc9139087c722292664a37d071a849c05c473350f1f2d", size = 396168, upload-time = "2026-06-18T16:13:31.977Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/2a/b4410f906c2ec0008f1608d3ab5143afc3ad3f4e6da0fed3ea2231d0bef4/msgpack-1.2.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:dd3bfe82d53edfe4b7fc9a7ec9761e23a7a5b1dac22264505af428253c29ed24", size = 371959, upload-time = "2026-06-18T16:13:33.282Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/86/1edc67270099a528fa2093ea60fe191233cd238e4bd30cfacf7db79fc959/msgpack-1.2.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5ad5467fc3f68b5468e06c5f788d712e9f8ffc8b0cd1bcb160c105c1ee92dae7", size = 408457, upload-time = "2026-06-18T16:13:34.567Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/90/8b630fef07d8c5ab457b71ff2c217910c83d333c7a68472c186e87cc504a/msgpack-1.2.1-cp314-cp314-win32.whl", hash = "sha256:98b58bdb89c46190e4609bb36abe17c6d4105ad13f9c5f8f6f64d320f8ced3fb", size = 65942, upload-time = "2026-06-18T16:13:36.056Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/f1/467b81e98b24dd3885d7b1857728797b4ffc76a7a7483af4fb321a07de3c/msgpack-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:74847557e28ce71bd3c438a447ca90e4b507e997ddbdef8a12a7b283b86c156b", size = 72627, upload-time = "2026-06-18T16:13:37.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/1d/5d8c4c89985feb6acefb82a09e501c60392261856d2408d20bfe4f0360b1/msgpack-1.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:b50b727bd652bdc37d950336c848ef20ec54a4cafc38dce19b1cd86ad625d0f7", size = 66908, upload-time = "2026-06-18T16:13:38.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/02/ad2afb678b4de94496cd432b581759b756a92c1192d8c767edd6b132efdc/msgpack-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8d00f177ca88a77c1cf848d204a38f249751650b601cb6532acc68805d8a8273", size = 86000, upload-time = "2026-06-18T16:13:39.44Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/74/0b797484013128837f3b1cbb6cea019277c4de4e377dc512b4d9a0f92940/msgpack-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5bb9c386f0a329c035ddbab4b72d1028bf9627add8dda41070288563d57ed1b1", size = 86544, upload-time = "2026-06-18T16:13:40.447Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/b4/b774d7eb95561739907fec675582f83203cf41c597a418c2589b4bfb8e9d/msgpack-1.2.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20466cca18c49c7292a8984bc15d65857b171e7264bdcb5f96baf8be238791fc", size = 427661, upload-time = "2026-06-18T16:13:41.574Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/f9/3243191dc9937e00756c8bc1b0272fed8f23758e43df2a3b46f533e5090f/msgpack-1.2.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:196300e7e5d6e74d50f1607ab9c06c4a1484c383cd22defd727902591f7e8dde", size = 426375, upload-time = "2026-06-18T16:13:42.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/c7/1693111db9944ba4ad4b67a1e788400d78a0b6af7a6523dc7e4e58f8274b/msgpack-1.2.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575957e79cd51903a4e8495a242442949641e08f1efd5197b43bebd3ea7682b4", size = 380495, upload-time = "2026-06-18T16:13:44.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3e/2b/92f86956a0c13e8662f7e2ad630c4eb4db07497b967589bd5245e018b2c1/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8c2ed1e48cc0f460bf3c7780e7137ff21a4e18433451916f2442c1b21036cd7d", size = 410897, upload-time = "2026-06-18T16:13:45.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/ea/1479f72d200313a76fc2f823a79d1e07ed052ab7b8a0280640aa7b95de42/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:5f6277e5f783c36786a145e0247fc189a03f35f84b251646e53592d2bc12b355", size = 378519, upload-time = "2026-06-18T16:13:46.998Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/4d/fa006060ffa1011d32bfae826fe766fe73e02982183601633b7121058ab3/msgpack-1.2.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9389552ecf4784886345ead0647e4edc96bee37cbab05b75540f542f766c48c", size = 419815, upload-time = "2026-06-18T16:13:48.205Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/e1/aab6c946570496b78e67804721f3d5e2d62a93081b9b37df77764ef56347/msgpack-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:c1c79a604a2969a868a78b6ebd27a887e00c624f14f66b3038e0590cb23332d1", size = 70914, upload-time = "2026-06-18T16:13:49.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/0a/e608956488a2af014cfe6e3d665e090b8ee42aa14b07f8f95b8880d66b09/msgpack-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f12038a35fabd52e56a3547bab42401af49a45caa6dd00b34c44de235bc93ee2", size = 77999, upload-time = "2026-06-18T16:13:50.467Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/8a/27e2e57055176e366a46b85d02d68e7a5bcfbdd8474c9706375d965f24d3/msgpack-1.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0adcf06ffde0777c0e1a9b771a2b1c4226ba1bbf748c8efcc02fcdeca3299107", size = 71160, upload-time = "2026-06-18T16:13:51.498Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1339,11 +1343,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pip"
|
||||
version = "26.0.1"
|
||||
version = "26.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/48/83/0d7d4e9efe3344b8e2fe25d93be44f64b65364d3c8d7bc6dc90198d5422e/pip-26.0.1.tar.gz", hash = "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8", size = 1812747, upload-time = "2026-02-05T02:20:18.702Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/01/91/47e7d486260f618783899587af63ccf7980fb60245c3e63dd4571c6b57ad/pip-26.1.2.tar.gz", hash = "sha256:f49cd134c61cf2fd75e0ce2676db03e4054504a5a4986d00f8299ae632dc4605", size = 1840799, upload-time = "2026-05-31T17:33:58.56Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723, upload-time = "2026-02-05T02:20:16.416Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/95/6b5cb3461ea5673ba0995989746db58eb18b91b54dbf331e72f569540946/pip-26.1.2-py3-none-any.whl", hash = "sha256:382ff9f685ee3bc25864f820aa50505825f10f5458ffff07e30a6d96e5715cab", size = 1813144, upload-time = "2026-05-31T17:33:56.772Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1565,20 +1569,20 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.2"
|
||||
version = "2.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.11.0"
|
||||
version = "2.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1643,7 +1647,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "9.0.2"
|
||||
version = "9.0.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
@@ -1652,9 +1656,9 @@ dependencies = [
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1855,7 +1859,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.5"
|
||||
version = "2.34.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@@ -1863,9 +1867,9 @@ dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2082,21 +2086,19 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tornado"
|
||||
version = "6.5.4"
|
||||
version = "6.5.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2176,11 +2178,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
version = "2.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Supply-chain cooldown: only resolve package versions that have been public for
|
||||
# at least this many DAYS (npm >= 11.16). This image runs `npm install`, so the
|
||||
# cooldown actively gates the Playwright dependency tree at build time. Override
|
||||
# a deliberately-fresh pin for a single run with `npm install --min-release-age=0`.
|
||||
min-release-age=1
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
FROM docker:27-cli AS docker-cli
|
||||
|
||||
# Main image
|
||||
FROM node:22-slim
|
||||
FROM node:24.18.0-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5
|
||||
|
||||
ENV npm_config_cache=/tmp/npm-cache
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/tmp/playwright-browsers
|
||||
@@ -22,7 +22,7 @@ WORKDIR /app
|
||||
|
||||
|
||||
# Install deps and run all npm commands as the user running the container
|
||||
COPY package*.json ./
|
||||
COPY package*.json .npmrc ./
|
||||
|
||||
# Install dependencies and postinstall playwright with deps
|
||||
RUN npm install
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ Caddy is used as a reverse proxy to serve the frontend and the backend on the sa
|
||||
|
||||
### Environment variables
|
||||
|
||||
E2E configuration files are located in `env.d/development/*.e2e`:
|
||||
E2E configuration files are located in `deploy/env/*.e2e`:
|
||||
- `backend.e2e`: Backend configuration for tests
|
||||
- `frontend.e2e`: Frontend configuration for tests
|
||||
- `keycloak.e2e`: Keycloak configuration for tests
|
||||
|
||||
+11
-11
@@ -20,8 +20,8 @@ services:
|
||||
environment:
|
||||
- DJANGO_CONFIGURATION=E2E
|
||||
env_file:
|
||||
- ../../env.d/development/backend.defaults
|
||||
- ../../env.d/development/backend.e2e
|
||||
- ../../deploy/env/backend.defaults
|
||||
- ../../deploy/env/backend.e2e
|
||||
depends_on: !override
|
||||
- backend
|
||||
|
||||
@@ -55,8 +55,8 @@ services:
|
||||
- --hostname-admin=$${ADMIN_HOST}
|
||||
- --http-port=8802
|
||||
env_file:
|
||||
- ../../env.d/development/keycloak.defaults
|
||||
- ../../env.d/development/keycloak.e2e
|
||||
- ../../deploy/env/keycloak.defaults
|
||||
- ../../deploy/env/keycloak.e2e
|
||||
ports: !reset []
|
||||
depends_on: !override
|
||||
- postgresql
|
||||
@@ -68,8 +68,8 @@ services:
|
||||
environment:
|
||||
- DJANGO_CONFIGURATION=E2E
|
||||
env_file:
|
||||
- ../../env.d/development/backend.defaults
|
||||
- ../../env.d/development/backend.e2e
|
||||
- ../../deploy/env/backend.defaults
|
||||
- ../../deploy/env/backend.e2e
|
||||
depends_on: !override
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
@@ -87,8 +87,8 @@ services:
|
||||
file: ../../compose.yaml
|
||||
service: mta-in
|
||||
env_file:
|
||||
- ../../env.d/development/mta-in.defaults
|
||||
- ../../env.d/development/mta-in.e2e
|
||||
- ../../deploy/env/mta-in.defaults
|
||||
- ../../deploy/env/mta-in.e2e
|
||||
ports: !reset []
|
||||
depends_on: !override
|
||||
- backend
|
||||
@@ -128,8 +128,8 @@ services:
|
||||
service: frontend-base
|
||||
command: ["npm", "run", "dev"]
|
||||
env_file:
|
||||
- ../../env.d/development/frontend.defaults
|
||||
- ../../env.d/development/frontend.e2e
|
||||
- ../../deploy/env/frontend.defaults
|
||||
- ../../deploy/env/frontend.e2e
|
||||
volumes:
|
||||
- ../frontend/:/home/frontend/
|
||||
ports: !reset []
|
||||
@@ -159,7 +159,7 @@ services:
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./playwright.config.ts:/app/playwright.config.ts
|
||||
- ../../env.d/development:/app/env.d/development:ro
|
||||
- ../../deploy/env:/app/deploy/env:ro
|
||||
depends_on:
|
||||
docker-sock-proxy:
|
||||
condition: service_started
|
||||
|
||||
Generated
+21
-21
@@ -10,22 +10,22 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.57.0",
|
||||
"@types/node": "22.19.3"
|
||||
"@playwright/test": "1.61.1",
|
||||
"@types/node": "24.13.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0 <23.0.0",
|
||||
"node": "24.18.0",
|
||||
"npm": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz",
|
||||
"integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==",
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz",
|
||||
"integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.57.0"
|
||||
"playwright": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -35,13 +35,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.19.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
|
||||
"integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
|
||||
"version": "24.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz",
|
||||
"integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
@@ -60,13 +60,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz",
|
||||
"integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==",
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.57.0"
|
||||
"playwright-core": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -79,9 +79,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.57.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz",
|
||||
"integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==",
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -92,9 +92,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "0.8.0",
|
||||
"description": "End-to-end tests for Messages application",
|
||||
"engines": {
|
||||
"node": ">=22.0.0 <23.0.0",
|
||||
"node": "24.18.0",
|
||||
"npm": ">=10.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -24,7 +24,7 @@
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.57.0",
|
||||
"@types/node": "22.19.3"
|
||||
"@playwright/test": "1.61.1",
|
||||
"@types/node": "24.13.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Use the isolated/symlinked install layout (pnpm-style) instead of the flat
|
||||
# hoisted one. This deduplicates the react-aria ecosystem to a single physical
|
||||
# copy per version (a `.store/` + symlinks), shrinking node_modules ~3x and
|
||||
# cutting install time from minutes to ~20s on a fast filesystem.
|
||||
install-strategy=linked
|
||||
|
||||
# Supply-chain cooldown: only resolve package versions that have been public for
|
||||
# at least this many DAYS (npm >= 11.16). Blunts "publish malware, get pulled in
|
||||
# instantly" attacks by refusing brand-new releases when the tree is (re)resolved
|
||||
# at `npm install` time. Frozen `npm ci` installs the already-vetted lockfile as
|
||||
# is. Complements the check:deps guardrail (scripts/check-node-modules.mjs).
|
||||
min-release-age=1
|
||||
+20
-8
@@ -1,24 +1,36 @@
|
||||
ARG FRONTEND_IMAGE=frontend-build-output
|
||||
|
||||
FROM node:22-alpine AS frontend-deps
|
||||
FROM node:24.18.0-slim@sha256:cb4e8f7c443347358b7875e717c29e27bf9befc8f5a26cf18af3c3dec80e58c5 AS frontend-dev-base
|
||||
|
||||
ENV npm_config_cache=/tmp/npm-cache
|
||||
|
||||
ARG DOCKER_USER=1000
|
||||
WORKDIR /home/frontend/
|
||||
|
||||
RUN mkdir -p /tmp/npm-cache && chown -R ${DOCKER_USER} /tmp/npm-cache /home/frontend
|
||||
# Pre-create an empty node_modules owned by DOCKER_USER. The runtime mounts a
|
||||
# named volume there (see compose.yaml); Docker initializes an empty volume from
|
||||
# the image's directory *including its ownership*, so this is what makes the
|
||||
# volume writable by the non-root container user. Without it the fresh volume is
|
||||
# root-owned and `npm ci` fails with EACCES.
|
||||
RUN mkdir -p /tmp/npm-cache /home/frontend/node_modules \
|
||||
&& chown -R ${DOCKER_USER} /tmp/npm-cache /home/frontend
|
||||
|
||||
# Let a process running with an arbitrary uid (the host uid on bind mounts) and
|
||||
# group root register itself in /etc/passwd at runtime. The Capacitor CLI calls
|
||||
# os.userInfo(), which throws ENOENT under musl when the uid has no passwd entry
|
||||
# (see the frontend-mobile entrypoint in compose.yaml). Harmless otherwise.
|
||||
# os.userInfo(), which throws when the uid has no passwd entry (glibc and musl
|
||||
# alike; see the frontend-mobile entrypoint in compose.yaml). Harmless otherwise.
|
||||
RUN chmod g=u /etc/passwd
|
||||
|
||||
# Install deps and run all npm commands as the user running the container
|
||||
# Run all npm commands as the user running the container.
|
||||
USER ${DOCKER_USER}
|
||||
COPY --chown=${DOCKER_USER} package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# ---- Production dependency install (ONLY for the prod build below) ----
|
||||
# frontend-build runs `npm run build`, which needs node_modules present in the
|
||||
# image; the dev/tools services use the lean frontend-dev-base above instead.
|
||||
# .npmrc sets install-strategy=linked (symlinked/deduplicated node_modules).
|
||||
FROM frontend-dev-base AS frontend-deps
|
||||
COPY --chown=${DOCKER_USER} package*.json .npmrc ./
|
||||
RUN npm ci
|
||||
|
||||
FROM frontend-deps AS frontend-build
|
||||
|
||||
@@ -39,7 +51,7 @@ COPY --from=frontend-build /home/frontend/dist /app
|
||||
# frontend-deps, frontend-build, and frontend-build-output entirely
|
||||
FROM ${FRONTEND_IMAGE} AS frontend-source
|
||||
|
||||
FROM alpine:3.21 AS tools-download
|
||||
FROM alpine:3.21@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d AS tools-download
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG CADDY_VERSION=2.9.1
|
||||
|
||||
Generated
+2922
-6508
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.0.0 <23.0.0"
|
||||
"node": "24.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -28,7 +28,8 @@
|
||||
"mobile:ota:bucket": "node scripts/create-ota-bucket.mjs",
|
||||
"mobile:ota:keygen": "node scripts/generate-ota-keys.mjs",
|
||||
"mobile:ota:publish": "node scripts/publish-ota.mjs",
|
||||
"mobile:assets": "capacitor-assets generate --ios --android --pwa --iconBackgroundColor '#ffffff' --iconBackgroundColorDark '#161B28' --splashBackgroundColor '#ffffff' --splashBackgroundColorDark '#161B28'"
|
||||
"mobile:assets": "capacitor-assets generate --ios --android --pwa --iconBackgroundColor '#ffffff' --iconBackgroundColorDark '#161B28' --splashBackgroundColor '#ffffff' --splashBackgroundColorDark '#161B28'",
|
||||
"check:deps": "node scripts/check-node-modules.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blocknote/core": "0.51.4",
|
||||
@@ -40,9 +41,9 @@
|
||||
"@capacitor/filesystem": "8.1.2",
|
||||
"@capacitor/share": "8.0.1",
|
||||
"@capgo/capacitor-updater": "8.50.1",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.3.0",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.3.1",
|
||||
"@gouvfr-lasuite/drive-sdk": "0.0.2",
|
||||
"@gouvfr-lasuite/ui-kit": "0.23.2",
|
||||
"@gouvfr-lasuite/ui-kit": "0.26.0",
|
||||
"@hookform/resolvers": "5.2.2",
|
||||
"@sentry/react": "10.53.1",
|
||||
"@tanstack/react-query": "5.90.20",
|
||||
@@ -50,6 +51,8 @@
|
||||
"@tanstack/react-router": "1.170.8",
|
||||
"@tanstack/react-router-devtools": "1.167.0",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tiptap/core": "^3.27.3",
|
||||
"@tiptap/pm": "^3.27.3",
|
||||
"@viselect/react": "3.9.0",
|
||||
"clsx": "2.1.1",
|
||||
"date-fns": "4.1.0",
|
||||
@@ -58,10 +61,12 @@
|
||||
"i18next": "25.7.4",
|
||||
"i18next-http-backend": "3.0.2",
|
||||
"react": "19.2.4",
|
||||
"react-aria-components": "1.19.0",
|
||||
"react-dom": "19.2.4",
|
||||
"react-dropzone": "14.4.0",
|
||||
"react-hook-form": "7.71.1",
|
||||
"react-i18next": "16.5.4",
|
||||
"react-icons": "5.7.0",
|
||||
"react-markdown": "10.1.0",
|
||||
"react-resizable-panels": "4.2.2",
|
||||
"react-toastify": "11.0.5",
|
||||
@@ -80,7 +85,7 @@
|
||||
"@tanstack/eslint-plugin-query": "5.91.4",
|
||||
"@tanstack/router-cli": "1.167.13",
|
||||
"@tanstack/router-plugin": "1.168.11",
|
||||
"@types/node": "25.2.0",
|
||||
"@types/node": "24.13.2",
|
||||
"@types/react": "19.2.10",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-legacy": "8.2.1",
|
||||
@@ -106,7 +111,18 @@
|
||||
"@rollup/rollup-linux-x64-musl": "4.57.1"
|
||||
},
|
||||
"overrides": {
|
||||
"zod": "4.3.6"
|
||||
"zod": "4.3.6",
|
||||
"@types/node": "24.13.2",
|
||||
"@gouvfr-lasuite/cunningham-react": "4.3.1",
|
||||
"react-aria": "3.50.0",
|
||||
"react-stately": "3.48.0",
|
||||
"react-aria-components": "1.19.0",
|
||||
"@internationalized/date": "3.12.2",
|
||||
"@react-aria/calendar": "3.10.1",
|
||||
"@react-aria/datepicker": "3.17.1",
|
||||
"@react-aria/i18n": "3.13.1",
|
||||
"@react-stately/calendar": "3.10.1",
|
||||
"@react-stately/datepicker": "3.17.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults",
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env node
|
||||
// Guardrail for the frozen dependency install.
|
||||
//
|
||||
// Fails loudly if node_modules regresses to the pre-cleanup bloat, i.e. if:
|
||||
// 1. the tree grows past LIMIT_GB (see below), or
|
||||
// 2. any Adobe React Spectrum package reappears (@adobe/react-spectrum,
|
||||
// @react-spectrum/*, @spectrum-icons/*). These are dragged in only by
|
||||
// mispackaged @react-types/* deps and are removed via package.json
|
||||
// "overrides" — this check is what keeps them removed.
|
||||
//
|
||||
// Only relevant at *freeze* time, when the lockfile can actually change: `make
|
||||
// install-front` runs `npm install` then `npm run check:deps`. Plain `npm ci`
|
||||
// (install-frozen-front, the Dockerfile) just reproduces an already-validated
|
||||
// lockfile, so it deliberately does NOT re-run this. Exit non-zero => freeze fails.
|
||||
|
||||
import { readdirSync, lstatSync } from 'node:fs';
|
||||
import { join, sep } from 'node:path';
|
||||
|
||||
const ROOT = 'node_modules';
|
||||
|
||||
// Size ceiling for node_modules. Bump this deliberately (with review) if the
|
||||
// tree legitimately needs to grow; the point is to catch accidental bloat.
|
||||
const LIMIT_GB = 1.0;
|
||||
const LIMIT_BYTES = LIMIT_GB * 1024 ** 3;
|
||||
|
||||
// Package names that must never come back.
|
||||
const FORBIDDEN = ['@adobe/react-spectrum', '@react-spectrum', '@spectrum-icons'];
|
||||
// Same, as encoded in the linked strategy's `.store/` dir names (scope+name@ver).
|
||||
const FORBIDDEN_STORE = [/^@adobe\+react-spectrum@/, /^@react-spectrum\+/, /^@spectrum-icons\+/];
|
||||
|
||||
let bytes = 0;
|
||||
const offenders = new Set();
|
||||
// Directories we failed to read. A truncated scan could hide bloat and make an
|
||||
// over-limit tree look fine, so these are surfaced as a hard failure below.
|
||||
const scanErrors = [];
|
||||
|
||||
function flagIfForbidden(fullPath, entryName) {
|
||||
const posix = fullPath.split(sep).join('/');
|
||||
for (const f of FORBIDDEN) {
|
||||
if (posix.includes(`/${f}/`) || posix.endsWith(`/${f}`)) offenders.add(f);
|
||||
}
|
||||
for (const re of FORBIDDEN_STORE) {
|
||||
if (re.test(entryName)) offenders.add(entryName.replace(/@[^@]*$/, '').replace(/\+/g, '/'));
|
||||
}
|
||||
}
|
||||
|
||||
function walk(dir) {
|
||||
let entries;
|
||||
try {
|
||||
entries = readdirSync(dir, { withFileTypes: true });
|
||||
} catch (err) {
|
||||
// Record and keep going so the scan covers as much as possible, but mark it
|
||||
// incomplete so the result can't be trusted as "under limit".
|
||||
scanErrors.push(`${dir}: ${err.code ?? err.message}`);
|
||||
return;
|
||||
}
|
||||
for (const e of entries) {
|
||||
const p = join(dir, e.name);
|
||||
if (e.isSymbolicLink()) continue; // don't follow: avoids double-counting + cycles
|
||||
if (e.isDirectory()) {
|
||||
flagIfForbidden(p, e.name);
|
||||
walk(p);
|
||||
} else if (e.isFile()) {
|
||||
try {
|
||||
bytes += lstatSync(p).size;
|
||||
} catch {
|
||||
/* transient/broken entry — ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
walk(ROOT);
|
||||
|
||||
const gb = bytes / 1024 ** 3;
|
||||
const problems = [];
|
||||
if (bytes > LIMIT_BYTES) {
|
||||
problems.push(`node_modules is ${gb.toFixed(2)} GB — over the ${LIMIT_GB} GB limit.`);
|
||||
}
|
||||
if (offenders.size) {
|
||||
problems.push(`forbidden packages present: ${[...offenders].sort().join(', ')}`);
|
||||
}
|
||||
if (scanErrors.length) {
|
||||
const shown = scanErrors.slice(0, 5).join('; ');
|
||||
const more = scanErrors.length > 5 ? ` (+${scanErrors.length - 5} more)` : '';
|
||||
problems.push(
|
||||
`scan incomplete — ${scanErrors.length} director${scanErrors.length === 1 ? 'y' : 'ies'} ` +
|
||||
`could not be read, so the ${gb.toFixed(2)} GB total may be understated: ${shown}${more}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (problems.length) {
|
||||
console.error('\n\x1b[1;31m✗ dependency guardrail failed\x1b[0m');
|
||||
for (const p of problems) console.error(` • ${p}`);
|
||||
console.error(
|
||||
'\nThe Adobe React Spectrum bloat (~2.3 GB) has regressed. It is kept out by\n' +
|
||||
'the react-aria / react-stately / @react-aria/calendar / @react-aria/datepicker\n' +
|
||||
'pins in package.json "overrides". Verify those are intact and run\n' +
|
||||
'`npm run check:deps` after regenerating the lockfile.\n',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.error(`\x1b[32m✓ node_modules OK\x1b[0m — ${gb.toFixed(2)} GB, no Adobe React Spectrum packages.`);
|
||||
@@ -21,7 +21,7 @@ make typecheck-jmap-email # ty (Astral)
|
||||
These spin up the same container image CI uses, so the only divergence
|
||||
between local results and CI is the host architecture (arm64 vs x86_64).
|
||||
|
||||
### Native Python 3.14.5+
|
||||
### Native Python 3.14.6+
|
||||
|
||||
```bash
|
||||
cd src/jmap-email
|
||||
@@ -56,7 +56,7 @@ Every PR should:
|
||||
|
||||
- **PEP 585 / PEP 604 typing.** Use `list[X]` / `dict[K, V]` /
|
||||
`X | None` rather than `typing.List[X]` etc. No `from __future__
|
||||
import annotations` — the supported floor is 3.14.5.
|
||||
import annotations` — the supported floor is 3.14.6.
|
||||
- **No legacy stdlib imports inside hot paths.** If a regex or
|
||||
`email.utils` helper is the wrong tool, write the loop.
|
||||
- **Module-private symbols** are prefixed with `_`. Anything in
|
||||
|
||||
+18
-12
@@ -1,14 +1,21 @@
|
||||
# Minimal test image for the jmap-email package.
|
||||
# Zero non-stdlib runtime deps; only pytest + hypothesis for the suite,
|
||||
# plus ``ty`` for static type-checking.
|
||||
FROM python:3.14.5-slim
|
||||
# Test image for the jmap-email package. Inherits the shared python-uv base
|
||||
# (managed CPython 3.14.6 + uv). NOTE: the PyPI *release* still builds on the
|
||||
# official python:3.14.6-slim image via bin/release-jmap-email.sh — that is the
|
||||
# release-parity environment; this image is only for CI lint/type/test.
|
||||
# Zero non-stdlib runtime deps; only pytest + hypothesis for the suite, plus
|
||||
# ``ty``/``ruff``/``pylint`` for type-checking and linting.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
FROM ${PYTHON_UV_IMAGE}
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ``ty`` ships as a single Rust binary on PyPI — no Node, no native
|
||||
# libs, no bootstrap step. Pin pytest / hypothesis / ty so the image
|
||||
# is deterministic; ``--no-cache-dir`` keeps the layer small.
|
||||
RUN pip install --no-cache-dir \
|
||||
# The shared base ships uv (no bundled pip on PATH); create a venv on the managed
|
||||
# Python and install the pinned dev tools into it. ``ty`` ships as a single Rust
|
||||
# binary on PyPI — no Node, no native libs.
|
||||
ENV VIRTUAL_ENV=/venv
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
RUN uv venv /venv \
|
||||
&& uv pip install --no-cache \
|
||||
"pytest==9.0.2" \
|
||||
"pytest-cov==7.0.0" \
|
||||
"pytest-xdist==3.8.0" \
|
||||
@@ -22,10 +29,9 @@ COPY pyproject.toml README.md LICENSE CHANGELOG.md ./
|
||||
COPY jmap_email ./jmap_email
|
||||
COPY tests ./tests
|
||||
|
||||
# Install the package itself (no runtime deps; zero new packages
|
||||
# resolved). Editable so that the mount-overlay in development
|
||||
# instantly picks up source edits.
|
||||
RUN pip install --no-cache-dir -e .
|
||||
# Install the package itself (no runtime deps; zero new packages resolved).
|
||||
# Editable so that the mount-overlay in development instantly picks up edits.
|
||||
RUN uv pip install --no-cache -e .
|
||||
|
||||
# Smoke import on build to catch package-layout breakage early.
|
||||
RUN python -c "import jmap_email; print('jmap-email', jmap_email.__version__)"
|
||||
|
||||
@@ -14,16 +14,17 @@ a regression test under `tests/`.
|
||||
> conforms to RFC 8621 §4 today; future 0.1.x releases will only add
|
||||
> fields, never remove or rename them.
|
||||
|
||||
## Why a Python 3.14.5 floor?
|
||||
## Why a Python 3.14.6 floor?
|
||||
|
||||
The standard library `email` package receives frequent bug fixes
|
||||
between patch releases, and this library wraps it directly — every fix
|
||||
to header parsing, RFC 2047 encoded-words, address-list defects, etc.
|
||||
surfaces immediately in our output. The 3.14.5 floor is not arbitrary:
|
||||
surfaces immediately in our output. The 3.14.6 floor is not arbitrary:
|
||||
it carries
|
||||
[gh-128110](https://github.com/python/cpython/issues/128110)
|
||||
(RFC 2047 §6.2 encoded-word adjacent-pair spacing under modern
|
||||
policies), which materially affects the composer.
|
||||
policies), which materially affects the composer, plus the further
|
||||
`email` fixes shipped in 3.14.6.
|
||||
|
||||
**Aligning on the latest 3.14.x patch is recommended for any
|
||||
production deployment.** Each CPython patch release that touches
|
||||
@@ -363,7 +364,7 @@ below. See the `tests/` directory for regression coverage of each.
|
||||
|
||||
## Compatibility
|
||||
|
||||
- **Python** 3.14.5+ (see [Why a Python 3.14.5 floor?](#why-a-python-3145-floor))
|
||||
- **Python** 3.14.6+ (see [Why a Python 3.14.6 floor?](#why-a-python-3146-floor))
|
||||
- **Platforms tested in CI:** Linux on x86_64 and arm64
|
||||
- **macOS / Windows / PyPy / free-threaded build:** untested; expected
|
||||
to work since the package has zero compiled extensions and zero
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "A strict-JMAP RFC 8621 Email object library for Python 3.14+ with
|
||||
readme = "README.md"
|
||||
license = "MIT"
|
||||
authors = [{ name = "ANCT", email = "contact@suite.anct.gouv.fr" }]
|
||||
requires-python = ">=3.14.5,<4.0"
|
||||
requires-python = ">=3.14.6,<4.0"
|
||||
keywords = ["jmap", "rfc5322", "rfc8621", "email", "mime", "parser", "composer"]
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
|
||||
@@ -14,7 +14,7 @@ fi
|
||||
zip -r /custom-scripts.jar META-INF *.js
|
||||
EOR
|
||||
|
||||
FROM quay.io/keycloak/keycloak:26.6.4 AS builder
|
||||
FROM quay.io/keycloak/keycloak:26.6.4@sha256:0aae0de7fca85525f727d3354df17896092de8bb26ae4c12d89c77e5df8cbce4 AS builder
|
||||
|
||||
WORKDIR /opt/keycloak
|
||||
COPY --chown=keycloak:keycloak --chmod=644 themes/dsfr-2.3.4.jar /opt/keycloak/providers/dsfr.jar
|
||||
@@ -32,7 +32,7 @@ ARG KC_DB=postgres
|
||||
|
||||
RUN /opt/keycloak/bin/kc.sh build
|
||||
|
||||
FROM quay.io/keycloak/keycloak:26.6.4
|
||||
FROM quay.io/keycloak/keycloak:26.6.4@sha256:0aae0de7fca85525f727d3354df17896092de8bb26ae4c12d89c77e5df8cbce4
|
||||
|
||||
COPY --from=builder /opt/keycloak/ /opt/keycloak/
|
||||
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM debian:12.10-slim
|
||||
FROM debian:trixie-slim@sha256:28de0877c2189802884ccd20f15ee41c203573bd87bb6b883f5f46362d24c5c2
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y lsb-release wget gpg ruby nginx netcat-openbsd procps && \
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
FROM python:3.13-slim
|
||||
# Test harness for the rspamd MPA. Inherits the shared python-uv base
|
||||
# (managed CPython 3.14.6 + uv); built by `make build-python-base`.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
FROM ${PYTHON_UV_IMAGE}
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# The shared base ships uv (no bundled pip on PATH); install into a venv.
|
||||
ENV VIRTUAL_ENV=/venv
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN uv venv /venv && uv pip install --no-cache -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
+50
-30
@@ -1,34 +1,27 @@
|
||||
# https://hub.docker.com/_/python
|
||||
FROM python:3.14.4-slim-trixie AS base
|
||||
# Postfix + milter inbound MTA image — counterpart to ./Dockerfile.pymta
|
||||
# (pure-Python aiosmtpd). This is the production default for now.
|
||||
#
|
||||
# Python comes from the shared uv-managed base (deploy/python-uv/Dockerfile:
|
||||
# debian-trixie + uv 0.11.28 digest-pinned + CPython 3.14.6), NOT the official
|
||||
# python image — so the interpreter matches the backend and pymta images.
|
||||
#
|
||||
# Global ARG so PYTHON_UV_IMAGE can be used in `FROM`. Built by
|
||||
# `make build-python-base`; overridden in CI.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
|
||||
# Bump this to force an update of the apt repositories
|
||||
ENV MIN_UPDATE_DATE="2026-02-20"
|
||||
# ---- uv build stage: shared base + pymilter build deps ----
|
||||
FROM ${PYTHON_UV_IMAGE} AS uv
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
postfix \
|
||||
postfix-pcre \
|
||||
curl \
|
||||
procps \
|
||||
net-tools \
|
||||
mailutils \
|
||||
libmilter-dev \
|
||||
# build-essential + libmilter headers are needed only to compile the pymilter
|
||||
# C extension (pulled by the `postfix` extra) during `uv sync`. They stay in
|
||||
# this build-only stage and never reach the runtime image.
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
iproute2 \
|
||||
iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---- uv package manager ----
|
||||
FROM base AS uv
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_PYTHON_DOWNLOADS=never
|
||||
ENV UV_PROJECT_ENVIRONMENT=/venv
|
||||
libmilter-dev
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
# ---- Base image with dependencies installed ----
|
||||
FROM uv AS base-with-deps
|
||||
@@ -52,8 +45,26 @@ ENV PYTHONIOENCODING=utf-8
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
||||
# ---- Base runtime image ----
|
||||
FROM base AS runtime-base
|
||||
# ---- Base runtime image: shared base (managed Python) + postfix runtime deps ----
|
||||
FROM ${PYTHON_UV_IMAGE} AS runtime-base
|
||||
|
||||
# Runtime packages only — no build-essential/libmilter-dev. libmilter1.0.1 is
|
||||
# the runtime shared library the compiled pymilter extension links against.
|
||||
# (ca-certificates comes from the shared base.)
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
postfix \
|
||||
postfix-pcre \
|
||||
curl \
|
||||
procps \
|
||||
net-tools \
|
||||
mailutils \
|
||||
libmilter1.0.1 \
|
||||
iproute2 \
|
||||
iputils-ping
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
@@ -69,11 +80,20 @@ FROM runtime-base AS runtime-dev
|
||||
|
||||
COPY --from=base-with-deps-dev /venv /venv
|
||||
|
||||
# The package lives at /app/src (mounted as a volume in dev); put it on the path
|
||||
# so the shared test suite can `import pymta` — matching Dockerfile.pymta.
|
||||
ENV PYTHONPATH="/app/src"
|
||||
|
||||
# /app will be mounted as a volume in the development container
|
||||
|
||||
# ---- Base runtime image for production ----
|
||||
FROM runtime-base AS runtime-prod
|
||||
|
||||
# Hardening: whiteout pip/tkinter/tests/headers from the runtime (see
|
||||
# strip-python). Not a size change (full Python is in an inherited layer), just
|
||||
# keeps those tools out of the running container.
|
||||
RUN strip-python
|
||||
|
||||
COPY --from=base-with-deps /venv /venv
|
||||
|
||||
COPY ./src /app/src
|
||||
|
||||
+28
-62
@@ -12,53 +12,23 @@
|
||||
# * `runtime-prod` (slim) and `runtime-distroless-prod` (cc-debian13:nonroot)
|
||||
# both available; the distroless variant is the security target.
|
||||
#
|
||||
# ---- Base OS ----
|
||||
FROM debian:trixie-slim AS base
|
||||
|
||||
# Bump this to force an update of the apt repositories
|
||||
ENV MIN_UPDATE_DATE="2026-06-05"
|
||||
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get upgrade -y
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Shared base image (deploy/python-uv/Dockerfile): debian-trixie + apt upgrade +
|
||||
# ca-certificates + uv (digest-pinned) + uv-managed CPython at /opt/python.
|
||||
# Global ARG so it can be used in `FROM` below. Built by `make build-python-base`;
|
||||
# overridden in CI.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
|
||||
# ---- uv + managed Python + build system deps ----
|
||||
FROM base AS uv
|
||||
|
||||
# Pin uv by SHA256 digest for supply chain security. We pull a newer uv than
|
||||
# src/backend/Dockerfile (0.11.10): uv 0.11.10 only knows Python 3.14.5rc1,
|
||||
# and python-build-standalone shipped 3.14.5 final on 2026-05-10 — which
|
||||
# only landed in uv from 0.11.16 onward.
|
||||
# Verify with: gh attestation verify --owner astral-sh oci://ghcr.io/astral-sh/uv:0.11.19
|
||||
COPY --from=ghcr.io/astral-sh/uv@sha256:b46b03ddfcfbf8f547af7e9eaefdf8a39c8cebcba7c98858d3162bd28cf536f6 /uv /uvx /bin/
|
||||
FROM ${PYTHON_UV_IMAGE} AS uv
|
||||
|
||||
# Build deps for compiling any C-extension wheels during `uv sync`.
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
build-essential
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_PYTHON_PREFERENCE=only-managed
|
||||
ENV UV_PYTHON_INSTALL_DIR=/opt/python
|
||||
ENV UV_PROJECT_ENVIRONMENT=/venv
|
||||
|
||||
# Install Python via uv — integrity verified against SHA256 checksums embedded in the uv binary.
|
||||
# Uses python-build-standalone: most C deps (ssl, ffi, sqlite, zlib, lzma, bz2) are statically linked.
|
||||
RUN uv python install 3.14.5
|
||||
|
||||
|
||||
# ---- Production dependencies ----
|
||||
FROM uv AS base-with-deps
|
||||
@@ -78,28 +48,16 @@ FROM base-with-deps AS base-with-deps-dev
|
||||
RUN --mount=type=cache,target=/root/.cache/uv uv sync --locked --no-install-project --no-editable --extra dev
|
||||
|
||||
|
||||
# ---- Strip Python for production (remove pip, idle, tkinter, tcl, headers, tests) ----
|
||||
FROM uv AS python-runtime
|
||||
RUN <<EOR
|
||||
set -e
|
||||
PYDIR=$(dirname $(dirname $(uv python find 3.14.5)))
|
||||
rm -rf \
|
||||
$PYDIR/bin/idle* $PYDIR/bin/pip* $PYDIR/bin/pydoc* $PYDIR/bin/*-config \
|
||||
$PYDIR/include $PYDIR/share \
|
||||
$PYDIR/lib/pkgconfig $PYDIR/lib/itcl* $PYDIR/lib/libtcl* \
|
||||
$PYDIR/lib/tcl* $PYDIR/lib/tk* $PYDIR/lib/thread* \
|
||||
$PYDIR/lib/python3.14/idlelib \
|
||||
$PYDIR/lib/python3.14/ensurepip \
|
||||
$PYDIR/lib/python3.14/tkinter \
|
||||
$PYDIR/lib/python3.14/turtledemo \
|
||||
$PYDIR/lib/python3.14/lib-dynload/_tkinter* \
|
||||
$PYDIR/lib/python3.14/lib-dynload/_ctypes_test* \
|
||||
/opt/python/.gitignore /opt/python/.lock /opt/python/.temp
|
||||
EOR
|
||||
# ---- Strip Python for the distroless image (see deploy/python-uv/strip-python.sh) ----
|
||||
# Only runtime-distroless-prod (the real production target) uses this. The slim
|
||||
# runtime-prod inherits the full managed Python from the shared base instead.
|
||||
FROM ${PYTHON_UV_IMAGE} AS python-runtime
|
||||
RUN strip-python
|
||||
|
||||
|
||||
# ---- Base runtime image (slim) ----
|
||||
FROM base AS runtime-base
|
||||
# Inherits the shared base directly (full managed Python at /opt/python).
|
||||
FROM ${PYTHON_UV_IMAGE} AS runtime-base
|
||||
|
||||
# Give the "root" group the same permissions as the "root" user on /etc/passwd
|
||||
# to allow a user belonging to the root group to add new users; mirrors the
|
||||
@@ -121,8 +79,7 @@ ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ]
|
||||
# ---- Development runtime ----
|
||||
FROM runtime-base AS runtime-dev
|
||||
|
||||
# Full Python installation (with headers, pip — useful for debugging)
|
||||
COPY --from=uv /opt/python /opt/python
|
||||
# Full managed Python (with headers, pip) is already present from the shared base.
|
||||
COPY --from=base-with-deps-dev /venv /venv
|
||||
|
||||
ENV PYTHONPATH="/app/src"
|
||||
@@ -131,14 +88,23 @@ ENV PYTHONPATH="/app/src"
|
||||
|
||||
|
||||
# ---- Production application source (strip tests, dev tooling, build files) ----
|
||||
FROM base AS app-prod
|
||||
FROM ${PYTHON_UV_IMAGE} AS app-prod
|
||||
COPY ./src /app/src
|
||||
|
||||
|
||||
# ---- Production runtime (slim) ----
|
||||
FROM runtime-base AS runtime-prod
|
||||
ARG DOCKER_USER=65532
|
||||
|
||||
COPY --from=python-runtime /opt/python /opt/python
|
||||
# Hardening: whiteout pip/tkinter/tests/headers from the runtime (see
|
||||
# strip-python). Does NOT shrink the image (full Python is in an inherited layer)
|
||||
# but keeps those tools out of the running container. Strip as root so
|
||||
# /opt/python stays root-owned (not writable by the app user), then restore it.
|
||||
USER root
|
||||
RUN strip-python
|
||||
USER ${DOCKER_USER}
|
||||
|
||||
# The distroless target below copies in an already-stripped Python instead.
|
||||
COPY --from=base-with-deps /venv /venv
|
||||
COPY --from=app-prod /app/src /app/src
|
||||
|
||||
@@ -149,7 +115,7 @@ CMD ["python", "-m", "pymta.server"]
|
||||
# Liveness probe: TCP-connect the SMTP listener. Uses stdlib socket — works in
|
||||
# both slim and distroless variants without curl/nc.
|
||||
HEALTHCHECK --interval=30s --timeout=2s --start-period=15s \
|
||||
CMD ["python", "-c", "import os, socket; s=socket.socket(); s.settimeout(1); s.connect(('127.0.0.1', int(os.getenv('PYMTA_SMTP_PORT', '25')))); s.close()"]
|
||||
CMD ["python", "-c", "import os, socket; s=socket.socket(); s.settimeout(1); s.connect(('127.0.0.1', int(os.getenv('PYMTA_SMTP_PORT', '25')))); s.close()"]
|
||||
|
||||
|
||||
# ---- Distroless production runtime ----
|
||||
@@ -178,4 +144,4 @@ ENV PYTHONPATH="/app/src"
|
||||
CMD ["python", "-m", "pymta.server"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=2s --start-period=15s \
|
||||
CMD ["python", "-c", "import os, socket; s=socket.socket(); s.settimeout(1); s.connect(('127.0.0.1', int(os.getenv('PYMTA_SMTP_PORT', '25')))); s.close()"]
|
||||
CMD ["python", "-c", "import os, socket; s=socket.socket(); s.settimeout(1); s.connect(('127.0.0.1', int(os.getenv('PYMTA_SMTP_PORT', '25')))); s.close()"]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# st-messages-mta-in package
|
||||
#
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.10.0,<0.11.0"]
|
||||
requires = ["uv_build>=0.11.0,<0.12.0"]
|
||||
build-backend = "uv_build"
|
||||
|
||||
[project]
|
||||
@@ -23,15 +23,15 @@ description = "A stateless MTA."
|
||||
keywords = ["Python", "MTA"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14.4,<4.0"
|
||||
requires-python = ">=3.14.6,<4.0"
|
||||
|
||||
# Note: after changing this list you must re-run `make deps-lock-mta-in`
|
||||
dependencies = [
|
||||
"requests==2.32.3",
|
||||
"PyJWT==2.10.1",
|
||||
"requests==2.34.2",
|
||||
"PyJWT==2.13.0",
|
||||
"aiosmtpd==1.4.6",
|
||||
"httpx==0.28.1",
|
||||
"prometheus-client==0.24.1",
|
||||
"prometheus-client==0.25.0",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -54,7 +54,7 @@ dev = [
|
||||
"uvicorn==0.34.1",
|
||||
"ruff==0.9.3",
|
||||
"pylint==3.3.4",
|
||||
"types-requests==2.32.0.20241016",
|
||||
"types-requests==2.33.0.20260518",
|
||||
]
|
||||
|
||||
[tool.uv.build-backend]
|
||||
|
||||
Generated
+26
-26
@@ -1,6 +1,6 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14.4, <4.0"
|
||||
requires-python = ">=3.14.6, <4.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiosmtpd"
|
||||
@@ -65,11 +65,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.1.4"
|
||||
version = "2026.6.17"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -219,11 +219,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.11"
|
||||
version = "3.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -282,11 +282,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "prometheus-client"
|
||||
version = "0.24.1"
|
||||
version = "0.25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -345,11 +345,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.10.1"
|
||||
version = "2.13.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -418,7 +418,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
version = "2.34.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
@@ -426,9 +426,9 @@ dependencies = [
|
||||
{ name = "idna" },
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218, upload-time = "2024-05-29T15:37:49.536Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -488,16 +488,16 @@ requires-dist = [
|
||||
{ name = "aiosmtpd", specifier = "==1.4.6" },
|
||||
{ name = "fastapi", marker = "extra == 'dev'", specifier = "==0.115.12" },
|
||||
{ name = "httpx", specifier = "==0.28.1" },
|
||||
{ name = "prometheus-client", specifier = "==0.24.1" },
|
||||
{ name = "pyjwt", specifier = "==2.10.1" },
|
||||
{ name = "prometheus-client", specifier = "==0.25.0" },
|
||||
{ name = "pyjwt", specifier = "==2.13.0" },
|
||||
{ name = "pylint", marker = "extra == 'dev'", specifier = "==3.3.4" },
|
||||
{ name = "pymilter", marker = "extra == 'postfix'", specifier = "==1.0.5" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==8.3.5" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = "==0.24.0" },
|
||||
{ name = "pytest-cov", marker = "extra == 'dev'", specifier = "==6.0.0" },
|
||||
{ name = "requests", specifier = "==2.32.3" },
|
||||
{ name = "requests", specifier = "==2.34.2" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = "==0.9.3" },
|
||||
{ name = "types-requests", marker = "extra == 'dev'", specifier = "==2.32.0.20241016" },
|
||||
{ name = "types-requests", marker = "extra == 'dev'", specifier = "==2.33.0.20260518" },
|
||||
{ name = "uvicorn", marker = "extra == 'dev'", specifier = "==0.34.1" },
|
||||
]
|
||||
provides-extras = ["postfix", "dev"]
|
||||
@@ -525,14 +525,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "types-requests"
|
||||
version = "2.32.0.20241016"
|
||||
version = "2.33.0.20260518"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "urllib3" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fa/3c/4f2a430c01a22abd49a583b6b944173e39e7d01b688190a5618bd59a2e22/types-requests-2.32.0.20241016.tar.gz", hash = "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", size = 18065, upload-time = "2024-10-16T02:46:10.818Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/01/c5a19253fe1ac159159ddf9a3a07cec8bb5e486ec4d9002ad2821da0e5d2/types_requests-2.33.0.20260518.tar.gz", hash = "sha256:df7bd3bfe0ca8402dfb841e7d9be714bb5578203283d66d7dc4ef69343449a5e", size = 24752, upload-time = "2026-05-18T06:07:37.966Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/01/485b3026ff90e5190b5e24f1711522e06c79f4a56c8f4b95848ac072e20f/types_requests-2.32.0.20241016-py3-none-any.whl", hash = "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747", size = 15836, upload-time = "2024-10-16T02:46:09.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -558,11 +558,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
version = "2.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+42
-33
@@ -1,33 +1,24 @@
|
||||
# https://hub.docker.com/_/python
|
||||
FROM python:3.14.4-slim-trixie AS base
|
||||
# Outbound MTA image (Postfix + SASL relay).
|
||||
#
|
||||
# Python comes from the shared uv-managed base (deploy/python-uv/Dockerfile:
|
||||
# debian-trixie + uv 0.11.28 digest-pinned + CPython 3.14.6), NOT the official
|
||||
# python image — so the interpreter matches the backend and mta-in images.
|
||||
#
|
||||
# Global ARG so PYTHON_UV_IMAGE can be used in `FROM`. Built by
|
||||
# `make build-python-base`; overridden in CI.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
|
||||
# Bump this to force an update of the apt repositories
|
||||
ENV MIN_UPDATE_DATE="2026-02-20"
|
||||
# ---- uv build stage: shared base + build deps ----
|
||||
FROM ${PYTHON_UV_IMAGE} AS uv
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
postfix \
|
||||
sasl2-bin \
|
||||
libsasl2-modules \
|
||||
ssl-cert \
|
||||
procps \
|
||||
curl \
|
||||
dnsutils \
|
||||
iproute2 \
|
||||
iputils-ping \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ---- uv package manager ----
|
||||
FROM base AS uv
|
||||
|
||||
COPY --from=ghcr.io/astral-sh/uv:0.10.4 /uv /uvx /bin/
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1
|
||||
ENV UV_LINK_MODE=copy
|
||||
ENV UV_PYTHON_DOWNLOADS=never
|
||||
ENV UV_PROJECT_ENVIRONMENT=/venv
|
||||
# build-essential only to compile any C-extension wheels during `uv sync`.
|
||||
# Build-only: it never reaches the runtime image.
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
build-essential
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
# ---- Base image with dependencies installed ----
|
||||
FROM uv AS base-with-deps
|
||||
@@ -49,8 +40,24 @@ ENV PYTHONIOENCODING=utf-8
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
|
||||
# ---- Base runtime image ----
|
||||
FROM base AS runtime-base
|
||||
# ---- Base runtime image: shared base (managed Python) + postfix/SASL runtime ----
|
||||
FROM ${PYTHON_UV_IMAGE} AS runtime-base
|
||||
|
||||
# Runtime packages only (ca-certificates comes from the shared base).
|
||||
RUN <<EOR
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
postfix \
|
||||
sasl2-bin \
|
||||
libsasl2-modules \
|
||||
ssl-cert \
|
||||
procps \
|
||||
curl \
|
||||
dnsutils \
|
||||
iproute2 \
|
||||
iputils-ping
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
EOR
|
||||
|
||||
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
@@ -58,9 +65,6 @@ RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
# Create directories for Postfix queues and SASL socket area
|
||||
RUN mkdir -p /etc/postfix/sasl /var/spool/postfix/private /var/spool/postfix/public
|
||||
|
||||
# Create directories for Postfix queues
|
||||
RUN mkdir -p /var/spool/postfix/private /var/spool/postfix/public
|
||||
|
||||
# Ensure self-signed certificates are available (can be overridden by env vars/mounts)
|
||||
RUN make-ssl-cert generate-default-snakeoil
|
||||
|
||||
@@ -83,6 +87,11 @@ COPY --from=base-with-deps-dev /venv /venv
|
||||
# ---- Base runtime image for production ----
|
||||
FROM runtime-base AS runtime-prod
|
||||
|
||||
# Hardening: whiteout pip/tkinter/tests/headers from the runtime (see
|
||||
# strip-python). Not a size change (full Python is in an inherited layer), just
|
||||
# keeps those tools out of the running container.
|
||||
RUN strip-python
|
||||
|
||||
COPY --from=base-with-deps /venv /venv
|
||||
|
||||
COPY ./etc /app/etc
|
||||
|
||||
@@ -23,7 +23,7 @@ description = "A stateless outgoing MTA."
|
||||
keywords = ["Python", "MTA", "SMTP"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14.4,<4.0"
|
||||
requires-python = ">=3.14.6,<4.0"
|
||||
dependencies = [
|
||||
"Jinja2==3.1.6"
|
||||
]
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.14.4, <4.0"
|
||||
requires-python = ">=3.14.6, <4.0"
|
||||
|
||||
[[package]]
|
||||
name = "aiosmtpd"
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
FROM python:3.13-slim-bookworm
|
||||
# Test harness for the socks-proxy (dante). Inherits the shared python-uv base
|
||||
# (managed CPython 3.14.6 + uv); built by `make build-python-base`.
|
||||
ARG PYTHON_UV_IMAGE=messages-python-uv:local
|
||||
FROM ${PYTHON_UV_IMAGE}
|
||||
|
||||
# Install system dependencies
|
||||
# netcat for the proxy connectivity checks.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
netcat-openbsd \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy requirements and install Python dependencies
|
||||
# The shared base ships uv (no bundled pip on PATH); install into a venv.
|
||||
ENV VIRTUAL_ENV=/venv
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN uv venv /venv && uv pip install --no-cache -r requirements.txt
|
||||
|
||||
# Copy test files
|
||||
COPY . .
|
||||
|
||||
# Default command (can be overridden)
|
||||
CMD ["pytest", "-v"]
|
||||
|
||||
Reference in New Issue
Block a user