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/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/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 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/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..1f31302 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:** Plan — not yet implemented. 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, @@ -50,7 +48,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 | @@ -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; + } } } ``` @@ -250,16 +254,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: @@ -323,10 +333,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 @@ -497,9 +516,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 +565,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 @@ -620,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). --- @@ -688,12 +714,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/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. diff --git a/fly/Dockerfile b/fly/Dockerfile new file mode 100644 index 0000000..11af474 --- /dev/null +++ b/fly/Dockerfile @@ -0,0 +1,18 @@ +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 \ + && apk add --no-cache iptables ip6tables + +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..90b649e --- /dev/null +++ b/fly/fly.toml @@ -0,0 +1,19 @@ +app = "blumeops-proxy" +primary_region = "sjc" + +[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..bf89e09 --- /dev/null +++ b/fly/nginx.conf @@ -0,0 +1,61 @@ +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; + proxy_ssl_server_name on; + + # 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; + } + + } + + # Catch-all: reject unknown hosts, but serve health check + server { + listen 8080 default_server; + + location /healthz { + return 200 "ok\n"; + } + + location / { + return 444; + } + } +} diff --git a/fly/start.sh b/fly/start.sh new file mode 100644 index 0000000..8478f09 --- /dev/null +++ b/fly/start.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +# 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 +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 — MagicDNS resolves *.tail8d86e.ts.net hostnames +nginx -g "daemon off;" diff --git a/mise-tasks/dns-preview b/mise-tasks/dns-preview index 7d7578e..be7b9e0 100755 --- a/mise-tasks/dns-preview +++ b/mise-tasks/dns-preview @@ -7,4 +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 a0d3849..2be5abb 100755 --- a/mise-tasks/dns-up +++ b/mise-tasks/dns-up @@ -7,4 +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/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..241c7fb --- /dev/null +++ b/mise-tasks/fly-setup @@ -0,0 +1,27 @@ +#!/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 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 +# 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/mise-tasks/tailnet-preview b/mise-tasks/tailnet-preview index ceb6439..3df1369 100755 --- a/mise-tasks/tailnet-preview +++ b/mise-tasks/tailnet-preview @@ -10,4 +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 f22048b..882fada 100755 --- a/mise-tasks/tailnet-up +++ b/mise-tasks/tailnet-up @@ -10,4 +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/__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/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/__main__.py b/pulumi/tailscale/__main__.py index 7c76c26..631780e 100644 --- a/pulumi/tailscale/__main__.py +++ b/pulumi/tailscale/__main__.py @@ -70,9 +70,22 @@ 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, + preauthorized=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"], + }, ], } 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" }, +]