From 4374e0c0a76a193950dde2295a3550526e08fa1a Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 01:01:25 -0800 Subject: [PATCH 1/8] Add Fly.io public reverse proxy infrastructure Introduces the fly/ directory with nginx + Tailscale container config, Pulumi changes for Tailscale ACLs and auth key, DNS CNAME for docs.eblu.me (staged but not yet deployed), mise tasks for deploy/setup/ shutoff, and Forgejo CI workflow for auto-deploy on push. First target service: docs.eblu.me Co-Authored-By: Claude Opus 4.6 --- .forgejo/workflows/deploy-fly.yaml | 37 +++++++++ Brewfile | 1 + .../feature-flyio-proxy.feature.md | 1 + docs/how-to/expose-service-publicly.md | 79 +++++++++++++------ fly/Dockerfile | 17 ++++ fly/fly.toml | 19 +++++ fly/nginx.conf | 56 +++++++++++++ fly/start.sh | 16 ++++ mise-tasks/fly-deploy | 7 ++ mise-tasks/fly-setup | 19 +++++ mise-tasks/fly-shutoff | 11 +++ pulumi/gandi/__main__.py | 14 ++++ pulumi/tailscale/__main__.py | 12 +++ pulumi/tailscale/policy.hujson | 15 ++++ 14 files changed, 278 insertions(+), 26 deletions(-) create mode 100644 .forgejo/workflows/deploy-fly.yaml create mode 100644 docs/changelog.d/feature-flyio-proxy.feature.md create mode 100644 fly/Dockerfile create mode 100644 fly/fly.toml create mode 100644 fly/nginx.conf create mode 100644 fly/start.sh create mode 100755 mise-tasks/fly-deploy create mode 100755 mise-tasks/fly-setup create mode 100755 mise-tasks/fly-shutoff diff --git a/.forgejo/workflows/deploy-fly.yaml b/.forgejo/workflows/deploy-fly.yaml new file mode 100644 index 0000000..a38e845 --- /dev/null +++ b/.forgejo/workflows/deploy-fly.yaml @@ -0,0 +1,37 @@ +name: Deploy Fly.io Proxy + +on: + workflow_dispatch: + push: + branches: [main] + paths: + - 'fly/**' + +jobs: + deploy: + runs-on: k8s + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install flyctl + run: | + curl -L https://fly.io/install.sh | sh + echo "/root/.fly/bin" >> "$GITHUB_PATH" + + - name: Deploy to Fly.io + env: + FLY_API_TOKEN: ${{ secrets.FLY_DEPLOY_TOKEN }} + run: | + cd fly + fly deploy + + - name: Verify health + env: + FLY_API_TOKEN: ${{ secrets.FLY_DEPLOY_TOKEN }} + run: | + fly status -a blumeops-proxy + echo "" + echo "Health check:" + sleep 10 + curl -sf https://blumeops-proxy.fly.dev/healthz || echo "Warning: health check failed (may need DNS propagation)" diff --git a/Brewfile b/Brewfile index a87fe8c..e5207a2 100644 --- a/Brewfile +++ b/Brewfile @@ -4,4 +4,5 @@ brew "argocd" # ArgoCD CLI for GitOps management brew "bat" # Syntax-highlighted file concatenation brew "mise" # Task runner and toolchain manager brew "tea" # Gitea/Forgejo CLI for forge.ops.eblu.me +brew "flyctl" # Fly.io CLI for public proxy management brew "podman" # Container CLI (uses VM on macOS, for building/pushing images) diff --git a/docs/changelog.d/feature-flyio-proxy.feature.md b/docs/changelog.d/feature-flyio-proxy.feature.md new file mode 100644 index 0000000..78bb168 --- /dev/null +++ b/docs/changelog.d/feature-flyio-proxy.feature.md @@ -0,0 +1 @@ +Add Fly.io public reverse proxy infrastructure for exposing services to the internet (first target: docs.eblu.me) diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index cb318cf..824fc57 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -11,7 +11,7 @@ id: expose-service-publicly # Expose a Service Publicly via Fly.io + Tailscale -> **Status:** Plan — not yet implemented. First target: `docs.eblu.me`. +> **Status:** In progress — first target: `docs.eblu.me`. This guide describes how to expose a BlumeOps service to the public internet using a reverse proxy container on [Fly.io](https://fly.io) that tunnels back @@ -497,9 +497,41 @@ Key differences for dynamic services: - **WebSocket support** — many modern web apps use WebSockets - **Larger body size** — git pushes and file uploads need more than the default 1MB -### 2. Add DNS CNAME (Pulumi) +### 2. Add Fly.io certificate -Add to `pulumi/gandi/__main__.py`: +```bash +fly certs add wiki.eblu.me -a blumeops-proxy +``` + +Or add it to `mise-tasks/fly-setup` so it's captured for future runs. + +### 3. Deploy + +```bash +mise run fly-deploy +``` + +Or push the `fly/nginx.conf` change to main — the Forgejo workflow deploys automatically. + +### 4. Verify against fly.dev + +Test the proxy before touching DNS. Use the `Host` header to simulate +the real domain: + +```bash +# Health check +curl -sf https://blumeops-proxy.fly.dev/healthz + +# Simulate real domain request +curl -I -H "Host: wiki.eblu.me" https://blumeops-proxy.fly.dev/ +# Should return 200 with X-Cache-Status header +``` + +If this fails, debug without any public DNS impact. + +### 5. Add DNS CNAME (Pulumi) + +Only after verifying the proxy works. Add to `pulumi/gandi/__main__.py`: ```python wiki_public = gandi.livedns.Record( @@ -514,30 +546,14 @@ wiki_public = gandi.livedns.Record( Deploy: `mise run dns-preview` then `mise run dns-up`. -### 3. Add Fly.io certificate - -```bash -fly certs add wiki.eblu.me -a blumeops-proxy -``` - -Or add it to `mise-tasks/fly-setup` so it's captured for future runs. - -### 4. Deploy - -```bash -mise run fly-deploy -``` - -Or push the `fly/nginx.conf` change to main — the Forgejo workflow deploys automatically. - -### 5. Verify +### 6. Verify with real domain ```bash curl -I https://wiki.eblu.me # Should return 200 with X-Cache-Status header ``` -### 6. Update Tailscale ACLs if needed +### 7. Update Tailscale ACLs if needed The one-time setup grants `tag:flyio-proxy` access to `tag:k8s` on port 443. If the new service needs a different grant, add it to @@ -688,12 +704,23 @@ The "semi" for Fly.io secrets is a one-time operation backed by a repeatable mis ## Verification -After initial deployment of a service (using `docs.eblu.me` as example): +### Pre-DNS (verify against fly.dev) + +Test the proxy works before creating any public DNS records: + +1. `curl -sf https://blumeops-proxy.fly.dev/healthz` — returns `ok` +2. `curl -I -H "Host: docs.eblu.me" https://blumeops-proxy.fly.dev/` — returns 200 with `X-Cache-Status` header +3. `fly status -a blumeops-proxy` — shows healthy machine +4. All `*.ops.eblu.me` services still work from tailnet (unchanged) +5. `mise run services-check` passes + +If anything fails here, debug without public DNS impact. + +### Post-DNS (after CNAME is live) + +After deploying DNS (`mise run dns-up`): 1. `curl -I https://docs.eblu.me` — returns 200 with `X-Cache-Status` header 2. `dig docs.eblu.me` — resolves to Fly.io IPs (not Tailscale IP) 3. `dig forge.ops.eblu.me` — still resolves to `100.98.163.89` (unchanged) -4. All `*.ops.eblu.me` services work from tailnet -5. `mise run services-check` passes -6. `fly status -a blumeops-proxy` shows healthy machine -7. Second request to same URL shows `X-Cache-Status: HIT` +4. Second request to same URL shows `X-Cache-Status: HIT` diff --git a/fly/Dockerfile b/fly/Dockerfile new file mode 100644 index 0000000..7d71d85 --- /dev/null +++ b/fly/Dockerfile @@ -0,0 +1,17 @@ +FROM nginx:alpine + +# Copy tailscale binaries from official image +COPY --from=docker.io/tailscale/tailscale:stable \ + /usr/local/bin/tailscaled /usr/local/bin/tailscaled +COPY --from=docker.io/tailscale/tailscale:stable \ + /usr/local/bin/tailscale /usr/local/bin/tailscale + +RUN mkdir -p /var/run/tailscale /var/lib/tailscale + +COPY nginx.conf /etc/nginx/nginx.conf +COPY start.sh /start.sh +RUN chmod +x /start.sh + +EXPOSE 8080 + +CMD ["/start.sh"] diff --git a/fly/fly.toml b/fly/fly.toml new file mode 100644 index 0000000..676b215 --- /dev/null +++ b/fly/fly.toml @@ -0,0 +1,19 @@ +app = "blumeops-proxy" +primary_region = "sea" + +[build] + +[http_service] +internal_port = 8080 +force_https = true +auto_stop_machines = "off" +auto_start_machines = true +min_machines_running = 1 + +[checks] +[checks.health] +port = 8080 +type = "http" +interval = "30s" +timeout = "5s" +path = "/healthz" diff --git a/fly/nginx.conf b/fly/nginx.conf new file mode 100644 index 0000000..c2ed6bb --- /dev/null +++ b/fly/nginx.conf @@ -0,0 +1,56 @@ +worker_processes auto; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Rate limiting zones — define per-service zones as needed + limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s; + + # Proxy cache: 200MB, evict after 24h of no access + proxy_cache_path /tmp/cache levels=1:2 keys_zone=services:10m + max_size=200m inactive=24h; + + # --- docs.eblu.me (static site) --- + server { + listen 8080; + server_name docs.eblu.me; + + limit_req zone=general burst=20 nodelay; + + location / { + proxy_pass https://docs.tail8d86e.ts.net; + proxy_ssl_verify off; + + # Cache aggressively — static site only. + # Do NOT use these settings for dynamic services. + proxy_cache services; + proxy_cache_valid 200 1d; + proxy_cache_valid 404 1m; + proxy_cache_use_stale error timeout updating; + proxy_cache_lock on; + + # Prevent cache-busting: ignore query strings and + # client cache-control headers. + # Safe for static sites; breaks dynamic services. + proxy_cache_key $host$uri; + proxy_ignore_headers Cache-Control Set-Cookie; + + add_header X-Cache-Status $upstream_cache_status; + } + + location /healthz { + return 200 "ok\n"; + } + } + + # Catch-all: reject unknown hosts + server { + listen 8080 default_server; + return 444; + } +} diff --git a/fly/start.sh b/fly/start.sh new file mode 100644 index 0000000..918c455 --- /dev/null +++ b/fly/start.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# Start tailscale in userspace networking mode (no TUN device needed) +tailscaled --tun=userspace-networking --statedir=/var/lib/tailscale & +sleep 2 + +# Authenticate and join tailnet +tailscale up --authkey="${TS_AUTHKEY}" --hostname=flyio-proxy + +# Wait for tailscale to be ready +until tailscale status > /dev/null 2>&1; do sleep 1; done +echo "Tailscale connected" + +# Start nginx +nginx -g "daemon off;" diff --git a/mise-tasks/fly-deploy b/mise-tasks/fly-deploy new file mode 100755 index 0000000..bb2b4f8 --- /dev/null +++ b/mise-tasks/fly-deploy @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +#MISE description="Deploy the Fly.io public proxy" + +set -euo pipefail + +cd "$(dirname "$0")/../fly" +fly deploy "$@" diff --git a/mise-tasks/fly-setup b/mise-tasks/fly-setup new file mode 100755 index 0000000..c5b1c71 --- /dev/null +++ b/mise-tasks/fly-setup @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +#MISE description="One-time setup: configure Fly.io secrets and certs (idempotent)" + +set -euo pipefail + +APP="blumeops-proxy" + +# Fetch Tailscale auth key from Pulumi state +echo "Fetching Tailscale auth key from Pulumi..." +TS_AUTHKEY=$(cd "$(dirname "$0")/../pulumi/tailscale" && pulumi stack output flyio_authkey --show-secrets) +fly secrets set TS_AUTHKEY="$TS_AUTHKEY" -a "$APP" +echo "Tailscale auth key set" + +# Add certs for all public domains (idempotent — fly ignores duplicates) +fly certs add docs.eblu.me -a "$APP" 2>/dev/null || true +# fly certs add wiki.eblu.me -a "$APP" 2>/dev/null || true # future services +echo "Certificates configured" + +echo "Done. Run 'mise run fly-deploy' to deploy." diff --git a/mise-tasks/fly-shutoff b/mise-tasks/fly-shutoff new file mode 100755 index 0000000..f9e4f90 --- /dev/null +++ b/mise-tasks/fly-shutoff @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +#MISE description="Emergency shutoff: stop all Fly.io proxy machines" + +set -euo pipefail + +APP="blumeops-proxy" + +echo "EMERGENCY SHUTOFF: Stopping all machines for $APP" +fly scale count 0 -a "$APP" --yes +echo "All machines stopped. Public services are offline." +echo "To restore: fly scale count 1 -a $APP" diff --git a/pulumi/gandi/__main__.py b/pulumi/gandi/__main__.py index 4361c91..55b7665 100644 --- a/pulumi/gandi/__main__.py +++ b/pulumi/gandi/__main__.py @@ -54,8 +54,22 @@ base_record = gandi.livedns.Record( values=[tailscale_ip], ) +# ============== Public Services (Fly.io proxy) ============== +# CNAME records pointing public subdomains to Fly.io for reverse proxying +# back to the tailnet. See docs/how-to/expose-service-publicly.md + +docs_public = gandi.livedns.Record( + "docs-public", + zone=domain, + name="docs", + type="CNAME", + ttl=300, + values=["blumeops-proxy.fly.dev."], +) + # ============== 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) +pulumi.export("docs_public_fqdn", f"docs.{domain}") diff --git a/pulumi/tailscale/__main__.py b/pulumi/tailscale/__main__.py index 7c76c26..80e2793 100644 --- a/pulumi/tailscale/__main__.py +++ b/pulumi/tailscale/__main__.py @@ -70,9 +70,21 @@ sifaka_tags = tailscale.DeviceTags( ], ) +# ============== Auth Keys ============== + +# Auth key for Fly.io proxy container (public reverse proxy) +flyio_key = tailscale.TailnetKey( + "flyio-proxy-key", + reusable=True, + ephemeral=True, + tags=["tag:flyio-proxy"], + expiry=7776000, # 90 days +) + # ============== Exports ============== pulumi.export("acl_id", acl.id) pulumi.export("policy_hash", policy_hash) +pulumi.export("flyio_authkey", flyio_key.key) pulumi.export("indri_device_id", indri.node_id) pulumi.export("indri_tags", indri_tags.tags) diff --git a/pulumi/tailscale/policy.hujson b/pulumi/tailscale/policy.hujson index 9949ade..43542dd 100644 --- a/pulumi/tailscale/policy.hujson +++ b/pulumi/tailscale/policy.hujson @@ -60,6 +60,14 @@ "ip": ["*"], }, + // --- Fly.io proxy --- + // Public reverse proxy can reach k8s services on HTTPS only + { + "src": ["tag:flyio-proxy"], + "dst": ["tag:k8s"], + "ip": ["tcp:443"], + }, + // --- CI Gateway --- // Ephemeral CI containers can push images to registry { @@ -136,6 +144,7 @@ "tag:k8s-operator": ["autogroup:admin", "tag:blumeops"], "tag:k8s": ["autogroup:admin", "tag:blumeops", "tag:k8s-operator"], "tag:ci-gateway": ["autogroup:admin", "tag:blumeops"], + "tag:flyio-proxy": ["autogroup:admin", "tag:blumeops"], }, // ============== ACL Tests ============== @@ -166,5 +175,11 @@ "src": "tag:ci-gateway", "accept": ["tag:registry:443"], }, + // Fly.io proxy can reach k8s services (HTTPS only), nothing else + { + "src": "tag:flyio-proxy", + "accept": ["tag:k8s:443"], + "deny": ["tag:homelab:22", "tag:nas:445", "tag:registry:443"], + }, ], } -- 2.50.1 (Apple Git-155) From 3f88dea9d00170b22f136d0d2f38d78eb577053d Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 01:04:32 -0800 Subject: [PATCH 2/8] Add explicit stack selection to Pulumi mise tasks Prevents "no stack selected" errors when running from a fresh environment or after stack state is cleared. Co-Authored-By: Claude Opus 4.6 --- mise-tasks/dns-preview | 1 + mise-tasks/dns-up | 1 + mise-tasks/tailnet-preview | 1 + mise-tasks/tailnet-up | 1 + 4 files changed, 4 insertions(+) diff --git a/mise-tasks/dns-preview b/mise-tasks/dns-preview index 7d7578e..47f8fd2 100755 --- a/mise-tasks/dns-preview +++ b/mise-tasks/dns-preview @@ -7,4 +7,5 @@ GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco export GANDI_PERSONAL_ACCESS_TOKEN cd "$(dirname "$0")/../pulumi/gandi" +pulumi stack select eblu-me pulumi preview "$@" diff --git a/mise-tasks/dns-up b/mise-tasks/dns-up index a0d3849..b33bed7 100755 --- a/mise-tasks/dns-up +++ b/mise-tasks/dns-up @@ -7,4 +7,5 @@ GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco export GANDI_PERSONAL_ACCESS_TOKEN cd "$(dirname "$0")/../pulumi/gandi" +pulumi stack select eblu-me pulumi up --yes "$@" diff --git a/mise-tasks/tailnet-preview b/mise-tasks/tailnet-preview index ceb6439..84ee7af 100755 --- a/mise-tasks/tailnet-preview +++ b/mise-tasks/tailnet-preview @@ -10,4 +10,5 @@ export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" cd "$(dirname "$0")/../pulumi/tailscale" +pulumi stack select tail8d86e pulumi preview "$@" diff --git a/mise-tasks/tailnet-up b/mise-tasks/tailnet-up index f22048b..397aa0b 100755 --- a/mise-tasks/tailnet-up +++ b/mise-tasks/tailnet-up @@ -10,4 +10,5 @@ export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" cd "$(dirname "$0")/../pulumi/tailscale" +pulumi stack select tail8d86e pulumi up --yes "$@" -- 2.50.1 (Apple Git-155) From bcefb088948ad4d55d33732ba91e3bf53c33eac9 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 01:19:37 -0800 Subject: [PATCH 3/8] Fix Pulumi venv: re-lock via devpi, add sync with fallback hint Re-enabled devpi cache and regenerated lock files against it. Removed uv.lock from tailscale .gitignore so locks are tracked. Mise tasks now run uv sync before Pulumi and suggest 'devpi off' if sync fails (e.g. during a power outage or devpi cache clear). Co-Authored-By: Claude Opus 4.6 --- mise-tasks/dns-preview | 1 + mise-tasks/dns-up | 1 + mise-tasks/tailnet-preview | 1 + mise-tasks/tailnet-up | 1 + pulumi/gandi/uv.lock | 288 ++++++++++++++++++------------------ pulumi/tailscale/.gitignore | 3 - pulumi/tailscale/uv.lock | 265 +++++++++++++++++++++++++++++++++ 7 files changed, 413 insertions(+), 147 deletions(-) create mode 100644 pulumi/tailscale/uv.lock diff --git a/mise-tasks/dns-preview b/mise-tasks/dns-preview index 47f8fd2..be7b9e0 100755 --- a/mise-tasks/dns-preview +++ b/mise-tasks/dns-preview @@ -7,5 +7,6 @@ GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco export GANDI_PERSONAL_ACCESS_TOKEN cd "$(dirname "$0")/../pulumi/gandi" +uv sync --quiet || { echo "uv sync failed — if devpi is down, run 'devpi off' and retry"; exit 1; } pulumi stack select eblu-me pulumi preview "$@" diff --git a/mise-tasks/dns-up b/mise-tasks/dns-up index b33bed7..2be5abb 100755 --- a/mise-tasks/dns-up +++ b/mise-tasks/dns-up @@ -7,5 +7,6 @@ GANDI_PERSONAL_ACCESS_TOKEN=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mco export GANDI_PERSONAL_ACCESS_TOKEN cd "$(dirname "$0")/../pulumi/gandi" +uv sync --quiet || { echo "uv sync failed — if devpi is down, run 'devpi off' and retry"; exit 1; } pulumi stack select eblu-me pulumi up --yes "$@" diff --git a/mise-tasks/tailnet-preview b/mise-tasks/tailnet-preview index 84ee7af..3df1369 100755 --- a/mise-tasks/tailnet-preview +++ b/mise-tasks/tailnet-preview @@ -10,5 +10,6 @@ export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" cd "$(dirname "$0")/../pulumi/tailscale" +uv sync --quiet || { echo "uv sync failed — if devpi is down, run 'devpi off' and retry"; exit 1; } pulumi stack select tail8d86e pulumi preview "$@" diff --git a/mise-tasks/tailnet-up b/mise-tasks/tailnet-up index 397aa0b..882fada 100755 --- a/mise-tasks/tailnet-up +++ b/mise-tasks/tailnet-up @@ -10,5 +10,6 @@ export TAILSCALE_OAUTH_CLIENT_SECRET export TAILSCALE_TAILNET="tail8d86e.ts.net" cd "$(dirname "$0")/../pulumi/tailscale" +uv sync --quiet || { echo "uv sync failed — if devpi is down, run 'devpi off' and retry"; exit 1; } pulumi stack select tail8d86e pulumi up --yes "$@" diff --git a/pulumi/gandi/uv.lock b/pulumi/gandi/uv.lock index 946d09b..dad6981 100644 --- a/pulumi/gandi/uv.lock +++ b/pulumi/gandi/uv.lock @@ -9,19 +9,19 @@ resolution-markers = [ [[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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, ] [[package]] @@ -42,128 +42,128 @@ requires-dist = [ [[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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c5d/cfa21de1f735a/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/806/d680024624400/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/783/a519e6dfb1f3c/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/140/35cbdbb1fe4b6/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/bcc/b1540a49cde77/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/e9c/68d9a382ec754/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/659/9cab8a783d149/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/66e/3d2fd8f2035a8/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/91e/35db2672a0aba/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/850/16a73ab84dea1/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/b60/5f17e89ba0ece/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c30/639998a9f9cd9/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/1e8/c4d1bd230067b/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d40/c016c1f538dbf/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/060/1708223fe1cd0/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8e1/9a725f5d486f2/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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/" } +source = { registry = "https://pypi.ops.eblu.me/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" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/2e1/743fbd7f5fa71/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/a8c/2cf1209497cf6/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/08c/aea849a9d3c71/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f0e/34c2079d47ae9/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/8ed/dfb4d203a237d/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/324/83fe2aab2c379/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/dcf/e41187da8992c/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/210/7b0c024d1b35f/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/522/175aba7af9113/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/81f/d9652b37b36f1/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/04b/be1bfe3a68bbf/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d38/8087771c837cd/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/9f8/f757bebaaea11/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/f92/f88e6c033db65/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4ba/f3cbe2f0be328/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/615/ba64c208aaceb/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/45d/59a649a82df57/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c08/8e7a90b601730/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/26e/f06c73eb53267/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/45e/0111e73f43f73/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/83d/57312a58dcfe2/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3e2/a27c89eb9ac3d/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/6a1/5c17af8839b68/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/25a/18e9810fbc7e7/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/931/091142fd8cc14/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/5e8/571632780e085/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f9f/7bd5faab55f47/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/ff8/a59ea85a1f219/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/06c/3d6b076e7b593/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fd5/ef5932f6475c4/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/b33/1680e46239e09/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/490/fa6d203992c47/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/479/496325ce55479/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/1c9/b93f79f48b03a/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/747/fa73efa9b8b14/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e" }, + { url = "https://pypi.ops.eblu.me/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/" } +source = { registry = "https://pypi.ops.eblu.me/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" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3f1/c6468a2cfd102/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3f7/6e3a3675b4a4d/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/e38/c5add5a311f2a/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fa1/8533a299d7ab6/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/638/48923da3325e1/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61" }, + { url = "https://pypi.ops.eblu.me/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/" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } dependencies = [ { name = "debugpy" }, { name = "dill" }, @@ -174,92 +174,92 @@ dependencies = [ { 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" }, + { url = "https://pypi.ops.eblu.me/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/" } +source = { registry = "https://pypi.ops.eblu.me/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" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/44e/dc64787392855/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/652/cb6edd41e7185/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/1d3/7d57ad971609c/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/375/03bfbfc9d2c40/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/809/8f252adfa6c80/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/9f3/bfb4965eb8744/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/7f0/47e29dcae4460/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fc0/9d0aa354569bc/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/8dc/52c23056b9ddd/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/417/15c910c881bc0/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/96b/533f0e99f6579/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/5fc/d34e47f6e0b79/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/643/86e5e707d03a7/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8da/9669d359f02c0/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/228/3a07e2c21a2aa/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/f70/57c9a337546ed/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/eda/16858a3cab07b/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d0e/ae10f8159e8fd/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/790/05a0d97d5ddab/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/549/8cd1645aa724a/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8d1/fab6bb153a416/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/34d/5fcd24b8445fa/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/7c6/610def4f16354/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/519/0d403f121660c/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4a2/e8cebe2ff6ab7/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/93d/da82c9c22deb0/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/028/93d100e99e03e/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c1f/f362665ae5072/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/66e/1674c3ef6f541/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/162/49ee61e95f858/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4ad/1906908f2f5ae/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/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" } +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/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" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, ] diff --git a/pulumi/tailscale/.gitignore b/pulumi/tailscale/.gitignore index 01a30d0..36cb85b 100644 --- a/pulumi/tailscale/.gitignore +++ b/pulumi/tailscale/.gitignore @@ -3,8 +3,5 @@ __pycache__/ *.py[cod] -# uv -uv.lock - # Pulumi *.pyc diff --git a/pulumi/tailscale/uv.lock b/pulumi/tailscale/uv.lock new file mode 100644 index 0000000..9462590 --- /dev/null +++ b/pulumi/tailscale/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/9e8/5ad35cfc6c938/Arpeggio-2.0.3.tar.gz", hash = "sha256:9e85ad35cfc6c938676817c7ae9a1000a7c72a34c71db0c687136c460d12b85e" } +wheels = [ + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/16d/5969b87f0859e/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/adc/f7e2a1fb3b36a/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373" }, +] + +[[package]] +name = "blumeops-tailnet" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pulumi" }, + { name = "pulumi-tailscale" }, +] + +[package.metadata] +requires-dist = [ + { name = "pulumi", specifier = ">=3.0.0" }, + { name = "pulumi-tailscale", specifier = ">=0.24.0" }, +] + +[[package]] +name = "debugpy" +version = "1.8.19" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/eea/7e5987445ab0b/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c5d/cfa21de1f735a/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/806/d680024624400/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/783/a519e6dfb1f3c/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/140/35cbdbb1fe4b6/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/bcc/b1540a49cde77/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/e9c/68d9a382ec754/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/659/9cab8a783d149/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/66e/3d2fd8f2035a8/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/91e/35db2672a0aba/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/850/16a73ab84dea1/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/b60/5f17e89ba0ece/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c30/639998a9f9cd9/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/1e8/c4d1bd230067b/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d40/c016c1f538dbf/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/060/1708223fe1cd0/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8e1/9a725f5d486f2/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/360/ffd231a780abb/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38" }, +] + +[[package]] +name = "dill" +version = "0.4.0" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/063/3f1d2df477324/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/44f/54bf6412c2c84/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/7be/78388d6da1a25/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/2e1/743fbd7f5fa71/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/a8c/2cf1209497cf6/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/08c/aea849a9d3c71/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f0e/34c2079d47ae9/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/8ed/dfb4d203a237d/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/324/83fe2aab2c379/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/dcf/e41187da8992c/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/210/7b0c024d1b35f/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/522/175aba7af9113/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/81f/d9652b37b36f1/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/04b/be1bfe3a68bbf/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d38/8087771c837cd/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/9f8/f757bebaaea11/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/f92/f88e6c033db65/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4ba/f3cbe2f0be328/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/615/ba64c208aaceb/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/45d/59a649a82df57/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c08/8e7a90b601730/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/26e/f06c73eb53267/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/45e/0111e73f43f73/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/83d/57312a58dcfe2/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3e2/a27c89eb9ac3d/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/6a1/5c17af8839b68/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/25a/18e9810fbc7e7/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/931/091142fd8cc14/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/5e8/571632780e085/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f9f/7bd5faab55f47/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/ff8/a59ea85a1f219/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/06c/3d6b076e7b593/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fd5/ef5932f6475c4/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/b33/1680e46239e09/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/490/fa6d203992c47/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/479/496325ce55479/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/1c9/b93f79f48b03a/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/747/fa73efa9b8b14/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +dependencies = [ + { name = "arpeggio" }, + { name = "attrs" }, +] +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/b9f/de1e6bb9ce9f0/parver-0.5.tar.gz", hash = "sha256:b9fde1e6bb9ce9f07e08e9c4bea8d8825c5e78e18a0052d02e02bf9517eb4777" } +wheels = [ + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/8d0/538dbbd7babbd/pip-25.3.tar.gz", hash = "sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343" } +wheels = [ + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/bc1/463bafd4b0929/protobuf-5.29.5.tar.gz", hash = "sha256:bc1463bafd4b0929216c35f437a8e28731a2b7fe3d98bb77a600efced5a15c84" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3f1/c6468a2cfd102/protobuf-5.29.5-cp310-abi3-win32.whl", hash = "sha256:3f1c6468a2cfd102ff4703976138844f78ebd1fb45f49011afc5139e9e283079" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/3f7/6e3a3675b4a4d/protobuf-5.29.5-cp310-abi3-win_amd64.whl", hash = "sha256:3f76e3a3675b4a4d867b52e4a5f5b78a2ef9565549d4037e06cf7b0942b1d3fc" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/e38/c5add5a311f2a/protobuf-5.29.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e38c5add5a311f2a6eb0340716ef9b039c1dfa428b28f25a7838ac329204a671" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fa1/8533a299d7ab6/protobuf-5.29.5-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:fa18533a299d7ab6c55a238bf8629311439995f2e7eca5caaff08663606e9015" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/638/48923da3325e1/protobuf-5.29.5-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:63848923da3325e1bf7e9003d680ce6e14b07e55d0473253a690c3a8b8fd6e61" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/6cf/42630262c59b2/protobuf-5.29.5-py3-none-any.whl", hash = "sha256:6cf42630262c59b2d8de33954443d94b746c952b01434fc58a417fdbd2e84bd5" }, +] + +[[package]] +name = "pulumi" +version = "3.215.0" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +dependencies = [ + { name = "debugpy" }, + { name = "dill" }, + { name = "grpcio" }, + { name = "pip" }, + { name = "protobuf" }, + { name = "pyyaml" }, + { name = "semver" }, +] +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/97a/380c26414f9ea/pulumi-3.215.0-py3-none-any.whl", hash = "sha256:97a380c26414f9ea14d7265513f7f667b139cfc44576e8d492117d304a70283c" }, +] + +[[package]] +name = "pulumi-tailscale" +version = "0.24.0" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +dependencies = [ + { name = "parver" }, + { name = "pulumi" }, + { name = "semver" }, +] +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/4bd/da5ca09b07efd/pulumi_tailscale-0.24.0.tar.gz", hash = "sha256:4bdda5ca09b07efd89886cc3d20c4e45cde5430b7a8559a25cc26643ad86a46a" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/6da/c592c7812c821/pulumi_tailscale-0.24.0-py3-none-any.whl", hash = "sha256:6dac592c7812c82164e06228f833fa93a8ccb73afbb599c36ce417f773729ba9" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/d76/623373421df22/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/44e/dc64787392855/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/652/cb6edd41e7185/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/1d3/7d57ad971609c/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/375/03bfbfc9d2c40/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/809/8f252adfa6c80/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/9f3/bfb4965eb8744/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/7f0/47e29dcae4460/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/fc0/9d0aa354569bc/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/8dc/52c23056b9ddd/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/417/15c910c881bc0/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/96b/533f0e99f6579/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/5fc/d34e47f6e0b79/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/643/86e5e707d03a7/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8da/9669d359f02c0/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/228/3a07e2c21a2aa/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/f70/57c9a337546ed/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/eda/16858a3cab07b/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/d0e/ae10f8159e8fd/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/790/05a0d97d5ddab/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/549/8cd1645aa724a/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/8d1/fab6bb153a416/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/34d/5fcd24b8445fa/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/7c6/610def4f16354/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/519/0d403f121660c/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4a2/e8cebe2ff6ab7/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/93d/da82c9c22deb0/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/028/93d100e99e03e/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/c1f/f362665ae5072/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/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.ops.eblu.me/root/pypi/+f/66e/1674c3ef6f541/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/162/49ee61e95f858/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65" }, + { url = "https://pypi.ops.eblu.me/root/pypi/+f/4ad/1906908f2f5ae/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9" }, + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/afc/7d8c584a5ed0a/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602" } +wheels = [ + { url = "https://pypi.ops.eblu.me/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.ops.eblu.me/root/pypi/+simple/" } +sdist = { url = "https://pypi.ops.eblu.me/root/pypi/+f/0ce/a48d173cc12fa/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466" } +wheels = [ + { url = "https://pypi.ops.eblu.me/root/pypi/+f/f0f/a19c6845758ab/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" }, +] -- 2.50.1 (Apple Git-155) From a580b0f0797a3b73c38f2b6c42f4e48c14d9c53b Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 01:29:49 -0800 Subject: [PATCH 4/8] Add IP allocation to fly-setup, improve idempotency fly-setup now allocates shared IPv4 + IPv6 (both free for HTTP/HTTPS), stages secrets with --stage to avoid unnecessary redeployments, and selects the Pulumi stack explicitly. Updated docs with cost note for dedicated IPv4. Co-Authored-By: Claude Opus 4.6 --- docs/how-to/expose-service-publicly.md | 19 ++++++++++++++----- mise-tasks/fly-setup | 14 +++++++++++--- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index 824fc57..26b9ce6 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -50,7 +50,7 @@ infrastructure. They can continue to operate in parallel for private access. | Decision | Choice | Rationale | |----------|--------|-----------| -| Proxy host | Fly.io (free tier) | Managed container, no server to maintain via Ansible | +| Proxy host | Fly.io (free tier) | Managed container, no server to maintain via Ansible. Shared IPv4 + IPv6 are free for HTTP/HTTPS; dedicated IPv4 is $2/mo if a service needs non-HTTP(S) protocols | | Tunnel | Tailscale (existing) | Already in use, WireGuard encryption, ACL control | | DNS | CNAME at [[gandi]] | No DNS migration needed, no Cloudflare dependency | | TLS (public) | Fly.io auto-provisions Let's Encrypt | No cert management, `$0.10/mo` per hostname | @@ -323,10 +323,19 @@ set -euo pipefail APP="blumeops-proxy" -# Fetch Tailscale auth key from 1Password -TS_AUTHKEY=$(op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get --fields ts-authkey --reveal) -fly secrets set TS_AUTHKEY="$TS_AUTHKEY" -a "$APP" -echo "Tailscale auth key set" +# Fetch Tailscale auth key from Pulumi state +echo "Fetching Tailscale auth key from Pulumi..." +TS_AUTHKEY=$(cd "$(dirname "$0")/../pulumi/tailscale" && pulumi stack select tail8d86e && pulumi stack output flyio_authkey --show-secrets) +fly secrets set TS_AUTHKEY="$TS_AUTHKEY" --stage -a "$APP" +echo "Tailscale auth key staged (will take effect on next deploy)" + +# Allocate IPs (idempotent — fly errors if already allocated) +# Shared IPv4 is free and sufficient for HTTP/HTTPS services. +# Use 'fly ips allocate-v4' (no --shared) for dedicated IPv4 ($2/mo) +# if the service needs non-HTTP protocols. +fly ips allocate-v4 --shared -a "$APP" 2>/dev/null || true +fly ips allocate-v6 -a "$APP" 2>/dev/null || true +echo "IPs allocated" # Add certs for all public domains (idempotent — fly ignores duplicates) fly certs add docs.eblu.me -a "$APP" 2>/dev/null || true diff --git a/mise-tasks/fly-setup b/mise-tasks/fly-setup index c5b1c71..241c7fb 100755 --- a/mise-tasks/fly-setup +++ b/mise-tasks/fly-setup @@ -7,9 +7,17 @@ APP="blumeops-proxy" # Fetch Tailscale auth key from Pulumi state echo "Fetching Tailscale auth key from Pulumi..." -TS_AUTHKEY=$(cd "$(dirname "$0")/../pulumi/tailscale" && pulumi stack output flyio_authkey --show-secrets) -fly secrets set TS_AUTHKEY="$TS_AUTHKEY" -a "$APP" -echo "Tailscale auth key set" +TS_AUTHKEY=$(cd "$(dirname "$0")/../pulumi/tailscale" && pulumi stack select tail8d86e && pulumi stack output flyio_authkey --show-secrets) +fly secrets set TS_AUTHKEY="$TS_AUTHKEY" --stage -a "$APP" +echo "Tailscale auth key staged (will take effect on next deploy)" + +# Allocate IPs (idempotent — fly errors if already allocated) +# Shared IPv4 is free and sufficient for HTTP/HTTPS services. +# Use 'fly ips allocate-v4' (no --shared) for dedicated IPv4 ($2/mo) +# if the service needs non-HTTP protocols. +fly ips allocate-v4 --shared -a "$APP" 2>/dev/null || true +fly ips allocate-v6 -a "$APP" 2>/dev/null || true +echo "IPs allocated" # Add certs for all public domains (idempotent — fly ignores duplicates) fly certs add docs.eblu.me -a "$APP" 2>/dev/null || true -- 2.50.1 (Apple Git-155) From 0c6223fcf13c65bc8d77a764a6b39295a04304eb Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 02:16:19 -0800 Subject: [PATCH 5/8] Fix Fly.io proxy: use TUN networking, preauthorize key, move healthz Resolves multiple issues found during first deploy: - Drop --tun=userspace-networking: Fly.io Firecracker VMs support TUN natively; userspace mode broke MagicDNS and Tailscale IP routing - Add preauthorized=True to TailnetKey: required when tailnet has device approval enabled, otherwise containers hang on restart - Move /healthz to default_server: Fly health checks send no Host header, so healthz must be on the catch-all server block - Change region from sea (deprecated) to sjc - Add iptables/ip6tables for TUN device support - Add proxy_ssl_server_name for proper TLS SNI Co-Authored-By: Claude Opus 4.6 --- docs/how-to/expose-service-publicly.md | 8 +++++++- fly/Dockerfile | 3 ++- fly/fly.toml | 2 +- fly/nginx.conf | 15 ++++++++++----- fly/start.sh | 7 ++++--- pulumi/tailscale/__main__.py | 1 + 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index 26b9ce6..ccbe1cb 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -250,16 +250,22 @@ Extend the existing `pulumi/tailscale/` project. ```python # Auth key for Fly.io proxy container -flyio_key = tailscale.TailscaleKey( +flyio_key = tailscale.TailnetKey( "flyio-proxy-key", reusable=True, ephemeral=True, + preauthorized=True, # Skip device approval on the tailnet tags=["tag:flyio-proxy"], expiry=7776000, # 90 days ) pulumi.export("flyio_authkey", flyio_key.key) ``` +> **Note:** `preauthorized=True` is required if your tailnet has device +> approval enabled. Without it, each new container start (including +> health-check restarts) creates a node that needs manual approval, +> causing the container to hang before nginx starts. + **Add to `pulumi/tailscale/policy.hujson`:** Tag owner: diff --git a/fly/Dockerfile b/fly/Dockerfile index 7d71d85..11af474 100644 --- a/fly/Dockerfile +++ b/fly/Dockerfile @@ -6,7 +6,8 @@ COPY --from=docker.io/tailscale/tailscale:stable \ COPY --from=docker.io/tailscale/tailscale:stable \ /usr/local/bin/tailscale /usr/local/bin/tailscale -RUN mkdir -p /var/run/tailscale /var/lib/tailscale +RUN mkdir -p /var/run/tailscale /var/lib/tailscale \ + && apk add --no-cache iptables ip6tables COPY nginx.conf /etc/nginx/nginx.conf COPY start.sh /start.sh diff --git a/fly/fly.toml b/fly/fly.toml index 676b215..90b649e 100644 --- a/fly/fly.toml +++ b/fly/fly.toml @@ -1,5 +1,5 @@ app = "blumeops-proxy" -primary_region = "sea" +primary_region = "sjc" [build] diff --git a/fly/nginx.conf b/fly/nginx.conf index c2ed6bb..bf89e09 100644 --- a/fly/nginx.conf +++ b/fly/nginx.conf @@ -25,6 +25,7 @@ http { location / { proxy_pass https://docs.tail8d86e.ts.net; proxy_ssl_verify off; + proxy_ssl_server_name on; # Cache aggressively — static site only. # Do NOT use these settings for dynamic services. @@ -43,14 +44,18 @@ http { add_header X-Cache-Status $upstream_cache_status; } + } + + # Catch-all: reject unknown hosts, but serve health check + server { + listen 8080 default_server; + location /healthz { return 200 "ok\n"; } - } - # Catch-all: reject unknown hosts - server { - listen 8080 default_server; - return 444; + location / { + return 444; + } } } diff --git a/fly/start.sh b/fly/start.sh index 918c455..8478f09 100644 --- a/fly/start.sh +++ b/fly/start.sh @@ -1,8 +1,9 @@ #!/bin/sh set -e -# Start tailscale in userspace networking mode (no TUN device needed) -tailscaled --tun=userspace-networking --statedir=/var/lib/tailscale & +# Start tailscale daemon. Fly.io runs Firecracker microVMs which support +# TUN devices natively — no need for --tun=userspace-networking. +tailscaled --statedir=/var/lib/tailscale & sleep 2 # Authenticate and join tailnet @@ -12,5 +13,5 @@ tailscale up --authkey="${TS_AUTHKEY}" --hostname=flyio-proxy until tailscale status > /dev/null 2>&1; do sleep 1; done echo "Tailscale connected" -# Start nginx +# Start nginx — MagicDNS resolves *.tail8d86e.ts.net hostnames nginx -g "daemon off;" diff --git a/pulumi/tailscale/__main__.py b/pulumi/tailscale/__main__.py index 80e2793..631780e 100644 --- a/pulumi/tailscale/__main__.py +++ b/pulumi/tailscale/__main__.py @@ -77,6 +77,7 @@ flyio_key = tailscale.TailnetKey( "flyio-proxy-key", reusable=True, ephemeral=True, + preauthorized=True, tags=["tag:flyio-proxy"], expiry=7776000, # 90 days ) -- 2.50.1 (Apple Git-155) From 494a359606274873178df5b377379d01df0f363c Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 02:20:21 -0800 Subject: [PATCH 6/8] Update expose-service-publicly docs to match deployed config Remove status line, update code examples to reflect lessons learned: TUN networking (not userspace), iptables, healthz on default_server, proxy_ssl_server_name, and preauthorized auth key. Co-Authored-By: Claude Opus 4.6 --- docs/how-to/expose-service-publicly.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index ccbe1cb..77a0220 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -11,8 +11,6 @@ id: expose-service-publicly # Expose a Service Publicly via Fly.io + Tailscale -> **Status:** In progress — first target: `docs.eblu.me`. - This guide describes how to expose a BlumeOps service to the public internet using a reverse proxy container on [Fly.io](https://fly.io) that tunnels back to [[indri]] over [[tailscale]]. The approach keeps the home IP hidden, @@ -146,7 +144,8 @@ COPY --from=docker.io/tailscale/tailscale:stable \ COPY --from=docker.io/tailscale/tailscale:stable \ /usr/local/bin/tailscale /usr/local/bin/tailscale -RUN mkdir -p /var/run/tailscale /var/lib/tailscale +RUN mkdir -p /var/run/tailscale /var/lib/tailscale \ + && apk add --no-cache iptables ip6tables COPY nginx.conf /etc/nginx/nginx.conf COPY start.sh /start.sh @@ -163,8 +162,9 @@ CMD ["/start.sh"] #!/bin/sh set -e -# Start tailscale in userspace networking mode (no TUN device needed) -tailscaled --tun=userspace-networking --statedir=/var/lib/tailscale & +# Start tailscale daemon. Fly.io runs Firecracker microVMs which support +# TUN devices natively — no need for --tun=userspace-networking. +tailscaled --statedir=/var/lib/tailscale & sleep 2 # Authenticate and join tailnet @@ -174,7 +174,7 @@ tailscale up --authkey="${TS_AUTHKEY}" --hostname=flyio-proxy until tailscale status > /dev/null 2>&1; do sleep 1; done echo "Tailscale connected" -# Start nginx +# Start nginx — MagicDNS resolves *.tail8d86e.ts.net hostnames nginx -g "daemon off;" ``` @@ -211,6 +211,7 @@ http { location / { proxy_pass https://docs.tail8d86e.ts.net; proxy_ssl_verify off; + proxy_ssl_server_name on; # Cache aggressively — static site only. # Do NOT use these settings for dynamic services. @@ -228,16 +229,19 @@ http { add_header X-Cache-Status $upstream_cache_status; } + } + + # Catch-all: reject unknown hosts, but serve health check + server { + listen 8080 default_server; location /healthz { return 200 "ok\n"; } - } - # Catch-all: reject unknown hosts - server { - listen 8080 default_server; - return 444; + location / { + return 444; + } } } ``` -- 2.50.1 (Apple Git-155) From 9a5444851c8584fdae7bdc30d144d4a28f0ddaf0 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 02:32:42 -0800 Subject: [PATCH 7/8] Update docs for public proxy: docs.eblu.me is canonical URL - Replace docs.ops.eblu.me with docs.eblu.me across all references - Add Fly.io proxy reference card and operations how-to - Move shutoff escalation levels to manage-flyio-proxy how-to - Update index, Caddy, and docs reference cards with Fly.io context - Update homepage link in docs ingress annotation Co-Authored-By: Claude Opus 4.6 --- README.md | 2 +- argocd/manifests/docs/ingress-tailscale.yaml | 2 +- docs/changelog.d/feature-flyio-proxy.doc.md | 1 + docs/how-to/expose-service-publicly.md | 13 +-- docs/how-to/how-to.md | 1 + docs/how-to/manage-flyio-proxy.md | 88 ++++++++++++++++++++ docs/how-to/update-documentation.md | 2 +- docs/index.md | 6 +- docs/reference/reference.md | 1 + docs/reference/services/caddy.md | 2 +- docs/reference/services/docs.md | 4 +- docs/reference/services/flyio-proxy.md | 64 ++++++++++++++ docs/tutorials/exploring-the-docs.md | 2 +- 13 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 docs/changelog.d/feature-flyio-proxy.doc.md create mode 100644 docs/how-to/manage-flyio-proxy.md create mode 100644 docs/reference/services/flyio-proxy.md diff --git a/README.md b/README.md index f9968fc..cd9fa73 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,6 @@ This repo uses [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/) ## Documentation -Documentation lives in `docs/` and follows the [Diataxis](https://diataxis.fr/) framework. Published at https://docs.ops.eblu.me. +Documentation lives in `docs/` and follows the [Diataxis](https://diataxis.fr/) framework. Published at https://docs.eblu.me. Docs use [Obsidian](https://obsidian.md) wiki-link syntax (`[[link]]`) for cross-references. Edit with any markdown editor, or use [obsidian.nvim](https://github.com/obsidian-nvim/obsidian.nvim) for enhanced navigation. diff --git a/argocd/manifests/docs/ingress-tailscale.yaml b/argocd/manifests/docs/ingress-tailscale.yaml index 4c6710f..b895cfb 100644 --- a/argocd/manifests/docs/ingress-tailscale.yaml +++ b/argocd/manifests/docs/ingress-tailscale.yaml @@ -11,7 +11,7 @@ metadata: gethomepage.dev/group: "Apps" gethomepage.dev/icon: "mdi-book-open-page-variant" gethomepage.dev/description: "BlumeOps Documentation" - gethomepage.dev/href: "https://docs.ops.eblu.me" + gethomepage.dev/href: "https://docs.eblu.me" gethomepage.dev/pod-selector: "app=docs" spec: ingressClassName: tailscale diff --git a/docs/changelog.d/feature-flyio-proxy.doc.md b/docs/changelog.d/feature-flyio-proxy.doc.md new file mode 100644 index 0000000..d6accbf --- /dev/null +++ b/docs/changelog.d/feature-flyio-proxy.doc.md @@ -0,0 +1 @@ +Update docs for public proxy: canonical URL is now docs.eblu.me, add Fly.io proxy reference card and operations how-to diff --git a/docs/how-to/expose-service-publicly.md b/docs/how-to/expose-service-publicly.md index 77a0220..1f31302 100644 --- a/docs/how-to/expose-service-publicly.md +++ b/docs/how-to/expose-service-publicly.md @@ -655,22 +655,13 @@ Setup considerations for Forgejo specifically: ### Break-glass shutoff -If the proxy is causing issues (DDoS, unexpected traffic, bandwidth consumption on the home network): +If the proxy is causing issues, stop it immediately: -**Level 1 — Stop the container (seconds, reversible):** ```bash mise run fly-shutoff -# or: fly scale count 0 -a blumeops-proxy --yes ``` -All public services go offline immediately. Tailscale tunnel drops. Zero traffic reaches indri. Restore with `fly scale count 1 -a blumeops-proxy`. -**Level 2 — Revoke Tailscale access (seconds):** -Remove the `flyio-proxy` node in the Tailscale admin console. Even if the container is running, it cannot reach the tailnet. Use this if the container itself may be compromised. - -**Level 3 — Remove DNS (minutes to hours):** -Delete the CNAME records at Gandi. Takes time for DNS propagation but is the permanent shutoff. - -**Level 1 is the primary response.** It is a single command, takes effect in seconds, and is trivially reversible. Document the `mise run fly-shutoff` command somewhere easily accessible (e.g., pinned in a notes app) so it can be run quickly under stress. +This stops all machines in seconds — zero traffic reaches indri. See [[manage-flyio-proxy#Emergency Shutoff]] for the full escalation ladder (container stop → Tailscale revoke → DNS removal). --- diff --git a/docs/how-to/how-to.md b/docs/how-to/how-to.md index 3a8a587..0d12ec4 100644 --- a/docs/how-to/how-to.md +++ b/docs/how-to/how-to.md @@ -41,4 +41,5 @@ Task-oriented instructions for common BlumeOps operations. These guides assume y | Guide | Description | |-------|-------------| | [[restart-indri]] | Safely shut down and restart indri | +| [[manage-flyio-proxy]] | Deploy, shutoff, and troubleshoot the public proxy | | [[troubleshooting]] | Diagnose and fix common issues | diff --git a/docs/how-to/manage-flyio-proxy.md b/docs/how-to/manage-flyio-proxy.md new file mode 100644 index 0000000..b8c04bb --- /dev/null +++ b/docs/how-to/manage-flyio-proxy.md @@ -0,0 +1,88 @@ +--- +title: Manage Fly.io Proxy +tags: + - how-to + - fly-io + - networking + - operations +--- + +# Manage Fly.io Proxy + +Operational tasks for the [[flyio-proxy]] public reverse proxy. + +## Deploy Changes + +After modifying files in `fly/`: + +```bash +mise run fly-deploy +``` + +Pushes to `fly/` on main also trigger automatic deployment via the Forgejo CI workflow. + +## Add a New Public Service + +See [[expose-service-publicly#Per-service setup]] for the full walkthrough. In short: + +1. Add a `server` block to `fly/nginx.conf` +2. Add a Fly.io certificate: `fly certs add -a blumeops-proxy` +3. Deploy: `mise run fly-deploy` +4. Verify against `blumeops-proxy.fly.dev` with a `Host` header +5. Add DNS CNAME via Pulumi: `mise run dns-preview` then `mise run dns-up` + +## Emergency Shutoff + +If the proxy is causing issues (DDoS, unexpected traffic, bandwidth consumption on the home network): + +**Level 1 — Stop the container (seconds, reversible):** +```bash +mise run fly-shutoff +# or: fly scale count 0 -a blumeops-proxy --yes +``` +All public services go offline immediately. Tailscale tunnel drops. Zero traffic reaches indri. Restore with `fly scale count 1 -a blumeops-proxy`. + +**Level 2 — Revoke Tailscale access (seconds):** +Remove the `flyio-proxy` node in the Tailscale admin console. Even if the container is running, it cannot reach the tailnet. Use this if the container itself may be compromised. + +**Level 3 — Remove DNS (minutes to hours):** +Delete the CNAME records at Gandi. Takes time for DNS propagation but is the permanent shutoff. + +**Level 1 is the primary response.** It is a single command, takes effect in seconds, and is trivially reversible. Keep `mise run fly-shutoff` somewhere easily accessible (e.g., pinned in a notes app) so it can be run quickly under stress. + +## Check Status + +```bash +# App and machine status +fly status -a blumeops-proxy + +# Live logs +fly logs -a blumeops-proxy + +# Health check +curl -sf https://blumeops-proxy.fly.dev/healthz + +# Certificate status +fly certs list -a blumeops-proxy +``` + +## Rotate Tailscale Auth Key + +The auth key expires every 90 days. To rotate: + +1. Re-apply Pulumi to generate a new key: `mise run tailnet-up` +2. Re-run setup to stage the new secret: `mise run fly-setup` +3. Deploy to pick up the new secret: `mise run fly-deploy` + +## Troubleshooting + +**502 Bad Gateway**: Check `fly logs` for nginx upstream errors. Verify the backend Tailscale service is running (`tailscale status` from inside the container via `fly ssh console`). + +**Health check failing**: `fly ssh console -a blumeops-proxy` then `curl localhost:8080/healthz` to test locally. + +**TLS errors on custom domain**: Check cert status with `fly certs show -a blumeops-proxy`. Certs auto-provision via Let's Encrypt and may take a few minutes. + +## Related + +- [[flyio-proxy]] - Service reference card +- [[expose-service-publicly]] - Full setup guide and architecture diff --git a/docs/how-to/update-documentation.md b/docs/how-to/update-documentation.md index 72dd2b1..400cdaf 100644 --- a/docs/how-to/update-documentation.md +++ b/docs/how-to/update-documentation.md @@ -8,7 +8,7 @@ tags: # Update Documentation -How to publish documentation changes to https://docs.ops.eblu.me. +How to publish documentation changes to https://docs.eblu.me. ## Quick Release diff --git a/docs/index.md b/docs/index.md index a385da8..5a5c1c5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,8 +22,10 @@ editor of choice. (I recommend vim.) These services run on my home [[hosts|infrastructure]], primarily an m1 mac mini named [[indri]] and a Synology NAS called [[sifaka]]. The infrastructure -is networked via [[tailscale]], with the domain `eblu.me` hosted via [[gandi]] -with [[caddy]] providing a reverse proxy to resolve tailnet devices. +is networked via [[tailscale]], with the domain `eblu.me` hosted via [[gandi]], +[[caddy]] providing a private reverse proxy for tailnet devices, and +[[flyio-proxy|Fly.io]] serving public-facing services like +[this documentation site](https://docs.eblu.me). The goal of BlumeOps is threefold: diff --git a/docs/reference/reference.md b/docs/reference/reference.md index 7c300ae..1041fb6 100644 --- a/docs/reference/reference.md +++ b/docs/reference/reference.md @@ -34,6 +34,7 @@ Individual service reference cards with URLs and configuration details. | [[zot]] | Container registry | indri | | [[devpi]] | PyPI caching proxy | k8s | | [[docs]] | Documentation site (Quartz) | k8s | +| [[flyio-proxy]] | Public reverse proxy (Fly.io + Tailscale) | Fly.io | | [[automounter]] | SMB share automounter | indri | ## Infrastructure diff --git a/docs/reference/services/caddy.md b/docs/reference/services/caddy.md index 631e4de..49b60e7 100644 --- a/docs/reference/services/caddy.md +++ b/docs/reference/services/caddy.md @@ -47,7 +47,7 @@ K8s services are proxied via their Tailscale Ingress endpoints: |-----------|---------|---------| | `grafana.ops.eblu.me` | `grafana.tail8d86e.ts.net` | [[grafana]] | | `argocd.ops.eblu.me` | `argocd.tail8d86e.ts.net` | [[argocd]] | -| `docs.ops.eblu.me` | `docs.tail8d86e.ts.net` | [[docs]] | +| `docs.ops.eblu.me` | `docs.tail8d86e.ts.net` | [[docs]] (now publicly available at `docs.eblu.me` via [[flyio-proxy]]) | | `feed.ops.eblu.me` | `feed.tail8d86e.ts.net` | [[miniflux]] | | ... | ... | (see defaults/main.yml for full list) | diff --git a/docs/reference/services/docs.md b/docs/reference/services/docs.md index fe2c6dc..79fe5da 100644 --- a/docs/reference/services/docs.md +++ b/docs/reference/services/docs.md @@ -13,11 +13,13 @@ Documentation site built with [Quartz](https://quartz.jzhao.xyz/) and served via | Property | Value | |----------|-------| -| **URL** | https://docs.ops.eblu.me | +| **Public URL** | https://docs.eblu.me | +| **Private URL** | `docs.ops.eblu.me` (tailnet only, via [[caddy]]) | | **Namespace** | `docs` | | **Container** | `registry.ops.eblu.me/blumeops/quartz:v1.0.0` | | **Source** | `docs/` directory in blumeops repo | | **Build** | Forgejo workflow `build-blumeops.yaml` | +| **Public proxy** | [[flyio-proxy]] (Fly.io → Tailscale tunnel) | ## Architecture diff --git a/docs/reference/services/flyio-proxy.md b/docs/reference/services/flyio-proxy.md new file mode 100644 index 0000000..17162b2 --- /dev/null +++ b/docs/reference/services/flyio-proxy.md @@ -0,0 +1,64 @@ +--- +title: Fly.io Proxy +tags: + - service + - networking + - fly-io +--- + +# Fly.io Proxy + +Public reverse proxy on [Fly.io](https://fly.io) that exposes selected BlumeOps services to the internet via a Tailscale tunnel back to the homelab. + +## Quick Reference + +| Property | Value | +|----------|-------| +| **App** | `blumeops-proxy` | +| **Region** | `sjc` (San Jose) | +| **Fly.io URL** | `blumeops-proxy.fly.dev` | +| **Config** | `fly/` directory in repo | +| **IaC** | `fly/fly.toml` (app), Pulumi (DNS + auth key) | + +## Exposed Services + +| Public domain | Backend | Service | +|---------------|---------|---------| +| `docs.eblu.me` | `docs.tail8d86e.ts.net` | [[docs]] | + +## Architecture + +Internet traffic hits Fly.io's Anycast edge, terminates TLS with a Let's Encrypt certificate, and is proxied by nginx to the backend service over a Tailscale WireGuard tunnel. See [[expose-service-publicly]] for the full architecture diagram. + +## Key Files + +| File | Purpose | +|------|---------| +| `fly/fly.toml` | App configuration | +| `fly/Dockerfile` | nginx + Tailscale container | +| `fly/nginx.conf` | Reverse proxy, caching, rate limiting | +| `fly/start.sh` | Entrypoint: start Tailscale, then nginx | +| `pulumi/tailscale/__main__.py` | Auth key (`tag:flyio-proxy`) | +| `pulumi/tailscale/policy.hujson` | ACL grants for proxy | +| `pulumi/gandi/__main__.py` | DNS CNAMEs | + +## Networking + +Fly.io runs Firecracker microVMs which support TUN devices natively. Tailscale runs with a real TUN interface (not userspace networking), so MagicDNS and direct Tailscale IP routing work normally. + +The Tailscale auth key is `preauthorized=True` to avoid device approval hangs on container restarts. + +## Secrets + +| Secret | Source | Description | +|--------|--------|-------------| +| `TS_AUTHKEY` | Pulumi state → `fly secrets` | Tailscale auth key for joining tailnet | +| `FLY_DEPLOY_TOKEN` | Fly.io → 1Password | Deploy token for CI | + +## Related + +- [[expose-service-publicly]] - Setup guide for adding new public services +- [[manage-flyio-proxy]] - Operational tasks (deploy, shutoff, troubleshoot) +- [[caddy]] - Private reverse proxy for `*.ops.eblu.me` (separate system) +- [[tailscale]] - WireGuard mesh network +- [[gandi]] - DNS hosting diff --git a/docs/tutorials/exploring-the-docs.md b/docs/tutorials/exploring-the-docs.md index 475419d..db9c8a0 100644 --- a/docs/tutorials/exploring-the-docs.md +++ b/docs/tutorials/exploring-the-docs.md @@ -67,7 +67,7 @@ Documentation uses `[[wiki-links]]` for cross-references: - `[[service-name]]` links to a reference page - `[[page|Display Text]]` customizes the link text -When reading on the web (docs.ops.eblu.me), these render as clickable links. The backlinks panel shows what references each page. +When reading on the web (docs.eblu.me), these render as clickable links. The backlinks panel shows what references each page. Pre-commit hooks automatically validate that all wiki-links point to existing files and that link targets are unambiguous. -- 2.50.1 (Apple Git-155) From 90c751ecca413cde02544c3e5e327889b8fbf067 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Sun, 8 Feb 2026 02:35:24 -0800 Subject: [PATCH 8/8] Add FLY_DEPLOY_TOKEN to Forgejo Actions secrets Extends the forgejo_actions_secrets role to sync the Fly.io deploy token from 1Password, enabling CI auto-deploy on push to fly/. Co-Authored-By: Claude Opus 4.6 --- ansible/playbooks/indri.yml | 11 +++++++++++ .../roles/forgejo_actions_secrets/defaults/main.yml | 2 ++ 2 files changed, 13 insertions(+) diff --git a/ansible/playbooks/indri.yml b/ansible/playbooks/indri.yml index 6fb9c4e..7698820 100644 --- a/ansible/playbooks/indri.yml +++ b/ansible/playbooks/indri.yml @@ -82,10 +82,21 @@ check_mode: false tags: [forgejo_actions_secrets] + - name: Fetch Fly.io deploy token for Forgejo Actions + ansible.builtin.command: + cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get on5slfaygtdjrxmdwezyhfmqsq --fields deploy-token --reveal + delegate_to: localhost + register: _fly_deploy_token + changed_when: false + no_log: true + check_mode: false + tags: [forgejo_actions_secrets] + - name: Set Forgejo Actions secrets facts ansible.builtin.set_fact: forgejo_api_token: "{{ _forgejo_api_token.stdout }}" forgejo_secret_argocd_token: "{{ _forgejo_argocd_token.stdout }}" + forgejo_secret_fly_deploy_token: "{{ _fly_deploy_token.stdout }}" no_log: true tags: [forgejo_actions_secrets] diff --git a/ansible/roles/forgejo_actions_secrets/defaults/main.yml b/ansible/roles/forgejo_actions_secrets/defaults/main.yml index dccee3f..d46a968 100644 --- a/ansible/roles/forgejo_actions_secrets/defaults/main.yml +++ b/ansible/roles/forgejo_actions_secrets/defaults/main.yml @@ -13,3 +13,5 @@ forgejo_actions_secrets_repo: blumeops forgejo_actions_secrets_list: - name: ARGOCD_AUTH_TOKEN value_var: forgejo_secret_argocd_token + - name: FLY_DEPLOY_TOKEN + value_var: forgejo_secret_fly_deploy_token -- 2.50.1 (Apple Git-155)