@celilo/e2e 0.7.11 → 0.7.13-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/.celilo-website-cache/_astro/docs.DwsUauWZ.css +1 -0
  2. package/.celilo-website-cache/_astro/network.BB0RR2mM.css +1 -0
  3. package/.celilo-website-cache/diagrams/physical.svg +1 -0
  4. package/.celilo-website-cache/diagrams/zone-topology.svg +1 -0
  5. package/.celilo-website-cache/docs/index.html +250 -0
  6. package/.celilo-website-cache/docs/network/index.html +300 -0
  7. package/.celilo-website-cache/favicon.svg +4 -0
  8. package/.celilo-website-cache/fonts/theia-300.otf +0 -0
  9. package/.celilo-website-cache/fonts/theia-400.otf +0 -0
  10. package/.celilo-website-cache/fonts/theia-500.otf +0 -0
  11. package/.celilo-website-cache/index.html +93 -0
  12. package/.celilo-website-cache/install.sh +330 -0
  13. package/.celilo-website-cache/modules/index.html +86 -0
  14. package/.celilo-website-cache/modules/view/index.html +69 -0
  15. package/.celilo-website-cache/vision/index.html +121 -0
  16. package/.npm-registry-cache/celilo-capabilities-0.1.10.tgz +0 -0
  17. package/.npm-registry-cache/celilo-cli-0.3.30.tgz +0 -0
  18. package/.npm-registry-cache/celilo-cli-display-0.1.9.tgz +0 -0
  19. package/.npm-registry-cache/celilo-e2e-0.7.13.tgz +0 -0
  20. package/.npm-registry-cache/celilo-event-bus-0.1.4.tgz +0 -0
  21. package/netapps/authentik.netapp +0 -0
  22. package/netapps/caddy.netapp +0 -0
  23. package/netapps/celilo-website.netapp +0 -0
  24. package/netapps/greenwave.netapp +0 -0
  25. package/netapps/homebridge.netapp +0 -0
  26. package/netapps/iptables.netapp +0 -0
  27. package/netapps/knot-unbound-internal.netapp +0 -0
  28. package/netapps/namecheap.netapp +0 -0
  29. package/netapps/technitium.netapp +0 -0
  30. package/package.json +6 -3
  31. package/src/docker-compose-generator.ts +30 -2
