Files
homelab-optimized/docs/infrastructure/offline-and-remote-access.md
Gitea Mirror Bot 316a8d8777
Some checks failed
Documentation / Deploy to GitHub Pages (push) Has been cancelled
Documentation / Build Docusaurus (push) Has started running
Sanitized mirror from private repository - 2026-04-05 12:15:35 UTC
2026-04-05 12:15:35 +00:00

12 KiB

Offline & Remote Access Guide

Last updated: 2026-03-20

How DNS Resolution Works

The homelab uses split-horizon DNS so services are reachable from anywhere — LAN, Tailscale VPN, or the open internet — using the same *.vish.gg domain names.

Three Access Paths

┌──────────────────────────────────────────────────────────────────────┐
│                     DNS Query: nb.vish.gg                            │
├──────────────┬──────────────────┬────────────────────────────────────┤
│  LAN Client  │  Tailscale Client│  Internet Client                   │
│  (at home)   │  (travel laptop) │  (phone on cellular)               │
├──────────────┼──────────────────┼────────────────────────────────────┤
│  DNS: AdGuard│  DNS: Headscale  │  DNS: Cloudflare                   │
│  (192.168.0  │  MagicDNS →      │  (1.1.1.1)                         │
│   .250)      │  AdGuard         │                                    │
├──────────────┼──────────────────┼────────────────────────────────────┤
│  Resolves to:│  Resolves to:    │  Resolves to:                      │
│  100.85.21.51│  100.85.21.51    │  104.21.73.214 (Cloudflare)        │
│  (NPM via TS)│  (NPM via TS)   │                                    │
├──────────────┼──────────────────┼────────────────────────────────────┤
│  Path:       │  Path:           │  Path:                             │
│  Client →    │  Client →        │  Client → Cloudflare →             │
│  NPM (direct)│  Tailscale →     │  Router → NPM →                   │
│  → backend   │  NPM → backend   │  backend                          │
├──────────────┼──────────────────┼────────────────────────────────────┤
│  Latency:    │  Latency:        │  Latency:                          │
│  ~1ms        │  ~5-50ms         │  ~50-100ms                         │
│  (LAN)       │  (Tailscale)     │  (Cloudflare roundtrip)            │
├──────────────┼──────────────────┼────────────────────────────────────┤
│  Internet    │  Internet        │  Internet                          │
│  required?   │  required?       │  required?                         │
│  NO          │  NO (peer-to-peer│  YES                               │
│              │  if both on TS)  │                                    │
└──────────────┴──────────────────┴────────────────────────────────────┘

Key: Everything Resolves to 100.85.21.51

