# 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

```bash
# 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:
```bash
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:

```bash
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

## Related Documentation

- [Split-Horizon DNS Implementation](split-horizon-dns.md)
- [NPM Migration Plan](npm-migration-to-matrix-ubuntu.md)
- [Authentik SSO](authentik-sso.md)
- [Image Update Guide](../admin/IMAGE_UPDATE_GUIDE.md)
