From cdeda4856f8ca46d68065cae1038919feb8f8ad4 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 07:59:22 -0800 Subject: [PATCH 1/2] Restructure Pulumi into separate projects for Tailscale and Gandi DNS - Move Tailscale ACL management to pulumi/tailscale/ - Add new Gandi DNS project at pulumi/gandi/ for eblu.me management - Create wildcard DNS record *.ops.eblu.me pointing to indri's Tailscale IP - Add mise tasks: dns-up, dns-preview - Update tailnet-up/preview to use new path and add --yes flag - Document PAT cycling process (expires every 30 days) This enables using real DNS names (*.ops.eblu.me) that resolve to Tailscale IPs, allowing containers to resolve services without MagicDNS dependency. Co-Authored-By: Claude Opus 4.5 --- mise-tasks/dns-preview | 10 +++ mise-tasks/dns-up | 10 +++ mise-tasks/tailnet-preview | 2 +- mise-tasks/tailnet-up | 4 +- pulumi/gandi/.gitignore | 1 + pulumi/gandi/Pulumi.eblu-me.yaml | 6 ++ pulumi/gandi/Pulumi.yaml | 7 ++ pulumi/gandi/README.md | 92 ++++++++++++++++++++ pulumi/gandi/__main__.py | 49 +++++++++++ pulumi/gandi/pyproject.toml | 5 ++ pulumi/{ => tailscale}/.gitignore | 0 pulumi/{ => tailscale}/Pulumi.tail8d86e.yaml | 0 pulumi/{ => tailscale}/Pulumi.yaml | 0 pulumi/{ => tailscale}/__main__.py | 0 pulumi/{ => tailscale}/policy.hujson | 0 pulumi/{ => tailscale}/pyproject.toml | 0 16 files changed, 183 insertions(+), 3 deletions(-) create mode 100755 mise-tasks/dns-preview create mode 100755 mise-tasks/dns-up create mode 100644 pulumi/gandi/.gitignore create mode 100644 pulumi/gandi/Pulumi.eblu-me.yaml create mode 100644 pulumi/gandi/Pulumi.yaml create mode 100644 pulumi/gandi/README.md create mode 100644 pulumi/gandi/__main__.py create mode 100644 pulumi/gandi/pyproject.toml rename pulumi/{ => tailscale}/.gitignore (100%) rename pulumi/{ => tailscale}/Pulumi.tail8d86e.yaml (100%) rename pulumi/{ => tailscale}/Pulumi.yaml (100%) rename pulumi/{ => tailscale}/__main__.py (100%) rename pulumi/{ => tailscale}/policy.hujson (100%) rename pulumi/{ => tailscale}/pyproject.toml (100%) diff --git a/mise-tasks/dns-preview b/mise-tasks/dns-preview new file mode 100755 index 0000000..7d7578e --- /dev/null +++ b/mise-tasks/dns-preview @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +#MISE description="Preview DNS changes to eblu.me with Pulumi" + +set -euo pipefail + +GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco6ka3dc3rmw7zkg2dhia5d2m --fields pat --reveal) +export GANDI_PERSONAL_ACCESS_TOKEN + +cd "$(dirname "$0")/../pulumi/gandi" +pulumi preview "$@" diff --git a/mise-tasks/dns-up b/mise-tasks/dns-up new file mode 100755 index 0000000..a0d3849 --- /dev/null +++ b/mise-tasks/dns-up @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +#MISE description="Apply DNS changes to eblu.me with Pulumi" + +set -euo pipefail + +GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco6ka3dc3rmw7zkg2dhia5d2m --fields pat --reveal) +export GANDI_PERSONAL_ACCESS_TOKEN + +cd "$(dirname "$0")/../pulumi/gandi" +pulumi up --yes "$@" diff --git a/mise-tasks/tailnet-preview b/mise-tasks/tailnet-preview index dd9e308..ceb6439 100755 --- a/mise-tasks/tailnet-preview +++ b/mise-tasks/tailnet-preview @@ -9,5 +9,5 @@ TAILSCALE_OAUTH_CLIENT_SECRET=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" -cd "$(dirname "$0")/../pulumi" +cd "$(dirname "$0")/../pulumi/tailscale" pulumi preview "$@" diff --git a/mise-tasks/tailnet-up b/mise-tasks/tailnet-up index 4b097b6..f22048b 100755 --- a/mise-tasks/tailnet-up +++ b/mise-tasks/tailnet-up @@ -9,5 +9,5 @@ TAILSCALE_OAUTH_CLIENT_SECRET=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get w export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" -cd "$(dirname "$0")/../pulumi" -pulumi up "$@" +cd "$(dirname "$0")/../pulumi/tailscale" +pulumi up --yes "$@" diff --git a/pulumi/gandi/.gitignore b/pulumi/gandi/.gitignore new file mode 100644 index 0000000..21d0b89 --- /dev/null +++ b/pulumi/gandi/.gitignore @@ -0,0 +1 @@ +.venv/ diff --git a/pulumi/gandi/Pulumi.eblu-me.yaml b/pulumi/gandi/Pulumi.eblu-me.yaml new file mode 100644 index 0000000..e5f86c4 --- /dev/null +++ b/pulumi/gandi/Pulumi.eblu-me.yaml @@ -0,0 +1,6 @@ +--- +config: + blumeops-dns:domain: eblu.me + blumeops-dns:subdomain: ops + # indri's Tailscale IP - only routable within tailnet + blumeops-dns:tailscale_ip: "100.98.163.89" diff --git a/pulumi/gandi/Pulumi.yaml b/pulumi/gandi/Pulumi.yaml new file mode 100644 index 0000000..81e7215 --- /dev/null +++ b/pulumi/gandi/Pulumi.yaml @@ -0,0 +1,7 @@ +--- +name: blumeops-dns +runtime: + name: python + options: + toolchain: uv +description: DNS configuration for eblu.me via Gandi LiveDNS diff --git a/pulumi/gandi/README.md b/pulumi/gandi/README.md new file mode 100644 index 0000000..469f589 --- /dev/null +++ b/pulumi/gandi/README.md @@ -0,0 +1,92 @@ +# Gandi DNS Management + +This Pulumi project manages DNS records for `eblu.me` via Gandi LiveDNS. + +## What It Does + +Creates DNS records that point `*.ops.eblu.me` to indri's Tailscale IP (`100.98.163.89`). + +Since Tailscale IPs (100.x.x.x) are not routable on the public internet, these +DNS records effectively make services accessible only from within the tailnet, +while still using real, resolvable DNS names. + +## Setup + +```bash +cd pulumi/gandi +uv sync +pulumi stack select eblu-me # or: pulumi stack init eblu-me +``` + +## Authentication + +This project requires a Gandi Personal Access Token (PAT) with LiveDNS permissions. + +**The PAT expires every 30 days and must be cycled manually.** + +### Cycling the PAT + +1. Go to [Gandi PAT Management](https://admin.gandi.net/organizations/1db8d76a-f729-11ed-b8d1-00163e94b645/account/pat) + +2. Create a new PAT: + - Name: `blumeops-pulumi` (or similar) + - Expiration: 30 days (maximum) + - Permissions: **Manage domain name technical configurations** (under Domains) + +3. Update 1Password: + ```bash + # Update the existing item with the new PAT value + op item edit mco6ka3dc3rmw7zkg2dhia5d2m pat="" --vault vg6xf6vvfmoh5hqjjhlhbeoaie + ``` + +4. Delete the old PAT from Gandi admin console + +### Running with Authentication + +The mise task handles fetching the PAT from 1Password: + +```bash +mise run dns-up # Preview and apply changes +mise run dns-preview # Preview only +``` + +Or manually: + +```bash +export GANDI_PERSONAL_ACCESS_TOKEN=$(op item get mco6ka3dc3rmw7zkg2dhia5d2m --field pat --reveal --vault vg6xf6vvfmoh5hqjjhlhbeoaie) +pulumi up +``` + +## DNS Records Created + +| Record | Type | Value | Purpose | +|--------|------|-------|---------| +| `*.ops.eblu.me` | A | 100.98.163.89 | Wildcard for all services | +| `ops.eblu.me` | A | 100.98.163.89 | Base subdomain | + +## Service Hostnames + +Once Caddy is configured, services will be accessible at: + +- `forge.ops.eblu.me` - Forgejo git server +- `registry.ops.eblu.me` - Zot container registry +- `grafana.ops.eblu.me` - Grafana dashboards +- `argocd.ops.eblu.me` - ArgoCD +- `feed.ops.eblu.me` - Miniflux RSS reader +- `pypi.ops.eblu.me` - DevPI Python index +- `kiwix.ops.eblu.me` - Kiwix offline content +- `tesla.ops.eblu.me` - TeslaMate +- `torrent.ops.eblu.me` - Transmission +- `prometheus.ops.eblu.me` - Prometheus metrics +- `loki.ops.eblu.me` - Loki logs + +## Changing the Target IP + +If indri's Tailscale IP changes, update `Pulumi.eblu-me.yaml`: + +```yaml +config: + blumeops-dns:tailscale_ip: "NEW_IP_HERE" +``` + +Then run `mise run dns-up` to apply. diff --git a/pulumi/gandi/__main__.py b/pulumi/gandi/__main__.py new file mode 100644 index 0000000..51679b6 --- /dev/null +++ b/pulumi/gandi/__main__.py @@ -0,0 +1,49 @@ +"""Pulumi program to manage eblu.me DNS via Gandi LiveDNS. + +This program manages DNS records for blumeops infrastructure: +- Wildcard record for *.ops.eblu.me pointing to indri's Tailscale IP +- This allows services to be accessed via real DNS names while remaining + tailnet-only (Tailscale IPs are not publicly routable) + +Authentication: + Set GANDI_PERSONAL_ACCESS_TOKEN environment variable. + See README.md for PAT management instructions. +""" + +import pulumi +import pulumiverse_gandi as gandi + +# Get configuration +config = pulumi.Config() +domain = config.require("domain") # eblu.me +subdomain = config.require("subdomain") # ops +tailscale_ip = config.require("tailscale_ip") # 100.98.163.89 + +# Wildcard A record for *.ops.eblu.me +# Points to indri's Tailscale IP, which is only routable within the tailnet. +# This allows containers and other systems to resolve real DNS names +# while keeping services private to the tailnet. +wildcard_record = gandi.livedns.Record( + "ops-wildcard", + zone=domain, + name=f"*.{subdomain}", + type="A", + ttl=300, + values=[tailscale_ip], +) + +# Base subdomain record (ops.eblu.me) - same IP +base_record = gandi.livedns.Record( + "ops-base", + zone=domain, + name=subdomain, + type="A", + ttl=300, + values=[tailscale_ip], +) + +# ============== Exports ============== +pulumi.export("domain", domain) +pulumi.export("wildcard_fqdn", f"*.{subdomain}.{domain}") +pulumi.export("base_fqdn", f"{subdomain}.{domain}") +pulumi.export("target_ip", tailscale_ip) diff --git a/pulumi/gandi/pyproject.toml b/pulumi/gandi/pyproject.toml new file mode 100644 index 0000000..472c93a --- /dev/null +++ b/pulumi/gandi/pyproject.toml @@ -0,0 +1,5 @@ +[project] +name = "blumeops-dns" +version = "0.1.0" +requires-python = ">=3.11" +dependencies = ["pulumi>=3.0.0", "pulumiverse-gandi>=2.3.0"] diff --git a/pulumi/.gitignore b/pulumi/tailscale/.gitignore similarity index 100% rename from pulumi/.gitignore rename to pulumi/tailscale/.gitignore diff --git a/pulumi/Pulumi.tail8d86e.yaml b/pulumi/tailscale/Pulumi.tail8d86e.yaml similarity index 100% rename from pulumi/Pulumi.tail8d86e.yaml rename to pulumi/tailscale/Pulumi.tail8d86e.yaml diff --git a/pulumi/Pulumi.yaml b/pulumi/tailscale/Pulumi.yaml similarity index 100% rename from pulumi/Pulumi.yaml rename to pulumi/tailscale/Pulumi.yaml diff --git a/pulumi/__main__.py b/pulumi/tailscale/__main__.py similarity index 100% rename from pulumi/__main__.py rename to pulumi/tailscale/__main__.py diff --git a/pulumi/policy.hujson b/pulumi/tailscale/policy.hujson similarity index 100% rename from pulumi/policy.hujson rename to pulumi/tailscale/policy.hujson diff --git a/pulumi/pyproject.toml b/pulumi/tailscale/pyproject.toml similarity index 100% rename from pulumi/pyproject.toml rename to pulumi/tailscale/pyproject.toml -- 2.50.1 (Apple Git-155) From e1d82935fb455936bb991d54f136a244513d5e8f Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 25 Jan 2026 08:12:47 -0800 Subject: [PATCH 2/2] Address PR feedback: dynamic IP resolution and documentation updates - Resolve indri's Tailscale IP dynamically via MagicDNS at deploy time - Add BLUMEOPS_REVERSE_PROXY_IP env var override for break-glass scenarios - Remove hardcoded IP from stack config - Clarify that indri hosts Caddy (the reverse proxy) in all docs - Update PAT permissions list with actual permissions enabled Co-Authored-By: Claude Opus 4.5 --- pulumi/gandi/Pulumi.eblu-me.yaml | 4 +- pulumi/gandi/README.md | 38 +++-- pulumi/gandi/__main__.py | 14 +- pulumi/gandi/uv.lock | 265 +++++++++++++++++++++++++++++++ 4 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 pulumi/gandi/uv.lock diff --git a/pulumi/gandi/Pulumi.eblu-me.yaml b/pulumi/gandi/Pulumi.eblu-me.yaml index e5f86c4..7c62cd9 100644 --- a/pulumi/gandi/Pulumi.eblu-me.yaml +++ b/pulumi/gandi/Pulumi.eblu-me.yaml @@ -2,5 +2,5 @@ config: blumeops-dns:domain: eblu.me blumeops-dns:subdomain: ops - # indri's Tailscale IP - only routable within tailnet - blumeops-dns:tailscale_ip: "100.98.163.89" + # The target IP is resolved dynamically from indri.tail8d86e.ts.net + # indri hosts Caddy, which reverse-proxies all blumeops services diff --git a/pulumi/gandi/README.md b/pulumi/gandi/README.md index 469f589..5a2a442 100644 --- a/pulumi/gandi/README.md +++ b/pulumi/gandi/README.md @@ -4,12 +4,19 @@ This Pulumi project manages DNS records for `eblu.me` via Gandi LiveDNS. ## What It Does -Creates DNS records that point `*.ops.eblu.me` to indri's Tailscale IP (`100.98.163.89`). +Creates DNS records that point `*.ops.eblu.me` to indri's Tailscale IP. + +**Why indri?** indri hosts Caddy, the reverse proxy for all blumeops services. +All `*.ops.eblu.me` requests route through Caddy, which proxies to the appropriate +backend service (either on indri itself or in the k8s cluster). Since Tailscale IPs (100.x.x.x) are not routable on the public internet, these DNS records effectively make services accessible only from within the tailnet, while still using real, resolvable DNS names. +The target IP is resolved dynamically from `indri.tail8d86e.ts.net` at deploy time, +so if indri's Tailscale IP changes, just re-run the deployment. + ## Setup ```bash @@ -31,7 +38,17 @@ This project requires a Gandi Personal Access Token (PAT) with LiveDNS permissio 2. Create a new PAT: - Name: `blumeops-pulumi` (or similar) - Expiration: 30 days (maximum) - - Permissions: **Manage domain name technical configurations** (under Domains) + - Permissions required: + - **Manage domain name technical configurations** (required for DNS records) + - See and renew domain names + - Optional permissions (enabled but not strictly required): + - See & download SSL certificates + - Manage Cloud resources + - See Cloud resources + - View Organization + - Deploy Web Hosting instances + - Manage Web Hosting instances + - See and renew Web Hosting instances 3. Update 1Password: ```bash @@ -61,12 +78,12 @@ pulumi up | Record | Type | Value | Purpose | |--------|------|-------|---------| -| `*.ops.eblu.me` | A | 100.98.163.89 | Wildcard for all services | -| `ops.eblu.me` | A | 100.98.163.89 | Base subdomain | +| `*.ops.eblu.me` | A | (indri's Tailscale IP) | Wildcard for all services | +| `ops.eblu.me` | A | (indri's Tailscale IP) | Base subdomain | ## Service Hostnames -Once Caddy is configured, services will be accessible at: +Once Caddy is configured on indri, services will be accessible at: - `forge.ops.eblu.me` - Forgejo git server - `registry.ops.eblu.me` - Zot container registry @@ -79,14 +96,3 @@ Once Caddy is configured, services will be accessible at: - `torrent.ops.eblu.me` - Transmission - `prometheus.ops.eblu.me` - Prometheus metrics - `loki.ops.eblu.me` - Loki logs - -## Changing the Target IP - -If indri's Tailscale IP changes, update `Pulumi.eblu-me.yaml`: - -```yaml -config: - blumeops-dns:tailscale_ip: "NEW_IP_HERE" -``` - -Then run `mise run dns-up` to apply. diff --git a/pulumi/gandi/__main__.py b/pulumi/gandi/__main__.py index 51679b6..4361c91 100644 --- a/pulumi/gandi/__main__.py +++ b/pulumi/gandi/__main__.py @@ -2,6 +2,7 @@ This program manages DNS records for blumeops infrastructure: - Wildcard record for *.ops.eblu.me pointing to indri's Tailscale IP +- indri hosts Caddy as the reverse proxy for all services - This allows services to be accessed via real DNS names while remaining tailnet-only (Tailscale IPs are not publicly routable) @@ -10,6 +11,9 @@ Authentication: See README.md for PAT management instructions. """ +import os +import socket + import pulumi import pulumiverse_gandi as gandi @@ -17,7 +21,15 @@ import pulumiverse_gandi as gandi config = pulumi.Config() domain = config.require("domain") # eblu.me subdomain = config.require("subdomain") # ops -tailscale_ip = config.require("tailscale_ip") # 100.98.163.89 + +# Resolve indri's Tailscale IP dynamically via MagicDNS +# This script runs on the tailnet, so we can resolve the hostname directly. +# indri hosts Caddy, which reverse-proxies all services. +# Break-glass: set BLUMEOPS_REVERSE_PROXY_IP env var to override DNS resolution +REVERSE_PROXY_HOST = "indri.tail8d86e.ts.net" +tailscale_ip = os.environ.get("BLUMEOPS_REVERSE_PROXY_IP") or socket.gethostbyname( + REVERSE_PROXY_HOST +) # Wildcard A record for *.ops.eblu.me # Points to indri's Tailscale IP, which is only routable within the tailnet. diff --git a/pulumi/gandi/uv.lock b/pulumi/gandi/uv.lock new file mode 100644 index 0000000..946d09b --- /dev/null +++ b/pulumi/gandi/uv.lock @@ -0,0 +1,265 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version < '3.14'", +] + +[[package]] +name = "arpeggio" +version = "2.0.3" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/9e8/5ad35cfc6c938/Arpeggio-2.0.3.tar.gz", hash = "sha256:9e85ad35cfc6c938676817c7ae9a1000a7c72a34c71db0c687136c460d12b85e" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/937/4d9c531b62018/Arpeggio-2.0.3-py2.py3-none-any.whl", hash = "sha256:9374d9c531b62018b787635f37fd81c9a6ee69ef2d28c5db3cd18791b1f7db2f" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, +] + +[[package]] +name = "blumeops-dns" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pulumi" }, + { name = "pulumiverse-gandi" }, +] + +[package.metadata] +requires-dist = [ + { name = "pulumi", specifier = ">=3.0.0" }, + { name = "pulumiverse-gandi", specifier = ">=2.3.0" }, +] + +[[package]] +name = "debugpy" +version = "1.8.19" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/eea/7e5987445ab0b/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/c5d/cfa21de1f735a/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/806/d680024624400/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/783/a519e6dfb1f3c/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/140/35cbdbb1fe4b6/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/bcc/b1540a49cde77/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/e9c/68d9a382ec754/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/659/9cab8a783d149/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/66e/3d2fd8f2035a8/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/91e/35db2672a0aba/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/850/16a73ab84dea1/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b60/5f17e89ba0ece/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/c30/639998a9f9cd9/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/1e8/c4d1bd230067b/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/d40/c016c1f538dbf/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/060/1708223fe1cd0/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8e1/9a725f5d486f2/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/360/ffd231a780abb/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/423/092df4182177d/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/1e1/ce33e978ae97f/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/7be/78388d6da1a25/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/2e1/743fbd7f5fa71/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/a8c/2cf1209497cf6/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/08c/aea849a9d3c71/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/f0e/34c2079d47ae9/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/884/3114c0cfce61b/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8ed/dfb4d203a237d/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/324/83fe2aab2c379/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/dcf/e41187da8992c/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/210/7b0c024d1b35f/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/522/175aba7af9113/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/81f/d9652b37b36f1/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/04b/be1bfe3a68bbf/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/d38/8087771c837cd/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/9f8/f757bebaaea11/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/980/a846182ce88c4/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/f92/f88e6c033db65/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/4ba/f3cbe2f0be328/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/615/ba64c208aaceb/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/45d/59a649a82df57/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/c08/8e7a90b601730/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/26e/f06c73eb53267/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/45e/0111e73f43f73/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/83d/57312a58dcfe2/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/3e2/a27c89eb9ac3d/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/61f/69297cba3950a/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/6a1/5c17af8839b68/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/25a/18e9810fbc7e7/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/931/091142fd8cc14/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/5e8/571632780e085/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/f9f/7bd5faab55f47/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/ff8/a59ea85a1f219/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/06c/3d6b076e7b593/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/fd5/ef5932f6475c4/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b33/1680e46239e09/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/222/9ae655ec4e899/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/490/fa6d203992c47/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/479/496325ce55479/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/1c9/b93f79f48b03a/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/747/fa73efa9b8b14/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/922/fa70ba549fce3/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e" }, +] + +[[package]] +name = "parver" +version = "0.5" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +dependencies = [ + { name = "arpeggio" }, + { name = "attrs" }, +] +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b9f/de1e6bb9ce9f0/parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/228/1b187276c8e8e/parver-0.5-py3-none-any.whl", hash = "sha256:2281b187276c8e8e3c15634f62287b2fb6fe0efe3010f739a6bd1e45fa2bf2b2" }, +] + +[[package]] +name = "pip" +version = "25.3" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8d0/538dbbd7babbd/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/965/5943313a94722/pip-25.3-py3-none-any.whl", hash = "sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd" }, +] + +[[package]] +name = "protobuf" +version = "5.29.5" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/bc1/463bafd4b0929/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/3f1/c6468a2cfd102/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/3f7/6e3a3675b4a4d/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/e38/c5add5a311f2a/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/fa1/8533a299d7ab6/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/638/48923da3325e1/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/6cf/42630262c59b2/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5" }, +] + +[[package]] +name = "pulumi" +version = "3.217.0" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +dependencies = [ + { name = "debugpy" }, + { name = "dill" }, + { name = "grpcio" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "semver" }, +] +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b5e/fb2e9fb34c2d2/pulumi-3.217.0-py3-none-any.whl", hash = "sha256:b5efb2e9fb34c2d2902d3ec39af0150775c27b80e38c5e421a77454d69dbae25" }, +] + +[[package]] +name = "pulumiverse-gandi" +version = "2.3.2" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/59a/621d46fc35be4/pulumiverse_gandi-2.3.2.tar.gz", hash = "sha256:59a621d46fc35be46196d6484a026cae8d6ab973a7241cbc44e0849c931d5ac6" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/13d/be5b0bb9c080d/pulumiverse_gandi-2.3.2-py3-none-any.whl", hash = "sha256:13dbe5b0bb9c080d6c389b1d0fcda907adf8904fa363c053b36bbbf60918220d" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/d76/623373421df22/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/44e/dc64787392855/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/652/cb6edd41e7185/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/108/92704fc220243/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/850/774a7879607d3/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b8b/b0864c5a28024/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/1d3/7d57ad971609c/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/375/03bfbfc9d2c40/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/809/8f252adfa6c80/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/9f3/bfb4965eb8744/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/7f0/47e29dcae4460/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/fc0/9d0aa354569bc/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/914/9cad251584d5f/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/5fd/ec68f91a0c673/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/ba1/cc08a7ccde2d2/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8dc/52c23056b9ddd/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/417/15c910c881bc0/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/96b/533f0e99f6579/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/5fc/d34e47f6e0b79/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/643/86e5e707d03a7/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8da/9669d359f02c0/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/228/3a07e2c21a2aa/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/ee2/922902c45ae8c/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/a33/284e20b78bd4a/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/0f2/9edc409a63924/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/f70/57c9a337546ed/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/eda/16858a3cab07b/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/d0e/ae10f8159e8fd/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/790/05a0d97d5ddab/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/549/8cd1645aa724a/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/8d1/fab6bb153a416/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/34d/5fcd24b8445fa/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/501/a031947e3a902/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/b3b/c83488de33889/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/c45/8b6d084f9b935/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/7c6/610def4f16354/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/519/0d403f121660c/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/4a2/e8cebe2ff6ab7/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/93d/da82c9c22deb0/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/028/93d100e99e03e/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/c1f/f362665ae5072/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/6ad/c77889b628398/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/a80/cb027f6b34984/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/00c/4bdeba853cc34/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/66e/1674c3ef6f541/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/162/49ee61e95f858/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/4ad/1906908f2f5ae/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9" }, + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/ebc/55a14a21cb140/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b" }, +] + +[[package]] +name = "semver" +version = "3.0.4" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/afc/7d8c584a5ed0a/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/9c8/24d87ba7f7ab4/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.tail8d86e.ts.net/root/pypi/+simple/" } +sdist = { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } +wheels = [ + { url = "https://pypi.tail8d86e.ts.net/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, +] -- 2.50.1 (Apple Git-155)