Homelab Ingress
@kitquiet/homelab-ingressv2026.07.28.1
01README
SSH-based management of a self-hosted, Cloudflare-free public ingress: nftables/Docker firewall reconciliation, a WireGuard tunnel, a Traefik reverse proxy with its own Let's Encrypt certs, an Authelia forward-auth gate (file or shared-LDAP-directory backed, with optional OIDC provider), an LLDAP user directory, and fail2ban jails. Encodes several non-obvious operational gotchas (Docker chain wipe on nftables reload, mobile-app auth bypass paths, fail2ban's reload-vs-restart trap) discovered standing up a real homelab edge.
02Models
@kitquiet/homelab-ingress/wireguard-tunnelv2026.07.28.1wireguard_tunnel.ts
Global Arguments
| Argument | Type | Description |
|---|---|---|
| nodeHost | string | Hostname or IP of the target node |
| nodeUser | string | SSH username |
| nodePort | number | SSH port |
| nodeIdentityFile? | string | Path to an SSH private key file on the machine running this method |
| nodeIdentityContent? | string | PEM private key content — supply via ${{ vault.get(vault, key) }} |
| become | boolean | Enable sudo privilege escalation (leave false if nodeUser is already root) |
| becomeUser | string | User to become via sudo |
| becomePassword? | string | Password for sudo -S, via ${{ vault.get(vault, key) }} |
| interfaceName | string | WireGuard interface name |
| role | enum | server listens on listenPort; client dials peerEndpoint |
| tunnelAddress | string | This side's address within the tunnel, CIDR — e.g. 10.100.0.1/24 |
| listenPort? | number | UDP port to listen on — required for role: server |
| peerPublicKey | string | The other side's WireGuard public key |
| peerEndpoint? | string | host:port to reach the peer — required for role: client |
| peerAllowedIPs | string | CIDR(s) routed through this peer, e.g. 10.100.0.2/32 |
| persistentKeepalive? | number | Seconds between keepalive packets — set on the side behind NAT (typically the client) |
fn check()
Check interface/config state and peer handshake freshness (dry-run)
fn apply()
Install WireGuard if missing, generate a keypair on the target host if none exists (never returned as plaintext), write the config, and bring the interface up
Resources
state(infinite)— Result of check or apply operation
@kitquiet/homelab-ingress/traefik-stackv2026.07.28.1traefik_stack.ts
Global Arguments
| Argument | Type | Description |
|---|---|---|
| nodeHost | string | Hostname or IP of the target node |
| nodeUser | string | SSH username |
| nodePort | number | SSH port |
| nodeIdentityFile? | string | Path to an SSH private key file on the machine running this method |
| nodeIdentityContent? | string | PEM private key content — supply via ${{ vault.get(vault, key) }} |
| become | boolean | Enable sudo privilege escalation (leave false if nodeUser is already root) |
| becomeUser | string | User to become via sudo |
| becomePassword? | string | Password for sudo -S, via ${{ vault.get(vault, key) }} |
| deployPath | string | Remote directory to hold compose file, static/dynamic config, ACME storage, and logs |
| acmeEmail | string | Contact email for Let's Encrypt |
| letsEncryptStaging | boolean | Use Let's Encrypt's staging CA — avoids production rate limits while testing, certs won't be browser-trusted |
| image | string | Traefik image |
| networkName | string | Docker network name the stack's containers join |
fn check()
Check whether the stack is deployed with current config and running (dry-run)
fn apply()
Deploy or update the Traefik stack — writes config, brings the stack up if needed
Resources
state(infinite)— Result of check or apply operation
route(infinite)— Result of an add-route/remove-route operation
@kitquiet/homelab-ingress/authelia-gatev2026.07.28.1authelia_gate.ts
Global Arguments
| Argument | Type | Description |
|---|---|---|
| nodeHost | string | Hostname or IP of the target node |
| nodeUser | string | SSH username |
| nodePort | number | SSH port |
| nodeIdentityFile? | string | Path to an SSH private key file on the machine running this method |
| nodeIdentityContent? | string | PEM private key content — supply via ${{ vault.get(vault, key) }} |
| become | boolean | Enable sudo privilege escalation (leave false if nodeUser is already root) |
| becomeUser | string | User to become via sudo |
| becomePassword? | string | Password for sudo -S, via ${{ vault.get(vault, key) }} |
| deployPath | string | Remote directory to hold compose file, config, secrets, db, and logs |
| traefikDynamicConfigPath | string | Path to the traefik_stack deployment's dynamic config — the forwardAuth middleware is written here |
| domain | string | Cookie domain, e.g. 'example.com' |
| authHostname | string | Public hostname of the Authelia portal itself, e.g. 'auth.example.com' |
| defaultRedirectionUrl? | string | Where to send a user after login with no specific destination |
| sessionExpiration | string | e.g. '1h', '24h' |
| sessionInactivity | string | e.g. '5m', '2h' |
| image | string | |
| networkName | string | Docker network name — must match the traefik_stack instance this gate sits behind |
| authBackend | enum | 'file' manages users_database.yml directly via add-user. 'ldap' delegates identity to a paired lldap_directory instance instead — see ldapAddress/ldapBaseDn/ldapBindUser/ldapBindPassword. |
| ldapAddress? | string | Required when authBackend is 'ldap', e.g. 'ldap://lldap:3890' — must be reachable from the authelia container, typically an lldap_directory container name on the same docker network |
| ldapBaseDn? | string | Required when authBackend is 'ldap', e.g. 'dc=example,dc=com' — must match the paired lldap_directory instance's ldapBaseDn |
| ldapBindUser? | string | Required when authBackend is 'ldap' — bind DN, e.g. 'uid=admin,ou=people,dc=example,dc=com' |
| ldapBindPassword? | string | Required when authBackend is 'ldap' — bind user's password, via ${{ vault.get(vault, key) }} |
| oidcEnabled | boolean | Turns Authelia into an OpenID Connect provider in addition to forward-auth, for apps that support native OIDC login |
| oidcIssuerPrivateKeyPem? | string | Required when oidcEnabled is true — RSA private key PEM for OIDC issuer signing, via ${{ vault.get(vault, key) }}. Generate with `authelia crypto pair rsa generate` yourself; never generated by this extension. |
fn check()
Check deployment/config state and running status (dry-run)
fn apply()
Deploy or update Authelia — generates secrets on first run (never returned as plaintext), ensures the forwardAuth middleware exists in the paired traefik_stack's dynamic config
Resources
state(infinite)— Result of check or apply operation
bypass(infinite)— Result of an add-bypass-rule operation
user(infinite)— Result of an add-user operation
oidc-client(infinite)— Result of an add-oidc-client operation
@kitquiet/homelab-ingress/lldap-directoryv2026.07.28.1lldap_directory.ts
Global Arguments
| Argument | Type | Description |
|---|---|---|
| nodeHost | string | Hostname or IP of the target node |
| nodeUser | string | SSH username |
| nodePort | number | SSH port |
| nodeIdentityFile? | string | Path to an SSH private key file on the machine running this method |
| nodeIdentityContent? | string | PEM private key content — supply via ${{ vault.get(vault, key) }} |
| become | boolean | Enable sudo privilege escalation (leave false if nodeUser is already root) |
| becomeUser | string | User to become via sudo |
| becomePassword? | string | Password for sudo -S, via ${{ vault.get(vault, key) }} |
| deployPath | string | Remote directory to hold compose file, config, secrets, and data |
| image | string | |
| ldapBaseDn | string | e.g. 'dc=example,dc=com' — must match the base_dn given to any authelia_gate instance pointed at this directory |
| adminUsername | string | LLDAP's built-in bootstrap admin account username |
| networkName | string | Docker network name — must match the traefik_stack/authelia_gate instances that need to reach this directory by container name |
fn check()
Check deployment/config state and running status (dry-run)
fn apply()
Deploy or update LLDAP — generates the JWT secret, key seed, and admin bootstrap password on first run (never returned as plaintext)
Resources
state(infinite)— Result of check or apply operation
user(infinite)— Result of an add-user operation
@kitquiet/homelab-ingress/fail2ban-jailv2026.07.28.1fail2ban_jail.ts
Global Arguments
| Argument | Type | Description |
|---|---|---|
| nodeHost | string | Hostname or IP of the target node |
| nodeUser | string | SSH username |
| nodePort | number | SSH port |
| nodeIdentityFile? | string | Path to an SSH private key file on the machine running this method |
| nodeIdentityContent? | string | PEM private key content — supply via ${{ vault.get(vault, key) }} |
| become | boolean | Enable sudo privilege escalation (leave false if nodeUser is already root) |
| becomeUser | string | User to become via sudo |
| becomePassword? | string | Password for sudo -S, via ${{ vault.get(vault, key) }} |
| jailName | string | Jail identifier, e.g. 'traefik-badbots' |
| filterRegex | string | failregex pattern using the <HOST> tag, e.g. \ |
| logPath | string | Path to the log file this jail watches |
| maxRetry | number | |
| findTime | string | |
| banTime | string | |
| port | string | |
| protocol | string | |
| banAction | string | fail2ban action — the default uses its own isolated nftables table, safe alongside docker_host_firewall |
fn check()
Check filter/jail config and whether the jail is actually active (dry-run)
fn apply()
Write filter+jail config, install fail2ban if missing, and always explicitly restart to actually load the jail (enable --now alone won't pick up a fresh jail.d file on an already-running service)
fn unban(ip: string)
Unban a specific IP from this jail (imperative, always runs)
| Argument | Type | Description |
|---|---|---|
| ip | string | IP address to unban |
Resources
state(infinite)— Result of check or apply operation
03Previous Versions
2026.07.27.1
04Stats
A
100 / 100
Downloads
0
Archive size
258.3 KB
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned
05Platforms