Skip to main content

Homelab Ingress

@kitquiet/homelab-ingressv2026.07.28.1· 28d agoMODELS
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.

02Models5
@kitquiet/homelab-ingress/wireguard-tunnelv2026.07.28.1wireguard_tunnel.ts

Global Arguments

ArgumentTypeDescription
nodeHoststringHostname or IP of the target node
nodeUserstringSSH username
nodePortnumberSSH port
nodeIdentityFile?stringPath to an SSH private key file on the machine running this method
nodeIdentityContent?stringPEM private key content — supply via ${{ vault.get(vault, key) }}
becomebooleanEnable sudo privilege escalation (leave false if nodeUser is already root)
becomeUserstringUser to become via sudo
becomePassword?stringPassword for sudo -S, via ${{ vault.get(vault, key) }}
interfaceNamestringWireGuard interface name
roleenumserver listens on listenPort; client dials peerEndpoint
tunnelAddressstringThis side's address within the tunnel, CIDR — e.g. 10.100.0.1/24
listenPort?numberUDP port to listen on — required for role: server
peerPublicKeystringThe other side's WireGuard public key
peerEndpoint?stringhost:port to reach the peer — required for role: client
peerAllowedIPsstringCIDR(s) routed through this peer, e.g. 10.100.0.2/32
persistentKeepalive?numberSeconds 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

ArgumentTypeDescription
nodeHoststringHostname or IP of the target node
nodeUserstringSSH username
nodePortnumberSSH port
nodeIdentityFile?stringPath to an SSH private key file on the machine running this method
nodeIdentityContent?stringPEM private key content — supply via ${{ vault.get(vault, key) }}
becomebooleanEnable sudo privilege escalation (leave false if nodeUser is already root)
becomeUserstringUser to become via sudo
becomePassword?stringPassword for sudo -S, via ${{ vault.get(vault, key) }}
deployPathstringRemote directory to hold compose file, static/dynamic config, ACME storage, and logs
acmeEmailstringContact email for Let's Encrypt
letsEncryptStagingbooleanUse Let's Encrypt's staging CA — avoids production rate limits while testing, certs won't be browser-trusted
imagestringTraefik image
networkNamestringDocker 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

ArgumentTypeDescription
nodeHoststringHostname or IP of the target node
nodeUserstringSSH username
nodePortnumberSSH port
nodeIdentityFile?stringPath to an SSH private key file on the machine running this method
nodeIdentityContent?stringPEM private key content — supply via ${{ vault.get(vault, key) }}
becomebooleanEnable sudo privilege escalation (leave false if nodeUser is already root)
becomeUserstringUser to become via sudo
becomePassword?stringPassword for sudo -S, via ${{ vault.get(vault, key) }}
deployPathstringRemote directory to hold compose file, config, secrets, db, and logs
traefikDynamicConfigPathstringPath to the traefik_stack deployment's dynamic config — the forwardAuth middleware is written here
domainstringCookie domain, e.g. 'example.com'
authHostnamestringPublic hostname of the Authelia portal itself, e.g. 'auth.example.com'
defaultRedirectionUrl?stringWhere to send a user after login with no specific destination
sessionExpirationstringe.g. '1h', '24h'
sessionInactivitystringe.g. '5m', '2h'
imagestring
networkNamestringDocker network name — must match the traefik_stack instance this gate sits behind
authBackendenum'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?stringRequired 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?stringRequired when authBackend is 'ldap', e.g. 'dc=example,dc=com' — must match the paired lldap_directory instance's ldapBaseDn
ldapBindUser?stringRequired when authBackend is 'ldap' — bind DN, e.g. 'uid=admin,ou=people,dc=example,dc=com'
ldapBindPassword?stringRequired when authBackend is 'ldap' — bind user's password, via ${{ vault.get(vault, key) }}
oidcEnabledbooleanTurns Authelia into an OpenID Connect provider in addition to forward-auth, for apps that support native OIDC login
oidcIssuerPrivateKeyPem?stringRequired 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

ArgumentTypeDescription
nodeHoststringHostname or IP of the target node
nodeUserstringSSH username
nodePortnumberSSH port
nodeIdentityFile?stringPath to an SSH private key file on the machine running this method
nodeIdentityContent?stringPEM private key content — supply via ${{ vault.get(vault, key) }}
becomebooleanEnable sudo privilege escalation (leave false if nodeUser is already root)
becomeUserstringUser to become via sudo
becomePassword?stringPassword for sudo -S, via ${{ vault.get(vault, key) }}
deployPathstringRemote directory to hold compose file, config, secrets, and data
imagestring
ldapBaseDnstringe.g. 'dc=example,dc=com' — must match the base_dn given to any authelia_gate instance pointed at this directory
adminUsernamestringLLDAP's built-in bootstrap admin account username
networkNamestringDocker 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

ArgumentTypeDescription
nodeHoststringHostname or IP of the target node
nodeUserstringSSH username
nodePortnumberSSH port
nodeIdentityFile?stringPath to an SSH private key file on the machine running this method
nodeIdentityContent?stringPEM private key content — supply via ${{ vault.get(vault, key) }}
becomebooleanEnable sudo privilege escalation (leave false if nodeUser is already root)
becomeUserstringUser to become via sudo
becomePassword?stringPassword for sudo -S, via ${{ vault.get(vault, key) }}
jailNamestringJail identifier, e.g. 'traefik-badbots'
filterRegexstringfailregex pattern using the <HOST> tag, e.g. \
logPathstringPath to the log file this jail watches
maxRetrynumber
findTimestring
banTimestring
portstring
protocolstring
banActionstringfail2ban 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)
ArgumentTypeDescription
ipstringIP address to unban

Resources

state(infinite)— Result of check or apply operation
03Previous Versions1
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