Document network operations and WireGuard maintenance
This commit is contained in:
@@ -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) |
|
| **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) |
|
| **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) |
|
| **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) |
|
| **ubnt** | UniFi Network Controller | `ssh -4 windy@192.168.66.46` | [hosts/ubnt.md](hosts/ubnt.md) |
|
||||||
|
|
||||||
### Public services
|
### Public services
|
||||||
|
|||||||
@@ -45,6 +45,17 @@ all:
|
|||||||
backup:
|
backup:
|
||||||
compose_args: [--no-deps, --force-recreate]
|
compose_args: [--no-deps, --force-recreate]
|
||||||
service_reconcile_traefik_restart_targets: [poweradmin]
|
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:
|
mailcow:
|
||||||
hosts:
|
hosts:
|
||||||
mx2:
|
mx2:
|
||||||
@@ -54,11 +65,15 @@ all:
|
|||||||
powerdns:
|
powerdns:
|
||||||
hosts:
|
hosts:
|
||||||
hk2:
|
hk2:
|
||||||
|
wireguard:
|
||||||
|
hosts:
|
||||||
|
us4:
|
||||||
docker_hosts:
|
docker_hosts:
|
||||||
children:
|
children:
|
||||||
mailcow:
|
mailcow:
|
||||||
vaultwarden:
|
vaultwarden:
|
||||||
powerdns:
|
powerdns:
|
||||||
|
wireguard:
|
||||||
# Matrix is a dedicated K3s node and intentionally remains outside the
|
# Matrix is a dedicated K3s node and intentionally remains outside the
|
||||||
# Docker-oriented managed group.
|
# Docker-oriented managed group.
|
||||||
k3s_servers:
|
k3s_servers:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
mailcow: mailcow.sh
|
||||||
vaultwarden: vaultwarden.sh
|
vaultwarden: vaultwarden.sh
|
||||||
pdns: pdns.sh
|
pdns: pdns.sh
|
||||||
|
wireguard: wireguard.sh
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -uo pipefail
|
set -o pipefail
|
||||||
exec '{{ healthcheck_install_root }}/{{ healthcheck_profiles[healthcheck_profile] }}' >> '{{ healthcheck_log_dir }}/healthcheck.log' 2>&1
|
'{{ 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
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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
@@ -25,6 +25,7 @@ Verify a changed key out of band before accepting it.
|
|||||||
| Image | `jacobalberty/unifi:v9.5.21` |
|
| Image | `jacobalberty/unifi:v9.5.21` |
|
||||||
| Network mode | host |
|
| Network mode | host |
|
||||||
| HTTPS UI | `https://192.168.66.46:8443` |
|
| 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
|
The controller binds `*:8443`. A local HTTPS request to
|
||||||
`https://127.0.0.1:8443/` is the primary quick health check.
|
`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
|
the OpenClash gateway instead of remaining local. Details and verification are
|
||||||
in [the UniFi localhost proxy note](../docs/unifi-openclash-localhost.md).
|
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
|
## Safe reconciliation and verification
|
||||||
|
|
||||||
```bash
|
```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
|
Recreating the service briefly interrupts the controller. Do not use `docker
|
||||||
compose down -v`; it can remove persistent data.
|
compose down -v`; it can remove persistent data.
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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 |
|
| 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 |
|
| 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 |
|
| 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) |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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) |
|
| **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) |
|
| **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) |
|
| **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.
|
`status: stub` = known to exist; fill `hosts/<name>.md` when next touched.
|
||||||
|
|||||||
Reference in New Issue
Block a user