All *.vish.gg, *.thevish.io, and *.crista.love domains resolve to 100.85.21.51 (matrix-ubuntu's Tailscale IP) when queried through AdGuard. This is NPM's address on the Tailscale network, reachable from:

  • LAN clients — via the router's DHCP DNS (AdGuard at 192.168.0.250)
  • Remote Tailscale clients — via Headscale MagicDNS which forwards to AdGuard
  • Both paths hit NPM on its Tailscale IP, which works from anywhere on the tailnet

When Internet Goes Down

If your WAN link drops:

What works How
All *.vish.gg services AdGuard returns Tailscale IP, NPM proxies locally
MagicDNS names (atlantis.tail.vish.gg) Headscale resolves directly
Direct Tailscale IPs (100.x.x.x) Always work between peers
Olares/K8s (k9s, kubectl) LAN access at 192.168.0.145
What breaks Why
External access (from internet) Cloudflare can't reach you
Cloudflare-only domains without split-horizon rewrite DNS returns unreachable CF proxy IP
Renovate, DDNS updates Need internet to reach APIs
DERP relays for remote peers Remote Tailscale clients may lose connectivity

Access from Travel Laptop

Your travel laptop (MSI Prestige) connects via Headscale VPN:

  1. Join the tailnet: tailscale up --login-server=https://headscale.vish.gg
  2. DNS is automatic: Headscale pushes AdGuard as the DNS server via MagicDNS
  3. All domains work: nb.vish.gg, git.vish.gg, etc. resolve to NPM's Tailscale IP
  4. No VPN split tunneling needed: Only homelab traffic routes through Tailscale
# From the travel laptop:
curl https://nb.vish.gg/       # → 100.85.21.51 (Tailscale) → NPM → backend
curl https://gf.vish.gg/       # → 100.85.21.51 (Tailscale) → NPM → Grafana
ssh homelab.tail.vish.gg       # → MagicDNS → direct Tailscale peer

If Headscale Is Down

If the Headscale control server (calypso) is unreachable, already-connected peers maintain their connections. New peers can't join. Use direct Tailscale IPs as fallback:

Service Direct URL
Grafana http://100.67.40.126:3300
NetBox http://100.67.40.126:8443
Portainer https://100.83.230.112:9443
Gitea http://100.103.48.78:3052

MagicDNS (.tail.vish.gg)

Headscale MagicDNS provides <hostname>.tail.vish.gg for all peers:

Hostname Tailscale IP Use
atlantis.tail.vish.gg 100.83.230.112 NAS, media
calypso.tail.vish.gg 100.103.48.78 NAS, Gitea, auth
homelab.tail.vish.gg 100.67.40.126 Monitoring, tools
matrix-ubuntu.tail.vish.gg 100.85.21.51 NPM, Matrix, Mastodon
pve.tail.vish.gg 100.87.12.28 Proxmox
pi-5.tail.vish.gg 100.77.151.40 Uptime Kuma
vish-concord-nuc.tail.vish.gg 100.72.55.21 Home Assistant, edge
setillo.tail.vish.gg 100.125.0.20 Remote NAS
seattle.tail.vish.gg 100.82.197.124 Cloud VPS
truenas-scale.tail.vish.gg 100.75.252.64 TrueNAS

.tail.vish.gg names are resolved by AdGuard rewrites (not MagicDNS) so they work on all LAN devices, not just Tailscale clients. Both AdGuard instances (Calypso and Atlantis) have identical entries.

.vish.local Names

AdGuard also resolves .vish.local shortnames to Tailscale IPs:

Hostname Tailscale IP
atlantis.vish.local 100.83.230.112
calypso.vish.local 100.103.48.78
homelab.vish.local 100.67.40.126
concordnuc.vish.local 100.72.55.21
pi5.vish.local 100.77.151.40
px.vish.local 100.87.12.28

DNS Infrastructure

Two Redundant AdGuard Instances

Both instances have identical configuration — same rewrites, filters, upstream DNS, and user rules.

Role Host IP Web UI
Primary DNS Calypso 192.168.0.250 http://192.168.0.250:9080
Backup DNS Atlantis 192.168.0.200 http://192.168.0.200:9080

Router DHCP hands out both as DNS servers. If Calypso reboots, Atlantis takes over seamlessly.

Login for both: username vish, same password.

Upstream DNS

Both AdGuard instances use:

  • https://dns.adguard-dns.com/dns-query (AdGuard DoH)
  • https://dns.cloudflare.com/dns-query (Cloudflare DoH)
  • [/tail.vish.gg/]100.100.100.100 (Headscale MagicDNS for tail.vish.gg)

AdGuard DNS Rewrites (Split-Horizon)

All rewrites are identical on both Calypso and Atlantis.

Wildcard rewrites (all services through NPM):

Domain Pattern Resolves To Purpose
*.vish.gg 100.85.21.51 NPM via Tailscale
*.thevish.io 100.85.21.51 NPM via Tailscale
*.crista.love 100.85.21.51 NPM via Tailscale

Specific overrides (bypass NPM wildcard):

Domain Resolves To Purpose
derp.vish.gg 192.168.0.250 DERP relay — direct, no NPM
derp-atl.vish.gg 192.168.0.200 DERP relay — direct, no NPM
derp-sea.vish.gg 100.82.197.124 DERP relay on Seattle VPS
turn.thevish.io 192.168.0.200 TURN/STUN — needs direct UDP

*Tailscale host rewrites (override .vish.gg wildcard):

Domain Resolves To
atlantis.tail.vish.gg 100.83.230.112
calypso.tail.vish.gg 100.103.48.78
homelab.tail.vish.gg 100.67.40.126
matrix-ubuntu.tail.vish.gg 100.85.21.51
pve.tail.vish.gg 100.87.12.28
pi-5.tail.vish.gg 100.77.151.40
vish-concord-nuc.tail.vish.gg 100.72.55.21
setillo.tail.vish.gg 100.125.0.20
seattle.tail.vish.gg 100.82.197.124
truenas-scale.tail.vish.gg 100.75.252.64
jellyfish.tail.vish.gg 100.69.121.120
shinku-ryuu.tail.vish.gg 100.98.93.15

Keeping Both Instances in Sync

When adding new DNS rewrites, update both AdGuard configs:

  • Calypso: /volume1/docker/adguard/config/AdGuardHome.yaml
  • Atlantis: /volume1/docker/adguard/config/AdGuardHome.yaml

Then restart both:

ssh calypso "sudo docker restart AdGuard"
ssh atlantis "sudo /var/packages/REDACTED_APP_PASSWORD/target/usr/bin/docker restart AdGuard"

Ad-Blocking Filters

Both instances use the same 5 filter lists:

  1. AdGuard DNS filter
  2. AdAway Default Blocklist
  3. AdGuard DNS Popup Hosts filter
  4. Dandelion Sprout's Anti Push Notifications
  5. AWAvenue Ads Rule

Plus 20 custom user rules blocking specific ad domains.

SSL Certificates

All services use Let's Encrypt wildcard certificates (issued via DNS challenge with Cloudflare API):

Certificate Domains Issuer
Cert 8 *.vish.gg, vish.gg ZeroSSL (via acme.sh)
Cert 9 *.thevish.io, thevish.io ZeroSSL (via acme.sh)
Cert 10 *.crista.love, crista.love ZeroSSL (via acme.sh)

These certs are publicly trusted — no certificate warnings on any access path (LAN, Tailscale, or internet).

Certificate Renewal

acme.sh is installed on matrix-ubuntu (/home/test/.acme.sh/) with auto-renewal via cron. To manually renew:

ssh matrix-ubuntu
export CF_Token="REDACTED_TOKEN"  # pragma: allowlist secret
~/.acme.sh/acme.sh --renew -d '*.vish.gg' -d 'vish.gg' --force
~/.acme.sh/acme.sh --renew -d '*.thevish.io' -d 'thevish.io' --force
~/.acme.sh/acme.sh --renew -d '*.crista.love' -d 'crista.love' --force

# Then re-upload to NPM (certs need to be uploaded via NPM API or UI)

Quick Reference

I'm at home on WiFi

Just use https://nb.vish.gg — AdGuard resolves to NPM's Tailscale IP, works instantly.

I'm traveling with the laptop

Connect to Headscale tailnet → same URLs work: https://nb.vish.gg

I'm on my phone (no VPN)

Use the public URLs: https://nb.vish.gg → goes through Cloudflare as normal.

Internet is down at home

All services still work from LAN via AdGuard → Tailscale IP → NPM. No Cloudflare dependency.

I need to access a service directly (no NPM)

Three options, all equivalent:

http://homelab.tail.vish.gg:3300      # .tail.vish.gg name
http://homelab.vish.local:3300        # .vish.local shortname
http://100.67.40.126:3300             # Tailscale IP directly

Everything is down — emergency access

SSH via Tailscale: ssh homelab (uses ~/.ssh/config with Tailscale IPs)

I need to manage DNS

  • Calypso AdGuard: http://192.168.0.250:9080 (primary)
  • Atlantis AdGuard: http://192.168.0.200:9080 (backup)
  • Login: vish / same password on both