@@ -0,0 +1,300 @@
1
+ <!DOCTYPE html><html lang="en" data-astro-cid-5hce7sga> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="Wire up your home lab for Celilo: VLANs, switch config, firewall, Proxmox bridges."><title>Network setup — Celilo</title><link rel="icon" type="image/svg+xml" href="/favicon.svg"><script>
2
+ // Resolve theme BEFORE first paint to avoid the flash of wrong
3
+ // theme. Order: explicit user choice (localStorage) → system
4
+ // preference (prefers-color-scheme) → fallback dark. The
5
+ // resolved value lands on <html data-theme="...">; CSS variables
6
+ // below pick the matching palette.
7
+ (function () {
8
+ try {
9
+ const stored = localStorage.getItem('celilo-theme');
10
+ const sys =
11
+ window.matchMedia &&
12
+ window.matchMedia('(prefers-color-scheme: light)').matches
13
+ ? 'light'
14
+ : 'dark';
15
+ const theme = stored === 'light' || stored === 'dark' ? stored : sys;
16
+ document.documentElement.setAttribute('data-theme', theme);
17
+ } catch (_e) {
18
+ document.documentElement.setAttribute('data-theme', 'dark');
19
+ }
20
+ })();
21
+ </script><link rel="stylesheet" href="/_astro/docs.DwsUauWZ.css">
22
+ <link rel="stylesheet" href="/_astro/network.BB0RR2mM.css"></head> <body data-astro-cid-5hce7sga> <!--
23
+ Background pattern — vertical streams of falling water drawn in
24
+ the geometric vocabulary of a Greek meander. The <pattern>
25
+ tile (240×240) repeats seamlessly across the viewport. Strokes
26
+ use `currentColor` so the .bg-pattern CSS rule above can tint
27
+ them via `color: var(--bg-pattern-color)` without rebuilding
28
+ the SVG. To change the streams themselves, edit the four <path>
29
+ elements below.
30
+ --> <svg class="bg-pattern" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-astro-cid-5hce7sga> <defs data-astro-cid-5hce7sga> <pattern id="celilo-streams" width="240" height="240" patternUnits="userSpaceOnUse" data-astro-cid-5hce7sga> <!-- Streams flow downward — the patternTransform shifts the
31
+ pattern origin by one full tile (240px) over `dur`, then
32
+ loops seamlessly. Edit `dur` for speed; delete this
33
+ <animateTransform> to freeze the flow. --> <animateTransform attributeName="patternTransform" type="translate" from="0 0" to="0 240" dur="30s" repeatCount="indefinite" data-astro-cid-5hce7sga></animateTransform> <g fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" data-astro-cid-5hce7sga> <!-- Stroke color cycle — driven by SMIL inside the SVG
34
+ (not CSS `color`) because Chromium doesn't reliably
35
+ repaint strokes that resolve via currentColor when
36
+ the parent's CSS `color` is animated. Edit the
37
+ `values` list to pick your palette; semicolons
38
+ separate stops, last value should match first to
39
+ close the loop seamlessly. --> <animate attributeName="stroke" values="#818cf8;#f472b6;#4ade80;#22d3ee;#818cf8" dur="10s" repeatCount="indefinite" data-astro-cid-5hce7sga></animate> <!-- Stream A (x=40): big staircase cascade near top, single hook near bottom --> <path d="M 40,0 V 30 H 55 V 45 H 70 V 60 H 55 V 75 H 40 V 175 H 25 V 195 H 40 V 240" data-astro-cid-5hce7sga></path> <!-- Stream B (x=100): small step near top, three-tier cascade in middle --> <path d="M 100,0 V 60 H 85 V 80 H 100 V 130 H 115 V 145 H 130 V 160 H 115 V 175 H 100 V 240" data-astro-cid-5hce7sga></path> <!-- Stream C (x=160): three small hooks distributed top-to-bottom --> <path d="M 160,0 V 20 H 175 V 35 H 160 V 110 H 145 V 130 H 160 V 200 H 175 V 220 H 160 V 240" data-astro-cid-5hce7sga></path> <!-- Stream D (x=220): single deep cascade in the upper-middle --> <path d="M 220,0 V 85 H 205 V 100 H 190 V 115 H 220 V 240" data-astro-cid-5hce7sga></path> </g> </pattern> </defs> <rect width="100%" height="100%" fill="url(#celilo-streams)" data-astro-cid-5hce7sga></rect> </svg> <nav data-astro-cid-5hce7sga> <div class="nav-inner" data-astro-cid-5hce7sga> <a href="/" class="nav-logo" data-astro-cid-5hce7sga>celilo</a> <ul class="nav-links" data-astro-cid-5hce7sga> <li data-astro-cid-5hce7sga> <a href="/docs" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M4 5a2 2 0 0 1 2-2h11v18H6a2 2 0 0 1-2-2V5z" data-astro-cid-5hce7sga></path> <path d="M9 7h6M9 11h6M9 15h4" data-astro-cid-5hce7sga></path> </svg>
40
+ docs
41
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="/modules" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <rect x="3" y="3" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="14" y="3" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="3" y="14" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="14" y="14" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> </svg>
42
+ modules
43
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="/vision" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" data-astro-cid-5hce7sga></path> <circle cx="12" cy="12" r="3" data-astro-cid-5hce7sga></circle> </svg>
44
+ vision
45
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="https://github.com/celilo/celilo" target="_blank" rel="noopener" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2 4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 0 0-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21" data-astro-cid-5hce7sga></path> </svg>
46
+ github
47
+ </a> </li> <li data-astro-cid-5hce7sga> <button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle light/dark theme" title="Toggle theme" data-astro-cid-5hce7sga> <svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <circle cx="12" cy="12" r="4" data-astro-cid-5hce7sga></circle> <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" data-astro-cid-5hce7sga></path> </svg> <svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" data-astro-cid-5hce7sga></path> </svg> </button> </li> </ul> </div> </nav> <script>
48
+ // Click handler for the toggle. Reads the current data-theme,
49
+ // flips it, persists to localStorage, applies to <html>. The
50
+ // resolution script in <head> already set the initial value
51
+ // (system-aware) before paint, so this only runs on user
52
+ // interaction.
53
+ (function () {
54
+ const btn = document.getElementById('theme-toggle');
55
+ if (!btn) return;
56
+ btn.addEventListener('click', function () {
57
+ const current = document.documentElement.getAttribute('data-theme');
58
+ const next = current === 'light' ? 'dark' : 'light';
59
+ document.documentElement.setAttribute('data-theme', next);
60
+ try {
61
+ localStorage.setItem('celilo-theme', next);
62
+ } catch (_e) {
63
+ // localStorage unavailable (private mode) — toggle still
64
+ // works for the session, just doesn't persist.
65
+ }
66
+ });
67
+ })();
68
+ </script> <main data-astro-cid-5hce7sga> <nav class="breadcrumb" aria-label="Breadcrumb" data-astro-cid-ppabjpgh> <a href="/docs" data-astro-cid-ppabjpgh>Docs</a> <span aria-hidden="true" data-astro-cid-ppabjpgh>/</span> <span data-astro-cid-ppabjpgh>Network setup</span> </nav> <div class="page-header" data-astro-cid-ppabjpgh> <h1 data-astro-cid-ppabjpgh>Network setup</h1> <p data-astro-cid-ppabjpgh>Wire up your home lab so zones mean something. VLANs, switch, firewall, Proxmox bridges.</p> </div> <div class="docs-layout" data-astro-cid-ppabjpgh> <nav data-astro-cid-ppabjpgh> <ul class="docs-nav" data-astro-cid-ppabjpgh> <li class="nav-section" data-astro-cid-ppabjpgh>On this page</li> <li data-astro-cid-ppabjpgh><a href="#why-vlans" data-astro-cid-ppabjpgh>Why VLANs</a></li> <li data-astro-cid-ppabjpgh><a href="#topology" data-astro-cid-ppabjpgh>Reference topology</a></li> <li data-astro-cid-ppabjpgh><a href="#physical" data-astro-cid-ppabjpgh>Physical layout</a></li> <li data-astro-cid-ppabjpgh><a href="#recommended" data-astro-cid-ppabjpgh>Recommended: managed switch</a></li> <li data-astro-cid-ppabjpgh><a href="#minimal" data-astro-cid-ppabjpgh>Minimal: no switch</a></li> <li data-astro-cid-ppabjpgh><a href="#per-nic" data-astro-cid-ppabjpgh>Per-zone NICs</a></li> <li data-astro-cid-ppabjpgh><a href="#ingress" data-astro-cid-ppabjpgh>Perimeter ingress</a></li> <li data-astro-cid-ppabjpgh><a href="#wireless" data-astro-cid-ppabjpgh>Wireless</a></li> <li data-astro-cid-ppabjpgh><a href="#telling-celilo" data-astro-cid-ppabjpgh>Telling Celilo</a></li> </ul> </nav> <article class="docs-content" data-astro-cid-ppabjpgh> <p data-astro-cid-ppabjpgh>
69
+ Celilo's <a href="/docs#zones" data-astro-cid-ppabjpgh>zones</a> describe what's allowed to talk to what.
70
+ The firewall enforces those rules — but the firewall can only enforce them if
71
+ traffic between zones is forced to go <em data-astro-cid-ppabjpgh>through</em> it. Without that, zones are
72
+ honor-system: a container in <code data-astro-cid-ppabjpgh>app</code> that knows the gateway IP for
73
+ <code data-astro-cid-ppabjpgh>secure</code> can ARP for the database directly, the switch happily floods
74
+ the frame, and your "isolation" is a sticker on the box.
75
+ </p> <p data-astro-cid-ppabjpgh>
76
+ 802.1q VLAN tagging is what makes zones real. This page walks through how to
77
+ wire your home lab so Celilo's policy actually has teeth.
78
+ </p> <h2 id="why-vlans" data-astro-cid-ppabjpgh>Why VLANs</h2> <p data-astro-cid-ppabjpgh>
79
+ Most home networks live on a single broadcast domain — one subnet, one switch,
80
+ every device's ARP requests reach every other device. That's fine for a flat
81
+ network, but it makes every "zone" the same zone. Anything talking to anything,
82
+ with the firewall as a polite suggestion.
83
+ </p> <p data-astro-cid-ppabjpgh>
84
+ 802.1q tags every Ethernet frame with the VLAN it belongs to. The switch refuses
85
+ to forward frames between tags, so each zone is its own broadcast domain. A host
86
+ on the <code data-astro-cid-ppabjpgh>app</code> VLAN can send packets toward an IP on the
87
+ <code data-astro-cid-ppabjpgh>secure</code> VLAN, but those packets only get there if a router decides
88
+ to route them — and the only router with interfaces in both VLANs is your
89
+ firewall machine. That's where Celilo's policy lives.
90
+ </p> <p data-astro-cid-ppabjpgh>The practical upshot:</p> <ul data-astro-cid-ppabjpgh> <li data-astro-cid-ppabjpgh>Compromising a workload in <code data-astro-cid-ppabjpgh>app</code> doesn't get you L2 access to
91
+ <code data-astro-cid-ppabjpgh>secure</code>; you have to defeat the firewall, not just guess an IP.</li> <li data-astro-cid-ppabjpgh>You can put non-Celilo devices on a zone (a NAS in <code data-astro-cid-ppabjpgh>secure</code>, an
92
+ IoT camera in <code data-astro-cid-ppabjpgh>internal</code>) and they get the same policy enforcement
93
+ as Celilo-managed containers.</li> <li data-astro-cid-ppabjpgh>Adding a zone is a switch-config change plus a firewall-config change, not
94
+ re-cabling.</li> </ul> <h2 id="topology" data-astro-cid-ppabjpgh>Reference topology</h2> <p data-astro-cid-ppabjpgh>Here's how the zones, hosts, and capabilities fit together at the logical level:</p> <figure class="diagram" data-astro-cid-ppabjpgh> <img src="/diagrams/zone-topology.svg" alt="Celilo zone topology: external cloud connects through a perimeter firewall to internal (VLAN 192), where the celilo orchestrator lives; the iptables firewall connects internal to dmz (VLAN 10, hosting caddy with the public_web capability), app (VLAN 20, hosting authentik with the idp capability), and secure (VLAN 30, hosting postgres)." data-astro-cid-ppabjpgh> </figure> <p data-astro-cid-ppabjpgh>The five zones, with the example modules placed on each:</p> <ul data-astro-cid-ppabjpgh> <li data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>external</code> — the public internet. Untagged, lives upstream of the firewall. The cloud at the top of the diagram.</li> <li data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>internal</code> — VLAN 192 by default. Where you, the operator, and (today) the Celilo orchestrator live.</li> <li data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>dmz</code> — VLAN 10 by default. Public-facing workloads. Caddy lives here, terminating HTTPS.</li> <li data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>app</code> — VLAN 20 by default. Internal services. Authentik (the identity provider) lives here, as do most application servers.</li> <li data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>secure</code> — VLAN 30 by default. Sensitive data. Postgres lives here.</li> </ul> <p data-astro-cid-ppabjpgh>Two firewalls do different jobs:</p> <ul data-astro-cid-ppabjpgh> <li data-astro-cid-ppabjpgh>The <strong data-astro-cid-ppabjpgh>perimeter firewall</strong> (labeled <code data-astro-cid-ppabjpgh>perimeter-fw</code> in the diagram) is the only thing facing the public internet. It does NAT for outbound traffic, DNAT for inbound traffic on ports 80/443, and provides the <code data-astro-cid-ppabjpgh>firewall</code> capability that public-facing modules (Caddy, anything wanting <code data-astro-cid-ppabjpgh>exposeService</code>) call into. In your setup it can be a port-forward on your ISP router, a Cloudflare tunnel, or a VPS — see <a href="#ingress" data-astro-cid-ppabjpgh>Perimeter ingress</a>.</li> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>iptables</code></strong> sits between zones. It's the inter-zone gateway: every packet from <code data-astro-cid-ppabjpgh>app</code> to <code data-astro-cid-ppabjpgh>secure</code>, or from <code data-astro-cid-ppabjpgh>dmz</code> to <code data-astro-cid-ppabjpgh>app</code>, passes through it and gets policy applied. Also provides the <code data-astro-cid-ppabjpgh>firewall</code> capability — modules don't have to know which firewall is which; they ask for <code data-astro-cid-ppabjpgh>firewall</code> and Celilo wires them to whichever provider serves their zone.</li> </ul> <p data-astro-cid-ppabjpgh>
95
+ The capability labels on the diagram (<code data-astro-cid-ppabjpgh>firewall</code>, <code data-astro-cid-ppabjpgh>idp</code>,
96
+ <code data-astro-cid-ppabjpgh>public_web</code>) are how modules find each other. Caddy declares it
97
+ provides <code data-astro-cid-ppabjpgh>public_web</code>; your web app's manifest declares it
98
+ <em data-astro-cid-ppabjpgh>requires</em> <code data-astro-cid-ppabjpgh>public_web</code>; Celilo wires the call without anyone
99
+ hardcoding hostnames or IPs. Same for <code data-astro-cid-ppabjpgh>idp</code> (Authentik) and
100
+ <code data-astro-cid-ppabjpgh>firewall</code> (whichever module provides the perimeter or inter-zone
101
+ firewall in your setup).
102
+ </p> <h2 id="physical" data-astro-cid-ppabjpgh>Physical layout</h2> <p data-astro-cid-ppabjpgh>
103
+ That's the logical view. Here's what the actual wiring looks like in a typical
104
+ deployment:
105
+ </p> <figure class="diagram" data-astro-cid-ppabjpgh> <img src="/diagrams/physical.svg" alt="Physical layout: ISP router with wifi hosts laptops, TVs, phones, IoT devices on a flat untrusted network. The firewall machine sits behind the ISP router with a single trunk port to a managed switch. The switch fans out: an access port to the management box and trunk ports to each of several Proxmox hosts." data-astro-cid-ppabjpgh> </figure> <p data-astro-cid-ppabjpgh>Five kinds of physical things:</p> <ul data-astro-cid-ppabjpgh> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh>ISP router with wifi.</strong> Stays as you have it. This is where untrusted endpoints (laptops, phones, smart TVs, IoT) live. It's not <em data-astro-cid-ppabjpgh>part</em> of the Celilo network; it's the public side of the firewall.</li> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh>Firewall machine.</strong> A small Linux box (Raspberry Pi, mini-PC, repurposed thin client) with two interfaces: one to the ISP router (untrusted, public side), one to the managed switch (trusted, trunk port carrying every VLAN). Runs the <code data-astro-cid-ppabjpgh>iptables</code> module to provide inter-zone firewalling. If you choose to also run a perimeter firewall locally (option A in <a href="#ingress" data-astro-cid-ppabjpgh>Perimeter ingress</a>), the same box can do both jobs — or you can split them onto separate hosts.</li> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh>Managed switch.</strong> Any switch that supports 802.1q. Trunk port to the firewall, trunk port to each Proxmox host, access port to anything else (mgmt box, NAS, wired endpoints).</li> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh>Proxmox hosts.</strong> One or more. Each connects to the switch over a single trunk port; <code data-astro-cid-ppabjpgh>vmbr0</code> is configured VLAN-aware, and containers attach with a <code data-astro-cid-ppabjpgh>tag=N</code> matching their zone.</li> <li data-astro-cid-ppabjpgh><strong data-astro-cid-ppabjpgh>Mgmt box.</strong> Whatever machine you run <code data-astro-cid-ppabjpgh>celilo</code> from. Usually on an access port carrying the <code data-astro-cid-ppabjpgh>internal</code> VLAN untagged.</li> </ul> <h2 id="recommended" data-astro-cid-ppabjpgh>Recommended: managed switch with 802.1q</h2> <p data-astro-cid-ppabjpgh>This is the layout above. Three things to configure:</p> <h3 data-astro-cid-ppabjpgh>Pick your numbers</h3> <p data-astro-cid-ppabjpgh>Celilo ships with these defaults, which match the diagrams above:</p> <table data-astro-cid-ppabjpgh> <thead data-astro-cid-ppabjpgh> <tr data-astro-cid-ppabjpgh><th data-astro-cid-ppabjpgh>Zone</th><th data-astro-cid-ppabjpgh>VLAN</th><th data-astro-cid-ppabjpgh>Default subnet</th><th data-astro-cid-ppabjpgh>Notes</th></tr> </thead> <tbody data-astro-cid-ppabjpgh> <tr data-astro-cid-ppabjpgh><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>dmz</code></td><td data-astro-cid-ppabjpgh>10</td><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>10.0.10.0/24</code></td><td data-astro-cid-ppabjpgh>Public-facing workloads (Caddy)</td></tr> <tr data-astro-cid-ppabjpgh><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>app</code></td><td data-astro-cid-ppabjpgh>20</td><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>10.0.20.0/24</code></td><td data-astro-cid-ppabjpgh>Internal services (Authentik, internal APIs)</td></tr> <tr data-astro-cid-ppabjpgh><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>secure</code></td><td data-astro-cid-ppabjpgh>30</td><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>10.0.30.0/24</code></td><td data-astro-cid-ppabjpgh>Databases, secret stores</td></tr> <tr data-astro-cid-ppabjpgh><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>internal</code></td><td data-astro-cid-ppabjpgh>192</td><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>192.168.1.0/24</code></td><td data-astro-cid-ppabjpgh>Operator LAN, mgmt</td></tr> <tr data-astro-cid-ppabjpgh><td data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>external</code></td><td data-astro-cid-ppabjpgh>—</td><td data-astro-cid-ppabjpgh>—</td><td data-astro-cid-ppabjpgh>Off-LAN; lives on the public side of the firewall</td></tr> </tbody> </table> <p data-astro-cid-ppabjpgh>If you have to use different VLAN numbers (existing network, conflicts with a vendor default), you can override them later — see <a href="#telling-celilo" data-astro-cid-ppabjpgh>Telling Celilo about it</a>.</p> <h3 data-astro-cid-ppabjpgh>Switch configuration</h3> <p data-astro-cid-ppabjpgh>Vendor specifics vary; the shape is the same:</p> <ul data-astro-cid-ppabjpgh> <li data-astro-cid-ppabjpgh>Create VLANs 10, 20, 30, 192 in the switch's admin UI.</li> <li data-astro-cid-ppabjpgh>The port to the firewall machine: <strong data-astro-cid-ppabjpgh>tagged trunk</strong> — all four VLANs tagged. (Some vendors call this "general" or "hybrid"; the point is the firewall sees the tag.)</li> <li data-astro-cid-ppabjpgh>Each port to a Proxmox host: <strong data-astro-cid-ppabjpgh>tagged trunk</strong> — same four VLANs tagged.</li> <li data-astro-cid-ppabjpgh>Port to the mgmt box: <strong data-astro-cid-ppabjpgh>access port, untagged on VLAN 192</strong>.</li> <li data-astro-cid-ppabjpgh>Ports for wired endpoints (printers, NAS, anything not running zone-aware software): <strong data-astro-cid-ppabjpgh>access port</strong>, untagged on whichever VLAN that device should live on.</li> </ul> <p data-astro-cid-ppabjpgh>
106
+ For step-by-step admin-UI walkthroughs, the vendor's docs are the source of
107
+ truth — UniFi, MikroTik, TP-Link/Omada, Cisco, and Netgear all have working
108
+ guides. The Celilo side doesn't care which switch you use, only that the tags
109
+ match the numbers above.
110
+ </p> <div class="callout" data-astro-cid-ppabjpgh> <strong data-astro-cid-ppabjpgh>Hardware suggestion:</strong> the
111
+ <a href="https://www.sodola-network.com/products/sodola-9-port-2-5g-smart-web-ethernet-switch-1-10g-sfp-slot-8-x-2-5g-base-t-ports-static-aggregation-qos-vlan-igmp-supported-metal-fanless-managed-multi-gigabit-switch-mtwg" target="_blank" rel="noopener" data-astro-cid-ppabjpgh>Sodola 9-port 2.5G smart switch</a>
112
+ (8× 2.5G + 1× 10G SFP+, fanless, ~$100) is a known-working option that's been
113
+ used in real Celilo deployments. If that link rots, search for "Sodola 9-port
114
+ 2.5G smart managed switch" — same shape, plenty of stock.
115
+ </div> <h3 data-astro-cid-ppabjpgh>Firewall machine</h3> <p data-astro-cid-ppabjpgh>
116
+ Celilo's <code data-astro-cid-ppabjpgh>iptables</code> module configures the firewall rules for you.
117
+ What you have to set up <em data-astro-cid-ppabjpgh>before</em> running Celilo is the VLAN sub-interfaces
118
+ themselves, so the kernel has somewhere to apply rules. On Debian/Ubuntu,
119
+ <code data-astro-cid-ppabjpgh>/etc/network/interfaces</code> looks like:
120
+ </p> <pre data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>auto eth0
121
+ iface eth0 inet manual
122
+ # Trunk to the switch — no IP on the trunk itself
123
+
124
+ auto eth0.10
125
+ iface eth0.10 inet static
126
+ address 10.0.10.1/24
127
+
128
+ auto eth0.20
129
+ iface eth0.20 inet static
130
+ address 10.0.20.1/24
131
+
132
+ auto eth0.30
133
+ iface eth0.30 inet static
134
+ address 10.0.30.1/24
135
+
136
+ auto eth0.192
137
+ iface eth0.192 inet static
138
+ address 192.168.1.1/24
139
+
140
+ # eth1 is the public side facing the ISP router
141
+ auto eth1
142
+ iface eth1 inet dhcp</code></pre> <p data-astro-cid-ppabjpgh>
143
+ Each <code data-astro-cid-ppabjpgh>eth0.N</code> sub-interface is the gateway for its zone — that's the
144
+ IP Celilo's auto-derived <code data-astro-cid-ppabjpgh>gateway</code> field will produce when a module
145
+ asks for one. Restart networking, confirm <code data-astro-cid-ppabjpgh>ip -br addr</code> shows the
146
+ sub-interfaces up, then <code data-astro-cid-ppabjpgh>celilo machine add</code> the firewall and run
147
+ <code data-astro-cid-ppabjpgh>module deploy iptables</code> to lay down the rules.
148
+ </p> <h3 data-astro-cid-ppabjpgh>Proxmox host</h3> <p data-astro-cid-ppabjpgh>
149
+ Each Proxmox host needs a single VLAN-aware bridge. <code data-astro-cid-ppabjpgh>/etc/network/interfaces</code>:
150
+ </p> <pre data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>auto vmbr0
151
+ iface vmbr0 inet static
152
+ address 192.168.1.10/24
153
+ gateway 192.168.1.1
154
+ bridge-ports eno1
155
+ bridge-stp off
156
+ bridge-fd 0
157
+ bridge-vlan-aware yes
158
+ bridge-vids 2-4094</code></pre> <p data-astro-cid-ppabjpgh>
159
+ Two important bits: <code data-astro-cid-ppabjpgh>bridge-vlan-aware yes</code> tells the bridge to
160
+ respect 802.1q tags, and <code data-astro-cid-ppabjpgh>bridge-vids 2-4094</code> permits the full tag
161
+ range on the bridge (Proxmox restricts it by default). The Proxmox host itself
162
+ is reachable on the untagged segment of the bridge — most setups put that on
163
+ <code data-astro-cid-ppabjpgh>internal</code> so you can SSH in from your mgmt box.
164
+ </p> <p data-astro-cid-ppabjpgh>
165
+ Once the bridge is up, container creation is hands-off. Celilo's Terraform
166
+ templates emit a <code data-astro-cid-ppabjpgh>network</code> block with <code data-astro-cid-ppabjpgh>tag = $self:vlan</code>,
167
+ and Celilo auto-derives <code data-astro-cid-ppabjpgh>vlan</code> from the zone the module requested
168
+ — see <a href="#telling-celilo" data-astro-cid-ppabjpgh>Telling Celilo about it</a>.
169
+ </p> <h2 id="minimal" data-astro-cid-ppabjpgh>Minimal: firewall ↔ Proxmox direct, no switch</h2> <p data-astro-cid-ppabjpgh>
170
+ If you don't have a managed switch (yet), you can still get zone enforcement.
171
+ The trick is to skip the switch entirely and run a trunk straight from the
172
+ firewall to the Proxmox host. Two viable shapes:
173
+ </p> <h3 data-astro-cid-ppabjpgh>Shape A: single cable, sub-interfaces on both ends</h3> <p data-astro-cid-ppabjpgh>
174
+ One Ethernet cable from the firewall's "trusted" NIC to a Proxmox NIC. Both
175
+ ends speak 802.1q. Configuration is identical to the recommended setup above —
176
+ <code data-astro-cid-ppabjpgh>eth0.10/20/30/192</code> on the firewall, VLAN-aware <code data-astro-cid-ppabjpgh>vmbr0</code>
177
+ on Proxmox — you just don't have a switch in between.
178
+ </p> <p data-astro-cid-ppabjpgh>
179
+ What you give up: there's nowhere to plug a non-Proxmox device into a specific
180
+ zone. Want a NAS in <code data-astro-cid-ppabjpgh>secure</code>? It has to be a Proxmox container, or
181
+ you have to add a switch.
182
+ </p> <h3 data-astro-cid-ppabjpgh>Shape B: multi-NIC firewall, untagged per-zone subnets</h3> <p data-astro-cid-ppabjpgh>
183
+ If your firewall machine has several NICs, you can give each zone its own
184
+ untagged NIC and crossover-cable each one to a Proxmox NIC. No 802.1q anywhere.
185
+ Simpler to debug, but it requires Proxmox bridges per zone (<code data-astro-cid-ppabjpgh>vmbr10</code>,
186
+ <code data-astro-cid-ppabjpgh>vmbr20</code>, etc.) and the same NIC-port-shortage problem as
187
+ <a href="#per-nic" data-astro-cid-ppabjpgh>per-zone NICs</a> below.
188
+ </p> <div class="callout" data-astro-cid-ppabjpgh> <strong data-astro-cid-ppabjpgh>Status:</strong> Shape A is what most no-switch home labs end up with;
189
+ Celilo doesn't need anything different from the recommended setup. Shape B
190
+ works in principle but is less tested. If you're standing one of these up,
191
+ let us know how it goes — the doc page wants a real wiring photo.
192
+ </div> <h2 id="per-nic" data-astro-cid-ppabjpgh>Per-zone NICs (no VLANs)</h2> <p data-astro-cid-ppabjpgh>
193
+ Each zone gets its own physical NIC on the firewall and on each Proxmox host.
194
+ Cleanest L2 isolation, no tagging to misconfigure — but you run out of NIC
195
+ ports fast. Five zones means five NICs per Proxmox host, and most home-lab
196
+ boxes ship with one or two onboard.
197
+ </p> <p data-astro-cid-ppabjpgh>
198
+ Worth knowing exists; in practice 802.1q is what most home labs use because
199
+ it makes "add another zone" a config change rather than a hardware change.
200
+ Vendor docs (Proxmox networking, Linux bridge) cover the per-NIC setup.
201
+ </p> <h2 id="ingress" data-astro-cid-ppabjpgh>Perimeter ingress</h2> <p data-astro-cid-ppabjpgh>
202
+ The diagrams show a Linux box at the perimeter doing public NAT and inbound
203
+ DNAT for ports 80/443. That's one option, but it's not the only one. Three
204
+ patterns work today; pick whichever fits your ISP situation and tolerance for
205
+ outsourced trust:
206
+ </p> <h3 data-astro-cid-ppabjpgh>A. Port-forwarding on your ISP router</h3> <p data-astro-cid-ppabjpgh>
207
+ The simplest setup. Configure port-forward rules in your ISP router's admin
208
+ UI to send TCP 80/443 to your DMZ host (typically the Caddy container — its
209
+ IP is what <code data-astro-cid-ppabjpgh>module deploy caddy</code> prints on success). No extra
210
+ hardware, no public IP beyond what the ISP gives you. Most consumer routers
211
+ support this.
212
+ </p> <p data-astro-cid-ppabjpgh>
213
+ Recommended starting point. A planned <code data-astro-cid-ppabjpgh>isp-router</code> Celilo module
214
+ will give you a checklist of port-forward rules to enter manually and confirm
215
+ when done; until then, do it by hand once and forget about it.
216
+ </p> <h3 data-astro-cid-ppabjpgh>B. Cloudflare tunnel</h3> <p data-astro-cid-ppabjpgh>
217
+ No port-forward, no public IP needed.
218
+ <a href="https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/" target="_blank" rel="noopener" data-astro-cid-ppabjpgh>Cloudflare Tunnel</a>
219
+ runs a daemon inside your network that dials out to Cloudflare's edge;
220
+ Cloudflare proxies inbound HTTPS through that tunnel back to your DMZ. Works
221
+ behind CG-NAT, works on residential connections that block inbound traffic.
222
+ </p> <p data-astro-cid-ppabjpgh>
223
+ Trade-offs: HTTPS terminates at Cloudflare (you trust them with TLS for the
224
+ domains you tunnel), and you're tied to their pricing/policy. Not yet wrapped
225
+ in a Celilo module; configure it manually, then point the tunnel's local
226
+ endpoint at Caddy.
227
+ </p> <h3 data-astro-cid-ppabjpgh>C. VPS + WireGuard</h3> <p data-astro-cid-ppabjpgh>
228
+ Rent a small public-IP VPS (Digital Ocean, Hetzner, Vultr — $4–$6/month),
229
+ run a WireGuard tunnel between it and a host on your <code data-astro-cid-ppabjpgh>dmz</code>, and
230
+ forward inbound 80/443 on the VPS through the tunnel into your network.
231
+ The VPS becomes your "public IP" for ingress purposes; TLS can terminate
232
+ either at the VPS or at home, your call.
233
+ </p> <p data-astro-cid-ppabjpgh>
234
+ Most flexible, most setup. Useful if you're behind CG-NAT, want a static
235
+ public IP, or want geographically-distant ingress. Not yet wrapped as a
236
+ Celilo module either; manual today.
237
+ </p> <div class="callout" data-astro-cid-ppabjpgh> <strong data-astro-cid-ppabjpgh>Today:</strong> none of A/B/C is automated by Celilo end-to-end. You
238
+ configure them manually, then Celilo's <code data-astro-cid-ppabjpgh>firewall</code> capability
239
+ operates inside the perimeter (DMZ container ↔ app ↔ secure). The
240
+ <code data-astro-cid-ppabjpgh>perimeter-fw</code> box in the topology diagram is a stand-in for
241
+ whichever of these you choose.
242
+ </div> <h2 id="wireless" data-astro-cid-ppabjpgh>Wireless</h2> <p data-astro-cid-ppabjpgh>
243
+ Wifi isn't part of the Celilo zone model. Connect to your ISP router's wifi
244
+ the way you always have — that's where laptops, phones, and IoT devices
245
+ live, and it's how you reach the orchestrator on <code data-astro-cid-ppabjpgh>internal</code>.
246
+ Celilo's protected zones (<code data-astro-cid-ppabjpgh>dmz</code>, <code data-astro-cid-ppabjpgh>app</code>,
247
+ <code data-astro-cid-ppabjpgh>secure</code>) are wired-only by design; there's no reason to want
248
+ clients on them over wifi.
249
+ </p> <h2 id="telling-celilo" data-astro-cid-ppabjpgh>Telling Celilo about it</h2> <p data-astro-cid-ppabjpgh>Two pieces of state Celilo cares about: <strong data-astro-cid-ppabjpgh>subnets</strong> and <strong data-astro-cid-ppabjpgh>VLAN tags</strong>.</p> <h3 data-astro-cid-ppabjpgh>Subnets</h3> <p data-astro-cid-ppabjpgh> <code data-astro-cid-ppabjpgh>celilo system init</code> prompts for each zone's subnet during initial
250
+ setup. Defaults match the table above. You can also set them non-interactively:
251
+ </p> <pre data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>celilo system config set network.dmz.subnet 10.0.10.0/24
252
+ celilo system config set network.app.subnet 10.0.20.0/24
253
+ celilo system config set network.secure.subnet 10.0.30.0/24
254
+ celilo system config set network.internal.subnet 192.168.1.0/24</code></pre> <p data-astro-cid-ppabjpgh>
255
+ Gateway IPs aren't prompted for. When you <code data-astro-cid-ppabjpgh>celilo machine add</code> the
256
+ firewall machine, Celilo SSHes in and inspects its interfaces; the IP it
257
+ finds on each VLAN sub-interface (<code data-astro-cid-ppabjpgh>eth0.10</code>, <code data-astro-cid-ppabjpgh>eth0.20</code>,
258
+ and so on) becomes the canonical gateway for that zone.
259
+ </p> <h3 data-astro-cid-ppabjpgh>VLAN tags</h3> <p data-astro-cid-ppabjpgh>
260
+ Celilo ships with VLAN tag defaults that match the diagrams above (<code data-astro-cid-ppabjpgh>dmz=10</code>,
261
+ <code data-astro-cid-ppabjpgh>app=20</code>, <code data-astro-cid-ppabjpgh>secure=30</code>, <code data-astro-cid-ppabjpgh>internal=192</code>). If your
262
+ switch uses different numbers — for example, a vendor default that conflicts —
263
+ override them:
264
+ </p> <pre data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>celilo system config set network.dmz.vlan 110
265
+ celilo system config set network.app.vlan 120
266
+ celilo system config set network.secure.vlan 130
267
+ celilo system config set network.internal.vlan 1</code></pre> <p data-astro-cid-ppabjpgh>
268
+ Whatever numbers you choose here have to match the tags on your switch and the
269
+ sub-interface names on the firewall (<code data-astro-cid-ppabjpgh>eth0.110</code> instead of
270
+ <code data-astro-cid-ppabjpgh>eth0.10</code>, etc.). They're the canonical record; modules pick up these
271
+ values automatically.
272
+ </p> <h3 data-astro-cid-ppabjpgh>What modules see</h3> <p data-astro-cid-ppabjpgh>
273
+ When a module requests an LXC container or a machine in a zone (e.g.
274
+ <code data-astro-cid-ppabjpgh>requires.machine.zone: app</code> in its manifest), Celilo auto-injects
275
+ the matching <code data-astro-cid-ppabjpgh>vlan</code>, <code data-astro-cid-ppabjpgh>gateway</code>, <code data-astro-cid-ppabjpgh>subnet</code>, and
276
+ <code data-astro-cid-ppabjpgh>bridge</code> values into that module's <code data-astro-cid-ppabjpgh>$self:</code> namespace.
277
+ The module's Terraform template
278
+ then drops them straight into the Proxmox container spec:
279
+ </p> <pre data-astro-cid-ppabjpgh><code data-astro-cid-ppabjpgh>resource "proxmox_lxc" "my_app" &#123;
280
+ network &#123;
281
+ name = "eth0"
282
+ bridge = "$self:bridge" # → vmbr0
283
+ tag = $self:vlan # → 20 (for the 'app' zone)
284
+ ip = "$self:target_ip"
285
+ gw = "$self:gateway" # → 10.0.20.1
286
+ &#125;
287
+ &#125;</code></pre> <p data-astro-cid-ppabjpgh>
288
+ You don't set per-module VLAN tags by hand. Declaring the zone is enough.
289
+ See <code data-astro-cid-ppabjpgh>apps/celilo/src/variables/context.ts</code> for the auto-derivation
290
+ logic if you want to dig in.
291
+ </p> <div class="callout" data-astro-cid-ppabjpgh> <strong data-astro-cid-ppabjpgh>Known limitation:</strong> <code data-astro-cid-ppabjpgh>celilo system init</code> prompts for
292
+ subnets but not VLAN tags. The defaults are usually right, but if you need
293
+ custom numbers you have to set them with <code data-astro-cid-ppabjpgh>system config set</code>
294
+ afterwards. Promoting VLAN tag prompts into the interactive init flow is a
295
+ likely improvement.
296
+ </div> <p data-astro-cid-ppabjpgh>
297
+ Once your network is wired and Celilo knows the numbers, you're done with this
298
+ page. Head back to <a href="/docs#proxmox" data-astro-cid-ppabjpgh>step 2 of the main walkthrough</a>
299
+ to add your Proxmox service.
300
+ </p> </article> </div> </main> <footer data-astro-cid-5hce7sga> <p data-astro-cid-5hce7sga>Celilo — home lab orchestration. Built with care.</p> </footer> </body></html>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2
+ <rect width="32" height="32" rx="8" fill="#0f1117"/>
3
+ <text x="4" y="24" font-family="monospace" font-size="22" font-weight="700" fill="#818cf8">c</text>
4
+ </svg>
@@ -0,0 +1,93 @@
1
+ <!DOCTYPE html><html lang="en" data-astro-cid-5hce7sga> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="Celilo — home lab orchestration. Deploy infrastructure the right way."><title>Home — Celilo</title><link rel="icon" type="image/svg+xml" href="/favicon.svg"><script>
2
+ // Resolve theme BEFORE first paint to avoid the flash of wrong
3
+ // theme. Order: explicit user choice (localStorage) → system
4
+ // preference (prefers-color-scheme) → fallback dark. The
5
+ // resolved value lands on <html data-theme="...">; CSS variables
6
+ // below pick the matching palette.
7
+ (function () {
8
+ try {
9
+ const stored = localStorage.getItem('celilo-theme');
10
+ const sys =
11
+ window.matchMedia &&
12
+ window.matchMedia('(prefers-color-scheme: light)').matches
13
+ ? 'light'
14
+ : 'dark';
15
+ const theme = stored === 'light' || stored === 'dark' ? stored : sys;
16
+ document.documentElement.setAttribute('data-theme', theme);
17
+ } catch (_e) {
18
+ document.documentElement.setAttribute('data-theme', 'dark');
19
+ }
20
+ })();
21
+ </script><link rel="stylesheet" href="/_astro/docs.DwsUauWZ.css">
22
+ <style>.hero[data-astro-cid-j7pv25f6]{padding:5rem 0 4rem;text-align:center}.hero-eyebrow[data-astro-cid-j7pv25f6]{font-family:var(--font-mono);font-size:.8rem;letter-spacing:.15em;text-transform:uppercase;color:var(--accent);margin-bottom:1.5rem}.hero[data-astro-cid-j7pv25f6] h1[data-astro-cid-j7pv25f6]{font-size:clamp(2.5rem,6vw,4.5rem);font-weight:800;letter-spacing:-.03em;margin-bottom:1.25rem;background:linear-gradient(135deg,var(--text) 0%,var(--accent-light) 60%,var(--accent) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}.hero-subtitle[data-astro-cid-j7pv25f6]{font-size:clamp(1rem,2.5vw,1.25rem);color:var(--muted);max-width:40rem;margin:0 auto 2.5rem;line-height:1.7}.hero-actions[data-astro-cid-j7pv25f6]{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}.btn[data-astro-cid-j7pv25f6]{display:inline-flex;align-items:center;gap:.5rem;padding:.75rem 1.75rem;border-radius:8px;font-size:.95rem;font-weight:500;transition:all .15s;cursor:pointer}.btn-primary[data-astro-cid-j7pv25f6]{background:var(--accent);color:#fff;border:1px solid transparent}.btn-primary[data-astro-cid-j7pv25f6]:hover{background:var(--accent-light);text-decoration:none}.btn-secondary[data-astro-cid-j7pv25f6]{background:transparent;color:var(--text);border:1px solid var(--border)}.btn-secondary[data-astro-cid-j7pv25f6]:hover{border-color:var(--muted);text-decoration:none}.terminal[data-astro-cid-j7pv25f6]{max-width:52rem;margin:4rem auto;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden}.terminal-bar[data-astro-cid-j7pv25f6]{background:color-mix(in srgb,var(--surface) 85%,var(--text) 5%);padding:.6rem 1rem;display:flex;align-items:center;gap:.5rem;border-bottom:1px solid var(--border)}.dot[data-astro-cid-j7pv25f6]{width:12px;height:12px;border-radius:50%}.dot-red[data-astro-cid-j7pv25f6]{background:#ff5f57}.dot-yellow[data-astro-cid-j7pv25f6]{background:#febc2e}.dot-green[data-astro-cid-j7pv25f6]{background:#28c840}.terminal-title[data-astro-cid-j7pv25f6]{font-family:var(--font-mono);font-size:.75rem;color:var(--muted);margin-left:auto;margin-right:auto}.terminal-body[data-astro-cid-j7pv25f6]{padding:1.5rem;font-family:var(--font-mono);font-size:.85rem;line-height:2}.cmd[data-astro-cid-j7pv25f6]{color:var(--green)}.cmd[data-astro-cid-j7pv25f6]:before{content:"$ ";color:var(--muted)}.out[data-astro-cid-j7pv25f6]{color:var(--muted)}.ok[data-astro-cid-j7pv25f6]{color:var(--green)}.features[data-astro-cid-j7pv25f6]{padding:4rem 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(18rem,1fr));gap:1.5rem}.feature[data-astro-cid-j7pv25f6]{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:1.75rem;transition:border-color .15s}.feature[data-astro-cid-j7pv25f6]:hover{border-color:var(--accent)}.feature-icon[data-astro-cid-j7pv25f6]{font-size:1.75rem;margin-bottom:1rem}.feature[data-astro-cid-j7pv25f6] h3[data-astro-cid-j7pv25f6]{font-size:1.05rem;font-weight:600;margin-bottom:.5rem}.feature[data-astro-cid-j7pv25f6] p[data-astro-cid-j7pv25f6]{color:var(--muted);font-size:.9rem;line-height:1.6}.section-title[data-astro-cid-j7pv25f6]{font-size:1.75rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.75rem}.section-sub[data-astro-cid-j7pv25f6]{color:var(--muted);font-size:.95rem;margin-bottom:2.5rem}
23
+ </style></head> <body data-astro-cid-5hce7sga> <!--
24
+ Background pattern — vertical streams of falling water drawn in
25
+ the geometric vocabulary of a Greek meander. The <pattern>
26
+ tile (240×240) repeats seamlessly across the viewport. Strokes
27
+ use `currentColor` so the .bg-pattern CSS rule above can tint
28
+ them via `color: var(--bg-pattern-color)` without rebuilding
29
+ the SVG. To change the streams themselves, edit the four <path>
30
+ elements below.
31
+ --> <svg class="bg-pattern" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" data-astro-cid-5hce7sga> <defs data-astro-cid-5hce7sga> <pattern id="celilo-streams" width="240" height="240" patternUnits="userSpaceOnUse" data-astro-cid-5hce7sga> <!-- Streams flow downward — the patternTransform shifts the
32
+ pattern origin by one full tile (240px) over `dur`, then
33
+ loops seamlessly. Edit `dur` for speed; delete this
34
+ <animateTransform> to freeze the flow. --> <animateTransform attributeName="patternTransform" type="translate" from="0 0" to="0 240" dur="30s" repeatCount="indefinite" data-astro-cid-5hce7sga></animateTransform> <g fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" data-astro-cid-5hce7sga> <!-- Stroke color cycle — driven by SMIL inside the SVG
35
+ (not CSS `color`) because Chromium doesn't reliably
36
+ repaint strokes that resolve via currentColor when
37
+ the parent's CSS `color` is animated. Edit the
38
+ `values` list to pick your palette; semicolons
39
+ separate stops, last value should match first to
40
+ close the loop seamlessly. --> <animate attributeName="stroke" values="#818cf8;#f472b6;#4ade80;#22d3ee;#818cf8" dur="10s" repeatCount="indefinite" data-astro-cid-5hce7sga></animate> <!-- Stream A (x=40): big staircase cascade near top, single hook near bottom --> <path d="M 40,0 V 30 H 55 V 45 H 70 V 60 H 55 V 75 H 40 V 175 H 25 V 195 H 40 V 240" data-astro-cid-5hce7sga></path> <!-- Stream B (x=100): small step near top, three-tier cascade in middle --> <path d="M 100,0 V 60 H 85 V 80 H 100 V 130 H 115 V 145 H 130 V 160 H 115 V 175 H 100 V 240" data-astro-cid-5hce7sga></path> <!-- Stream C (x=160): three small hooks distributed top-to-bottom --> <path d="M 160,0 V 20 H 175 V 35 H 160 V 110 H 145 V 130 H 160 V 200 H 175 V 220 H 160 V 240" data-astro-cid-5hce7sga></path> <!-- Stream D (x=220): single deep cascade in the upper-middle --> <path d="M 220,0 V 85 H 205 V 100 H 190 V 115 H 220 V 240" data-astro-cid-5hce7sga></path> </g> </pattern> </defs> <rect width="100%" height="100%" fill="url(#celilo-streams)" data-astro-cid-5hce7sga></rect> </svg> <nav data-astro-cid-5hce7sga> <div class="nav-inner" data-astro-cid-5hce7sga> <a href="/" class="nav-logo" data-astro-cid-5hce7sga>celilo</a> <ul class="nav-links" data-astro-cid-5hce7sga> <li data-astro-cid-5hce7sga> <a href="/docs" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M4 5a2 2 0 0 1 2-2h11v18H6a2 2 0 0 1-2-2V5z" data-astro-cid-5hce7sga></path> <path d="M9 7h6M9 11h6M9 15h4" data-astro-cid-5hce7sga></path> </svg>
41
+ docs
42
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="/modules" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <rect x="3" y="3" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="14" y="3" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="3" y="14" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> <rect x="14" y="14" width="7" height="7" rx="1" data-astro-cid-5hce7sga></rect> </svg>
43
+ modules
44
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="/vision" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" data-astro-cid-5hce7sga></path> <circle cx="12" cy="12" r="3" data-astro-cid-5hce7sga></circle> </svg>
45
+ vision
46
+ </a> </li> <li data-astro-cid-5hce7sga> <a href="https://github.com/celilo/celilo" target="_blank" rel="noopener" data-astro-cid-5hce7sga> <svg viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6 0 0 0-1.3-3.2 4.2 4.2 0 0 0-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3 0 0 0-6.2 0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2 0 0 0-.1 3.2A4.6 4.6 0 0 0 4 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21" data-astro-cid-5hce7sga></path> </svg>
47
+ github
48
+ </a> </li> <li data-astro-cid-5hce7sga> <button type="button" class="theme-toggle" id="theme-toggle" aria-label="Toggle light/dark theme" title="Toggle theme" data-astro-cid-5hce7sga> <svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <circle cx="12" cy="12" r="4" data-astro-cid-5hce7sga></circle> <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" data-astro-cid-5hce7sga></path> </svg> <svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true" data-astro-cid-5hce7sga> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" data-astro-cid-5hce7sga></path> </svg> </button> </li> </ul> </div> </nav> <script>
49
+ // Click handler for the toggle. Reads the current data-theme,
50
+ // flips it, persists to localStorage, applies to <html>. The
51
+ // resolution script in <head> already set the initial value
52
+ // (system-aware) before paint, so this only runs on user
53
+ // interaction.
54
+ (function () {
55
+ const btn = document.getElementById('theme-toggle');
56
+ if (!btn) return;
57
+ btn.addEventListener('click', function () {
58
+ const current = document.documentElement.getAttribute('data-theme');
59
+ const next = current === 'light' ? 'dark' : 'light';
60
+ document.documentElement.setAttribute('data-theme', next);
61
+ try {
62
+ localStorage.setItem('celilo-theme', next);
63
+ } catch (_e) {
64
+ // localStorage unavailable (private mode) — toggle still
65
+ // works for the session, just doesn't persist.
66
+ }
67
+ });
68
+ })();
69
+ </script> <main data-astro-cid-5hce7sga> <section class="hero" data-astro-cid-j7pv25f6> <p class="hero-eyebrow" data-astro-cid-j7pv25f6>home lab orchestration</p> <h1 data-astro-cid-j7pv25f6>Infrastructure, done right.</h1> <p class="hero-subtitle" data-astro-cid-j7pv25f6>
70
+ Celilo takes your home lab from a pile of machines and domain names to a
71
+ fully-orchestrated, reproducible infrastructure — with one CLI and a library
72
+ of battle-tested modules.
73
+ </p> <div class="hero-actions" data-astro-cid-j7pv25f6> <a href="/docs" class="btn btn-primary" data-astro-cid-j7pv25f6>Get started →</a> <a href="/modules" class="btn btn-secondary" data-astro-cid-j7pv25f6>Browse modules</a> </div> </section> <div class="terminal" data-astro-cid-j7pv25f6> <div class="terminal-bar" data-astro-cid-j7pv25f6> <span class="dot dot-red" data-astro-cid-j7pv25f6></span> <span class="dot dot-yellow" data-astro-cid-j7pv25f6></span> <span class="dot dot-green" data-astro-cid-j7pv25f6></span> <span class="terminal-title" data-astro-cid-j7pv25f6>celilo — zsh</span> </div> <div class="terminal-body" data-astro-cid-j7pv25f6> <p class="cmd" data-astro-cid-j7pv25f6>celilo module import caddy</p> <p class="out" data-astro-cid-j7pv25f6>✓ Downloading caddy 2.8.4+1 from celilo.computer...</p> <p class="out" data-astro-cid-j7pv25f6>→ Running on_install hook</p> <p class="out" data-astro-cid-j7pv25f6> → firewall.exposeService (ports 80, 443)</p> <p class="out" data-astro-cid-j7pv25f6> ✓ firewall.exposeService → 203.0.113.42</p> <p class="out" data-astro-cid-j7pv25f6> → dns_registrar.registerHost (www → 203.0.113.42)</p> <p class="out" data-astro-cid-j7pv25f6> ✓ dns_registrar.registerHost</p> <p class="ok" data-astro-cid-j7pv25f6>✓ caddy installed — https://www.example.com</p> </div> </div> <section data-astro-cid-j7pv25f6> <h2 class="section-title" data-astro-cid-j7pv25f6>Everything you need to run a home lab.</h2> <p class="section-sub" data-astro-cid-j7pv25f6>
74
+ Modules for networking, identity, DNS, web hosting, and more — all wired together automatically.
75
+ </p> <div class="features" data-astro-cid-j7pv25f6> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>🔌</div> <h3 data-astro-cid-j7pv25f6>Capability system</h3> <p data-astro-cid-j7pv25f6>
76
+ Modules declare what they provide and what they need. Celilo wires them
77
+ together automatically — no YAML templating, no hardcoded IPs.
78
+ </p> </div> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>📦</div> <h3 data-astro-cid-j7pv25f6>Module registry</h3> <p data-astro-cid-j7pv25f6>
79
+ Install modules with one command. Each module bundles Terraform,
80
+ Ansible, and TypeScript hooks into a single <code data-astro-cid-j7pv25f6>.netapp</code> file.
81
+ </p> </div> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>🔒</div> <h3 data-astro-cid-j7pv25f6>Secrets first</h3> <p data-astro-cid-j7pv25f6>
82
+ Secrets are encrypted at rest and injected at runtime. Modules never
83
+ touch plaintext credentials — they receive typed, validated values.
84
+ </p> </div> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>🛡️</div> <h3 data-astro-cid-j7pv25f6>Reproducible deploys</h3> <p data-astro-cid-j7pv25f6>
85
+ Every module generates complete Terraform and Ansible projects.
86
+ Destroy and rebuild your infrastructure from source at any time.
87
+ </p> </div> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>🌐</div> <h3 data-astro-cid-j7pv25f6>Automatic HTTPS</h3> <p data-astro-cid-j7pv25f6>
88
+ Caddy handles TLS termination. Add a module, get a domain. DNS is
89
+ updated, certificates are provisioned — all in one step.
90
+ </p> </div> <div class="feature" data-astro-cid-j7pv25f6> <div class="feature-icon" data-astro-cid-j7pv25f6>🐕</div> <h3 data-astro-cid-j7pv25f6>Dogfooded</h3> <p data-astro-cid-j7pv25f6>
91
+ This website is a Celilo module. The registry that hosts these modules
92
+ is a Celilo module. We eat our own cooking.
93
+ </p> </div> </div> </section> </main> <footer data-astro-cid-5hce7sga> <p data-astro-cid-5hce7sga>Celilo — home lab orchestration. Built with care.</p> </footer> </body></html>