Document network operations and WireGuard maintenance

This commit is contained in:
windyboy
2026-08-06 10:20:43 +08:00
parent 64484cd484
commit 29bdf06383
14 changed files with 678 additions and 4 deletions
+1
View File
@@ -28,6 +28,7 @@ Also readable as `agent.md` (symlink → this file).
| **us2.wsvc.info** | Vaultwarden + Traefik (+ Soft Serve, …) | `ssh -4 windy@us2.wsvc.info` | [hosts/us2.wsvc.info.md](hosts/us2.wsvc.info.md) |
| **hk2.chans.xyz** | PowerDNS auth ns1 (`/opt/pdns`) | `ssh -4 windy@hk2.chans.xyz` | [hosts/hk2.chans.xyz.md](hosts/hk2.chans.xyz.md) |
| **gfw.windy.lan** | OpenWrt LAN gateway / OpenClash | `ssh -4 root@192.168.66.1` | [hosts/gfw.windy.lan.md](hosts/gfw.windy.lan.md) |
| **gw** | EdgeRouter X primary LAN gateway | `ssh -4 zhiqiang@192.168.66.254` | [hosts/gw.md](hosts/gw.md) |
| **ubnt** | UniFi Network Controller | `ssh -4 windy@192.168.66.46` | [hosts/ubnt.md](hosts/ubnt.md) |
### Public services
+15
View File
@@ -45,6 +45,17 @@ all:
backup:
compose_args: [--no-deps, --force-recreate]
service_reconcile_traefik_restart_targets: [poweradmin]
us4:
ansible_host: us4.wsvc.info
ansible_host_ipv4: 185.201.226.122
service_role: wireguard
compose_project_dir: /opt/wireguard
healthcheck_profile: wireguard
wireguard_image: >-
lscr.io/linuxserver/wireguard@sha256:ac43e1226878d2611315172d6ea357a95cb326ee73124b91108118efc8666889
service_reconcile_services:
wireguard:
compose_args: [--no-deps, --force-recreate]
mailcow:
hosts:
mx2:
@@ -54,11 +65,15 @@ all:
powerdns:
hosts:
hk2:
wireguard:
hosts:
us4:
docker_hosts:
children:
mailcow:
vaultwarden:
powerdns:
wireguard:
# Matrix is a dedicated K3s node and intentionally remains outside the
# Docker-oriented managed group.
k3s_servers:
+62
View File
@@ -0,0 +1,62 @@
---
# This owns the non-secret Compose declaration only. WireGuard key material
# remains under the server-local /opt/wireguard/config bind mount.
- name: Apply the reviewed WireGuard Compose hardening
hosts: wireguard
become: true
gather_facts: false
serial: 1
vars:
wireguard_harden_confirm: false
wireguard_compose_candidate: "{{ compose_project_dir }}/compose.yml.candidate"
tasks:
- name: Require explicit WireGuard hardening confirmation
ansible.builtin.assert:
that:
- wireguard_harden_confirm | bool
fail_msg: >-
This operation replaces the non-secret Compose declaration. Run only
during the approved maintenance window after a rollback archive exists.
- name: Verify that a protected pre-change archive exists
ansible.builtin.find:
paths: "{{ compose_project_dir }}/backups"
patterns: 'pre-hardening-*.tar.gz'
file_type: file
register: wireguard_pre_hardening_archives
- name: Require a pre-change rollback archive
ansible.builtin.assert:
that:
- wireguard_pre_hardening_archives.matched | int > 0
fail_msg: >-
Create a protected pre-hardening archive before replacing compose.yml.
- name: Render the candidate non-secret Compose declaration
ansible.builtin.template:
src: ../templates/wireguard-compose.yml.j2
dest: "{{ wireguard_compose_candidate }}"
owner: root
group: root
mode: "0644"
- name: Validate the candidate Compose declaration
ansible.builtin.command:
argv:
- docker
- compose
- --project-directory
- "{{ compose_project_dir }}"
- --file
- "{{ wireguard_compose_candidate }}"
- config
- --quiet
changed_when: false
- name: Atomically activate the validated Compose declaration
ansible.builtin.command:
argv:
- mv
- "{{ wireguard_compose_candidate }}"
- "{{ compose_project_dir }}/compose.yml"
changed_when: true
+114
View File
@@ -0,0 +1,114 @@
---
# Deliberately narrow maintenance path for the us4 WireGuard service. The
# Compose file and all private key material remain on the server; this playbook
# only pulls its declared immutable image and recreates the named service.
- name: Update and recreate the approved WireGuard service
hosts: wireguard
become: true
gather_facts: false
serial: 1
vars:
wireguard_maintenance_confirm: false
tasks:
- name: Require explicit WireGuard maintenance confirmation
ansible.builtin.assert:
that:
- wireguard_maintenance_confirm | bool
fail_msg: >-
This operation pulls the image declared in /opt/wireguard/compose.yml
and briefly recreates WireGuard. Set wireguard_maintenance_confirm=true
only during the approved maintenance window.
- name: Create the local WireGuard rollback directory
ansible.builtin.file:
path: "{{ compose_project_dir }}/backups"
state: directory
owner: root
group: root
mode: "0700"
- name: Create a timestamp for the rollback archive
ansible.builtin.command:
argv: [date, +%Y%m%dT%H%M%SZ]
changed_when: false
register: wireguard_backup_timestamp
- name: Create a protected local rollback archive of WireGuard configuration
ansible.builtin.shell:
cmd: >-
umask 077 && tar --create --gzip
--file={{ compose_project_dir }}/backups/config-{{ wireguard_backup_timestamp.stdout }}.tar.gz
--directory={{ compose_project_dir }} config
executable: /bin/bash
changed_when: true
- name: Verify the compose declaration uses an immutable image digest
ansible.builtin.command:
argv:
- docker
- compose
- --project-directory
- "{{ compose_project_dir }}"
- config
changed_when: false
register: wireguard_compose_config
- name: Reject mutable WireGuard image declarations
ansible.builtin.assert:
that:
- "'@sha256:' in wireguard_compose_config.stdout"
fail_msg: >-
Pin the WireGuard image to an approved immutable digest before using
this maintenance playbook.
- name: Pull the declared WireGuard image
ansible.builtin.command:
argv:
- docker
- compose
- --project-directory
- "{{ compose_project_dir }}"
- pull
- wireguard
changed_when: true
- name: Recreate the WireGuard container
ansible.builtin.command:
argv:
- docker
- compose
- --project-directory
- "{{ compose_project_dir }}"
- up
- -d
- --no-deps
- --force-recreate
- wireguard
changed_when: true
- name: Verify WireGuard container state
ansible.builtin.command:
argv:
- docker
- compose
- --project-directory
- "{{ compose_project_dir }}"
- ps
- --all
changed_when: false
register: wireguard_compose_ps
- name: Display WireGuard container state
ansible.builtin.debug:
var: wireguard_compose_ps.stdout_lines
- name: Run the deployed WireGuard health check
ansible.builtin.command:
argv: [/usr/local/lib/vps-health/run]
changed_when: false
register: wireguard_health_report
failed_when: wireguard_health_report.rc not in [0, 1]
- name: Display sanitized WireGuard health result
ansible.builtin.debug:
msg: "{{ wireguard_health_report.stdout | default(wireguard_health_report.stderr, true) }}"
@@ -11,3 +11,4 @@ healthcheck_profiles:
mailcow: mailcow.sh
vaultwarden: vaultwarden.sh
pdns: pdns.sh
wireguard: wireguard.sh
@@ -1,3 +1,4 @@
#!/usr/bin/env bash
set -uo pipefail
exec '{{ healthcheck_install_root }}/{{ healthcheck_profiles[healthcheck_profile] }}' >> '{{ healthcheck_log_dir }}/healthcheck.log' 2>&1
set -o pipefail
'{{ healthcheck_install_root }}/{{ healthcheck_profiles[healthcheck_profile] }}' 2>&1 | tee -a '{{ healthcheck_log_dir }}/healthcheck.log'
exit "${PIPESTATUS[0]}"
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -uo pipefail
source '{{ healthcheck_install_root }}/health-common.sh'
require_command docker
require_command ss
check_compose
container='wireguard'
if ! docker inspect "$container" >/dev/null 2>&1; then
record critical 'wireguard_container_missing'
else
running="$(docker inspect "$container" --format '{{ '{{' }}.State.Running{{ '}}' }}' 2>/dev/null)"
[[ "$running" == true ]] || record critical 'wireguard_container_not_running'
interface="$(docker exec "$container" wg show interfaces 2>/dev/null || true)"
grep -qw 'wg0' <<<"$interface" || record critical 'wireguard_wg0_missing'
configured_peer_count="$(docker exec "$container" sh -c 'find /config -mindepth 1 -maxdepth 1 -type d -name "peer_*" | wc -l' 2>/dev/null || true)"
runtime_peer_count="$(docker exec "$container" wg show wg0 dump 2>/dev/null | awk 'NR > 1 { count++ } END { print count + 0 }')"
[[ "$configured_peer_count" =~ ^[0-9]+$ ]] || record unknown 'wireguard_configured_peer_count_unknown'
[[ "$runtime_peer_count" =~ ^[0-9]+$ ]] || record unknown 'wireguard_runtime_peer_count_unknown'
if [[ "$configured_peer_count" =~ ^[0-9]+$ && "$runtime_peer_count" =~ ^[0-9]+$ ]]; then
[[ "$configured_peer_count" -eq "$runtime_peer_count" ]] || record warning "wireguard_peer_count_mismatch_${configured_peer_count}_${runtime_peer_count}"
fi
fi
if ss -H -uln 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)51820$'; then
record ok 'wireguard_udp_51820_listening'
else
record critical 'wireguard_udp_51820_missing'
fi
emit_result
exit "$EXIT_CODE"
@@ -0,0 +1,24 @@
services:
wireguard:
image: {{ wireguard_image }}
container_name: wireguard
cap_add:
- NET_ADMIN
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- SERVERURL=us4.wsvc.info
- SERVERPORT=51820
- PEERS=ha,phone,mbp
- PEERDNS=1.1.1.1
# Intentional: client IPv6 traffic is not tunneled.
- ALLOWEDIPS=0.0.0.0/0
ports:
- 51820:51820/udp
volumes:
- ./config:/config
read_only: true
tmpfs:
- /run:exec
restart: unless-stopped
+120
View File
@@ -0,0 +1,120 @@
# EdgeRouter X configuration record
This is a read-only, sanitized configuration record for
[gw](../hosts/gw.md), taken on 2026-08-05. It intentionally excludes PPPoE
credentials, user/password configuration, key material, and full DHCP client
or reservation inventories.
## Platform and topology
| Item | Current configuration |
|---|---|
| Appliance / OS | EdgeRouter X 5-Port; EdgeOS `v3.0.1` build `5862409` |
| WAN physical interface | `eth4`, described as `Internet (PPPoE)` |
| WAN routed interface | `pppoe0`; dynamic IPv4 default route; MTU `1492` |
| LAN66 | `eth0`, `192.168.66.254/24` |
| LAN55 | `switch0`, `192.168.55.254/24`; member ports `eth1``eth3` |
| IPv4 routing | Both LAN prefixes are directly connected; default route is through `pppoe0` |
Despite its name, LAN55 is not currently an 802.1Q VLAN configuration:
`switch0` has `vlan-aware` disabled. It is a separate Layer-3 subnet carried
by the built-in switch ports, while LAN66 is connected through `eth0`.
## IPv6
The PPPoE uplink requests a delegated `/60`, enables rapid commit and assigns
SLAAC-capable `/64` networks to the LANs:
| Interface | Delegated prefix slot | Router address suffix |
|---|---|---|
| `eth0` (LAN66) | `:1` | `::1` |
| `switch0` (LAN55) | `:2` | `::1` |
IPv6 has a default route through `pppoe0`. The currently delegated prefixes
are dynamic and are deliberately not treated as persistent configuration
facts.
## Effective firewall policy
| Ruleset | Direction / attachment | Effective policy |
|---|---|---|
| `WAN_IN` | `pppoe0`, IPv4 `in` | Active; default drop; permits established/related, drops invalid |
| `WAN_LOCAL` | `pppoe0`, IPv4 `local` | Active; default drop; permits established/related, drops invalid |
| `WANv6_IN` | `pppoe0`, IPv6 `in` | Active; default drop; permits established/related and ICMPv6; drops invalid |
| `WANv6_LOCAL` | `pppoe0`, IPv6 `local` | Active; default drop; permits established/related, ICMPv6, and DHCPv6 replies; drops invalid |
| `LAN_IN` | None | Inactive; configured default drop with explicit LAN55 ↔ LAN66 allows |
| `LAN_OUT` | None | Inactive; configured default drop with an Internet allow |
Therefore, LAN55 and LAN66 currently have ordinary bidirectional routed
access. The configured `LAN_IN`/`LAN_OUT` rules do **not** enforce an access
boundary because neither is bound to an interface. From source
`192.168.55.254`, the controller at `192.168.66.46` replied to 3/3 ICMP probes
on 2026-08-05. The online LAN55 AP also confirms the operational Inform path
to `192.168.66.46:9080`.
Global firewall settings enable normal ICMP echo replies, disable broadcast
ping and IPv4/IPv6 source routing, enable SYN cookies and martian logging, set
an MSS clamp of `1412`, and leave source validation disabled. Receive redirects
are disabled; IPv4 send redirects remain enabled.
The [official EdgeOS User Guide](https://dl.ubnt.com/guides/edgemax/EdgeOS_UG.pdf)
describes the distinction: a firewall policy is a ruleset that must be applied
to its target interface and direction before it affects traffic.
## NAT and inbound exposure
IPv4 traffic leaving `pppoe0` is covered by NAT masquerade rule `5010`.
Port-forward auto-firewall and hairpin NAT are enabled. The configured WAN
forwards are below; their external reachability was not tested in this review.
| WAN protocol / port | Internal destination | Purpose |
|---|---|---|
| TCP/UDP `8123` | `192.168.55.11:8123` | Home Assistant |
| TCP/UDP `51413` | `192.168.66.51:51413` | Transmission |
| TCP/UDP `5822` | `192.168.66.36:22` | SSH forward |
| TCP/UDP `1194` | `192.168.66.32:1194` | OpenVPN |
Treat every forward as an intentional public attack surface. Changes require
the normal firewall/change review and external verification from a trusted
network.
## LAN services
| Service | LAN66 | LAN55 |
|---|---|---|
| DHCP pool | `.38``.243` | `.38``.243` |
| Default gateway | `.254` | `.254` |
| DNS server handed out | `192.168.66.36` | `192.168.66.36` |
| Lease duration | 24 hours | 24 hours |
| UniFi Controller DHCP option | `192.168.66.46` | `192.168.66.46` |
DNS forwarding listens on `eth0` and `switch0` with a cache size of `512`.
Management services are SSH/TCP `22`, HTTP/TCP `80`, and HTTPS/TCP `443`.
Since no active LAN-local firewall is documented, treat access to this gateway
management plane from either LAN as permitted unless verified otherwise.
Infrastructure devices, including the two APs and the UniFi controller, have
DHCP static mappings. Keep the authoritative full mapping list only in the
gateway configuration; do not copy unrelated household device inventories into
this operations repository.
## Acceleration and maintenance implications
At verification time, `show ubnt offload` reported that both IPSec and HWNAT
offload modules were not loaded. No `system offload` enablement was present in
the inspected configuration. This may constrain routing/NAT throughput under
load; it is an observation, not authorization to alter the router.
Use these safe effective-state checks after any planned change:
```bash
show interfaces
show ip route
show ipv6 route
show firewall
show nat rules
show ubnt offload
```
Avoid recording raw `show configuration commands` output: it includes PPPoE
credentials and may include other secrets.
+129
View File
@@ -0,0 +1,129 @@
# UniFi network configuration
## Controller
| Item | Value |
|---|---|
| Host | [ubnt](../hosts/ubnt.md) |
| Address | `192.168.66.46` |
| Deployment | Docker, `jacobalberty/unifi:v9.5.21`, host networking |
| Inform endpoint | `http://192.168.66.46:9080/inform` |
| MongoDB database / port | `ace` / `27117` |
Although `8080` is a common UniFi inform port, this controller uses `9080`.
All AP adoption and recovery procedures must use the endpoint above.
## Inter-VLAN reachability
The AP management networks are routed by [gw](../hosts/gw.md),
`192.168.66.254`. On 2026-08-05, a probe sourced from the LAN55 gateway
(`192.168.55.254`) reached the controller (`192.168.66.46`) with 3/3 ICMP
replies. The UAP-AC-Lite is online through the `:9080/inform` endpoint, which
also confirms the required application path in practice.
`LAN_IN` contains explicit allow rules for `192.168.55.0/24 ↔
192.168.66.0/24`, but the firewall set is currently inactive (not attached to
an interface). Treat the networks as bidirectionally reachable by default;
do not rely on those inactive rules as an access boundary. Recheck this after
attaching any LAN firewall policy or changing VLAN routing.
To inspect device records from the controller host:
```bash
docker exec -it unifi-controller bash
mongo --port 27117
```
```javascript
use ace
db.device.find(
{ type: "uap" },
{ name: 1, ip: 1, model: 1, inform_url: 1 }
).pretty()
```
## Managed access points
| Name | Model | Management IP | Firmware | Management network | Inform port |
|---|---|---|---|---|---|
| U6 Lite | U6 Lite (`UAL6`) | `192.168.66.6` | `6.7.54.15663` | LAN66 (`192.168.66.0/24`, gateway `192.168.66.254`) | `9080` |
| UAP-AC-Lite | `U7LT` | `192.168.55.5` | `6.8.2.15592` | LAN55 (`192.168.55.0/24`, gateway `192.168.55.254`) | `9080` |
AP MAC addresses are recorded only where operationally needed: U6 Lite
`78:45:58:4d:cc:30`; UAP-AC-Lite `f0:9f:c2:20:04:e9`.
## SSH administration
Adopted APs use the SSH account configured in the controller, rather than the
factory `ubnt/ubnt` credential. The password is stored in the password manager
and must not be added to this repository.
In UniFi Network `9.5.21`, configure it at:
```text
Devices → Device Updates and Settings → Device SSH Settings
→ Device SSH Authentication
```
Use `ssh zhiqiangf@AP_IP` for the adopted-device account. Do not query or copy
the controller's `mgmt` database setting into logs or documentation: it can
contain the managed SSH password.
On 2026-08-06, key-only IPv4 SSH was verified for both managed APs using the
`zhiqiangf` account. Verify future access without permitting password or
keyboard-interactive fallback:
```bash
ssh -4 -o BatchMode=yes -o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no -o StrictHostKeyChecking=yes \
zhiqiangf@192.168.66.6 'mca-cli-op info'
ssh -4 -o BatchMode=yes -o PasswordAuthentication=no \
-o KbdInteractiveAuthentication=no -o StrictHostKeyChecking=yes \
zhiqiangf@192.168.55.5 'mca-cli-op info'
```
Both devices reported `Connected` to
`http://192.168.66.46:9080/inform` during that verification. The AP shell maps
this management account to UID 0, so treat it as administrative access.
## Offline AP recovery
An AP can be reachable via ICMP and SSH but offline in the controller when its
saved inform URL still points to port `8080`. This occurred for UAP-AC-Lite
after its inform configuration had diverged from the controller endpoint.
From the AP, inspect the active endpoint and test controller reachability:
```bash
info
nc 192.168.66.46 9080
```
The AP's BusyBox `nc` may not support `-zv`; use the plain `nc` invocation
above when testing the TCP path.
If `info` reports `http://192.168.66.46:8080/inform` (or another obsolete
endpoint), correct it. UniFi devices commonly require the command twice while
adoption is completed:
```bash
set-inform http://192.168.66.46:9080/inform
set-inform http://192.168.66.46:9080/inform
```
Verify that the AP changes to connected/online in the controller.
## Controller migration check
After a controller migration or restore, inspect stored device records for a
stale default-port endpoint before treating an AP as faulty:
```javascript
use ace
db.device.find(
{ inform_url: /8080/ },
{ name: 1, ip: 1, inform_url: 1 }
)
```
Do not include credentials or controller database dumps in incident notes.
+102
View File
@@ -0,0 +1,102 @@
# gw — EdgeRouter X primary LAN gateway
## Role and access
| Item | Value |
|---|---|
| Role | Primary router for the `192.168.66.0/24` and `192.168.55.0/24` LANs |
| IPv4 | `192.168.66.254` |
| Verified hostname | `gw` |
| SSH | `ssh -4 zhiqiang@192.168.66.254` |
| Platform | Ubiquiti EdgeRouter X 5-Port, EdgeOS `v3.0.1` (Build `5862409`) |
| Kernel | `4.14.54-UBNT` |
| Time zone | `Asia/Shanghai` |
Use `zhiqiang` as the default account for all routine gateway operations. Its
key-only SSH login and passwordless `sudo` were verified on 2026-08-04; use
`BatchMode=yes` for unattended read-only checks. `ubnt` remains an existing
administrative account, but is not the routine operations entry point.
Do not store account passwords, PPPoE credentials, SSH keys, configuration
password hashes, or UISP/UNMS connection strings in this repository. Both
administrative accounts have EdgeOS configuration and sudo privileges. Verify a
new SSH host key out of band before accepting it.
## Network topology
| Interface | Address / role |
|---|---|
| `eth0` | `192.168.66.254/24`; LAN 66 |
| `switch0` (`eth1``eth3`) | `192.168.55.254/24`; LAN 55 |
| `eth4` | WAN physical port; PPPoE uplink |
| `pppoe0` | WAN default route; MTU `1492`; IPv6 prefix delegation `/60` |
IPv6 prefix delegation assigns SLAAC-capable `/64` networks to both LANs.
`eth4` applies the WAN IPv4 and IPv6 firewall policies.
Detailed effective configuration, including firewall binding and WAN exposure,
is recorded in [the EdgeRouter X configuration record](../docs/edgerouter-x-configuration.md).
## Services and policy
- DHCP serves both LANs with 24-hour leases. Client DNS is `192.168.66.36`.
Pools are `.38``.243` on both networks; selected infrastructure and client
addresses have static mappings. The UniFi controller is advertised as
`192.168.66.46`.
- DNS forwarding listens on `eth0` and `switch0` with a cache size of 512.
- WAN NAT masquerades all IPv4 traffic leaving `pppoe0`.
- `WAN_IN` and `WAN_LOCAL` default to drop, permitting established/related
sessions and rejecting invalid state. Equivalent IPv6 policies permit
necessary ICMPv6 and DHCPv6 traffic.
- `LAN_IN` defines a default drop plus explicit `55 → 66` and `66 → 55`
allows, but it is **not currently applied to an interface**. The active
inter-VLAN posture is therefore ordinary routed access: LAN55 and LAN66 can
communicate bidirectionally unless a different active policy intervenes.
`LAN_OUT` is likewise defined but inactive; WAN policies are active on
`pppoe0`.
- Management services: SSH on TCP 22; EdgeOS GUI on HTTP 80 and HTTPS 443.
## Safe inspection
Use the EdgeOS operational CLI for routine inspection. `show configuration
commands` can expose credentials and remote-management tokens, so either avoid
copying it or filter sensitive lines before recording output.
```bash
ssh -4 -o BatchMode=yes zhiqiang@192.168.66.254
show version
show interfaces
show ip route
show system uptime
show firewall
```
The [official EdgeOS User Guide](https://dl.ubnt.com/guides/edgemax/EdgeOS_UG.pdf)
explains that a firewall policy is a ruleset and must be applied to the
relevant interface/direction to take effect. Use the operational `show
firewall` output—not merely the configured rule definitions—to determine the
effective policy.
## Maintenance notes
- EdgeOS writes persistent changes through its configuration tree: enter
`configure`, make the intended change, then run `commit` and `save`.
- The current SSH service reported a non-post-quantum key-exchange warning.
Treat any cryptography or SSH-policy maintenance as a planned, lockout-safe
change following the repository SSH safety procedure.
- This device is distinct from `gfw.windy.lan` (`192.168.66.1`), the OpenWrt
OpenClash gateway. Do not apply OpenWrt procedures to this router.
## Verification record
Configuration and reachability were checked by read-only SSH on 2026-08-04.
No network policy changes were made during that inspection. The `zhiqiang`
account password was subsequently rotated using the EdgeOS configuration tree
and verified by a separate SSH login; the password is intentionally not
recorded here.
Inter-VLAN access was rechecked by read-only SSH on 2026-08-05. A probe sourced
from `192.168.55.254` reached the UniFi controller at `192.168.66.46` with
3/3 ICMP replies. This supports the AP Inform path to
`192.168.66.46:9080`; the controller listener and an online LAN55 AP provide
the corresponding application-level evidence. No firewall changes were made.
+9 -1
View File
@@ -25,6 +25,7 @@ Verify a changed key out of band before accepting it.
| Image | `jacobalberty/unifi:v9.5.21` |
| Network mode | host |
| HTTPS UI | `https://192.168.66.46:8443` |
| Device inform endpoint | `http://192.168.66.46:9080/inform` |
The controller binds `*:8443`. A local HTTPS request to
`https://127.0.0.1:8443/` is the primary quick health check.
@@ -43,6 +44,14 @@ Without this bypass, a controller request to `localhost:8443` can be sent to
the OpenClash gateway instead of remaining local. Details and verification are
in [the UniFi localhost proxy note](../docs/unifi-openclash-localhost.md).
## Managed access points
The controller manages the APs listed in [the UniFi network record](../docs/unifi-network.md).
The inform endpoint is deliberately on port `9080`, not UniFi's common default
of `8080`. During adoption or recovery, use the documented `:9080/inform` URL;
an AP left on `:8080` can remain reachable by ping and SSH while showing
offline in the controller.
## Safe reconciliation and verification
```bash
@@ -55,4 +64,3 @@ curl -k -sS -o /dev/null -w '%{http_code}\n' https://127.0.0.1:8443/
Recreating the service briefly interrupts the controller. Do not use `docker
compose down -v`; it can remove persistent data.
+61
View File
@@ -0,0 +1,61 @@
# us4.wsvc.info
| Item | Value |
|------|-------|
| Role | WireGuard VPN server (LinuxServer Docker image) |
| SSH | `ssh -4 windy@us4.wsvc.info` (prefer IPv4 from WSL) |
| IPv4 | `185.201.226.122` |
| Compose project | `/opt/wireguard` |
| Compose file | `/opt/wireguard/compose.yml` |
| Container | `wireguard` |
| Image policy | Immutable digest, updated only in an approved maintenance window |
| Public port | UDP `51820` on IPv4 and IPv6 |
| Tunnel subnet | `10.13.13.0/24` |
| Routing policy | IPv4-only full tunnel (`ALLOWEDIPS=0.0.0.0/0`); IPv6 traffic is not guaranteed to use the VPN |
## Safety
- Private keys, preshared keys, peer configuration files, and QR codes remain
only in `/opt/wireguard/config`; do not copy them to this repository or
Linear.
- Local rollback archives are stored in `/opt/wireguard/backups` (directory
mode `0700`, archives mode `0600`). They contain private keys, are not an
off-host disaster-recovery backup, and must never leave the server.
- Do not delete, move, or regenerate `/opt/wireguard/config` during
maintenance.
- Before a container recreation, validate `docker compose config` and retain a
verified SSH session for rollback. Do not run `docker compose down -v`.
## Routine operations
Run read-only checks through Ansible:
```bash
cd ansible
ansible-playbook playbooks/health-report.yml --limit wireguard
```
The image update and recreate procedure is deliberately separate and requires
an immutable image digest in the server-side Compose file plus an explicit
maintenance-window confirmation:
```bash
cd ansible
ansible-playbook playbooks/wireguard-maintenance.yml --limit wireguard \
-e '{"wireguard_maintenance_confirm": true}'
```
Before that recreate, apply the reviewed Compose hardening from the repository:
```bash
ansible-playbook playbooks/wireguard-harden.yml --limit wireguard \
-e '{"wireguard_harden_confirm": true}'
```
## Verification
- Container `wireguard` is running and interface `wg0` exists.
- UDP `51820` is listening on the host.
- Validate a known client can handshake and sends IPv4 traffic through the VPN.
- Do not treat inactive mobile peers as a failure solely because their latest
handshake is old.
+2 -1
View File
@@ -13,7 +13,7 @@ diagnosis and procedures that are deliberately interactive or destructive; see
| repo.windy.me | Soft Serve git (on us2) | `ssh -p 2222 windy@repo.windy.me` | 193.9.44.165 | stub | see us2 |
| auth.wsvc.info | Vaultwarden public hostname | — (HTTPS) | → us2 | active | see us2 |
| us1.wsvc.info | PowerDNS secondary (ns2 host) | TBD | 202.91.35.141 | stub | Auth 5.0.5; see hk2 |
| us4.wsvc.info | Traefik / services | TBD | TBD | stub | — |
| us4.wsvc.info | WireGuard VPN | `ssh -4 windy@us4.wsvc.info` | 185.201.226.122 | active | [hosts/us4.wsvc.info.md](../hosts/us4.wsvc.info.md) |
| hk2.chans.xyz | PowerDNS auth (ns1) | `ssh -4 windy@hk2.chans.xyz` | 154.36.174.161 | active | [hosts/hk2.chans.xyz.md](../hosts/hk2.chans.xyz.md) |
| ns1.wsvc.info | PowerDNS public NS name | — (DNS) | → hk2 `154.36.174.161` | active | see hk2 |
| ns2.wsvc.info | Secondary NS (AXFR/NOTIFY peer) | — (DNS) | → us1 `202.91.35.141` | active | see hk2 |
@@ -21,6 +21,7 @@ diagnosis and procedures that are deliberately interactive or destructive; see
| pgweb.wsvc.info | PowerDNS Postgres UI | — (HTTPS) | → hk2 | active | see hk2 |
| **synapse.chans.xyz** | Matrix homeserver (ESS: Synapse + MAS + Element) | `ssh -4 windy@synapse.chans.xyz` | `169.58.86.13` | **active** | [hosts/synapse.chans.xyz.md](../hosts/synapse.chans.xyz.md) |
| **gfw.windy.lan** | OpenWrt LAN gateway / OpenClash | `ssh -4 root@192.168.66.1` | `192.168.66.1` | **active** | [hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md) |
| **gw** | EdgeRouter X primary LAN gateway | `ssh -4 zhiqiang@192.168.66.254` | `192.168.66.254` | **active** | [hosts/gw.md](../hosts/gw.md) |
| **ubnt** | UniFi Network Controller | `ssh -4 windy@192.168.66.46` | `192.168.66.46` | **active** | [hosts/ubnt.md](../hosts/ubnt.md) |
`status: stub` = known to exist; fill `hosts/<name>.md` when next touched.