diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md index aa3a9dd..6503201 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,22 @@ # Jupiter Notes A repository to store the notes taken. + +## Development + +Requires [uv](https://docs.astral.sh/uv/getting-started/installation/). +Python 3.12 and `.venv` are managed by uv. + +```sh +uv sync --locked +uv run --locked zensical serve +``` + +Build the static site into `site/`: + +```sh +uv run --locked zensical build +``` + +Dependencies: `pyproject.toml`; resolved versions: `uv.lock`. +After editing dependencies, run `uv lock` and commit both files. diff --git a/docs/home-server/authentication.md b/docs/home-server/authentication.md new file mode 100644 index 0000000..06f30ca --- /dev/null +++ b/docs/home-server/authentication.md @@ -0,0 +1,79 @@ +--- +schema_version: 1 +id: home-server.authentication +type: reference +scope: [jupiter, authelia, forward-auth, secrets] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Deployed Authelia file matches repository; Portainer supplies environment variables, 2026-10-06" + - kind: owner-report + reference: "Portainer state added to Backrest plan, 2026-10-06" + - kind: owner-report + reference: "All stack environment values/secrets managed in Portainer; GitHub access uses PAT, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/authelia/configuration.yml + - stacks/authelia/docker-compose.yml +related: [home-server.reference, home-server.networking, home-server.portainer, home-server.backups, home-server.deployment] +update_triggers: [access-policy-change, authentication-change, secret-rotation, middleware-change] +unknowns: + - authelia-file-placeholder-expansion-mechanism + - off-host-authelia-secret-and-user-database-recovery +--- + +# Authentication + +| Component | Configuration | +| --- | --- | +| Portal | `https://auth.umbra.mom` | +| Backend | File `/config/users_database.yml`; Argon2id | +| Second factor | TOTP; issuer `Jupiter` | +| Policy | Default deny; listed domains require `two_factor` | +| Session cookie domain | `umbra.mom` | +| Default redirect | `https://sonarr.umbra.mom` | +| Storage | SQLite `/data/db.sqlite3`; storage encryption key | +| Notifications | Filesystem `/data/notification.txt` | + +Two-factor domains: `sonarr`, `radarr`, `prowlarr`, `logs`, `notes`, `backups` +under `umbra.mom`. + +## Enforcement + +Protected routers attach `authelia@docker`: + +```text +Traefik -> http://authelia:9091/api/authz/forward-auth +``` + +`trustForwardHeader=true`; response headers: +`Remote-User, Remote-Groups, Remote-Email, Remote-Name`. + +Policy applies only to requests routed through forward-auth. It is not a +global gate for every service or directly published port. + +## Configuration / secrets + +Host `/data/authelia/{configuration.yml,users_database.yml}` mounts read-only +under `/config`; `/data/authelia` also mounts read-write at `/data`. + +Stack variable overrides/secrets are managed in Portainer; Compose retains +non-secret configuration. Authelia environment: + +```text +AUTHELIA_SESSION_SECRET +AUTHELIA_STORAGE_ENCRYPTION_KEY +AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET +``` + +Checked-in YAML also contains `${AUTHELIA_*}` placeholders. Container environment +injection alone does not prove mounted-file substitution; expansion mechanism +unconfirmed. + +Authelia data remains outside the supplied backup plan. +[Portainer state](portainer.md) is covered; secret recovery remains untested. +GitHub repository access uses a PAT; host SSH keys and tunnel credential files +are separate from stack environment variables. diff --git a/docs/home-server/backups.md b/docs/home-server/backups.md new file mode 100644 index 0000000..6edf645 --- /dev/null +++ b/docs/home-server/backups.md @@ -0,0 +1,95 @@ +--- +schema_version: 1 +id: home-server.backups +type: reference +scope: [jupiter, backrest, restic, google-drive, recovery] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Backrest plan JSON, destination, key custody and no restore test, 2026-10-06" + - kind: owner-report + reference: "Updated four-service Backrest plan and deployed Portainer bind mount/container name, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/backrest/docker-compose.yml + - stacks/gitea/docker-compose.yml + - stacks/paperless-ngx/docker-compose.yml + - stacks/vaultwarden/docker-compose.yml +related: [home-server.reference, home-server.storage, home-server.portainer, home-server.authentication] +update_triggers: [backup-plan-change, destination-change, hook-change, credential-change, restore-test] +unknowns: + - last-successful-backup-and-repository-check + - google-drive-repository-path-and-credential-recovery + - off-host-backrest-config-recovery + - measured-rpo-and-rto +--- + +# Backups + +| Setting | Value | +| --- | --- | +| Engine | Backrest / restic; plan `jupiter-backup` | +| Repository | `jayo60013-gdrive`; Google Drive; account `jayo60013` | +| Schedule | `0 3 * * *`; UTC; daily 03:00 | +| Retention | `policyKeepLastN: 4`; last four snapshots, not four days | +| Encryption key | iCloud password app; no key stored here | +| Restore test | Never performed | + +## Coverage + +Backrest bind-mounts host `/data` at container `/userdata:ro`. + +| Host source | Plan path | +| --- | --- | +| `/data/gitea` | `/userdata/gitea` | +| `/data/paperless-ngx` | `/userdata/paperless-ngx` | +| `/data/portainer` | `/userdata/portainer` | +| `/data/vaultwarden` | `/userdata/vaultwarden` | + +Case-insensitive exclusion: `/userdata/gitea/gitea/packages`. +`excludes: []`; CLI `--skip-if-unchanged` supplied despite +plan `skipIfUnchanged: false`. Unchanged runs may create no snapshot. + +## Consistency hooks + +Stop timeout `120s`; chains use `&&`. Errors: `ON_ERROR_FATAL`, +except Portainer restart (`ON_ERROR_IGNORE`). + +| Event | Ordered commands / containers | +| --- | --- | +| `SNAPSHOT_START` | Stop `paperless` -> `paperless_db` | +| `SNAPSHOT_START` | Stop `gitea_runner` -> `gitea` -> `gitea_db` | +| `SNAPSHOT_START` | Stop `vaultwarden` | +| `SNAPSHOT_START` | Stop `portainer` | +| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `paperless_db` -> `paperless` | +| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `gitea_db` -> `gitea` -> `gitea_runner` | +| `SNAPSHOT_END`, `ANY_ERROR` | Start `vaultwarden` | +| `SNAPSHOT_END`, `SNAPSHOT_ERROR` | Start `portainer`; ignore hook failure | + +Cold filesystem copies of applications/databases; no database dumps supplied. +Broker stays running. `docker start` does not wait for database readiness. +Hook failure can leave services stopped; recovery is not demonstrated. +Portainer restart failure does not fail the backup operation. + +## Backrest state + +| Host path | Container path | +| --- | --- | +| `/data/backrest/data` | `/data` | +| `/data/backrest/config` | `/config`; plan/repository configuration `config.json` | +| `/data/backrest/rclone` | `/root/.config/rclone` | +| `/home/jay/data/backrest/cache` | `/cache` | +| `/data/backrest/restore` | `/restore` | +| `/var/run/docker.sock` | `/var/run/docker.sock`; lifecycle hooks | + +## Recovery limits + +Only the four listed directories are covered. No supplied coverage for Authelia, +Traefik, Backrest configuration, Docker named volumes, OS, or Mercury. + +Full-host recovery requires independently retrievable repository credentials, +configuration and restic key. Key custody alone does not establish recoverability. +`/restore` is on the same RAID array; not an independent backup. diff --git a/docs/home-server/deployment.md b/docs/home-server/deployment.md new file mode 100644 index 0000000..c3a032e --- /dev/null +++ b/docs/home-server/deployment.md @@ -0,0 +1,43 @@ +--- +schema_version: 1 +id: home-server.deployment +type: reference +scope: [jupiter, portainer, github, stack-deployment, secrets] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Portainer GitHub/main/PAT configuration, environment management and manual pull/redeploy workflow, 2026-10-06" +related: [home-server.reference, home-server.host, home-server.portainer, home-server.authentication, home-server.backups] +update_triggers: [deployment-workflow-change, repository-change, credential-rotation, rollback-policy-change] +unknowns: + - github-pat-scopes-expiration-and-independent-recovery + - rollback-and-post-deployment-health-procedure + - rules-for-portainer-ui-versus-repository-drift +--- + +# Deployment + +| Setting | Source / workflow | +| --- | --- | +| Stack definitions | | +| Git ref | `main` | +| Repository authentication | GitHub PAT configured in Portainer | +| Environment values/secrets | Portainer stack environment settings | +| Deployment | Manually pull/redeploy each affected stack in Portainer | +| Portainer itself | [Host shell script](portainer.md), not a managed Compose stack | + +```text +Compose change -> push main -> manual Portainer pull/redeploy -> running stack +``` + +A Git commit is not deployment proof. Installed host Compose version is not +proof of Portainer's Compose implementation. + +Recovering deployment requires Portainer state or independently retrievable +repository credentials and stack environment values. [Portainer backups](backups.md) +exist; credential/state restoration is untested. Do not put secret values in +this site or Git. + +Host apt packages, fstab, RAID assembly, systemd units and Mercury nginx are +outside the Compose deployment workflow. diff --git a/docs/home-server/host.md b/docs/home-server/host.md new file mode 100644 index 0000000..4cb7a00 --- /dev/null +++ b/docs/home-server/host.md @@ -0,0 +1,74 @@ +--- +schema_version: 1 +id: home-server.host +type: reference +scope: [jupiter, hardware, operating-system, docker] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Hardware, OS, bare-metal Docker and data-root confirmation, 2026-10-06" + - kind: owner-report + reference: "hostnamectl, timedatectl, user/group records, Docker info/version and systemd units, 2026-10-06" +related: [home-server.reference, home-server.storage, home-server.networking, home-server.deployment, home-server.operations] +update_triggers: [hardware-change, os-upgrade, kernel-upgrade, docker-reconfiguration, account-change, unit-change] +unknowns: + - exact-gpu-model + - jupiter-firewall-policy + - docker-package-installation-source + - backup-puller-account-purpose +--- + +# Host + +| Component | Configuration | +| --- | --- | +| Host | Jupiter; bare metal | +| OS | Debian GNU/Linux 13 (trixie); x86_64 | +| Kernel | `6.12.94+deb13-amd64` | +| CPU | AMD Ryzen 5 5600X; 6 cores / 12 threads; reported 4.65 GHz | +| RAM | 1 x 16 GB Corsair Vengeance LPX | +| GPU | AMD Radeon HD 5000/6000/7350/8350 Series; discrete; exact model unresolved | +| Motherboard | Gigabyte B550M DS3H | +| Firmware | `FB`; dated `2023-06-08` | +| PSU | Corsair CV550 | +| SSD | Samsung 860 EVO; 1 TB; OS and SSD-backed caches | +| HDD | 2 x Seagate BarraCuda; 2 TB each; RAID 0 | +| Docker | Native host engine; data root `/var/lib/docker` | +| LAN | `192.168.1.56` | +| WireGuard | `10.0.0.2/24`; Mercury peer/server `10.0.0.1/24` | +| Time | `Europe/London`; RTC UTC; NTP active; clock synchronized at review | + +## Docker runtime + +| Setting | Observed value | +| --- | --- | +| Engine / API | Docker CE `29.8.2` / `1.56` | +| Compose CLI plugin | `v5.6.0`; not proof of Portainer's Compose implementation | +| containerd / runc | `v2.3.6` / `1.5.1` | +| Storage | `overlayfs`; containerd snapshotter | +| Cgroups | `systemd`; v2 | +| Logging | `json-file`; default | +| Firewall backend | `iptables` | +| Runtime security | AppArmor, seccomp, cgroup namespaces | +| Swarm / live restore | Inactive / disabled | + +Docker/containerd services and `docker.socket` are enabled. +Docker starts `/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock`. +Supplied Docker unit has no explicit `/data` mount dependency or ordering against +`rclone-seedbox.service`. + +## Host administration + +| Account / group | Identity | +| --- | --- | +| `jay` | UID/GID `1000:1000`; `/home/jay`; bash; member of `sudo`, `docker`, `video`, `backups` | +| `backup-puller` | UID/GID `1001:1001`; `/home/backup-puller`; bash; member of `backups`; purpose unconfirmed | +| `backups` | Shared group; GID `1002` | + +WireGuard and rclone: apt-managed. `wg-quick@wg0.service` is active in the supplied +snapshot. No host-level nginx on Jupiter; nginx still exists in application +containers. `unattended-upgrades.service` is enabled; effective update policy +not supplied. + +Hardware/versions are a dated snapshot, not live telemetry. diff --git a/docs/home-server/index.md b/docs/home-server/index.md new file mode 100644 index 0000000..8d2c736 --- /dev/null +++ b/docs/home-server/index.md @@ -0,0 +1,52 @@ +--- +schema_version: 1 +id: home-server.reference +type: reference +scope: [jupiter, mercury, seedbox, shared-infrastructure] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Owner interview, 2026-10-06" +related: + - home-server.host + - home-server.mercury + - home-server.seedbox + - home-server.portainer + - home-server.deployment + - home-server.operations + - home-server.networking + - home-server.storage + - home-server.authentication + - home-server.backups +update_triggers: [topology-change, source-change, infrastructure-change] +unknowns: [] +--- + +# Home server + +| Reference | Scope | +| --- | --- | +| [Host](host.md) | Jupiter hardware; OS; Docker | +| [Mercury VPS](mercury.md) | Hosting; ingress; WireGuard; firewall | +| [Seedbox](seedbox.md) | Ultra.cc; qBittorrent; rclone mount | +| [Portainer](portainer.md) | Script-managed container; state; backups | +| [Deployment](deployment.md) | GitHub; Portainer; environment values | +| [Operations / decisions](operations.md) | Tradeoffs; monitoring; recovery gaps | +| [Networking](networking.md) | Mercury; WireGuard; DNS; ingress | +| [Storage](storage.md) | SSD; RAID 0; mounts; volumes | +| [Authentication](authentication.md) | Authelia; policy; secrets | +| [Backups](backups.md) | Backrest; coverage; retention; recovery | + +## Evidence / agent contract + +- `owner-report`: supplied host state/configuration; not independently inspected. +- `repository`: intended configuration at `revision`; not deployment proof. +- `revision: working-tree`: uncommitted source; `base_revision`: base commit. +- `unknowns`: unresolved facts; never infer defaults as deployed state. +- `related`: stable document IDs; `update_triggers`: re-review conditions. +- On change: update affected prose, Mermaid, sources, unknowns, and `last_reviewed`. +- No credentials. Preserve evidence distinctions; no live status implied. + +Mermaid renders from versioned text; diagrams are manually maintained, not +automatically discovered. diff --git a/docs/home-server/mercury.md b/docs/home-server/mercury.md new file mode 100644 index 0000000..3b2d28d --- /dev/null +++ b/docs/home-server/mercury.md @@ -0,0 +1,75 @@ +--- +schema_version: 1 +id: home-server.mercury +type: reference +scope: [mercury, vps, hosting, nginx, wireguard, firewall] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Fasthosts plan, fastfetch, nginx sites, iptables and fail2ban output, 2026-10-06" + - kind: owner-report + reference: "nginx/WireGuard apt management and public IPv4/CGNAT rationale, 2026-10-06" +related: [home-server.reference, home-server.host, home-server.networking, home-server.operations] +update_triggers: [hosting-renewal, vps-resize, os-upgrade, proxy-change, firewall-change, certificate-change] +unknowns: + - renewal-price-and-renewal-date + - creation-timestamp-timezone + - certificate-renewal-method + - wireguard-allowedips-and-keepalive + - backup-and-restore-policy +--- + +# Mercury VPS + +## Host / subscription + +| Field | Configuration | +| --- | --- | +| Provider | [Fasthosts](https://www.fasthosts.co.uk/); UK data centre | +| Role / OS hostname | Mercury / `my-vps` | +| Plan | `1-1-10`; KVM virtual machine | +| CPU | 1 vCore; reported AMD EPYC-Milan @ 2.00 GHz | +| RAM | 1 GB plan; guest reports 864.37 MiB | +| Disk | 10 GB NVMe SSD plan; guest root ext4, 9.64 GiB | +| OS | Debian GNU/Linux 13 (trixie); x86_64 | +| Kernel | `6.12.85+deb13-amd64` | +| Swap | Disabled | +| Public interface | `ens6`; `185.230.217.66/32` | +| WireGuard | Server `10.0.0.1/24`; Jupiter peer `10.0.0.2/24` | +| Created | `2026-03-01 20:48:51`; timezone unconfirmed | +| Payment | GBP 10.00 prepaid for one year; renewal terms unconfirmed | + +Capacities distinguish provider allocation from guest-visible values. + +## Ingress + +nginx runs directly in the VM, not in Docker; nginx and WireGuard are +apt-managed. Mercury provides static public IPv4 ingress because Jupiter's +home connection uses CGNAT. Residential-IP exposure is avoided by design. + +| Hostname | Upstream | +| --- | --- | +| `umbra.mom`, `*.umbra.mom` | `https://10.0.0.2:443` over WireGuard | +| `gitea.umbra.mom` | Same; dedicated registry location `/v2/` | +| `john.gatward.dev`, `samstoreymusic.com` | `https://10.0.0.2:443` | +| `uptime.umbra.mom` | `http://127.0.0.1:3001` | + +- HTTP -> HTTPS: `301`; unmatched default server: `418`. +- TLS terminates at nginx; upstream HTTPS terminates again at Jupiter Traefik. +- Upstream certificate verification disabled: `proxy_ssl_verify off`. +- Certificates: `/etc/letsencrypt/live//{fullchain.pem,privkey.pem}`; + `umbra.mom` certificate also serves its subdomains. +- Preserves `Host`; sets `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`; + forwards WebSocket upgrade headers. +- Gitea `/v2/`: unlimited request body; proxy timeouts `900s`. + +## Firewall / fail2ban + +- UFW-managed iptables: INPUT/FORWARD `DROP`; OUTPUT `ACCEPT`. +- UFW allows TCP `22,80,443`; UDP `80,443,51820`. +- Docker forwarding has separate rules; INPUT policy alone does not define + container exposure. +- Fail2ban jails: `nginx-botsearch`, `nginx-http-auth`, `sshd`. + +Rules and versions are supplied snapshots, not live inspection. diff --git a/docs/home-server/networking.md b/docs/home-server/networking.md new file mode 100644 index 0000000..9140e05 --- /dev/null +++ b/docs/home-server/networking.md @@ -0,0 +1,203 @@ +--- +schema_version: 1 +id: home-server.networking +type: reference +scope: [jupiter, mercury, dns, ingress, wireguard, cloudflare] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Network topology, Mercury nginx/iptables/fail2ban output, LAN DNS, 2026-10-06" + - kind: owner-report + reference: "Cloudflare ingress YAML and client SSH configurations, 2026-10-06" + - kind: owner-report + reference: "CGNAT rationale, Docker network and container inspection, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/traefik/docker-compose.yml + - stacks/cloudflared/docker-compose.yml + - stacks/gitea/docker-compose.yml + - stacks/jellyfin/docker-compose.yml + - stacks/audiobookshelf/docker-compose.yml +related: [home-server.reference, home-server.host, home-server.mercury, home-server.seedbox, home-server.authentication, home-server.operations] +update_triggers: [dns-change, proxy-change, tunnel-change, firewall-change, certificate-change] +unknowns: + - public-dns-record-types-and-cloudflare-proxy-status + - cloudflare-access-applications-and-login-policies + - wireguard-allowedips-and-keepalive + - jupiter-firewall-policy + - traefik-forwarded-header-trust-configuration +--- + +# Networking + +## HTTPS ingress + +```mermaid +--- +config: + themeCSS: | + .node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; } + .cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; } + .label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; } + .edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; } + .flowchart-link { stroke: var(--infra-line) !important; } + marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; } + .infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; } + .infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; } + .infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; } + flowchart: + curve: basis + nodeSpacing: 35 + rankSpacing: 55 +--- +flowchart TB + Internet["Public clients"] + + subgraph VPS["MERCURY / PUBLIC EDGE"] + Nginx["nginx
185.230.217.66
WireGuard 10.0.0.1"] + Uptime["Uptime service
127.0.0.1:3001"] + Nginx -->|"uptime.umbra.mom / HTTP"| Uptime + end + + subgraph Home["JUPITER / HOME SERVER"] + LAN["LAN clients
Jellyfin / Bookshelf local DNS"] + Traefik["Traefik
192.168.1.56
WireGuard 10.0.0.2"] + Apps["Application containers
Docker network: traefik"] + LAN -->|"HTTPS :443"| Traefik + Traefik -->|"Application routing"| Apps + end + + Internet -->|"HTTPS :443"| Nginx + Nginx -->|"HTTPS :443 over WireGuard"| Traefik + + class Internet,LAN infraClient + class Nginx,Traefik infraEdge + class Apps,Uptime infraService +``` + +## Cloudflare Tunnel + +```mermaid +--- +config: + themeCSS: | + .node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; } + .cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; } + .label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; } + .edgeLabel, .edgeLabel p { background-color: var(--infra-zone) !important; color: var(--infra-text) !important; } + .flowchart-link { stroke: var(--infra-line) !important; } + marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; } + .infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; } + .infraCloud rect { fill: var(--infra-cloud-fill) !important; stroke: var(--infra-cloud-stroke) !important; } + .infraEdge rect { fill: var(--infra-edge-fill) !important; stroke: var(--infra-edge-stroke) !important; } + .infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; } + flowchart: + curve: basis + nodeSpacing: 35 + rankSpacing: 45 +--- +flowchart TB + Shell["SSH client
Host jupiter / user jay"] + Git["Git over SSH
Host git.umbra.mom / user git"] + Cloud["Cloudflare Tunnel edge
Access policy unconfirmed"] + + subgraph Home["JUPITER / HOME SERVER"] + Connector["cloudflared
Locally configured ingress"] + SSH["Host sshd
192.168.1.56:22"] + Gitea["Gitea SSH
192.168.1.56:2222"] + Connector -->|"ssh.umbra.mom / SSH :22"| SSH + Connector -->|"git.umbra.mom / SSH :2222"| Gitea + end + + Shell -->|"cloudflared access ssh / ssh.umbra.mom"| Cloud + Git -->|"cloudflared access ssh / git.umbra.mom"| Cloud + Cloud <-->|"Tunnel traffic / initiated outbound by Jupiter"| Connector + + class Shell,Git infraClient + class Cloud infraCloud + class Connector infraEdge + class SSH,Gitea infraService +``` + +Client `cloudflared` transports SSH through Cloudflare; Jupiter's connector opens +the outbound tunnel. Origins still perform SSH authentication. No Mercury, +WireGuard, or Traefik hop on this path; no home-router port forwarding. + +| Ingress hostname | Origin | Client SSH user | +| --- | --- | --- | +| `ssh.umbra.mom` | `ssh://192.168.1.56:22` | `jay` | +| `git.umbra.mom` | `ssh://192.168.1.56:2222` | `git` | +| Unmatched | `http_status:404` | N/A | + +Both client configurations use `IdentityFile ~/.ssh/id_ed25519`. + +```sshconfig +Host jupiter + User jay + IdentityFile ~/.ssh/id_ed25519 + ProxyCommand cloudflared access ssh --hostname ssh.umbra.mom + +Host git.umbra.mom + User git + IdentityFile ~/.ssh/id_ed25519 + ProxyCommand cloudflared access ssh --hostname %h +``` + +Local tunnel configuration is separate from Cloudflare Access applications. +Access login/provider/allow rules remain unconfirmed; `ProxyCommand` alone +does not establish enforcement. + +Ingress source: host `/data/cloudflared/config.yaml`, mounted at +`/etc/cloudflared/config.yaml`. + +## DNS / routes + +| DNS / route | Destination | +| --- | --- | +| Public service ingress | Mercury `185.230.217.66` | +| `jellyfin.local.umbra.mom` | Jupiter `192.168.1.56` | +| `bookshelf.local.umbra.mom` | Jupiter `192.168.1.56` | +| `ssh.umbra.mom`, `git.umbra.mom` | Cloudflare Tunnel | +| `uptime.umbra.mom` | Mercury nginx -> `http://127.0.0.1:3001` | + +## Mercury + +[Mercury VPS](mercury.md): native nginx; WireGuard server `10.0.0.1/24`; +Jupiter client `10.0.0.2/24`. Hosting, TLS, firewall and fail2ban details are +maintained on that page. + +Home ISP uses CGNAT. Mercury supplies a static public IPv4 and forwards over +WireGuard; owner preference is to avoid public ingress via the residential IP. + +## Observed Docker networks + +Snapshot from supplied inspection, not fixed-address configuration. Container +IPs may change; use Docker service names. + +| Network | IPv4 subnet | Internal | Observed role | +| --- | --- | --- | --- | +| `traefik` | `172.18.0.0/16` | No | Reverse proxy and application ingress | +| `gitea_network` | `172.23.0.0/16` | No | Gitea, runner, notes builder, Havox sync | +| `cloudflared_cloudflare` | `172.26.0.0/16` | No | Tunnel connector | +| `gitea_data` | `192.168.96.0/20` | Yes | Gitea and PostgreSQL | +| `paperless_data` | `192.168.112.0/20` | Yes | Paperless, PostgreSQL, Redis | +| `dozzle_dozzle` | `172.27.0.0/16` | Yes | Dozzle and socket proxy | + +Cloudflared's observed `172.26.0.2` matches the supplied proxied SSH login source. +An internal network alone does not isolate a multi-network container from +egress through its other networks. Empty network entries do not establish +whether they are obsolete or safe to remove. + +## Jupiter: repository configuration + +- Traefik publishes TCP `80,443`; HTTP redirects to HTTPS. +- Docker provider: opt-in `traefik.enable=true`; external network `traefik`. +- File provider: `/data/traefik/dynamic`. +- ACME: Let's Encrypt; Cloudflare DNS-01; `/data/traefik/acme/acme.json`; + apex + `*.umbra.mom` certificate requested. +- HTTPS fallback: priority `1`; unmatched non-apex hosts redirect to `https://umbra.mom`. +- `cloudflared`: `/data/cloudflared` -> `/etc/cloudflared`; + stack-local bridge `cloudflare`; no published ports. diff --git a/docs/home-server/operations.md b/docs/home-server/operations.md new file mode 100644 index 0000000..dc8a6f9 --- /dev/null +++ b/docs/home-server/operations.md @@ -0,0 +1,51 @@ +--- +schema_version: 1 +id: home-server.operations +type: reference +scope: [jupiter, mercury, design-decisions, monitoring, recovery] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "RAID/media tradeoff, CGNAT/public IPv4 rationale, no alerting and running monitoring/log containers, 2026-10-06" +related: [home-server.reference, home-server.host, home-server.mercury, home-server.networking, home-server.storage, home-server.backups, home-server.deployment] +update_triggers: [data-priority-change, architecture-change, alerting-change, recovery-test] +unknowns: + - acceptable-downtime-and-data-loss-by-service + - data-criticality-outside-the-four-backed-up-directories + - tested-full-host-recovery-order + - monitoring-review-frequency +--- + +# Operations / decisions + +## Owner decisions + +| Decision | Reason / accepted tradeoff | +| --- | --- | +| RAID 0 for HDD data | Capacity prioritized over disk redundancy; downloaded media can be reacquired | +| Back up important state | Four application directories covered; media redundancy is not the objective | +| Mercury public edge | Static public IPv4; home ISP uses CGNAT | +| No residential public ingress | Owner preference to avoid exposing the home IP | + +RAID 0 still stores important state alongside media: either HDD failure loses +the array; recovery depends on working backups. Owner intent is not evidence +that every important dataset is covered. + +## Observability + +| Facility | Current configuration | +| --- | --- | +| Host/container monitoring | Beszel hub and host-network agent present | +| Container logs | Dozzle with Docker socket proxy present | +| Automatic alerting | None configured | + +Monitoring does not imply notification delivery. Backup-hook failures, +stopped services and storage failures require manual detection. + +## Recovery status + +No restore test performed. Backup scope is documented in [Backups](backups.md); +OS, host configuration and named-volume coverage must not be inferred from +the four-directory plan. Recovery priorities, RPO/RTO and validated bootstrap +instructions remain unconfirmed. diff --git a/docs/home-server/portainer.md b/docs/home-server/portainer.md new file mode 100644 index 0000000..642dc19 --- /dev/null +++ b/docs/home-server/portainer.md @@ -0,0 +1,65 @@ +--- +schema_version: 1 +id: home-server.portainer +type: reference +scope: [jupiter, portainer, deployment, configuration-state] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Deployed container, ~/portainer_start.sh and updated Backrest plan, 2026-10-06" + - kind: owner-report + reference: "GitHub main/PAT access, Portainer environment values, manual redeployment and container inspection, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: working-tree + base_revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/portainer/portainer_start.sh +related: [home-server.reference, home-server.storage, home-server.authentication, home-server.backups, home-server.deployment] +update_triggers: [script-change, portainer-upgrade, state-migration, backup-plan-change] +unknowns: [local-hostname-dns-resolution, successful-state-and-secret-restore] +--- + +# Portainer + +## Deployment + +Managed by handwritten `~/portainer_start.sh`, **not a Compose stack**. +Reference script: [portainer_start.sh](https://github.com/jayo60013/jupiter-stacks). + +| Setting | Deployed value | +| --- | --- | +| Image | `portainer/portainer-ce:2.45.1` | +| Container | `portainer` | +| Restart policy | `always` | +| Network | External Docker network `traefik` | +| State | Host `/data/portainer` -> container `/data` | +| Docker API | `/var/run/docker.sock` bind mount | +| Host publication | TCP `9123` -> container `9000` | +| HTTPS routers | `portainer.umbra.mom`, `portainer.local.umbra.mom` | +| Traefik backend | HTTP; container port `9000` | +| TLS | `https` entrypoint; `letsencrypt` resolver | + +Script stops/removes the existing container, then recreates it; bind-mounted +state persists. Requires Docker, the `traefik` network, and mounted `/data`. +Supplied routers attach no Authelia middleware. Local router does not establish +local DNS resolution. + +## Stack management + +Portainer fetches , branch `main`, +using a GitHub PAT. Stack environment values/secrets are managed in Portainer. +Repository changes are pulled/redeployed manually; committing alone does not +update containers. See [deployment](deployment.md). + +## Backups + +Daily 03:00 UTC; Google Drive; last four snapshots. Backrest reads +`/userdata/portainer` from host `/data/portainer`. + +- Before snapshot: stop `portainer`; timeout `120s`; errors fatal. +- Snapshot end/error: start `portainer`; restart errors ignored. +- Restore remains untested; script itself is outside the listed backup paths. + +The former `portainer_data` named volume is not used by the deployed container. diff --git a/docs/home-server/seedbox.md b/docs/home-server/seedbox.md new file mode 100644 index 0000000..d11f75b --- /dev/null +++ b/docs/home-server/seedbox.md @@ -0,0 +1,92 @@ +--- +schema_version: 1 +id: home-server.seedbox +type: reference +scope: [seedbox, ultra-cc, qbittorrent, rclone, sftp, media-import] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "Ultra.cc plan, remaining capacity, rclone remote/process and enabled systemd unit, 2026-10-06" + - kind: owner-report + reference: "apt-managed rclone, active FUSE mount and Radarr/Sonarr bind-mount inspection, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/radarr/docker-compose.yml + - stacks/sonarr/docker-compose.yml +related: [home-server.reference, home-server.networking, home-server.storage, home-server.backups] +update_triggers: [hosting-renewal, quota-change, remote-change, mount-change, media-import-change] +unknowns: + - total-traffic-allowance-and-reset-period + - renewal-behaviour + - rclone-cache-location-and-size-limit + - import-copy-move-and-remote-deletion-policy + - mount-startup-ordering-relative-to-docker-containers + - seedbox-backup-policy +--- + +# Seedbox + +## Service / subscription + +| Field | Configuration | +| --- | --- | +| Provider / plan | [Ultra.cc](https://ultra.cc/); `Lancer-v2` | +| Host | `terra.usbx.me` | +| Account / home | `jayo60013` / `/home/jayo60013` | +| Cost | GBP 4.50/month | +| Reported expiration | `2026-10-25`; renewal behaviour unconfirmed | +| Disk | 1001 GB allocation; 984 GB available at review | +| Remaining traffic | 2 TB at review; total allowance/reset period unconfirmed | +| Download speed | Provider reports `Unlimited`; no numeric rate supplied | +| Upload speed | Provider reports `50000 Mbps`; not measured throughput | +| qBittorrent UI | | + +## Jupiter connection + +Actual connection: **SFTP via rclone/FUSE**, not WebDAV or scheduled sync. + +| Component | Path / value | +| --- | --- | +| rclone remote | `seedbox`; type `sftp`; host `terra.usbx.me`; user `jayo60013` | +| SSH key file | `/home/jay/.ssh/id_ed25519`; key contents excluded | +| Remote source | `seedbox:downloads/qbittorrent` | +| Remote absolute path | `/home/jayo60013/downloads/qbittorrent` | +| Jupiter mount | `/mnt/seedbox` | +| Container source | `/downloads` in Radarr and Sonarr | +| Radarr library | Container `/movies` -> host `/data/jellyfin/movies` | +| Sonarr library | Container `/tv` -> host `/data/jellyfin/tv_shows` | + +rclone remote: `shell_type=unix`; checksum commands `md5sum`, `sha1sum`. +rclone is apt-managed. Observed FUSE mount: UID/GID `1000:1000`; +`rw,nosuid,nodev,allow_other`. + +## Mount lifecycle + +`/etc/systemd/system/rclone-seedbox.service`; enabled at boot. +User/group `jay`; `Type=simple`; wants/starts after `network-online.target`. + +```sh +/usr/bin/rclone mount seedbox:downloads/qbittorrent /mnt/seedbox \ + --allow-other \ + --dir-cache-time 1m \ + --vfs-cache-mode writes +``` + +Stop: `/bin/fusermount -u /mnt/seedbox`. +Restart: `on-failure`; delay `10s`; target `multi-user.target`. + +`--allow-other` permits other local users, subject to filesystem permissions. +`writes` caches write/read-write opens; read-only opens stream from the remote. +No explicit cache path/size limit is supplied in the unit. + +The remote mount is not a complete local replica or backup. Radarr/Sonarr +library destinations are on the HDD array; cross-filesystem hardlinks are +impossible. Mount startup ordering relative to Docker containers is unconfirmed. +Their `/downloads` binds use `rprivate`; host remounts are not automatically +propagated into existing containers. + +The FUSE-reported capacity is not evidence of the account quota; use the +provider allocation above. diff --git a/docs/home-server/storage.md b/docs/home-server/storage.md new file mode 100644 index 0000000..dc171b4 --- /dev/null +++ b/docs/home-server/storage.md @@ -0,0 +1,163 @@ +--- +schema_version: 1 +id: home-server.storage +type: reference +scope: [jupiter, disks, raid, filesystems, docker-volumes] +sensitivity: private-infrastructure +last_reviewed: "2026-10-06" +sources: + - kind: owner-report + reference: "lsblk filesystem output, RAID level, mount placement and Docker data root, 2026-10-06" + - kind: owner-report + reference: "Seedbox rclone mount and systemd unit, 2026-10-06" + - kind: owner-report + reference: "Deployed Portainer uses /data/portainer bind mount, not portainer_data, 2026-10-06" + - kind: owner-report + reference: "mdstat, mdadm scans/detail, fstab, findmnt and Docker volume/container inspection, 2026-10-06" + - kind: repository + repository: jupiter-stacks + revision: 610e325ef6a98850511ce7a089b0b9a77bfecf15 + paths: + - stacks/backrest/docker-compose.yml + - stacks/jellyfin/docker-compose.yml + - stacks/authelia/docker-compose.yml + - stacks/traefik/docker-compose.yml +related: [home-server.reference, home-server.host, home-server.seedbox, home-server.portainer, home-server.backups, home-server.operations] +update_triggers: [disk-change, raid-change, mount-change, volume-change, backup-coverage-change] +unknowns: [persistent-mdadm-config-and-initramfs-content, disk-health] +--- + +# Storage + +```mermaid +--- +config: + themeCSS: | + .node rect { rx: 8px; ry: 8px; stroke-width: 1.5px; } + .cluster rect { fill: var(--infra-zone) !important; stroke: var(--infra-border) !important; stroke-dasharray: 5 5; rx: 12px; ry: 12px; } + .label, .nodeLabel, .cluster-label { color: var(--infra-text) !important; } + .flowchart-link { stroke: var(--infra-line) !important; } + marker path { fill: var(--infra-line) !important; stroke: var(--infra-line) !important; } + .infraStorage rect, .infraStorage path { fill: var(--infra-storage-fill) !important; stroke: var(--infra-storage-stroke) !important; } + .infraCloud rect { fill: var(--infra-cloud-fill) !important; stroke: var(--infra-cloud-stroke) !important; } + .infraClient rect { fill: var(--infra-client-fill) !important; stroke: var(--infra-client-stroke) !important; } + .infraService rect { fill: var(--infra-service-fill) !important; stroke: var(--infra-service-stroke) !important; } + flowchart: + curve: basis + nodeSpacing: 30 + rankSpacing: 45 +--- +flowchart LR + subgraph Array["HDD ARRAY / APPLICATION DATA"] + direction TB + A["Seagate BarraCuda
sda1 / 2 TB"] + B["Seagate BarraCuda
sdb1 / 2 TB"] + RAID["Linux MD RAID 0
md0 / no redundancy"] + DATA[("/data
ext4")] + A --> RAID + B --> RAID + RAID --> DATA + end + + subgraph System["SSD / SYSTEM"] + direction TB + SSD["Samsung 860 EVO
sdc / 1 TB"] + EFI["/boot/efi
sdc1 / FAT32"] + SWAP["Swap
sdc2"] + ROOT["/
sdc3 / ext4"] + DOCKER["/var/lib/docker
Named volumes"] + CACHE["/home/jay/data
Backrest / Jellyfin caches"] + SSD --> EFI + SSD --> SWAP + SSD --> ROOT + ROOT --> DOCKER + ROOT --> CACHE + end + + class A,B,SSD,DATA,ROOT infraStorage + class RAID infraCloud + class EFI,SWAP infraClient + class DOCKER,CACHE infraService +``` + +RAID 0: striping; no redundancy; either HDD failure loses the array. + +## Filesystems + +Device names reflect the supplied snapshot; UUIDs identify filesystems. + +| Mount | Device | Filesystem | UUID | +| --- | --- | --- | --- | +| `/data` | `/dev/md0` | ext4 | `d60390a1-7db3-44cc-bbde-693c23e42001` | +| `/` | `/dev/sdc3` | ext4 | `7a5e0ee6-4a65-4f68-9128-b3b4f3406447` | +| `/boot/efi` | `/dev/sdc1` | FAT32 | `1DDE-CDF9` | +| swap | `/dev/sdc2` | swap | `4781ea5d-fba5-438d-8d97-6a0801669ca1` | + +MD members: `/dev/sda1`, `/dev/sdb1`; metadata `1.2`; array label `jupiter:0`; +MD UUID `c67fccaa-e541-bf2d-2bf6-83221086e0e9`. + +## RAID assembly / geometry + +Linux MD, inspected with mdadm; ext4 directly on the array, no intervening LVM. + +| Setting | Observed value | +| --- | --- | +| Device / scan name | `/dev/md0` / `/dev/md/0` | +| mdadm UUID | `c67fccaa:e541bf2d:2bf68322:1086e0e9` | +| Capacity | 3.64 TiB / 4.00 TB | +| Layout / chunk | `original` / 512 KiB | +| Member order | Slot 0: `/dev/sdb1`; slot 1: `/dev/sda1` | +| State at review | `clean`; 2 active members; no failed/spare devices | + +Scan output identifies the array; persisted mdadm configuration/initramfs were +not supplied. Assembly is not array creation; do not substitute `mdadm --create`. + +## Mount configuration + +Supplied `/etc/fstab`: + +```fstab +UUID=7a5e0ee6-4a65-4f68-9128-b3b4f3406447 / ext4 errors=remount-ro 0 1 +UUID=1DDE-CDF9 /boot/efi vfat utf8 0 0 +UUID=d60390a1-7db3-44cc-bbde-693c23e42001 /data ext4 defaults 0 2 +UUID=4781ea5d-fba5-438d-8d97-6a0801669ca1 none swap sw 0 0 +``` + +Observed `/data`: `rw,relatime,stripe=256`; `/tmp`: tmpfs. +Seedbox FUSE mount is service-managed, not listed in fstab. + +## Placement + +| Host path | Backing / purpose | +| --- | --- | +| `/data/` | HDD array; service bind mounts | +| `/mnt/seedbox` | Remote SFTP/FUSE mount; [seedbox](seedbox.md); not a local replica | +| `/home/jay/data/backrest/cache` | SSD; Backrest cache | +| `/home/jay/data/jellyfin/cache` | SSD; Jellyfin cache | +| `/var/lib/docker` | SSD; Docker state and default local named volumes | +| `/var/lib/containerd` | SSD; observed containerd overlayfs snapshot storage | +| `/data/portainer` | HDD array; deployed [Portainer](portainer.md) state; backed up | +| `/data/authelia` | Configuration, users, SQLite, notifications | +| `/data/traefik/{acme,dynamic,certs}` | ACME state, dynamic configuration, certificates | + +Cache placement is per mount, not globally SSD-backed. `/data` backups do not +implicitly include SSD-backed Docker volumes. + +## Named-volume snapshot + +Inspected volumes below use driver `local`, no driver options, and +`/var/lib/docker/volumes//_data` on SSD. + +| Volume | Running consumer / container path | +| --- | --- | +| `audiobookshelf_config` | Audiobookshelf `/config` | +| `jellyfin_config` | Jellyfin `/config` | +| `notes_static` | Notes web `/usr/share/nginx/html`; builder `/site-output` | +| `dozzle_dozzle_data` | Dozzle `/data` | +| `ctq_ctq_postgres_data` | CTQ PostgreSQL `/var/lib/postgresql/data` | +| `havox_src` | Havox web/sync `/git` | + +Other volumes are present without a running consumer shown, including +`jellyfin_cache`, `notes_notes_static`, and monitoring volumes. Presence does +not prove use or justify deletion. Named volumes are outside the supplied +four-directory backup plan. diff --git a/docs/stylesheets/diagrams.css b/docs/stylesheets/diagrams.css new file mode 100644 index 0000000..67957f7 --- /dev/null +++ b/docs/stylesheets/diagrams.css @@ -0,0 +1,48 @@ +:root { + --infra-panel: #f8fafc; + --infra-panel-glow: #eef2ff; + --infra-border: #cbd5e1; + --infra-zone: #ffffff; + --infra-text: #1e293b; + --infra-line: #64748b; + --infra-client-fill: #f1f5f9; + --infra-client-stroke: #94a3b8; + --infra-edge-fill: #eef2ff; + --infra-edge-stroke: #818cf8; + --infra-service-fill: #ecfdf5; + --infra-service-stroke: #34d399; + --infra-cloud-fill: #fff7ed; + --infra-cloud-stroke: #fb923c; + --infra-storage-fill: #eff6ff; + --infra-storage-stroke: #60a5fa; +} + +[data-md-color-scheme="slate"] { + --infra-panel: #141b2b; + --infra-panel-glow: #202944; + --infra-border: #475569; + --infra-zone: #1e293b; + --infra-text: #e2e8f0; + --infra-line: #94a3b8; + --infra-client-fill: #293548; + --infra-client-stroke: #94a3b8; + --infra-edge-fill: #30335c; + --infra-edge-stroke: #a5b4fc; + --infra-service-fill: #153c35; + --infra-service-stroke: #6ee7b7; + --infra-cloud-fill: #493324; + --infra-cloud-stroke: #fdba74; + --infra-storage-fill: #203654; + --infra-storage-stroke: #93c5fd; +} + +.md-typeset .mermaid { + --md-mermaid-label-fg-color: var(--infra-text); + --md-mermaid-label-bg-color: var(--infra-zone); + margin: 1.25rem 0; + padding: 1rem; + overflow-x: auto; + border: 1px solid var(--infra-border); + border-radius: 16px; + background: linear-gradient(135deg, var(--infra-panel), var(--infra-panel-glow)); +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..34f8fa4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "jupiter-notes" +version = "0.1.0" +description = "University notes and personal infrastructure reference" +requires-python = ">=3.12" +dependencies = [ + "zensical==0.0.67", +] + +[tool.uv] +package = false diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 11c1225..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -zensical==0.0.67 diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..5bb0207 --- /dev/null +++ b/uv.lock @@ -0,0 +1,316 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "click" +version = "8.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" }, +] + +[[package]] +name = "deepmerge" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/6e/5cb3548b4d3112fea529375e55e6f3cdc52b8054e3a66f203b1f888ba885/deepmerge-3.0.1.tar.gz", hash = "sha256:35b39a4cb92cf328d6eca61cbbf65f68a37c2ceb3085f0f853cbb2e52a59fc23", size = 22328, upload-time = "2026-09-01T14:09:44.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/91/600003aaad107e27553fbc9cbfc57e96fa37e0223d2ef9d7d3a0e8d8d070/deepmerge-3.0.1-py3-none-any.whl", hash = "sha256:35c96f6a68fcf90719a5b31d9f8042ecef6c00fb56836660d33455d0f5cfda65", size = 14909, upload-time = "2026-09-01T14:09:43.364Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jupiter-notes" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "zensical" }, +] + +[package.metadata] +requires-dist = [{ name = "zensical", specifier = "==0.0.67" }] + +[[package]] +name = "markdown" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/4f/700155c8c20d9e655dd0732b5fc3c7614f291b9148da271d7388e50bf774/markdown-3.11.tar.gz", hash = "sha256:180224db6aed87ba9ce1f2781ebcd5826253de8ff637112090e24b84502bbf9f", size = 485623, upload-time = "2026-09-25T13:46:23.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/1e/32971905a7ab47f8b66866ed949fa48b104ba1c4a6fa57794c4f2c4b2cb8/markdown-3.11-py3-none-any.whl", hash = "sha256:cd6c89e7eb308c8b332ed673215a52d208a43f8bacc030b1419376129408719e", size = 111296, upload-time = "2026-09-25T13:46:22.163Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/9b/e422a865e1d5d57d0e509b4e0bf1c1a70a7f6382c29a5aa428df994c8bc8/markupsafe-3.0.4.tar.gz", hash = "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", size = 153777, upload-time = "2026-10-02T23:07:22.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/09/4c59d56b8461ae8eb0d8ba34bb25b7e618547044679d58a82ef9b2479fc1/markupsafe-3.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6", size = 11658, upload-time = "2026-10-02T23:04:51.876Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f0/d6613774d86fbf6d145751d43c59875e47a6f9f17daee0aef173bd36d90e/markupsafe-3.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f", size = 12050, upload-time = "2026-10-02T23:04:52.931Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f2/8f18e0b806eb13c1f8d07d917a720831ead54253a6dec011fbc78098a6f8/markupsafe-3.0.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b", size = 24363, upload-time = "2026-10-02T23:04:53.895Z" }, + { url = "https://files.pythonhosted.org/packages/60/ce/fa07dbe8a5675558fa36dea033e19995bc783de2dec5f540ccb9030b06aa/markupsafe-3.0.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df", size = 28525, upload-time = "2026-10-02T23:04:54.905Z" }, + { url = "https://files.pythonhosted.org/packages/85/40/be87c01f3868ec217f8a2015089d71c22c8c5a75324822e5ed1cdd87210d/markupsafe-3.0.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c", size = 24733, upload-time = "2026-10-02T23:04:56.229Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/aeedb5140afa41fc74c225e9184ab96723a6e873b6ee1c9fede7283456d8/markupsafe-3.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581", size = 22985, upload-time = "2026-10-02T23:04:57.521Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fc/e91352bb08c6a59da3ef0909d457bf95a5f5908fbf151b30a06d9dbcfbb4/markupsafe-3.0.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77", size = 22001, upload-time = "2026-10-02T23:04:58.597Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f8/bffee5e7d2a3deb59748a797650a48af7e672025cf641a79344a771ad106/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c", size = 23793, upload-time = "2026-10-02T23:04:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/ed/59/b853d6628ecb4d658e1d637224846d5e9bb4adf4f8df97f3be9f29dce2ec/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749", size = 22640, upload-time = "2026-10-02T23:05:00.768Z" }, + { url = "https://files.pythonhosted.org/packages/09/b2/1506df394f0f075797c418d0301498f49e43be194e3ffcb49e6fe6ccf022/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed", size = 24074, upload-time = "2026-10-02T23:05:01.813Z" }, + { url = "https://files.pythonhosted.org/packages/c7/81/5ed69cda630ac69ef60d06c09ba5a7f84ff66a2e28cf986fd5614ab3c6e6/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786", size = 21563, upload-time = "2026-10-02T23:05:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fe/fb1e79be0fea60aa32602ebefc9c35a82bb42b4df157285ab7dfec12341a/markupsafe-3.0.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e", size = 23048, upload-time = "2026-10-02T23:05:04.479Z" }, + { url = "https://files.pythonhosted.org/packages/c8/52/7632a53360671a9b750cdbabaf9cdd89f18b42248b8e4cb42c0b0296e459/markupsafe-3.0.4-cp312-cp312-win32.whl", hash = "sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237", size = 14108, upload-time = "2026-10-02T23:05:05.513Z" }, + { url = "https://files.pythonhosted.org/packages/3f/bf/62495e180b7000aaf30000fff849e933f74264638057176cf46852500adc/markupsafe-3.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7", size = 14303, upload-time = "2026-10-02T23:05:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8e/4c24208776a65878d656996945aacfbfe010d3720d1a98fc0eb8491fc03b/markupsafe-3.0.4-cp312-cp312-win_arm64.whl", hash = "sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9", size = 14174, upload-time = "2026-10-02T23:05:07.617Z" }, + { url = "https://files.pythonhosted.org/packages/6d/18/4bc5ba32499e87bb2b0ef5b3a9bb9c00a131fa961ddf0be548cb550f548b/markupsafe-3.0.4-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", size = 12786, upload-time = "2026-10-02T23:05:08.709Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6f/17f0c099bf25f3e31e63cc19244d9f6af861a9a4ab778c203997903cfdd0/markupsafe-3.0.4-cp313-cp313-android_24_x86_64.whl", hash = "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", size = 12510, upload-time = "2026-10-02T23:05:09.93Z" }, + { url = "https://files.pythonhosted.org/packages/11/af/1a141081b905036ee904ec4bd945e1f70b4e1b32d33c4e59e8cf1d58b247/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", size = 11583, upload-time = "2026-10-02T23:05:10.884Z" }, + { url = "https://files.pythonhosted.org/packages/e7/0a/a89385ae590232622a03e091805cff12f24fabe6c11e0e8bae096cece81c/markupsafe-3.0.4-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", size = 11801, upload-time = "2026-10-02T23:05:11.913Z" }, + { url = "https://files.pythonhosted.org/packages/ed/85/ea548dc013962eb73653124bc595635fbf9e0fa41d1f181a967ccb784dfb/markupsafe-3.0.4-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", size = 11443, upload-time = "2026-10-02T23:05:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/cc/72/15f2e5ec9cf2eb00d5cdfe968d94e4156a7bd7303832c3f3b2c403a36839/markupsafe-3.0.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", size = 11665, upload-time = "2026-10-02T23:05:13.829Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e0/4030bea613677e333c8a2c901fd405055f657f9d06acba5b7357984b6ef7/markupsafe-3.0.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", size = 12061, upload-time = "2026-10-02T23:05:14.807Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a5/28b76a7449eb702966b88bef599e2360b411fbb3afeee8fe560939be06ec/markupsafe-3.0.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", size = 24395, upload-time = "2026-10-02T23:05:15.909Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/21232811afc3a063b5e934b1ae2efda52f46154ec382f585149c020e61fe/markupsafe-3.0.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", size = 27183, upload-time = "2026-10-02T23:05:16.976Z" }, + { url = "https://files.pythonhosted.org/packages/14/38/6ccdfa5b59049cb36fb80cbc80aee9cf1fc9bb77d1335ad435f2070b08cf/markupsafe-3.0.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", size = 24772, upload-time = "2026-10-02T23:05:18.209Z" }, + { url = "https://files.pythonhosted.org/packages/63/e0/cec6865dfe88cb48fedd4b20aed6af5158e41092adcbf3e028bcc6ec2108/markupsafe-3.0.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", size = 23017, upload-time = "2026-10-02T23:05:19.286Z" }, + { url = "https://files.pythonhosted.org/packages/ee/76/6ed4940bb7648a9aac457c14f870cfdd5105f139a0fb1f29cd61fafa47d1/markupsafe-3.0.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", size = 22036, upload-time = "2026-10-02T23:05:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/ed476226d4fe46a09090a36025bf319296810028df55eb12f1253b540f3a/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", size = 23786, upload-time = "2026-10-02T23:05:21.576Z" }, + { url = "https://files.pythonhosted.org/packages/9a/35/66ff30450e35ef5fba9ebc930c9411747e537fd9447b65e44f5007e2b84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", size = 22672, upload-time = "2026-10-02T23:05:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/32/0b/72f45ce4b4efcbca4b80cf1b06703eff0be8d37e82abb78f66c85a7ead1e/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", size = 24107, upload-time = "2026-10-02T23:05:24.175Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/71776e5fdcba04614b384cc102e8a4198208579d896fd1394cb7cb9aa900/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", size = 21615, upload-time = "2026-10-02T23:05:25.215Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/801ce02a02e7aee0f784b1ec7843026178f6adeb9c93ac67eb1992a9a84d/markupsafe-3.0.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", size = 23076, upload-time = "2026-10-02T23:05:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/c43776625428f3bb4a61e8633940400e3efe6409e3c6f5bff26de5e45618/markupsafe-3.0.4-cp313-cp313-win32.whl", hash = "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", size = 14118, upload-time = "2026-10-02T23:05:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/6f/36/163da64de88a13db79214ef75fa041be7fa13bdb42261cf5b7484de14bfb/markupsafe-3.0.4-cp313-cp313-win_amd64.whl", hash = "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", size = 14313, upload-time = "2026-10-02T23:05:28.749Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", size = 14189, upload-time = "2026-10-02T23:05:29.917Z" }, + { url = "https://files.pythonhosted.org/packages/5c/c3/a944f3b0df22bd129e96915b9f4e98d2eeca6516687d7618304a966c3c74/markupsafe-3.0.4-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", size = 12787, upload-time = "2026-10-02T23:05:30.971Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d6/a44863f69d88b6c7e27889108f70d47aed259edf89d5df3c5fca1eac87d6/markupsafe-3.0.4-cp314-cp314-android_24_x86_64.whl", hash = "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", size = 12525, upload-time = "2026-10-02T23:05:32.263Z" }, + { url = "https://files.pythonhosted.org/packages/17/8f/168ba80e532dd6a93f96f8f706f1ad41d7990b6e1aeedc1cc0d211a33497/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", size = 11589, upload-time = "2026-10-02T23:05:33.251Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/aa2c95a574d3af39403a469b295886eb9b6d448da568cbebb5a2cbfdc2e5/markupsafe-3.0.4-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", size = 11801, upload-time = "2026-10-02T23:05:34.315Z" }, + { url = "https://files.pythonhosted.org/packages/60/d0/34b810107d83840e768bf485de795893ebbae35b26ab061b487adfa0a692/markupsafe-3.0.4-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", size = 11447, upload-time = "2026-10-02T23:05:35.302Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ab/2f8488f0f817a39fca068d2b17daf446bf5cdb3eae28c3720af534d873b4/markupsafe-3.0.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", size = 11722, upload-time = "2026-10-02T23:05:36.363Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/e2d117b048d47282ade906fbfd92814cbee5647afc13fda88a3406039372/markupsafe-3.0.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", size = 12061, upload-time = "2026-10-02T23:05:37.397Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a8/73a81135e85ba66217f5af7facb03bbb386807e1a729ab64532e4c802652/markupsafe-3.0.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", size = 24434, upload-time = "2026-10-02T23:05:38.407Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/fa9216dd01efee2dfdacafe7df32b4d0170fbac694b0c258a193d6e53999/markupsafe-3.0.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", size = 27183, upload-time = "2026-10-02T23:05:39.581Z" }, + { url = "https://files.pythonhosted.org/packages/fa/4e/a469509e538d37af51103b17b073126973f2b1cbf197ff32c7ddf025cfe5/markupsafe-3.0.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", size = 24813, upload-time = "2026-10-02T23:05:40.671Z" }, + { url = "https://files.pythonhosted.org/packages/8f/db/d7282caf7ab03af44d5d6fdbaa019b35c7d7f1c90588b839c07cba640d6a/markupsafe-3.0.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", size = 23049, upload-time = "2026-10-02T23:05:41.864Z" }, + { url = "https://files.pythonhosted.org/packages/30/f3/b6a425206e6964efda6acee544d0eb01d1501784d0b8e2dcc74986f33b17/markupsafe-3.0.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", size = 22088, upload-time = "2026-10-02T23:05:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8a/84d3582fc1f0d5bd466cdf2eebf175e172158a6e70701aacec1de1b35430/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", size = 23846, upload-time = "2026-10-02T23:05:44.098Z" }, + { url = "https://files.pythonhosted.org/packages/1c/65/db101cce51b7ba4864ac491a9859d297dd1adf0e55b103fee9db9c47c527/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", size = 22674, upload-time = "2026-10-02T23:05:45.23Z" }, + { url = "https://files.pythonhosted.org/packages/e0/49/ddee9813d71db0c7a5c9d97c832125e6758a0c844777f1cf076569bb0e22/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", size = 24104, upload-time = "2026-10-02T23:05:46.398Z" }, + { url = "https://files.pythonhosted.org/packages/aa/0e/7d8518d726726870a2399d69fd30d0fa36c5e57a2132c336b58d7c491073/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", size = 21637, upload-time = "2026-10-02T23:05:47.48Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b0/b505e8a361ba557dbf3b3aa7331ea39b00d2022a26e925ff8463b9714bb3/markupsafe-3.0.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", size = 23081, upload-time = "2026-10-02T23:05:48.611Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ea/9cc3cea873f980c75cbdb6f4277ce30ee955de38be0b3d02f14c108e0698/markupsafe-3.0.4-cp314-cp314-win32.whl", hash = "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", size = 14272, upload-time = "2026-10-02T23:05:49.707Z" }, + { url = "https://files.pythonhosted.org/packages/80/f0/5792ff768a410f93ee3f84fc19345295ffc352d2c936b424cb37e514714c/markupsafe-3.0.4-cp314-cp314-win_amd64.whl", hash = "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", size = 14501, upload-time = "2026-10-02T23:05:50.788Z" }, + { url = "https://files.pythonhosted.org/packages/5f/cf/3d074a8edffcc6899355232ff2543ae8d929733239596423b7db79698bc9/markupsafe-3.0.4-cp314-cp314-win_arm64.whl", hash = "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", size = 14364, upload-time = "2026-10-02T23:05:51.857Z" }, + { url = "https://files.pythonhosted.org/packages/d9/31/87ce42159aae2163cf3bbbd0c44bc87780510eecab1ea3859099aed95dcb/markupsafe-3.0.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", size = 11759, upload-time = "2026-10-02T23:05:52.951Z" }, + { url = "https://files.pythonhosted.org/packages/5f/53/b047207eeb7752e960aca3eb1df5fb7eefa7dd4c62ac49bb156456c8a702/markupsafe-3.0.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", size = 12096, upload-time = "2026-10-02T23:05:54.066Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/4326c88a13c7b755657d44b4bb986f8c3d9843ecba7e22d98661d87f9a57/markupsafe-3.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", size = 25699, upload-time = "2026-10-02T23:05:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bb/990581b7474bfcf2cf34bed6ba5ea23bd87adb9d671213d68e88620e7a6b/markupsafe-3.0.4-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", size = 27804, upload-time = "2026-10-02T23:05:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/6b/89/89491878c28e8291f5aa2fffe2c2d57230d10ae366d55dd810b840513d78/markupsafe-3.0.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", size = 25912, upload-time = "2026-10-02T23:05:57.416Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/680998b54efdea06fc114565cd739b6d059f826a0279219b218dfa750d29/markupsafe-3.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", size = 23623, upload-time = "2026-10-02T23:05:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/ae/75/2709f5ac5de9467b40b10e2bb8f89cc63dfb74582e09aa734b1124a217de/markupsafe-3.0.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", size = 23301, upload-time = "2026-10-02T23:05:59.94Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c8/39eadc6c5b14c9c7679bfb98f4d4c6a97863b5beb91839aca4d2d6e16e55/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", size = 24950, upload-time = "2026-10-02T23:06:01.289Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5e/01037f8a43e8ccb0bffb4fbdc5212db05bf080fdd7286cd392332d58128a/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", size = 23330, upload-time = "2026-10-02T23:06:02.441Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f4/23e83ce0596bb0cbe670502d31df8f757bbd01a392aa486fa3b40d1ed399/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", size = 25239, upload-time = "2026-10-02T23:06:03.579Z" }, + { url = "https://files.pythonhosted.org/packages/88/5b/3708897368073cc683d524750474f41a77d2986152c380dcc55b20fdf340/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", size = 22767, upload-time = "2026-10-02T23:06:04.699Z" }, + { url = "https://files.pythonhosted.org/packages/c6/61/ebda1307864b409e6b3115757a3d4a09cca46cfb6cc65191b5de226b424b/markupsafe-3.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", size = 23668, upload-time = "2026-10-02T23:06:05.9Z" }, + { url = "https://files.pythonhosted.org/packages/09/15/98075cceac3b5ba0dbb8e4762a847be967d2befc349a2cf2d0ac77f62c9d/markupsafe-3.0.4-cp314-cp314t-win32.whl", hash = "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", size = 14301, upload-time = "2026-10-02T23:06:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a3/768b560fcc4156685cb563d922b217810cfa7bc135773367f62f1f9d2078/markupsafe-3.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", size = 14526, upload-time = "2026-10-02T23:06:08.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/63/da554b4c97a6b0ea3229ca7fe8cbfb620be81613d517f482e85958550537/markupsafe-3.0.4-cp314-cp314t-win_arm64.whl", hash = "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", size = 14421, upload-time = "2026-10-02T23:06:09.402Z" }, + { url = "https://files.pythonhosted.org/packages/a9/30/54d11c8ca027114898cab97421fb39e4ffd9ddf47cdbc44df2ec76722da9/markupsafe-3.0.4-cp315-cp315-android_24_arm64_v8a.whl", hash = "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", size = 12791, upload-time = "2026-10-02T23:06:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/10/6d/97c913e253a14bd3cd0e15a5c56d13203b823fa7ee32498342896a072dc4/markupsafe-3.0.4-cp315-cp315-android_24_x86_64.whl", hash = "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", size = 12527, upload-time = "2026-10-02T23:06:11.834Z" }, + { url = "https://files.pythonhosted.org/packages/26/f9/b86d032042a4d597d9e1997f0e5f63a3eedaf11258e0a05760b0a0a826ea/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", size = 11602, upload-time = "2026-10-02T23:06:13.122Z" }, + { url = "https://files.pythonhosted.org/packages/f2/dc/73c14c1eedf0ac5fa3292ba43435e6c49d2c2050f33cebde541f8f4807f1/markupsafe-3.0.4-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", size = 11817, upload-time = "2026-10-02T23:06:14.227Z" }, + { url = "https://files.pythonhosted.org/packages/8f/69/2c2fcaa5fcee22d72c7819c0d536fd181c74a688e6143845419579cd2863/markupsafe-3.0.4-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", size = 11446, upload-time = "2026-10-02T23:06:15.574Z" }, + { url = "https://files.pythonhosted.org/packages/88/54/9e5ec76c62e6e2834d5a93623018c943e8b3bb41d663e3fd4c03303b9b85/markupsafe-3.0.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", size = 11719, upload-time = "2026-10-02T23:06:16.701Z" }, + { url = "https://files.pythonhosted.org/packages/96/24/3ec292b44064c16229e064d770b2625bd8ea941aa61f44905a9fa44942c0/markupsafe-3.0.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", size = 12075, upload-time = "2026-10-02T23:06:17.855Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/b64fdb1f304848518742136983c24e96d967bfb59a0ea160e92736901ab0/markupsafe-3.0.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", size = 24656, upload-time = "2026-10-02T23:06:18.963Z" }, + { url = "https://files.pythonhosted.org/packages/9c/18/23997d4c65b355da6390d61cd56e0ab3befd6ba8dda25cb40c602bd0fa6b/markupsafe-3.0.4-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", size = 27737, upload-time = "2026-10-02T23:06:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/35998dead3c6af88c38265a56e58100211f036234ab88eb2283fd4cbce44/markupsafe-3.0.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", size = 25003, upload-time = "2026-10-02T23:06:21.284Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/ef49135ce260db4ca4a12b119ed468449cd248db6b1468e2112b546d7a2e/markupsafe-3.0.4-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", size = 23111, upload-time = "2026-10-02T23:06:22.524Z" }, + { url = "https://files.pythonhosted.org/packages/50/7d/83126e338bd88c17a220668235368ad719fd4638e426739858cbb8508f77/markupsafe-3.0.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", size = 22403, upload-time = "2026-10-02T23:06:23.785Z" }, + { url = "https://files.pythonhosted.org/packages/83/dd/daf7e420de23c8206c365204e7b85e1251d8e19d34196a56336f316e5ed2/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", size = 24083, upload-time = "2026-10-02T23:06:25.037Z" }, + { url = "https://files.pythonhosted.org/packages/19/3c/11eecdc06bc44ad5570350085b572ebf049e8f9a38d1ece6d76640b739cd/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", size = 22931, upload-time = "2026-10-02T23:06:26.328Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/ac0fd77f2a726e56ecc3ca0235d095feace1358d1b822406c2a2ef26a4dc/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", size = 24299, upload-time = "2026-10-02T23:06:27.742Z" }, + { url = "https://files.pythonhosted.org/packages/d7/09/c6bd842ad58ff5b3bc76eeed7e9a42a6f11adc5d090ec697b72c9672731e/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", size = 21948, upload-time = "2026-10-02T23:06:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/a3/46/82f586711fed61e86faa1ee1bc317d68cd45a10c8bdbe3f7d1fdf9026ad8/markupsafe-3.0.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", size = 23150, upload-time = "2026-10-02T23:06:30.583Z" }, + { url = "https://files.pythonhosted.org/packages/19/2d/2dfdce99318abbfa26925195fbc17db188c46a1ec6457be121b6f9cfeb42/markupsafe-3.0.4-cp315-cp315-win32.whl", hash = "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", size = 14269, upload-time = "2026-10-02T23:06:31.949Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ec/6000fd82e8791e58fcd0456ec20f098957e2b03d5ed02eb73241a577c0ba/markupsafe-3.0.4-cp315-cp315-win_amd64.whl", hash = "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", size = 14496, upload-time = "2026-10-02T23:06:33.258Z" }, + { url = "https://files.pythonhosted.org/packages/bc/66/e73bd5016421d5d6e2fb6de7dd609f9de020942ac8c626526bd8c6eeaf82/markupsafe-3.0.4-cp315-cp315-win_arm64.whl", hash = "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", size = 14361, upload-time = "2026-10-02T23:06:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/90/df/cb8c3dc98d313a951df2f8968f44e4cb5643df6d3cab749a530ce2f7d972/markupsafe-3.0.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", size = 11760, upload-time = "2026-10-02T23:06:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/d6/bb/4af9b3ca0753d654ac75f9531d5bd741bb77ca6e696f36807c475ffc099a/markupsafe-3.0.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", size = 12108, upload-time = "2026-10-02T23:06:37.089Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d4/b56429313aee5fd59b079c3df5615299959e25e7113eb6d8caadbdd7d38a/markupsafe-3.0.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", size = 25762, upload-time = "2026-10-02T23:06:38.419Z" }, + { url = "https://files.pythonhosted.org/packages/65/f5/34c181e891aa4f7d59c918584672e0c5eb7fffe76c1387d1246008bf4081/markupsafe-3.0.4-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", size = 28400, upload-time = "2026-10-02T23:06:39.819Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b5/ad14694fd0ac9a5ce30bc6498f2999378f418583dd1679cca5a1b512957e/markupsafe-3.0.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", size = 25946, upload-time = "2026-10-02T23:06:41.381Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a8/26b606445387d0ceb1eb1f21840094b84e4e3c3c3983d80d10b89823b490/markupsafe-3.0.4-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", size = 23634, upload-time = "2026-10-02T23:06:42.748Z" }, + { url = "https://files.pythonhosted.org/packages/39/a2/b8814de672f1f0094d498bf646f2fec9d6356b503d28ef500b71c5095377/markupsafe-3.0.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", size = 23488, upload-time = "2026-10-02T23:06:44.176Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/287223376fb73335a3cc5d6eb22c6ab01358cf33945a9c39c06b9dac3f4b/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", size = 25037, upload-time = "2026-10-02T23:06:45.646Z" }, + { url = "https://files.pythonhosted.org/packages/f9/29/4df8355e313426d19e62ba33e0253c009ca12a0894ee77d67fa67255361c/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", size = 23543, upload-time = "2026-10-02T23:06:47.264Z" }, + { url = "https://files.pythonhosted.org/packages/71/e5/8377731e8495668dcc768f645e717df18318c841edaf023a99395f6da9b4/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", size = 25282, upload-time = "2026-10-02T23:06:48.795Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5f/373456e37ceb1478d657d6fe769cbe0a39f0a8dfc1548eeb19c471eefdd9/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", size = 22993, upload-time = "2026-10-02T23:06:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/2cbd5628435afb6f541bbaced4bce0c2edac4b09a142e6e928b8b0da9858/markupsafe-3.0.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", size = 23712, upload-time = "2026-10-02T23:06:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/81/99/157e10966b033b363aeda5263e82596ee232a0b1d082fdbf90aa417ff083/markupsafe-3.0.4-cp315-cp315t-win32.whl", hash = "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", size = 14300, upload-time = "2026-10-02T23:06:53.241Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/55884815414c9706a23deca150b72c25a62109e65b0b6ce232077802c719/markupsafe-3.0.4-cp315-cp315t-win_amd64.whl", hash = "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", size = 14522, upload-time = "2026-10-02T23:06:54.729Z" }, + { url = "https://files.pythonhosted.org/packages/92/f9/ecbde7149e95b8a0f18e16d5d747f7dc06049d5da2e4f77f6f5e4a1f46a8/markupsafe-3.0.4-cp315-cp315t-win_arm64.whl", hash = "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", size = 14417, upload-time = "2026-10-02T23:06:56.246Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/94/858e0163cb4d83d6d8234018a01792c749a56daee41794cac7d6c46661e8/pymdown_extensions-12.1.tar.gz", hash = "sha256:fdb8f47f5d7fd069d10ef2a7d8908e613d7865ff0419d544a0ffd3984e884f11", size = 868245, upload-time = "2026-09-23T00:08:42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/d1/98313da89960a604402266a115311b510254900ff1295ea426403f9423cc/pymdown_extensions-12.1-py3-none-any.whl", hash = "sha256:4a254b771acfcddc6c110a7f4590d818f1f849e2d5aacf69b4b07c8dd2a9700d", size = 277069, upload-time = "2026-09-23T00:08:40.069Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "zensical" +version = "0.0.67" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "jinja2" }, + { name = "markdown" }, + { name = "pathspec" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/11/d655563a922eb96bc930543132a6cb404971d980b36dcf7aa90751f4af8f/zensical-0.0.67.tar.gz", hash = "sha256:6691e79d1fec9e6235ded01a7d98c19342bfff144fceae7d043c4d3830890038", size = 4300450, upload-time = "2026-09-30T09:26:24.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/88/15bf6b4eef8895c37e96d6beb1c57fedcdcaa51527ba728869b81db0341d/zensical-0.0.67-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2fbb86fb0773d32aab2ce5bb40be64a6962871ef049c925d633996a6f54d7130", size = 18057715, upload-time = "2026-09-30T09:26:01.536Z" }, + { url = "https://files.pythonhosted.org/packages/00/c0/bb8dd996633ba4ad2a0d73bd0b09381fff309cdccec965b0978746e22189/zensical-0.0.67-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:679a513ffb0013b6db3bd5aacbc73484f6a734df702e5ba41baf049753d38995", size = 17588275, upload-time = "2026-09-30T09:26:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6b/f3aa259b9ee24a26de148d0d49a239683a43ecd985dd35b30cf0c3f5dead/zensical-0.0.67-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:100b21a42eb6f48c5f3fb89c7a4b3e3b12dcf6f7e2bd7da65b344842b1da30ad", size = 18039684, upload-time = "2026-09-30T09:26:06.963Z" }, + { url = "https://files.pythonhosted.org/packages/94/9c/f850af0905eb7319201f823a9fdedf32d0a479845dd3186e4a80f845f181/zensical-0.0.67-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:610c577a3d1200e21c443590e12334b46cb38ce0ef2dad12110afe19925160e2", size = 17988405, upload-time = "2026-09-30T09:26:09.578Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/9a9210bb96cbeb848df301462ac2d557e67548366014f4fa33dac10385d8/zensical-0.0.67-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ee6369a253c9ff18f3c221bb48ac8c82eda22cb46f03650b937be8786b00b9", size = 18508864, upload-time = "2026-09-30T09:26:11.917Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5d/7180698732ca5cf566f88300fa9ec487d81e852392be98d84d7cbc7cca80/zensical-0.0.67-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7f9baff27363551dcdb4970936a126a714f7ac84fd646bb673e2676ff2ab526d", size = 18204439, upload-time = "2026-09-30T09:26:14.152Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/04d8768895a39bb0ea65c1275fca0af0b1e1e5f3722a075fbd77476c199a/zensical-0.0.67-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3e1a7b7d5edf58394df235ade589b187da8f97107b11056ca989dbd6d7e646cc", size = 18769301, upload-time = "2026-09-30T09:26:16.585Z" }, + { url = "https://files.pythonhosted.org/packages/79/63/45108c9b220d73af5dc3577edf48c63f4336d7d80295fa2abc8c6ee3a4fe/zensical-0.0.67-cp310-abi3-win_amd64.whl", hash = "sha256:df5cc21899b4a5f3956d959e403dc754d7abcdcc2704c40dc43cff84e257b037", size = 18700647, upload-time = "2026-09-30T09:26:19.028Z" }, + { url = "https://files.pythonhosted.org/packages/93/43/09836dcb02085b7da0339d753b2b153352205209bfe14a3e16ec56740a0d/zensical-0.0.67-cp310-abi3-win_arm64.whl", hash = "sha256:20fb40bd0b8d05be92a793887e312de5b522fbda8fbdf80267395fd97baefc7b", size = 18120901, upload-time = "2026-09-30T09:26:21.683Z" }, +] diff --git a/zensical.toml b/zensical.toml index 431267a..21ce8f2 100644 --- a/zensical.toml +++ b/zensical.toml @@ -5,8 +5,24 @@ site_description = "University notes & personal notes" extra_javascript = [ "javascripts/mathjax.js", ] +extra_css = [ + "stylesheets/diagrams.css", +] nav = [ { "Home" = "index.md" }, + { "Home Server" = [ + { "Reference" = "home-server/index.md" }, + { "Host" = "home-server/host.md" }, + { "Mercury VPS" = "home-server/mercury.md" }, + { "Seedbox" = "home-server/seedbox.md" }, + { "Portainer" = "home-server/portainer.md" }, + { "Deployment" = "home-server/deployment.md" }, + { "Operations / decisions" = "home-server/operations.md" }, + { "Networking" = "home-server/networking.md" }, + { "Storage" = "home-server/storage.md" }, + { "Authentication" = "home-server/authentication.md" }, + { "Backups" = "home-server/backups.md" }, + ] }, { "Books" = [ { "Designing Data-Intensive Applications" = [ { "Preface" = "books/designing_data_intensive_applications/preface/index.md" }, @@ -201,6 +217,9 @@ permalink = true [project.markdown_extensions.extra] [project.markdown_extensions."pymdownx.highlight"] [project.markdown_extensions."pymdownx.superfences"] +custom_fences = [ + { name = "mermaid", class = "mermaid", format = "pymdownx.superfences.fence_code_format" }, +] [project.markdown_extensions."pymdownx.inlinehilite"] [project.markdown_extensions."pymdownx.magiclink"] [project.markdown_extensions."pymdownx.betterem"]