# Concord NUC

**Hostname**: concord-nuc / vish-concord-nuc
**IP Address**: 192.168.68.100 (static, eno1)
**Tailscale IP**: 100.72.55.21
**OS**: Ubuntu (cloud-init based)
**SSH**: `ssh vish-concord-nuc` (via Tailscale — see `~/.ssh/config`)

---

## Network Configuration

### Static IP Setup

`eno1` is configured with a **static IP** (`192.168.68.100/22`) via netplan. This is required because AdGuard Home binds its DNS listener to a specific IP, and DHCP lease changes would cause it to crash.

**Netplan config**: `/etc/netplan/50-cloud-init.yaml`

```yaml
network:
    ethernets:
        eno1:
            dhcp4: false
            addresses:
                - 192.168.68.100/22
            routes:
                - to: default
                  via: 192.168.68.1
            nameservers:
                addresses:
                    - 9.9.9.9
                    - 1.1.1.1
    version: 2
    wifis:
        wlp1s0:
            access-points:
                This_Wifi_Sucks:
                    password: "REDACTED_PASSWORD"
            dhcp4: true
```

**Cloud-init is disabled** from managing network config:
`/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg` — prevents reboots from reverting to DHCP.

> **Warning**: If you ever re-enable cloud-init networking or wipe this file, eno1 will revert to DHCP and AdGuard will start crash-looping on the next restart. See the Troubleshooting section below.

---

## Services

| Service | Port | URL |
|---------|------|-----|
| AdGuard Home (Web UI) | 9080 | http://192.168.68.100:9080 |
| AdGuard Home (DNS) | 53 | 192.168.68.100:53, 100.72.55.21:53 |
| Home Assistant | - | see homeassistant.yaml |
| Plex | - | see plex.yaml |
| Syncthing | - | see syncthing.yaml |
| Invidious | 3000 | https://in.vish.gg (public), http://192.168.68.100:3000 |
| Materialious | 3001 | http://192.168.68.100:3001 |
| YourSpotify | 4000, 15000 | see yourspotify.yaml |

---

## Deployed Stacks

| Compose File | Service | Notes |
|-------------|---------|-------|
| `adguard.yaml` | AdGuard Home | DNS ad blocker, binds to 192.168.68.100 |
| `homeassistant.yaml` | Home Assistant | Home automation |
| `plex.yaml` | Plex | Media server |
| `syncthing.yaml` | Syncthing | File sync |
| `wireguard.yaml` | WireGuard / wg-easy | VPN |
| `dyndns_updater.yaml` | DynDNS | Dynamic DNS |
| `node-exporter.yaml` | Node Exporter | Prometheus metrics |
| `piped.yaml` | Piped | YouTube alternative frontend |
| `yourspotify.yaml` | YourSpotify | Spotify stats |
| `invidious/invidious.yaml` | Invidious + Companion + DB + Materialious | YouTube frontend — https://in.vish.gg |

---

## Troubleshooting

### AdGuard crash-loops on startup

**Symptom**: `docker ps` shows AdGuard as "Restarting" or "Up Less than a second"

**Cause**: AdGuard binds DNS to a specific IP (`192.168.68.100`). If the host's IP changes (DHCP), or if AdGuard rewrites its config to the current DHCP address, it will fail to bind on next start.

**Diagnose**:
```bash
docker logs AdGuard --tail 20
# Look for: "bind: cannot assign requested address"
# The log will show which IP it tried to use
```

**Fix**:
```bash
# 1. Check what IP AdGuard thinks it should use
sudo grep -A3 'bind_hosts' /home/vish/docker/adguard/config/AdGuardHome.yaml

# 2. Check what IP eno1 actually has
ip addr show eno1 | grep 'inet '

# 3. If they don't match, update the config
sudo sed -i 's/- 192.168.68.XXX/- 192.168.68.100/' /home/vish/docker/adguard/config/AdGuardHome.yaml

# 4. Restart AdGuard
docker restart AdGuard
```

**If the host IP has reverted to DHCP** (e.g. after a reboot wiped the static config):
```bash
# Re-apply static IP
sudo netplan apply

# Verify
ip addr show eno1 | grep 'inet '
# Should show: inet 192.168.68.100/22
```

---

## Incident History

### 2026-02-22 — AdGuard crash-loop / IP mismatch

- **Root cause**: Host had drifted from `192.168.68.100` to DHCP-assigned `192.168.68.87`. AdGuard briefly started, rewrote its config to `.87`, then the static IP was applied and `.87` was gone — causing a bind failure loop.
- **Resolution**:
  1. Disabled cloud-init network management
  2. Set `eno1` to static `192.168.68.100/22` via netplan
  3. Corrected `AdGuardHome.yaml` `bind_hosts` back to `.100`
  4. Restarted AdGuard — stable

---

### 2026-02-27 — Invidious 502 / crash-loop

- **Root cause 1**: PostgreSQL 14 defaults `pg_hba.conf` to `scram-sha-256` for host connections. Invidious's Crystal DB driver does not support scram-sha-256, causing a "password authentication failed" crash loop even with correct credentials.
  - **Fix**: Changed last line of `/var/lib/postgresql/data/pg_hba.conf` in the `invidious-db` container from `host all all all scram-sha-256` to `host all all 172.21.0.0/16 trust`, then ran `SELECT pg_reload_conf();`.
- **Root cause 2**: Portainer had saved the literal string `REDACTED_SECRET_KEY` as the `SERVER_SECRET_KEY` env var for the companion container (Portainer's secret-redaction placeholder was baked in as the real value). The latest companion image validates the key strictly (exactly 16 alphanumeric chars), causing it to crash.
  - **Fix**: Updated the Portainer stack file via API (`PUT /api/stacks/584`), replacing all `REDACTED_*` placeholders with the real values.

---

*Last updated: 2026-02-27*
