Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d43fc5aa01 | ||
|
|
ab0fd16a92 | ||
|
|
9c1d5ab6fe | ||
|
|
562de68127 | ||
|
|
86bfb5a953 | ||
|
|
3ec51299cc | ||
|
|
7a54e8d9ec | ||
|
|
7b4c48eb93 | ||
|
|
ffb37a916e | ||
|
|
35577d0c5c | ||
|
|
e90eef9647 | ||
|
|
14a758a28a | ||
|
|
c386fe8136 | ||
|
|
e2884069a8 | ||
|
|
b03d7019c4 | ||
|
|
29bdf06383 |
@@ -27,7 +27,10 @@ Also readable as `agent.md` (symlink → this file).
|
||||
| **mx2.windy.me** | mailcow (`/opt/mail`, project `cow`) | `ssh -4 windy@mx2.windy.me` | [hosts/mx2.windy.me.md](hosts/mx2.windy.me.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) |
|
||||
| **synapse.chans.xyz** | Matrix ESS (Synapse + MAS + Element) on K3s | `ssh -4 windy@synapse.chans.xyz` | [hosts/synapse.chans.xyz.md](hosts/synapse.chans.xyz.md) |
|
||||
| **dns.windy.lan** | AdGuard Home LAN DNS + Mihomo explicit proxy | `ssh -4 windy@192.168.66.36` | [hosts/dns.windy.lan.md](hosts/dns.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) |
|
||||
|
||||
### Public services
|
||||
@@ -59,7 +62,7 @@ Also readable as `agent.md` (symlink → this file).
|
||||
|
||||
| Task | Path |
|
||||
|------|------|
|
||||
| mailcow health | [runbooks/mailcow-health.md](runbooks/mailcow-health.md) · `./scripts/check-mx2.sh` |
|
||||
| mailcow health | [runbooks/mailcow-health.md](runbooks/mailcow-health.md) |
|
||||
| mailcow update | [runbooks/mailcow-update.md](runbooks/mailcow-update.md) |
|
||||
| mailcow SMTP/IMAP client | [runbooks/mailcow-smtp-client.md](runbooks/mailcow-smtp-client.md) |
|
||||
| Vaultwarden health | [runbooks/vaultwarden-health.md](runbooks/vaultwarden-health.md) |
|
||||
@@ -68,16 +71,24 @@ Also readable as `agent.md` (symlink → this file).
|
||||
| PowerDNS health (hk2) | [runbooks/pdns-health.md](runbooks/pdns-health.md) |
|
||||
| PowerDNS upstream refs | [docs/pdns-upstream.md](docs/pdns-upstream.md) |
|
||||
| Matrix health | [runbooks/matrix-health.md](runbooks/matrix-health.md) |
|
||||
| AdGuard Home health | [runbooks/adguard-home-health.md](runbooks/adguard-home-health.md) |
|
||||
| Matrix upstream refs | [docs/matrix-upstream.md](docs/matrix-upstream.md) |
|
||||
| Hermes Agent Matrix channel | [docs/hermes-matrix.md](docs/hermes-matrix.md) |
|
||||
| UniFi local-service proxy bypass | [docs/unifi-openclash-localhost.md](docs/unifi-openclash-localhost.md) |
|
||||
| UniFi SSO login setting (Ansible) | `cd ansible && ansible-playbook playbooks/unifi-sso.yml --limit unifi` |
|
||||
| Routine Ansible operations | [runbooks/ansible-operations.md](runbooks/ansible-operations.md) |
|
||||
|
||||
```bash
|
||||
./scripts/check-mx2.sh
|
||||
```
|
||||
Routine mailcow health: `cd ansible && ansible-playbook playbooks/health-report.yml --limit mailcow`. The local stub resolver is flaky; DNS probes use `1.1.1.1` / `8.8.8.8`.
|
||||
|
||||
`check-mx2.sh` defaults to IPv4; set `MX2_ALLOW_IPV6=1` to allow IPv6. DNS checks use `1.1.1.1` / `8.8.8.8` (local stub is flaky).
|
||||
## Agent skills
|
||||
|
||||
### Issue tracker
|
||||
|
||||
Issues are tracked in Linear and created/updated via the Linear MCP (`vps` project). See `docs/agents/issue-tracker.md`.
|
||||
|
||||
### Triage labels
|
||||
|
||||
Default triage labels: needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix. See `docs/agents/triage-labels.md`.
|
||||
|
||||
## Safety
|
||||
|
||||
@@ -115,5 +126,4 @@ inventory/hosts.md # machine index
|
||||
hosts/ # per-host facts
|
||||
runbooks/ # step-by-step ops
|
||||
docs/ # upstream doc indexes / design notes
|
||||
scripts/ # read-only helper scripts
|
||||
```
|
||||
|
||||
@@ -45,6 +45,26 @@ 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]
|
||||
dns_windy_lan:
|
||||
ansible_host: 192.168.66.36
|
||||
ansible_host_ipv4: 192.168.66.36
|
||||
service_role: adguardhome
|
||||
compose_project_dir: /opt/adguardhome
|
||||
healthcheck_profile: adguardhome
|
||||
service_reconcile_services:
|
||||
adguardhome:
|
||||
compose_args: [--no-deps, --force-recreate]
|
||||
mailcow:
|
||||
hosts:
|
||||
mx2:
|
||||
@@ -54,11 +74,30 @@ all:
|
||||
powerdns:
|
||||
hosts:
|
||||
hk2:
|
||||
wireguard:
|
||||
hosts:
|
||||
us4:
|
||||
adguardhome:
|
||||
hosts:
|
||||
dns_windy_lan:
|
||||
unifi:
|
||||
hosts:
|
||||
ubnt:
|
||||
ansible_host: 192.168.66.46
|
||||
ansible_host_ipv4: 192.168.66.46
|
||||
vars:
|
||||
service_role: unifi
|
||||
compose_project_dir: /home/windy/unifi-9
|
||||
unifi_container: unifi-controller
|
||||
unifi_mongo_port: 27117
|
||||
docker_hosts:
|
||||
children:
|
||||
mailcow:
|
||||
vaultwarden:
|
||||
powerdns:
|
||||
wireguard:
|
||||
adguardhome:
|
||||
unifi:
|
||||
# Matrix is a dedicated K3s node and intentionally remains outside the
|
||||
# Docker-oriented managed group.
|
||||
k3s_servers:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# Reconcile the UniFi controller SSO login setting
|
||||
# (super_sdn.sso_login_enabled = "Sync Local Admin with Ubiquiti SSO").
|
||||
#
|
||||
# Safe by default: run with --check for a read-only status report, or supply
|
||||
# unifi_sso_confirm=true to apply the declared target value.
|
||||
#
|
||||
# # Read-only status
|
||||
# ansible-playbook playbooks/unifi-sso.yml --limit ubnt --check
|
||||
#
|
||||
# # Apply (disable SSO login; local accounts use local passwords, no MFA)
|
||||
# ansible-playbook playbooks/unifi-sso.yml --limit ubnt \
|
||||
# -e '{"unifi_sso_confirm": true, "unifi_sso_target_value": false}'
|
||||
#
|
||||
# # Rollback (re-enable SSO login)
|
||||
# ansible-playbook playbooks/unifi-sso.yml --limit ubnt \
|
||||
# -e '{"unifi_sso_confirm": true, "unifi_sso_target_value": true}'
|
||||
- name: Reconcile UniFi SSO login setting
|
||||
hosts: unifi
|
||||
become: true
|
||||
gather_facts: false
|
||||
serial: 1
|
||||
roles:
|
||||
- role: unifi_sso
|
||||
tags: [unifi, sso, mutating]
|
||||
@@ -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,5 @@ healthcheck_profiles:
|
||||
mailcow: mailcow.sh
|
||||
vaultwarden: vaultwarden.sh
|
||||
pdns: pdns.sh
|
||||
wireguard: wireguard.sh
|
||||
adguardhome: adguardhome.sh
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
source '{{ healthcheck_install_root }}/health-common.sh'
|
||||
|
||||
require_command docker
|
||||
require_command curl
|
||||
require_command dig
|
||||
require_command ss
|
||||
require_command df
|
||||
check_compose
|
||||
|
||||
container='adguardhome'
|
||||
if ! docker inspect "$container" >/dev/null 2>&1; then
|
||||
record critical 'adguardhome_container_missing'
|
||||
else
|
||||
running="$(docker inspect "$container" --format '{{ '{{' }}.State.Running{{ '}}' }}' 2>/dev/null)"
|
||||
[[ "$running" == true ]] || record critical 'adguardhome_container_not_running'
|
||||
fi
|
||||
|
||||
config_check="$(docker exec "$container" /opt/adguardhome/AdGuardHome --check-config \
|
||||
-c /opt/adguardhome/conf/AdGuardHome.yaml 2>&1 || true)"
|
||||
if grep -qiE 'error|invalid|fatal' <<<"$config_check"; then
|
||||
record critical 'adguardhome_config_invalid'
|
||||
else
|
||||
record ok 'adguardhome_config_valid'
|
||||
fi
|
||||
|
||||
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)53$'; then
|
||||
record ok 'dns_tcp_53_listening'
|
||||
else
|
||||
record critical 'dns_tcp_53_missing'
|
||||
fi
|
||||
if ss -H -uln 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)53$'; then
|
||||
record ok 'dns_udp_53_listening'
|
||||
else
|
||||
record critical 'dns_udp_53_missing'
|
||||
fi
|
||||
if ss -H -ltn 2>/dev/null | awk '{print $4}' | grep -Eq '(^|:)80$'; then
|
||||
record ok 'ui_http_80_listening'
|
||||
else
|
||||
record critical 'ui_http_80_missing'
|
||||
fi
|
||||
|
||||
dns_a="$(dig +time=5 +tries=1 @192.168.66.36 example.com A +noall +answer 2>/dev/null)"
|
||||
[[ -n "$dns_a" ]] && record ok 'dns_example_a' || record critical 'dns_example_a_failed'
|
||||
dns_aaaa="$(dig +time=5 +tries=1 @192.168.66.36 example.com AAAA +noall +answer 2>/dev/null)"
|
||||
[[ -n "$dns_aaaa" ]] && record ok 'dns_example_aaaa' || record warning 'dns_example_aaaa_none'
|
||||
dns_local="$(dig +time=5 +tries=1 @192.168.66.36 hass.windy.lan A +noall +answer 2>/dev/null)"
|
||||
[[ -n "$dns_local" ]] && record ok 'dns_local_name' || record warning 'dns_local_name_unresolved'
|
||||
|
||||
ui_code="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' --max-time 10 http://192.168.66.36/ 2>/dev/null || true)"
|
||||
case "$ui_code" in
|
||||
200|301|302) record ok "ui_http_${ui_code}" ;;
|
||||
'') record critical 'ui_unreachable' ;;
|
||||
*) record critical "ui_http_${ui_code}" ;;
|
||||
esac
|
||||
|
||||
usage="$(df -P /opt/adguardhome/work 2>/dev/null | awk 'NR==2 {print $5}' | tr -d '%')"
|
||||
if [[ "$usage" =~ ^[0-9]+$ ]]; then
|
||||
if (( usage >= 90 )); then record critical 'disk_usage_high'
|
||||
elif (( usage >= 80 )); then record warning 'disk_usage_elevated'
|
||||
else record ok 'disk_usage_ok'; fi
|
||||
else
|
||||
record unknown 'disk_usage_unknown'
|
||||
fi
|
||||
|
||||
logs="$(docker logs --since 30m "$container" 2>&1 || true)"
|
||||
if grep -qiE '\[(error|warn|fatal)\]|level=(error|warn|fatal)' <<<"$logs"; then
|
||||
record warning 'adguardhome_log_errors'
|
||||
else
|
||||
record ok 'adguardhome_logs_clean'
|
||||
fi
|
||||
|
||||
emit_result
|
||||
exit "$EXIT_CODE"
|
||||
@@ -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,11 @@
|
||||
---
|
||||
# Reconcile super_sdn.sso_login_enabled on the UniFi Network Controller DB.
|
||||
# Safe by default: without unifi_sso_confirm=true the role only reports the
|
||||
# current state and refuses to change anything.
|
||||
unifi_sso_confirm: false
|
||||
# Declared desired state (true = Sync Local Admin with Ubiquiti SSO ON).
|
||||
unifi_sso_target_value: false
|
||||
# Restart the controller container after applying a change. Only needed when
|
||||
# the setting was changed directly in the DB while the controller was running
|
||||
# (the UI path applies it immediately without a restart).
|
||||
unifi_sso_restart_controller: false
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
# Reconcile super_sdn.sso_login_enabled on the UniFi controller DB.
|
||||
# Idempotent: reads the current value, reports compliance when it already
|
||||
# matches the target, and only mutates with explicit confirmation.
|
||||
|
||||
- name: Require a boolean target value
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- unifi_sso_target_value | type_debug == 'bool'
|
||||
fail_msg: "unifi_sso_target_value must be a boolean (true or false)."
|
||||
|
||||
- name: Read current SSO login setting
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- "{{ unifi_container }}"
|
||||
- mongo
|
||||
- --port
|
||||
- "{{ unifi_mongo_port }}"
|
||||
- --quiet
|
||||
- ace
|
||||
- --eval
|
||||
- 'db.setting.findOne({key:"super_sdn"}).sso_login_enabled'
|
||||
register: unifi_sso_current
|
||||
changed_when: false
|
||||
failed_when: unifi_sso_current.rc != 0
|
||||
check_mode: false
|
||||
|
||||
- name: Report current setting
|
||||
ansible.builtin.debug:
|
||||
msg: >-
|
||||
sso_login_enabled={{ unifi_sso_current.stdout | trim }}
|
||||
(target={{ unifi_sso_target_value | string | lower }})
|
||||
|
||||
- name: Report already compliant
|
||||
ansible.builtin.debug:
|
||||
msg: "Already compliant; no change needed."
|
||||
when: unifi_sso_current.stdout | trim | lower == unifi_sso_target_value | string | lower
|
||||
|
||||
- name: Refuse to change without explicit confirmation
|
||||
ansible.builtin.fail:
|
||||
msg: >-
|
||||
Change required (sso_login_enabled={{ unifi_sso_current.stdout | trim }}
|
||||
!= target {{ unifi_sso_target_value | string | lower }}) but
|
||||
unifi_sso_confirm is not true. Supply unifi_sso_confirm=true to apply.
|
||||
when:
|
||||
- unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower
|
||||
- not (unifi_sso_confirm | bool)
|
||||
|
||||
- name: Apply SSO login setting change
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- "{{ unifi_container }}"
|
||||
- mongo
|
||||
- --port
|
||||
- "{{ unifi_mongo_port }}"
|
||||
- --quiet
|
||||
- ace
|
||||
- --eval
|
||||
- >-
|
||||
db.setting.updateOne({key:"super_sdn"},
|
||||
{$set:{sso_login_enabled: {{ unifi_sso_target_value | string | lower }}}})
|
||||
when:
|
||||
- unifi_sso_confirm | bool
|
||||
- unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower
|
||||
changed_when: true
|
||||
register: unifi_sso_update
|
||||
|
||||
- name: Restart controller to apply setting when requested
|
||||
ansible.builtin.command:
|
||||
argv: [docker, restart, "{{ unifi_container }}"]
|
||||
when:
|
||||
- unifi_sso_restart_controller | bool
|
||||
- unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower
|
||||
changed_when: true
|
||||
|
||||
- name: Verify setting after change
|
||||
ansible.builtin.command:
|
||||
argv:
|
||||
- docker
|
||||
- exec
|
||||
- "{{ unifi_container }}"
|
||||
- mongo
|
||||
- --port
|
||||
- "{{ unifi_mongo_port }}"
|
||||
- --quiet
|
||||
- ace
|
||||
- --eval
|
||||
- 'db.setting({key:"super_sdn"}).sso_login_enabled'
|
||||
register: unifi_sso_verify
|
||||
changed_when: false
|
||||
check_mode: false
|
||||
when:
|
||||
- unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower
|
||||
- not ansible_check_mode
|
||||
|
||||
- name: Assert verified value matches target
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- unifi_sso_verify.stdout | trim | lower == unifi_sso_target_value | string | lower
|
||||
fail_msg: >-
|
||||
Verification failed: sso_login_enabled is
|
||||
{{ unifi_sso_verify.stdout | trim }} but expected
|
||||
{{ unifi_sso_target_value | string | lower }}.
|
||||
when:
|
||||
- unifi_sso_current.stdout | trim | lower != unifi_sso_target_value | string | lower
|
||||
- not ansible_check_mode
|
||||
@@ -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,164 @@
|
||||
# AdGuard Home official configuration review basis (2026-08)
|
||||
|
||||
This page is the **official source basis and recommendation baseline** used
|
||||
during the on-site review of AdGuard Home (AGH) on `192.168.66.36`. It does not
|
||||
claim to have read that host's configuration and contains no credentials.
|
||||
On-site conclusions must be based on the live `AdGuardHome.yaml`, OpenWrt
|
||||
`dnsmasq`/DHCP, firewall, routing, and actual client leases.
|
||||
|
||||
Official sources (AdGuard only):
|
||||
|
||||
- [Configuration](https://adguard-dns.io/kb/adguard-home/configuration/)
|
||||
- [Setting up AdGuard Home securely](https://adguard-dns.io/kb/adguard-home/running-securely/)
|
||||
- [Getting started](https://adguard-dns.io/kb/adguard-home/getting-started/)
|
||||
- [DHCP](https://adguard-dns.io/kb/adguard-home/dhcp/)
|
||||
- [DNS encryption](https://adguard-dns.io/kb/adguard-home/encryption/)
|
||||
|
||||
## Recommended role split (for the existing OpenWrt gateway)
|
||||
|
||||
Preferred: keep **OpenWrt/dnsmasq as the single DHCP and local-domain
|
||||
authority, with AGH as the LAN DNS filter/forwarder**: DHCP option 6 hands
|
||||
clients `.36`; AGH sets the router's local DNS (usually the gateway LAN IP) as
|
||||
`local_ptr_upstreams` and, where needed, uses domain-specific upstreams to route
|
||||
the LAN search domain / local names back to dnsmasq. Clients are filtered while
|
||||
still keeping DHCP lease names, static hosts, local reverse resolution, and
|
||||
gateway-internal service discovery.
|
||||
|
||||
Rationale: AGH supports dnsmasq-style domain-specific upstreams for internal
|
||||
private name servers; private PTR should be configured in **Private reverse DNS
|
||||
servers** / `local_ptr_upstreams` rather than regular upstreams; when not
|
||||
explicitly configured, it falls back to the OS default resolver, which shifts as
|
||||
host networking changes. [Official config: domain-specific upstreams](https://adguard-dns.io/kb/adguard-home/configuration/#configuring-upstreams);
|
||||
[private rDNS](https://adguard-dns.io/kb/adguard-home/configuration/#private-addresses).
|
||||
|
||||
Do not have OpenWrt dnsmasq and AGH both listen on `:53` at the same LAN IP;
|
||||
port conflicts, or pointing dnsmasq's upstream at AGH while AGH points back at
|
||||
dnsmasq, cause failures/forwarding loops. When AGH runs on a separate `.36`
|
||||
host, the simplest topology is clients → AGH `.36:53` → public/encrypted
|
||||
upstreams, forwarding only local resolution (PTR, LAN domain) → gateway dnsmasq.
|
||||
This is an operational inference based on the official capabilities above and
|
||||
must be validated against the live `ss -lntup`, dnsmasq config, and query path.
|
||||
|
||||
AGH also has built-in DHCP, but it should only be enabled after **disabling the
|
||||
existing gateway DHCP, explicitly migrating leases/reservations and verifying
|
||||
options 3/6**. The official docs describe it as an alternative when the router
|
||||
cannot hand out custom DNS, not as a dual-DHCP setup; the DHCP docs also state
|
||||
leases/static leases are managed by AGH. [Getting started: router DHCP/DNS
|
||||
handout and alternative DHCP](https://adguard-dns.io/kb/adguard-home/getting-started/#router);
|
||||
[DHCP configuration](https://adguard-dns.io/kb/adguard-home/dhcp/).
|
||||
|
||||
## Exposure and access control
|
||||
|
||||
1. `dns.bind_hosts` should bind only loopback and the actual LAN address (here
|
||||
prefer `.36`'s LAN address); do not use `0.0.0.0`/public interfaces for a
|
||||
home recursive resolver. The official docs note full-interface binding
|
||||
exposes DDoS risk; a small isolated network should pick local service
|
||||
interfaces and may add loopback alongside. [Secure deployment: server
|
||||
addresses](https://adguard-dns.io/kb/adguard-home/running-securely/#choosing-server-addresses);
|
||||
[`bind_hosts` definition](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
2. In the firewall, allow only managed LAN/VLAN → `.36` TCP/UDP 53; bind the web
|
||||
admin UI to the management network/loopback and avoid WAN forwarding. At the
|
||||
app layer, further restrict with `allowed_clients` to the local subnets; when
|
||||
non-empty, only the CIDR/IP/ClientIDs in it are accepted, and it overrides
|
||||
the effect of `disallowed_clients`. [Official access
|
||||
control](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
3. Keep `refuse_any: true` and a non-zero `ratelimit` (default 20 qps), unless
|
||||
on-site evidence shows the service can never be reached from the internet and
|
||||
LAN high-concurrency needs are data-backed; the official docs list both as
|
||||
protection against reflection/DDoS. [Configuration
|
||||
notes](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
4. The web UI must have a user with login-failure lockout enabled;
|
||||
`users: []` disables authentication, and `auth_attempts` or `block_auth_min`
|
||||
of 0 disables the corresponding lockout logic. [Web/auth
|
||||
parameters](https://adguard-dns.io/kb/adguard-home/configuration/#users).
|
||||
|
||||
## Upstreams, encryption, and DNSSEC
|
||||
|
||||
- Use two trusted, differently-networked DoH/DoT/DoQ upstreams for normal public
|
||||
resolution; AGH supports these protocols and DNS Stamps. If an upstream
|
||||
URL/hostname needs resolving, `bootstrap_dns` is its initial resolver; do not
|
||||
point bootstrap at a name that requires AGH itself to resolve, and use
|
||||
directly reachable, stable IPs. A `fallback_dns` can be set separately; it is
|
||||
used only when primary upstreams do not respond. [Upstream protocols and
|
||||
bootstrap/fallback](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
- If local IPv6's actual egress path is unstable, do not enable
|
||||
`bootstrap_prefer_ipv6`; the official docs note it makes DoH/DoQ/DoT bootstrap
|
||||
prefer IPv6. Decide on-site based on each upstream's IPv4/IPv6 reachability,
|
||||
latency, and failure rate. [Same reference](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
- Consider enabling `enable_dnssec`, choosing recursive upstreams that actually
|
||||
handle DNSSEC; but understand precisely: officially AGH sets the DO flag on
|
||||
requests to upstreams; it does not perform recursive validation itself. On
|
||||
site, query a known-bad-signature domain and observe AD/failure responses to
|
||||
confirm upstream validation behavior. [DNSSEC parameter
|
||||
definition](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
- Prefer `upstream_mode: load_balance` unless measurement shows `parallel` first
|
||||
answer latency is clearly worth the privacy/traffic cost of querying all
|
||||
upstreams. The former picks by failure rate and average latency weighting; the
|
||||
latter queries all upstreams concurrently; this is an operational judgment
|
||||
based on documented behavior. [Upstream
|
||||
modes](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
|
||||
## Cache, ECS, and privacy
|
||||
|
||||
- Keep DNS cache enabled; use `cache_ttl_min`/`cache_ttl_max` overrides
|
||||
cautiously to avoid stale or unnecessarily short TTLs. `cache_optimistic`
|
||||
answers with a 10-second TTL after expiry and attempts refresh — good for
|
||||
low-latency scenarios that tolerate briefly stale results, not to be enabled
|
||||
blindly on internal domains sensitive to immediate changes. [Cache
|
||||
parameters](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
- ECS is off by default: when enabled, AGH adds EDNS Client Subnet to upstream
|
||||
requests; if CDN geo-scheduling is truly needed, use `use_custom: true` with a
|
||||
coarse, non-real-client-subnet `custom_ip`, then compare hit rate vs. privacy
|
||||
impact. [ECS parameters](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
- Review log retention and who can read it. If device identity is not needed,
|
||||
consider `anonymize_client_ip: true`; this anonymizes client addresses in logs
|
||||
and stats. [Anonymization
|
||||
parameter](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
|
||||
## Proxy / OpenClash coexistence
|
||||
|
||||
If OpenClash/transparent proxy redirects DNS, validate hop by hop to ensure:
|
||||
client DNS requests reach AGH uniquely; AGH's outbound traffic to public
|
||||
encrypted upstreams is not hijacked back to AGH; and the router's local dnsmasq
|
||||
PTR/local-domain requests return to dnsmasq. Create explicit proxy/hijack
|
||||
exceptions for `.36` and its upstreams, or explicitly design the proxy to take
|
||||
over AGH's outbound resolution; do not rely on "it seems to resolve" to conclude
|
||||
there is no loop.
|
||||
|
||||
This is not an AGH-official OpenClash instruction (the official docs do not
|
||||
maintain that integration), but an on-site verification requirement derived from
|
||||
AGH's independent listening, upstream forwarding, and local-PTR-split
|
||||
capabilities. The officially supported mechanisms are referenced at
|
||||
[`bind_hosts`/upstreams/PTR](https://adguard-dns.io/kb/adguard-home/configuration/#dns).
|
||||
|
||||
If serving DoH via a reverse proxy, `trusted_proxies` must list only the actual
|
||||
proxy IP/CIDR; AGH will only trust `X-Real-IP`, `X-Forwarded-For`, etc. from
|
||||
those sources. Do not write the whole LAN/WAN for convenience;
|
||||
`http.doh.insecure_enabled` should only be enabled when a reverse proxy really
|
||||
terminates TLS in front. [Proxy and
|
||||
DoH](https://adguard-dns.io/kb/adguard-home/configuration/#dns); [encrypted DNS
|
||||
docs](https://adguard-dns.io/kb/adguard-home/encryption/). For a pure-LAN
|
||||
plaintext-53 service, publishing DoH/DoT/DoQ outward is usually unnecessary.
|
||||
|
||||
## On-site review checklist (read-only)
|
||||
|
||||
1. Record the version, service method, and the full effective configuration
|
||||
(secrets redacted); run the AGH config check. Officially provided via
|
||||
`--check-config`. [Command-line
|
||||
arguments](https://adguard-dns.io/kb/adguard-home/configuration/#command-line-arguments).
|
||||
2. On `.36`, the gateway, and a wired/wireless client, record `ss` listeners,
|
||||
DHCP option 6, default route, `/etc/config/dhcp`, and OpenClash
|
||||
DNS/firewall-redirect rules.
|
||||
3. From a client test: public A/AAAA, ad-rule hits, DHCP hostnames, forward LAN
|
||||
domain, private PTR, DNSSEC bad-signature domain, and the expected failure
|
||||
mode when AGH stops. Then check whether client IPs in AGH query logs are
|
||||
still genuinely identifiable.
|
||||
4. From the AGH host, test each upstream and bootstrap over the actual protocol
|
||||
for IPv4 and IPv6 reachability; compare with transparent proxy on/off to rule
|
||||
out loops and leaks.
|
||||
5. Verify the web UI/DNS ports have no WAN exposure; confirm allowed clients and
|
||||
firewall match actual LAN/VLAN, guest network, and VPN subnets.
|
||||
|
||||
Before changing anything, export the current configuration and DHCP/firewall
|
||||
state; adjust one layer at a time (DHCP handout, AGH listening, upstreams, or
|
||||
DNS hijack) and keep a known-good management and DNS fallback path.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Issue tracker: Linear
|
||||
|
||||
Issues and PRDs for this repo are tracked in Linear, driven entirely through the
|
||||
Linear MCP configured in the current runtime. VPS operation records use the
|
||||
Linear **`vps`** project (team `W1ndy`).
|
||||
|
||||
## Conventions
|
||||
|
||||
- **Create**: `linear_save_issue` (requires `title` + `team`); description as
|
||||
real-newline Markdown.
|
||||
- **Read**: `linear_get_issue <identifier>` (e.g. LIN-123), optionally with
|
||||
`includeRelations`.
|
||||
- **List**: `linear_list_issues`, filtered by `query` / `state` / `assignee` /
|
||||
`team` / `project`.
|
||||
- **Comment**: `linear_save_comment` (pass `issueId` + `body`).
|
||||
- **Labels**: `linear_save_issue` `labels` field (replaces the whole label set).
|
||||
- **Status**: `linear_save_issue` `state` field (type / name / ID).
|
||||
|
||||
Resolve team, project, and cycle names through the Linear MCP rather than
|
||||
hard-coding them.
|
||||
|
||||
## Cross-issue constraint baseline
|
||||
|
||||
The `plans/` directory is retired (removed 2026-08-06). Plans and baselines now
|
||||
live in the vps project document "VPS 运维重构:已确认决策与验收标准". Open work
|
||||
items are tracked as individual issues with dependencies expressed via
|
||||
`blockedBy` / `blocks`. Read that document before adding new tasks.
|
||||
|
||||
## When a skill says "publish to the issue tracker"
|
||||
|
||||
Create a Linear issue with `linear_save_issue` (`project: vps`).
|
||||
|
||||
## When a skill says "fetch the relevant ticket"
|
||||
|
||||
Use `linear_get_issue <identifier>`.
|
||||
|
||||
## Wayfinding operations
|
||||
|
||||
- **Map**: one issue as the map; child tickets link via `parentId`.
|
||||
- **Child ticket**: `linear_save_issue` with `parentId` set to the map; labels
|
||||
`wayfinder:<type>` (research / prototype / grilling / task).
|
||||
- **Blocking**: `linear_save_issue` `blockedBy` / `blocks` (append-only).
|
||||
- **Frontier query**: list the map's open children, excluding those blocked or
|
||||
already assigned; take the first in map order.
|
||||
- **Claim**: `linear_save_issue` `assignee: me`.
|
||||
- **Resolve**: `linear_save_comment` the answer → update `state` → append a
|
||||
pointer under the map's decisions-so-far.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Triage Labels
|
||||
|
||||
The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker.
|
||||
|
||||
| Label in mattpocock/skills | Label in our tracker | Meaning |
|
||||
| -------------------------- | -------------------- | ---------------------------------------- |
|
||||
| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue |
|
||||
| `needs-info` | `needs-info` | Waiting on reporter for more information |
|
||||
| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent |
|
||||
| `ready-for-human` | `ready-for-human` | Requires human implementation |
|
||||
| `wontfix` | `wontfix` | Will not be actioned |
|
||||
|
||||
When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table.
|
||||
|
||||
Edit the right-hand column to match whatever vocabulary you actually use.
|
||||
@@ -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,156 @@
|
||||
# LAN 核心交换机升级计划(保留 ER-X)
|
||||
|
||||
**状态:** SE5420 **已采购**(2026-08-09)。**实施与验证以 [lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md) 为准**;
|
||||
本文为历史规划参考,**不得作为现场执行步骤**;所有实际操作均以部署指南为准。
|
||||
**锁定硬件:** TP-Link **`TL-SE5420`**(16 × 2.5GbE RJ45 + 4 × 10GbE SFP+)。
|
||||
**目标:** SE5420 承接全部 LAN 物理接入与二层转发;ER-X 继续承担公网、NAT、防火墙、
|
||||
LAN66/LAN55 网关与 DHCP。
|
||||
|
||||
**拓扑与流量的详细说明**(职责、逻辑网、流量路径、Wi-Fi 分工、验收边界)见:
|
||||
[lan-erx-se5420-network.md](lan-erx-se5420-network.md)。
|
||||
|
||||
SE5420 官方资料:静态功耗 8 W、最大功耗 32 W;VLAN、LACP、STP/RSTP/MSTP、ACL、
|
||||
CLI/SNMP、配置导入导出与固件下载。无 PoE——AP 使用本地取电 + 普通网线。
|
||||
|
||||
## 已确认边界(摘要)
|
||||
|
||||
| 项 | 结论 |
|
||||
|---|---|
|
||||
| 硬目标 | 同 VLAN 2.5G;SFP+ 先空槽 |
|
||||
| 核心角色 | 纯 L2;不开 L3 / DHCP Server/Relay / NAT |
|
||||
| 网关 | 默认一律 ER-X `.254`;升级专用 SSID(后续)才走 `gfw` `.1` |
|
||||
| 本次 Done | 阶段 0–3;VLAN10 / 客人 SSID / 升级 SSID 另立项目 |
|
||||
| 切换 | 30–60 分钟维护窗;旧交换迁完后闲置 |
|
||||
| 首批 2.5G | NAS + 主力 PC(或 PVE) |
|
||||
| 管理 | 先本地 HTTPS/SSH;云以后再说 |
|
||||
|
||||
### 保留的 ER-X 职责
|
||||
|
||||
| 项目 | 迁移后职责 |
|
||||
|---|---|
|
||||
| PPPoE / WAN、NAT、端口转发、WAN 防火墙 | ER-X,不变 |
|
||||
| LAN66 (`192.168.66.0/24`) 默认网关与 DHCP | ER-X `eth0`,不变 |
|
||||
| LAN55 (`192.168.55.0/24`) 默认网关与 DHCP | ER-X `switch0`,不变 |
|
||||
| LAN66 ↔ LAN55 三层转发 | ER-X,不变 |
|
||||
| 升级专用 Wi-Fi 网关(后续) | `gfw` VM,不是 ER-X 或核心交换机 |
|
||||
|
||||
ER-X 与核心之间使用两条**独立无标签 access**(LAN66 + LAN55),不向 ER-X 送
|
||||
VLAN tag。
|
||||
|
||||
### 核心交换机职责
|
||||
|
||||
- 所有有线设备、AP 与 PVE 的物理接入;
|
||||
- VLAN66、VLAN55 的二层转发;
|
||||
- 为日后 U6 Lite 与 PVE 预留 VLAN10 trunk(本次不启用业务);
|
||||
- 同 VLAN 的 1G/2.5G 转发;
|
||||
- SFP+ 预留未来 10G(DAC/AOC/光;不用 10GBASE-T 作默认)。
|
||||
|
||||
## 目标拓扑(简图)
|
||||
|
||||
```text
|
||||
Internet
|
||||
|
|
||||
ER-X / PPPoE
|
||||
+-------+--------+
|
||||
| |
|
||||
eth0, LAN66 switch0 port, LAN55
|
||||
access access
|
||||
| |
|
||||
+-------+--------+
|
||||
|
|
||||
TL-SE5420 核心(纯 L2)
|
||||
| | |
|
||||
PVE / gfw U6 Lite LAN66/LAN55 access
|
||||
(日后 trunk) (日后 trunk) NAS, PC, dns, ubnt, AC-Lite …
|
||||
```
|
||||
|
||||
完整端口表、流量路径与 Wi-Fi 分工见
|
||||
[lan-erx-se5420-network.md](lan-erx-se5420-network.md)。
|
||||
|
||||
## VLAN 与端口设计
|
||||
|
||||
| VLAN / 逻辑网络 | 核心配置 | 网关 / DHCP | 用途 |
|
||||
|---|---|---|---|
|
||||
| 66 | access;PVE/U6 日后 trunk 的 native | ER-X `eth0` / `.254` | 主 LAN、管理 |
|
||||
| 55 | access | ER-X `switch0` / `.254` | LAN55、AC-Lite |
|
||||
| 10(后续) | 仅 PVE 与 U6 trunk 上 tagged | `gfw` | 升级专用 SSID |
|
||||
| 管理 | LAN66 管理地址;不新建 VLAN | reservation / 静态 | SE5420 管理面 |
|
||||
|
||||
管理地址分配前先查 ER-X DHCP/reservation;禁用不需要的 HTTP/Telnet;云管延后;
|
||||
保留 HTTPS/SSH 与离线配置备份(备份不进本仓库)。
|
||||
|
||||
### 初始端口分配
|
||||
|
||||
| 预留 | 对端 | 模式 |
|
||||
|---|---|---|
|
||||
| 铜口 1 | ER-X `eth0` | access VLAN66 |
|
||||
| 铜口 2 | ER-X `switch0` 成员口 | access VLAN55 |
|
||||
| 铜口 3 | PVE(`gfw`) | 现 access 66;日后 trunk native66+tag10 |
|
||||
| 铜口 4 | U6 Lite | 同上;本地取电 |
|
||||
| 铜口 5 | UAP-AC-Lite | access VLAN55;本地取电 |
|
||||
| 其余铜口 | NAS、PC、`dns`、`ubnt`… | 默认 access VLAN66 |
|
||||
| SFP+ 1–4 | 未来 | 空槽 |
|
||||
|
||||
## 分阶段实施与验收
|
||||
|
||||
### 阶段 0:采购前核验
|
||||
|
||||
1. 确认 SE5420 revision、保修、手册、固件页。
|
||||
2. 确认 VLAN trunk、RSTP/MSTP、LACP、镜像、配置导出、错误计数。
|
||||
3. SFP+ 模块本次不买;有对端后再选 DAC/AOC/光。
|
||||
4. 盘点线缆与对端(LAN66/55、PVE、两台 AP);确认关键 2.5G 链路可协商。
|
||||
|
||||
### 阶段 1:离线初始化核心
|
||||
|
||||
1. 仅电源 + 隔离管理本:管理 IP、强密码、时区、NTP、HTTPS/SSH;**关闭 L3/DHCP**。
|
||||
2. 导出初始配置(离线保存,不进仓库)。
|
||||
3. 建立 VLAN66/55/10 与端口模板;VLAN10 不接生产。
|
||||
4. 开启 RSTP/MSTP;不制造物理环路。
|
||||
|
||||
### 阶段 2:维护窗迁移 LAN66
|
||||
|
||||
1. 使用约定维护窗(约 30–60 分钟);保留原接线作回滚。
|
||||
2. 先接铜口 1 ↔ ER-X `eth0`;验证管理地址、`.254`、DNS `.36`、互联网。
|
||||
3. 逐台迁到 VLAN66 access;每台确认地址/DNS/路由/业务。
|
||||
4. 最后迁 `ubnt`、`dns`、NAS/PC;禁止无意双上行。
|
||||
|
||||
### 阶段 3:迁移 LAN55
|
||||
|
||||
1. 接铜口 2 ↔ ER-X `switch0` 成员口。
|
||||
2. 测试设备获 `192.168.55.x`、网关 `.254`、可达 DNS。
|
||||
3. AC-Lite → 铜口 5;确认 `.55.5` 与 Inform Connected。
|
||||
4. 旧交换下电闲置。
|
||||
|
||||
### 阶段 4:VLAN10 升级专用 Wi-Fi(独立项目)
|
||||
|
||||
不与本次 Done 捆绑。仅 U6;网关 `gfw`;详见
|
||||
[lan-erx-se5420-network.md](lan-erx-se5420-network.md) 第 6.5 / 8 节与
|
||||
[unifi-network.md](unifi-network.md)。
|
||||
|
||||
## 性能预期与不变瓶颈
|
||||
|
||||
- 同 VLAN、两端在核心上的 2.5G:可测 2.5G 级二层。
|
||||
- 上网与 LAN66↔LAN55:仍经 ER-X 1G 路径。
|
||||
- 空 SFP+ 不使网络「变成 10G」。
|
||||
|
||||
## 验收清单
|
||||
|
||||
- 管理面、口令、离线备份;
|
||||
- 端口速率与错误计数;
|
||||
- LAN66/55 的 DHCP、网关、DNS、互联网与关键本地服务;
|
||||
- 两台 AP Connected;Inform 仍为 `http://192.168.66.46:9080/inform`;
|
||||
- 无无意环路;旧交换已闲置;
|
||||
- (建议)两台 2.5G 终端同 VLAN `iperf3`。
|
||||
|
||||
## 回滚
|
||||
|
||||
验证失败则停迁、恢复原接线;不改 ER-X WAN/DHCP/SSH。VLAN10 失败只撤新 SSID。
|
||||
|
||||
## 参考
|
||||
|
||||
- [ER-X + SE5420 网络与拓扑说明](lan-erx-se5420-network.md)
|
||||
- [LAN 概览](lan-overview.md)
|
||||
- [ER-X 配置记录](edgerouter-x-configuration.md)
|
||||
- [UniFi 网络](unifi-network.md)
|
||||
- [`gfw`](../hosts/gfw.windy.lan.md)
|
||||
- [TL-SE5420 官方规格](https://www.tp-link.com.cn/product_2899.html?v=specification)
|
||||
@@ -0,0 +1,118 @@
|
||||
# 内网 DNS 架构调研与优化建议
|
||||
|
||||
> 状态:2026-08-12 调研,Linear **W1N-56**。基于网络工程师视角,方案待实施评审。
|
||||
> 2026-08-12 现场核查修正:gfw 上 mosdns 已并入 OpenClash DNS 链(作为 clash 的
|
||||
> `nameserver`,DIRECT 规则真实 IP 解析用),**不再闲置**;推荐方案(AGH 前端 +
|
||||
> `.36` 伴生 mosdns 后端)仍待评审落地。
|
||||
> 相关:`docs/lan-overview.md`、`hosts/dns.windy.lan.md`、`hosts/gfw.windy.lan.md`。
|
||||
|
||||
## 1. 现状(实测)
|
||||
|
||||
| 角色 | 部署 | 职责 | 是否在活动路径 |
|
||||
|------|------|------|----------------|
|
||||
| **AdGuard Home** | `192.168.66.36`(PVE VM 120,Docker host 网络) | EdgeRouter DHCP 通告给 LAN55/66 客户端的 DNS;广告/过滤、查询统计、Web 面板 | ✅ **是** |
|
||||
| **mosdns** | `192.168.66.1`(gfw OpenWrt)监听 `127.0.0.1:6052` | OpenClash custom DNS 的 `nameserver`(DIRECT 规则真实 IP 分流:国内→AGH `.36`,国外→国内公网 DNS `223.5.5.5`/`119.29.29.29`) | ✅ 网关侧(clash 消费,不面向客户端) |
|
||||
| **OpenClash / clash(meta)** | `192.168.66.1`(gfw) | gateway 自身/被劫持流量的 fake-ip + 代理,DNS 走 dnsmasq→clash `#7874` | 仅网关侧 |
|
||||
|
||||
**关键事实:** LAN 客户端 DNS 直连 `66.36`,**不经过** gfw(EdgeRouter `service dns forwarding` cache 512,通告 `.36`)。所以 gfw/clash 的 fake-ip 分流对"直连 AGH 的客户端"不起作用;gfw 上 mosdns 作为 clash 的 `nameserver` 供 DIRECT 规则连接的真实 IP 解析用(国内→AGH,国外→国内公网 DNS),不面向 LAN 客户端。
|
||||
|
||||
**现状缺口:**
|
||||
1. AGH 上游是**固定 DoH**(alidns/doh.pub,兜底 adg.chans.xyz),**没有"国内/国外分流"能力** → 国外域名解析易受 DNS 污染/时延差,也无法为不同 region 选最优上游。
|
||||
2. 国内/国外分流逻辑(geo)与代理分流逻辑(clash fake-ip)混在网关上,职责不清。
|
||||
|
||||
## 2. 两个候选方案评估
|
||||
|
||||
### 方案 A:AGH 单独作为统一入口(现状演进)
|
||||
- 优点:单解析点、面板/拦截/日志集中、维护简单。
|
||||
- 缺点:AGH 对 geo 分流 + 防污染支持弱(官方定位是"过滤/家长控制",见 adguard README "Encrypted DNS upstream... requires additional software")。固定 DoH 上游无法按域名 region 选路。→ **不足以解决防污染/分流问题。**
|
||||
|
||||
### 方案 B:mosdns 作为智能上游分流器
|
||||
mosdns(v5)用 `sequence` 编排:`geosite/geoip` 匹配器 → 国内域名转发国内 DoH、国外域名转发加密 DoH(防污染),可加 `cache`、`reject`(屏蔽)。
|
||||
- 优点:真正解决"国内快 / 国外不被污染"的分流;性能高(百万域名表也不卡)。
|
||||
- 缺点:纯转发器,无 Web 面板、无每客户端统计、拦截要靠域名表(不如 AGH 体验)。→ 单独当入口会退回原始体验。
|
||||
|
||||
**结论:两个方案是互补的,不是二选一。** 单用 A 无法分流防污染,单用 B 失去 AGH 的管理体验。
|
||||
|
||||
## 3. 推荐:分层架构(AGH 前端 + mosdns 后端)
|
||||
|
||||
```
|
||||
局域网客户端(DHCP DNS = 192.168.66.36)
|
||||
│
|
||||
▼
|
||||
AdGuard Home (66.36) ── 前端:广告/过滤、拦截表、每客户端统计、Web 面板
|
||||
│ 上游 = mosdns
|
||||
▼
|
||||
mosdns(66.36 伴生容器) ── 后端:智能分流 + 防污染
|
||||
│ - geosite:cn → 国内 DoH/UDP(aliDNS / 腾讯 DNSPod)
|
||||
│ - 其他 → 加密 DoH(Cloudflare/Google/自建 adg.chans.xyz)
|
||||
▼
|
||||
上游 DoH
|
||||
```
|
||||
|
||||
职责分离,每个工具只做自己最擅长的事:
|
||||
- **AGH = 策略/拦截/可观测**(拦截表、每客户端日志、面板)。AGH 原生干不了"按域名选路",所以不做分流。
|
||||
- **mosdns = 智能转发**(geo 分流 + 加密防污染)。不用它当入口,所以保持 AGH 的 UX。
|
||||
- **OpenClash(gfw)= 代理选路**(fake-ip + 规则决定"哪些流量走代理")。与"DNS 解析选上游"是**两个独立决策**,分开放在不同工具最干净——DNS 解析在 66.36 做,代理路由在网关做,互不耦合。
|
||||
|
||||
### 推荐部署位置:mosdns 与 AGH 同机(66.36),而非 gf(.1)
|
||||
|
||||
| 位置 | 评价 |
|
||||
|------|------|
|
||||
| **66.36 伴生容器(推荐)** | 单点即 AGH 所在;AGH→mosdns 走本机/近端一跳;不受网关重启/OpenClash churn 影响;可纳入现有 ansible compose 管理;不占用 OpenWrt 资源 |
|
||||
| gf(.1) | 虽近网络边缘,但该网关已有 clash fake-ip + 多种劫持规则,再叠 mosdns 会与 clash 的 DNS 处理互相干扰、耦合;且网关重启即断 DNS(影响整个 LAN)。**不推荐** |
|
||||
|
||||
> 注意:若把 mosdns 放 gfw,必须先理清与 OpenClash `dnsmasq→clash #7874` + `nft fw4 DNS-hijack` 的先后/覆盖关系,否则会出现"部分设备解析走了 clash、部分走了 mosdns"的混乱。放 66.36 则完全避开这个冲突。
|
||||
|
||||
## 4. 更优替代方案(也一并考虑)
|
||||
|
||||
1. **分层(推荐,见上)**:AGH(66.36)→ mosdns(66.36 伴生)→ 上游。体验最好、职责最清。
|
||||
2. **纯 mosdns + 前端面板**:不用 AGH,用 mosdns + 其他统计面板。→ 会明显损失拦截/统计管理体验,除非你讨厌 AGH 的 Docker 部署。**不推荐**用于替换。
|
||||
3. **AGH 只挂一个带分流的上游(第三方 DoH 聚合)**:例如接一个已做分流的公共 DoH。→ 失去可控性,且不可信。不推荐做主路径。
|
||||
4. **全部交给 OpenClash fake-ip,关闭 AGH**:把 LAN 客户端 DNS 指到 gfw。→ 让"代理网关"成为全 LAN DNS 单点,网关重启/代理抖动整个内网断网;且 AGH 的拦截/日志也没了。**不推荐。**
|
||||
|
||||
## 5. mosdns 配置要点(mosdns v5,预留实施)
|
||||
|
||||
核心是 `sequence` + 上游拆分 + 缓存 + 屏蔽:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- tag: main
|
||||
type: sequence
|
||||
args:
|
||||
- exec: cache 1024 # 缓存加速
|
||||
# 国内分流:命中 geosite:cn → 国内 DoH
|
||||
- matches: [ qname &geosite:cn ]
|
||||
exec: forward https://dns.alidns.com/dns-query
|
||||
# 广告域名可选屏蔽(或交给 AGH 前置拦截,二选一)
|
||||
# - matches: [ qname &./blocklist.txt ]
|
||||
# exec: reject 3
|
||||
# 其余(国外)→ 加密 DoH 防污染
|
||||
- exec: forward https://1.1.1.1/dns-query
|
||||
# 备选国外上游/兜底
|
||||
- matches: [ has_resp ]
|
||||
exec: accept
|
||||
- exec: forward_addr https://208.67.222.222:443/dns-query
|
||||
- type: udp_server
|
||||
args: { entry: main, listen: "127.0.0.1:5353" }
|
||||
- type: tcp_server
|
||||
args: { entry: main, listen: "127.0.0.1:5353" }
|
||||
```
|
||||
|
||||
要点:
|
||||
- 上游可加 `upstream` 的 `concurrent > 1` 与 `addr` 做多/故障切换。
|
||||
- `geosite:cn` / `geoip:cn` 数据插件自动从 repo 更新;国内用 aliDNS/腾讯,国外用 DoH(Cloudflare/Google/自建 adg.chans.xyz)。
|
||||
- 屏蔽交由 AGH 前置(推荐),不要 AGH 和 mosdns 都自己维护一套拦截表(重复)。
|
||||
|
||||
## 6. 迁移 / 实施顺序(待评审)
|
||||
|
||||
1. 在 66.36 起 mosdns 伴生容器(`/opt/mosdns` + compose,固定 digest,纳入 ansible)。
|
||||
2. AGH「上游 DNS 服务器」改为指向 mosdns(`http://127.0.0.1:5353/dns-query` 或 `127.0.0.1:5353`)。AGH 的 `bootstrap` 仍用公网 IP(避免 AGH → mosdns → AGH 死循环)。
|
||||
3. 验证:国内域名(如 `taobao.com`)、国外域名(如 `google.com`)、被拦截域名、每客户端日志。
|
||||
4. 确认后,`disable`/移除 gfw 上闲置的 mosdns(6052)以免混淆。
|
||||
5. 回归:EdgeRouter 通告不变(仍 `.36`),因此 LAN 客户端无感;重启 AGH/mosdns 单点验证。
|
||||
|
||||
## 7. 风险与备注
|
||||
- mosdns 仅监听 `127.0.0.1`(不对外),由 AGH 消费;避免 LAN 直连 mosdns 造成两套入口。
|
||||
- AGH 上游指向本机 mosdns 时,务必配 bootstrap 公网 IP,否则自举死循环。
|
||||
- 本方案不改 EdgeRouter DHCP/通告,不改 gfw OpenClash 代理规则,只动 66.36 上的 DNS 链路,风险可控。
|
||||
- 与 W1N-40「审查并修正 AdGuard Home」联动:该 issue 侧重 AGH 本身,本 issue 侧重整体 DNS 分层。
|
||||
@@ -0,0 +1,315 @@
|
||||
# ER-X + TL-SE5420 网络与拓扑说明
|
||||
|
||||
**状态:** SE5420 **已采购**(2026-08-09)。**实施与验证以 [lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md) 为准**;
|
||||
本文保留为拓扑/流量路径/职责边界的设计说明。
|
||||
**锁定硬件:** EdgeRouter X(保留)+ TP-Link `TL-SE5420`(新建核心)。
|
||||
**本文目的:** 说明升级后物理拓扑、逻辑网络、流量路径与职责边界;唯一实施步骤见
|
||||
[lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话架构
|
||||
|
||||
```text
|
||||
公网 ← ER-X(网关 / NAT / DHCP / 防火墙)
|
||||
↑ 两条独立 access 上联(LAN66 + LAN55,无 VLAN tag)
|
||||
TL-SE5420(纯 L2 核心:所有接入、同 VLAN 转发、未来 trunk)
|
||||
↑
|
||||
有线终端 / PVE(gfw) / 两台 UniFi AP
|
||||
```
|
||||
|
||||
- **ER-X** 继续当唯一主网关:PPPoE、NAT、端口转发、LAN66/LAN55 的 `.254` 与 DHCP。
|
||||
- **SE5420** 只做二层:接线、VLAN 隔离转发、端口统计;**不开** L3、静态路由、DHCP
|
||||
Server/Relay、NAT。
|
||||
- **`gfw`(PVE 上的 OpenWrt)** 仍是旁路由;默认客户端网关仍是 `.254`。以后若启用
|
||||
「设备升级」专用 SSID,该 SSID 的客户端才把网关指向 `.1`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已确认的设计决策
|
||||
|
||||
| 决策 | 结论 |
|
||||
|---|---|
|
||||
| 硬目标 | 同 VLAN **2.5G** 二层转发;10G 仅 SFP+ 预留 |
|
||||
| 核心型号 | **`TL-SE5420`**(16×2.5G RJ45 + 4×10G SFP+) |
|
||||
| 终局形态 | ER-X 边缘网关 + 核心纯 L2;可分阶段实施 |
|
||||
| 本次升级 Done | 迁完 LAN66/LAN55 接入与验收(计划阶段 0–3);不含 VLAN10 |
|
||||
| 默认网关 | **`192.168.66.254` / `192.168.55.254`(ER-X)** |
|
||||
| 专用升级 SSID | 仅 **U6 Lite**;客户端网关 **`gfw` `.1`**(后续项目) |
|
||||
| 客人网 | 仅 **UAP-AC-Lite**;UniFi Guest 隔离,仍在 **LAN55**(后续) |
|
||||
| 切换方式 | **30–60 分钟维护窗** 集中切;旧交换迁完后闲置 |
|
||||
| 首批 2.5G | NAS + 主力 PC(或 PVE);其余可先 1G |
|
||||
| SFP+ | 先空槽;有明确 10G 对端再买 DAC/光模块 |
|
||||
| AP 供电 | **本地取电 + 普通网线**(SE5420 **无 PoE**) |
|
||||
| 管理面 | 先本地 HTTPS/SSH;云以后再说;L3/DHCP 严格关闭 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 设备职责
|
||||
|
||||
### 3.1 EdgeRouter X(`gw`,`192.168.66.254`)
|
||||
|
||||
| 职责 | 说明 |
|
||||
|---|---|
|
||||
| WAN | `eth4` → PPPoE(`pppoe0`),NAT、WAN 防火墙、端口转发 |
|
||||
| LAN66 网关/DHCP | `eth0` = `192.168.66.254/24`;DHCP 池与静态映射不变 |
|
||||
| LAN55 网关/DHCP | `switch0`(`eth1`–`eth3`)= `192.168.55.254/24` |
|
||||
| 跨网段路由 | LAN66 ↔ LAN55 仍由 ER-X 三层转发 |
|
||||
| 与核心的连接 | **两条无标签 access**,不是 802.1Q trunk |
|
||||
|
||||
ER-X 当前不是 trunk 设计:不要向它发送 VLAN66/55 的 tag。核心上 VLAN66 的
|
||||
untagged 帧进 `eth0`;VLAN55 的 untagged 帧进 `switch0` 的某个成员口。
|
||||
|
||||
### 3.2 TL-SE5420(新建核心)
|
||||
|
||||
| 职责 | 说明 |
|
||||
|---|---|
|
||||
| 物理接入汇聚 | 有线设备、PVE、两台 AP 都接到 SE5420 |
|
||||
| VLAN66 / VLAN55 | 二层转发(access 或 trunk 的 native/allowed) |
|
||||
| 同 VLAN 高速交换 | 两端都是 2.5G 时可跑满协商速率(受 NIC/线材限制) |
|
||||
| 未来 trunk | PVE、U6:native 66 + tagged 10(仅后续阶段启用) |
|
||||
| 不做 | 不当默认网关;不开 DHCP;不做 NAT;第一阶段不开 SVI/路由 |
|
||||
|
||||
管理地址:落在 **LAN66** 的一个未占用地址(先查 ER-X DHCP reservation,再分配);
|
||||
不新建管理 VLAN。
|
||||
|
||||
### 3.3 旁路由 `gfw`(`192.168.66.1`)
|
||||
|
||||
| 现状 | 升级后(阶段 0–3) | 更后续 |
|
||||
|---|---|---|
|
||||
| PVE 上的 ImmortalWrt VM | 仍挂在 **untagged LAN66** | 可增 VLAN10 接口 |
|
||||
| OpenClash 旁路/透明代理 | 路径不变:客户端默认仍走 `.254` | 升级专用 SSID → 网关 `.1` |
|
||||
| 不能单独「做出」VLAN10 | 需 PVE vSwitch + 核心 trunk + U6 trunk | 见部署指南第二步 |
|
||||
|
||||
### 3.4 其它现网角色(地址与角色不变)
|
||||
|
||||
| 主机 | 地址 | 角色 |
|
||||
|---|---|---|
|
||||
| `dns.windy.lan` | `192.168.66.36` | AdGuard Home + Mihomo |
|
||||
| `ubnt` | `192.168.66.46` | UniFi Controller;Inform `http://192.168.66.46:9080/inform` |
|
||||
| U6 Lite | `192.168.66.6` | LAN66 AP;后续承载升级专用 SSID |
|
||||
| UAP-AC-Lite | `192.168.55.5` | LAN55 AP;后续承载客人 SSID |
|
||||
| PVE | `192.168.66.26`(DHCP 静态) | 宿主机;上挂 `gfw` |
|
||||
| NAS / 主力 PC 等 | 既有 reservation | 首批 2.5G 候选 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 物理拓扑
|
||||
|
||||
### 4.1 目标连线(阶段 3 完成后)
|
||||
|
||||
```text
|
||||
Internet
|
||||
│
|
||||
PPPoE / WAN
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ EdgeRouter X │
|
||||
│ (gw / 网关) │
|
||||
│ │
|
||||
│ eth0 switch0 成员口
|
||||
│ LAN66 LAN55
|
||||
│ untagged untagged
|
||||
└───┬───────────────┬─┘
|
||||
│ │
|
||||
铜口1│ │铜口2
|
||||
│ │
|
||||
┌─────────┴───────────────┴─────────┐
|
||||
│ TL-SE5420 核心 │
|
||||
│ 16×2.5G RJ45 + 4×SFP+(先空) │
|
||||
│ 纯 L2 · VLAN66 / VLAN55 │
|
||||
└───┬─────┬─────┬─────┬─────┬───────┘
|
||||
│ │ │ │ │
|
||||
铜口3 铜口4 铜口5 其余铜口 SFP+
|
||||
│ │ │ │ (预留)
|
||||
│ │ │ │
|
||||
PVE U6 AC-Lite NAS/PC/
|
||||
(gfw) Lite (LAN55) dns/ubnt…
|
||||
LAN66 LAN66 默认 LAN66
|
||||
(日后可 (日后可
|
||||
trunk) trunk)
|
||||
```
|
||||
|
||||
要点:
|
||||
|
||||
1. **星型**:终端与 AP 只上联到 SE5420;旧交换机迁完后**闲置**,避免双上行环路。
|
||||
2. **ER-X 双上联**:LAN66、LAN55 各一条独立铜缆;两条都是 access,**不打 tag**。
|
||||
3. **AP 供电**:SE5420 无 PoE;U6 / AC-Lite 在安装点**本地取电**,网线只传数据。
|
||||
4. **SFP+**:四个槽位先空着;有 10G NIC/对端与兼容模块后再上 DAC/AOC/光纤。
|
||||
|
||||
### 4.2 与「现在」的差异(概念上)
|
||||
|
||||
```text
|
||||
现在(简化) 目标
|
||||
───────────── ────
|
||||
终端/AP ──► ER-X 内置交换/旧交换 终端/AP ──► SE5420 ──► ER-X(仅网关)
|
||||
同网段 1G 交换受 ER-X/旧设备限制 同 VLAN 2.5G 在 SE5420 上本地交换
|
||||
跨网段 / 上网 仍经 ER-X 跨网段 / 上网 仍经 ER-X(不变)
|
||||
```
|
||||
|
||||
ER-X **不会被换成**「只接一条 trunk 的路由」;WAN 与双 LAN 接口模型保持不变。
|
||||
|
||||
---
|
||||
|
||||
## 5. 逻辑网络(VLAN / 子网)
|
||||
|
||||
### 5.1 本次升级范围内(阶段 0–3)
|
||||
|
||||
| 名称 | 子网 | 核心上的形态 | 网关 | DHCP | 典型成员 |
|
||||
|---|---|---|---|---|---|
|
||||
| **LAN66** | `192.168.66.0/24` | VLAN 66:access,或 trunk 的 native | ER-X `eth0` `.254` | ER-X | PVE、gfw、dns、ubnt、U6、NAS、PC… |
|
||||
| **LAN55** | `192.168.55.0/24` | VLAN 55:access | ER-X `switch0` `.254` | ER-X | UAP-AC-Lite、原 LAN55 设备 |
|
||||
|
||||
客户端 DNS 仍按现网:DHCP 下发 `192.168.66.36`(AdGuard)。
|
||||
LAN55 ↔ LAN66 互通仍走 ER-X(现网 `LAN_IN`/`LAN_OUT` 未挂到接口时,两边默认可达;不要把未生效的防火墙当隔离边界)。
|
||||
|
||||
### 5.2 后续能力(不进本次 Done)
|
||||
|
||||
| 名称 | 打算 | 网关 | 落点 | 依赖 |
|
||||
|---|---|---|---|---|
|
||||
| **升级专用 Wi-Fi** | 独立 SSID → 独立 VLAN(如 VLAN10) | **`gfw` `.1`**(该 VLAN 内) | **仅 U6 Lite** | 核心 trunk + PVE vSwitch + `gfw` DHCP/NAT |
|
||||
| **客人网** | 客人 SSID + UniFi Guest 隔离 | 仍 **ER-X `.254`(LAN55)** | **仅 UAP-AC-Lite** | UniFi 策略;**不**新建 VLAN |
|
||||
|
||||
客人网与升级 SSID **分工到两台 AP**,避免一上来就做「两台 AP 同一客人 VLAN + ER-X trunk」的改造。
|
||||
|
||||
### 5.3 VLAN10 的时序与边界
|
||||
|
||||
第一阶段**不**在 SE5420 创建 VLAN10,也**不**把 PVE/U6 改为 trunk。VLAN10 必须在 LAN55/LAN66 稳定后,以独立维护窗完成 PVE VLAN-aware 隔离、SE5420 trunk、gfw IPv4 NAT/内网拒绝策略和 IPv6 策略;仅在这些验收通过后才启用专用 SSID。详见部署指南第二步。
|
||||
|
||||
---
|
||||
|
||||
## 6. 流量怎么走
|
||||
|
||||
### 6.1 同 VLAN、两端都在核心上(例如 NAS ↔ PC,皆 LAN66)
|
||||
|
||||
```text
|
||||
NAS ──(2.5G)── SE5420 ──(2.5G)── PC
|
||||
(二层交换,不经过 ER-X)
|
||||
```
|
||||
|
||||
这是本次升级的**主要收益**。吞吐受 NIC、线材、磁盘限制;与 ER-X 的 1G 无关。
|
||||
|
||||
### 6.2 访问互联网(任意 LAN66/LAN55 客户端)
|
||||
|
||||
```text
|
||||
客户端 → SE5420 →(对应 access 上联)→ ER-X → PPPoE → Internet
|
||||
默认网关 = .254
|
||||
```
|
||||
|
||||
上网路径仍受 **ER-X 上联 1G** 与 PPPoE 限制;换核心**不会**把公网变成 2.5G。
|
||||
|
||||
### 6.3 LAN66 ↔ LAN55
|
||||
|
||||
```text
|
||||
LAN66 设备 → SE5420(VLAN66) → ER-X eth0
|
||||
↕ 三层转发
|
||||
LAN55 设备 → SE5420(VLAN55) → ER-X switch0
|
||||
```
|
||||
|
||||
跨网段必须过 ER-X;两条上联各 1G,且 ER-X 是否硬件加速需实测,**不要**按 2.5G 承诺跨 VLAN 吞吐。
|
||||
|
||||
### 6.4 旁路由 `gfw`(默认主网行为)
|
||||
|
||||
默认:DHCP 仍发网关 `.254`。客户端若把代理/策略指到 `gfw`,或由上游策略引流,则
|
||||
`gfw` 以旁路由身份处理;**主网关身份仍是 ER-X**。
|
||||
|
||||
### 6.5 未来:升级专用 SSID(仅说明,非本次范围)
|
||||
|
||||
```text
|
||||
手机 --SSID(升级)--> U6 Lite --tag VLAN10--> SE5420 --tag VLAN10--> PVE --> gfw
|
||||
网关 .1
|
||||
gfw 上游仍回 LAN66 → SE5420 → ER-X → Internet
|
||||
```
|
||||
|
||||
主 SSID(如现有 `ubnt-windy`)继续 untagged/LAN66,网关仍是 `.254`,互不影响。
|
||||
|
||||
---
|
||||
|
||||
## 7. 端口预留(SE5420)
|
||||
|
||||
端口号以面板为准;下表是角色预留。
|
||||
|
||||
| 预留 | 对端 | 模式 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 铜口 1 | ER-X `eth0` | access VLAN66 | 主网上联 |
|
||||
| 铜口 2 | ER-X `switch0` 成员口 | access VLAN55 | LAN55 上联 |
|
||||
| 铜口 3 | PVE(`gfw` 所在宿主机) | 阶段 0–3:access VLAN66;日后 trunk native66+tag10 | 只允许需要的 VLAN |
|
||||
| 铜口 4 | U6 Lite | 同上 | 本地取电;日后升级 SSID |
|
||||
| 铜口 5 | UAP-AC-Lite | access VLAN55 | 本地取电;日后客人 SSID |
|
||||
| 其余铜口 | NAS、PC、`dns`、`ubnt`… | 默认 access VLAN66 | 首批 2.5G:NAS + 主力 PC/PVE |
|
||||
| SFP+ 1–4 | 未来 10G | 空槽 | 优先 DAC/AOC/光;不用 10GBASE-T 模块作默认 |
|
||||
|
||||
禁止:同一设备同时接旧路径与新核心形成**无意双上行**(环路/环回保护误杀/不稳定)。
|
||||
|
||||
---
|
||||
|
||||
## 8. Wi-Fi 与 UniFi(目标语义)
|
||||
|
||||
| SSID 类型 | AP | 二层落点 | 客户端网关 | 本次升级是否必须完成 |
|
||||
|---|---|---|---|---|
|
||||
| 现有主 SSID(如 `ubnt-windy`) | 按现网 | LAN66 等 | `.254` | 迁线后保持 Connected 即可 |
|
||||
| 客人 SSID | **仅 AC-Lite** | LAN55 + UniFi Guest 隔离 | `.254` | **否**(后续) |
|
||||
| 升级专用 SSID | **仅 U6** | 未来 VLAN10 | **`.1`(gfw)** | **否**(后续) |
|
||||
|
||||
Inform 仍为:`http://192.168.66.46:9080/inform`。
|
||||
AC-Lite 在 LAN55、Controller 在 LAN66:跨网段由 ER-X 转发(现网已验证可达)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 管理与安全面
|
||||
|
||||
| 项 | 做法 |
|
||||
|---|---|
|
||||
| SE5420 管理 IP | LAN66 未占用地址;DHCP reservation 或静态;先查 ER-X 再写 |
|
||||
| 管理协议 | 本地 HTTPS / SSH;禁用不需要的 HTTP/Telnet |
|
||||
| 云管 | 先本地管稳;商云/App **以后再说** |
|
||||
| L3 功能 | **全部关闭**(无 SVI、无静态路由、无 DHCP Server/Relay) |
|
||||
| STP | 开启 RSTP/MSTP;合理根桥优先级;迁完后不留生产环路 |
|
||||
| 配置备份 | 离线导出;**不得**把含口令/SNMP 的备份放进本仓库 |
|
||||
| AP | 本地电源 + 数据网线;核心不供电 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 性能预期(诚实边界)
|
||||
|
||||
| 场景 | 预期 |
|
||||
|---|---|
|
||||
| 同 VLAN、两端 2.5G 接在 SE5420 | 可达约 2.5G 级二层(测 `iperf3` 验收) |
|
||||
| 上网 / PPPoE | 仍受 ER-X WAN 与 1G 上联限制 |
|
||||
| LAN66 ↔ LAN55 | 必过 ER-X;按 1G 级规划,实测为准 |
|
||||
| SFP+ 空槽 | **不等于**网络已是 10G |
|
||||
| 未来 VLAN10 + `gfw` | 受 PVE NIC/vSwitch 与 `gfw` 处理能力限制,勿预先承诺 2.5G 路由 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 验收时「网络算正常」的样子
|
||||
|
||||
阶段 3 完成后,应同时满足:
|
||||
|
||||
1. 有线设备从 SE5420 获取正确网段地址;默认路由为对应 `.254`;DNS `.36` 可用。
|
||||
2. 互联网、端口转发、现有本地服务行为与迁线前一致(除同 VLAN 内网吞吐变快)。
|
||||
3. U6(`.6`)与 AC-Lite(`.55.5`)在 UniFi 为 Connected;Inform 未改。
|
||||
4. SE5420 管理面仅可信 LAN66 可达;默认口令已改;配置已离线备份。
|
||||
5. 端口协商速率、CRC/错误计数正常;旧交换已下电闲置。
|
||||
6. (可选强化)两台 2.5G 终端同 VLAN `iperf3` 结果入库/记笔记。
|
||||
|
||||
VLAN10 / 升级 SSID / 客人 SSID **失败或未做,不否决**本次核心升级。
|
||||
|
||||
---
|
||||
|
||||
## 12. 回滚语义
|
||||
|
||||
任一阶段失败:停止继续搬家;把刚迁的设备插回原 ER-X/旧交换端口。
|
||||
**不要**在故障中改 ER-X 的 WAN、DHCP 池、网关地址或 SSH 策略。
|
||||
后续 VLAN10 失败:只撤 SSID/VLAN 绑定,主 SSID 与 `.254` 路径保持不动。
|
||||
|
||||
---
|
||||
|
||||
## 13. 参考
|
||||
|
||||
- 实施阶段与清单:[lan-core-switch-upgrade-plan.md](lan-core-switch-upgrade-plan.md)
|
||||
- 现网地图:[lan-overview.md](lan-overview.md)
|
||||
- ER-X:[edgerouter-x-configuration.md](edgerouter-x-configuration.md)、[hosts/gw.md](../hosts/gw.md)
|
||||
- UniFi / VLAN10 前置:[unifi-network.md](unifi-network.md)
|
||||
- `gfw`:[hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md)
|
||||
- SE5420 规格:<https://www.tp-link.com.cn/product_2899.html?v=specification>
|
||||
@@ -0,0 +1,183 @@
|
||||
# LAN network overview
|
||||
|
||||
A standalone, live-verified map of the **internal LAN** hosts and the software
|
||||
deployed on them. Written for a future self / another agent to get oriented fast.
|
||||
Focused per-host facts and runbooks live in [`../hosts/`](../hosts/) and are linked
|
||||
from each section below.
|
||||
|
||||
> **Scope:** this document covers the real LAN only. It deliberately excludes the public VPS
|
||||
> hosts (`mx2`, `us2`, `us4`, `hk2`, `synapse`) and any dormant/stub software. The
|
||||
> WireGuard VPN server is external (`us4.wsvc.info`); there is **no WireGuard interface on
|
||||
> the LAN side**.
|
||||
|
||||
> **Verified live on 2026-08-06** by read-only SSH from the WSL client. No
|
||||
> changes were made. `gfw.windy.lan` root SSH was re-verified the same day after
|
||||
> the key was installed; its facts below are from the fresh probe.
|
||||
|
||||
---
|
||||
|
||||
## Topology
|
||||
|
||||
```
|
||||
Internet
|
||||
│ PPPoE (pppoe0, MTU 1492, IPv6 PD /60)
|
||||
▼
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ gw — EdgeRouter X (192.168.66.254) │
|
||||
│ eth0 = LAN66 · switch0(eth1-3) = LAN55 │
|
||||
│ WAN_IN active on pppoe0 (default-drop) │
|
||||
│ port-forwards: hass/transmission/ssh/openvpn │
|
||||
LAN66 └───────────────┬──────────────────────────────┘
|
||||
(192.168.66.0/24) │
|
||||
|
||||
│ gfw.windy.lan — OpenWrt / OpenClash (192.168.66.1)
|
||||
│ dns.windy.lan — AdGuard Home + Mihomo (192.168.66.36)
|
||||
│ ubnt — UniFi Network Controller (192.168.66.46)
|
||||
```
|
||||
|
||||
> **SE5420 purchased (2026-08-09):** TP-Link `TL-SE5420` acquired; deployment plan is
|
||||
> [lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md). Design/planning refs:
|
||||
> [lan-erx-se5420-network.md](lan-erx-se5420-network.md),
|
||||
> [lan-core-switch-upgrade-plan.md](lan-core-switch-upgrade-plan.md).
|
||||
|
||||
---
|
||||
|
||||
## Hosts
|
||||
|
||||
| Host | Role | SSH | IPv4 | Facts |
|
||||
|------|------|-----|------|-------|
|
||||
| **gw** | EdgeRouter X primary LAN gateway | `ssh -4 zhiqiang@192.168.66.254` | `192.168.66.254` | [hosts/gw.md](../hosts/gw.md) |
|
||||
| **PVE** | Proxmox host (`.66.26`/vmbr0 · `.55.26`/vmbr1) — hosts gfw/dns/ubnt/haos VMs | `ssh -4 root@192.168.66.26` | `192.168.66.26` | — |
|
||||
| **gfw.windy.lan** | OpenWrt LAN gateway / OpenClash — **PVE VM 140** | `ssh -4 root@192.168.66.1` | `192.168.66.1` | [hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md) |
|
||||
| **dns.windy.lan** | AdGuard Home LAN DNS + Mihomo explicit proxy — **PVE VM 120** (`pihole`) | `ssh -4 windy@192.168.66.36` | `192.168.66.36` | [hosts/dns.windy.lan.md](../hosts/dns.windy.lan.md) |
|
||||
| **ubnt** | UniFi Network Controller — **PVE VM 160** | `ssh -4 windy@192.168.66.46` | `192.168.66.46` | [hosts/ubnt.md](../hosts/ubnt.md) |
|
||||
| **haos** | Home Assistant (HAOS) — **PVE VM 180** (LAN55) | — | `192.168.55.11` | — |
|
||||
| **NAS/FreeNAS** | NAS; `transmission` jail runs here (`.51`) | — | — | — |
|
||||
| **U6 Lite** | UniFi AP (LAN66) | `ssh -4 zhiqiangf@192.168.66.6` | `192.168.66.6` | [docs/unifi-network.md](../docs/unifi-network.md) |
|
||||
| **UAP-AC-Lite** | UniFi AP (LAN55) | `ssh -4 zhiqiangf@192.168.55.5` | `192.168.55.5` | [docs/unifi-network.md](../docs/unifi-network.md) |
|
||||
|
||||
> **Positioning facts (verified 2026-08-09):** `dns`/`ubnt`/`gfw`/`haos` are all VMs on PVE
|
||||
> (no separate physical hosts); `transmission` is a FreeNAS/NAS jail. Only gw, PVE,
|
||||
> NAS, U6, UAP-AC-Lite, and wired PCs/NAS are physical SE5420 ports. See
|
||||
> [lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md) §1.
|
||||
|
||||
---
|
||||
|
||||
## gw — EdgeRouter X (LAN gateway)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| Platform | EdgeOS v3.0.1 (Build 5862409), kernel 4.14.54-UBNT |
|
||||
| Interfaces | `eth0`=192.168.66.254/24 (LAN66) · `switch0`=192.168.55.254/24 (LAN55) · `eth4`=WAN PPPoE · `pppoe0`=WAN default route (MTU 1492, IPv6 PD /60) |
|
||||
| WAN | `pppoe0`=113.68.54.159 (PPPoE) · IPv6 SLAAC /64 on both LANs |
|
||||
| Firewall | `WAN_IN` **active** on pppoe0 (established/related accept, invalid drop, default drop) · `LAN_IN`/ `LAN_OUT` defined but **inactive** (not attached) |
|
||||
| DHCP | LAN1 (66) + LAN2 (55), 24h leases, client DNS = `192.168.66.36`, pools .38–.243, static mappings for infra hosts |
|
||||
| DNS | `service dns forwarding` cache 512, listen on eth0 + switch0; DHCP advertises `192.168.66.36` as client DNS |
|
||||
| Port-forwards | `hass`→192.168.55.11:8123 · `transmission`→192.168.66.51:51413 · `ssh`→192.168.66.36:22 (orig 5822) · `openvpn`→192.168.66.32:1194 · WAN iface pppoe0 |
|
||||
| Management | SSH TCP 22 · EdgeOS GUI HTTP 80 / HTTPS 443 |
|
||||
|
||||
**Static DHCP mappings (LAN66):** `OnePlus-12`=.37, `gfw`=.1, `hp-nas`=.32, `pihole`=.36, `pve`=.26, `transmission`=.51, `ubnt-6`=.6, `ubnt-app`=.46, `windy-pc`=.99. LAN55: `Aqara-Hub-M3-10CB`=.248.
|
||||
|
||||
> **Note:** `LAN_IN`/`LAN_OUT` are defined but not applied to an interface, so LAN55
|
||||
> and LAN66 are bidirectionally reachable by default. Do not rely on those rules as
|
||||
> an access boundary. See [hosts/gw.md](../hosts/gw.md).
|
||||
|
||||
---
|
||||
|
||||
## gfw.windy.lan — OpenWrt / OpenClash gateway
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| IPv4 | `192.168.66.1` |
|
||||
| OS | **ImmortalWrt 25.12.0** (r37854), Linux 6.12.87, x86/64 |
|
||||
| SSH | `ssh -4 root@192.168.66.1` (key-only, verified 2026-08-06) |
|
||||
| OpenClash | `/etc/openclash/clash` (clash_meta core) + config `/etc/openclash/pass-cat.yaml` |
|
||||
| Mode | **fake-ip + TPROXY transparent proxy** (`operation_mode=fake-ip`, `en_mode=fake-ip`, `proxy_mode=rule`) |
|
||||
| DNS | dnsmasq → clash DNS `127.0.0.1#7874`; `mosdns` also listens on `127.0.0.1:6052` (not the active path) |
|
||||
| nft | `table inet fw4` with OpenClash TPROXY/redirect + DNS-hijack rules; residual `table inet passwall` (0 packets, unused) |
|
||||
|
||||
**OpenClash listeners:** HTTP `7890` · SOCKS `7891` · Redirect `7892` · Mixed `7893` · TPROXY `7895` · DNS `7874` · dashboard `9090`. `8443` is **not** an OpenClash listener (only in its TLS-sniffing port list).
|
||||
|
||||
> **Operational note:** a LAN client request logged as `<client> --> localhost:8443` + refusal is normally an explicit-proxy client sending its own loopback to the gateway. Fix the
|
||||
> originating host's `NO_PROXY`/`no_proxy` config; do not create a listener on this router to mask it. See [docs/unifi-openclash-localhost.md](../docs/unifi-openclash-localhost.md).
|
||||
|
||||
---
|
||||
|
||||
## dns.windy.lan — AdGuard Home + Mihomo (DNS & proxy host)
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| IPv4 | `192.168.66.36` |
|
||||
| OS | Debian 12 (kernel 6.1.0-43) |
|
||||
| SSH | `ssh -4 windy@192.168.66.36` |
|
||||
| AdGuard Home | Docker container `adguardhome` (host network, pinned digest), `/opt/adguardhome` |
|
||||
| DNS | TCP/UDP `53` (no source ACL — serves both LAN55 and LAN66) |
|
||||
| UI | `192.168.66.36:80` (LAN66 only via nft `agh_ui_access`) |
|
||||
| Upstreams | `https://dns.alidns.com/dns-query` + `https://doh.pub/dns-query` (load-balanced) · bootstrap `223.5.5.5`/`223.6.6.6` · fallback `https://adg.chans.xyz/dns-query` |
|
||||
| DNSSEC | **disabled** (upstream path failed known-bad-signature check) |
|
||||
| Rewrites | `hass.local` + `hass.windy.lan` → same Home Assistant address |
|
||||
| Query log | 14 days, anonymized client IPs |
|
||||
| Mihomo | **systemd service** (NOT Docker) — `/usr/local/bin/mihomo -d /etc/mihomo`, config `/etc/mihomo/config.yaml` |
|
||||
| Mihomo listeners | HTTP `7890` · SOCKS `7891` · Mixed `7892` · DNS `1053` · external-controller `9090` · `allow-lan: true` · mode `rule` |
|
||||
| nft | `table inet agh_ui_access` permits only `192.168.66.0/24` to TCP/80, drops other TCP/80 input |
|
||||
|
||||
**Routing boundary:** read-only checks found no host TUN policy routing or nftables/iptables transparent-redirection on this host. A Mihomo listener alone does not mean it intercepts AdGuard traffic; the default gateway (`gfw.windy.lan`) can still affect outbound, so inspect its OpenClash rules separately before changing upstream routing.
|
||||
|
||||
---
|
||||
|
||||
## ubnt — UniFi controller host
|
||||
|
||||
| Item | Value |
|
||||
|------|-------|
|
||||
| IPv4 | `192.168.66.46` |
|
||||
| Compose | `/home/windy/unifi-9/compose.yml` · service `unifi` · container `unifi-controller` |
|
||||
| Image | `jacobalberty/unifi:v9.5.21` (host network) |
|
||||
| HTTPS UI | `https://192.168.66.46:8443` |
|
||||
| Inform | `http://192.168.66.46:9080/inform` (not 8080) |
|
||||
| Health | `docker inspect --format '{{.State.Health.Status}}' unifi-controller` → `healthy` (2026-08-06) |
|
||||
| Also running | **dockge** (`louislam/dockge:1`, healthy) — compose UI, `/opt/stacks` |
|
||||
| Dormant | `semaphoreui` (semaphore + postgres, exited 9 months) · `homarr` dir (no container) |
|
||||
|
||||
**Proxy contract:** the container keeps `NO_PROXY`/`no_proxy` enabled with
|
||||
`localhost,127.0.0.1,::1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12` so a controller
|
||||
request to `localhost:8443` stays local instead of going to the OpenClash gateway.
|
||||
See [docs/unifi-openclash-localhost.md](../docs/unifi-openclash-localhost.md).
|
||||
|
||||
---
|
||||
|
||||
## Managed access points
|
||||
|
||||
| Name | Model | Mgmt IP | Firmware | Network | Inform |
|
||||
|------|-------|---------|----------|---------|--------|
|
||||
| U6 Lite | U6 Lite (UAL6) | `192.168.66.6` | 6.7.54.15663 | LAN66 | `:9080` |
|
||||
| UAP-AC-Lite | U7LT | `192.168.55.5` | 6.8.2.15592 | LAN55 | `:9080` |
|
||||
|
||||
Both reported **Connected** to `http://192.168.66.46:9080/inform` on 2026-08-06.
|
||||
AP SSH account is `zhiqiangf` (key-only, verified). See [docs/unifi-network.md](../docs/unifi-network.md).
|
||||
|
||||
---
|
||||
|
||||
## Quick orientation (who runs what)
|
||||
|
||||
| Service | Host | How to reach |
|
||||
|---------|------|--------------|
|
||||
| LAN gateway / routing / DHCP | gw | `ssh -4 zhiqiang@192.168.66.254` |
|
||||
| Transparent/explicit proxy (OpenClash) | gfw.windy.lan | `ssh -4 root@192.168.66.1` |
|
||||
| LAN DNS (AdGuard Home) + Mihomo proxy | dns.windy.lan | `ssh -4 windy@192.168.66.36` |
|
||||
| UniFi controller + dockge | ubnt | `ssh -4 windy@192.168.66.46` |
|
||||
| Wi-Fi APs | U6 Lite / UAP-AC-Lite | via controller |
|
||||
|
||||
---
|
||||
|
||||
## Related docs
|
||||
|
||||
- [hosts/gw.md](../hosts/gw.md) — EdgeRouter X detail, firewall, verification record
|
||||
- [hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md) — OpenClash listeners
|
||||
- [hosts/dns.windy.lan.md](../hosts/dns.windy.lan.md) — AdGuard Home + Mihomo detail
|
||||
- [hosts/ubnt.md](../hosts/ubnt.md) — UniFi controller + proxy contract
|
||||
- [docs/unifi-network.md](../docs/unifi-network.md) — APs, inform endpoint, recovery
|
||||
- [docs/unifi-third-party-vlan10-dhcp.md](unifi-third-party-vlan10-dhcp.md) — VLAN Wi-Fi feasibility and DHCP boundary
|
||||
- [docs/unifi-openwrt-vlan10-implementation-examples.md](unifi-openwrt-vlan10-implementation-examples.md) — supported topology and examples
|
||||
- [docs/edgerouter-x-configuration.md](../docs/edgerouter-x-configuration.md) — effective gw config
|
||||
- [docs/unifi-openclash-localhost.md](../docs/unifi-openclash-localhost.md) — proxy bypass
|
||||
- [runbooks/adguard-home-health.md](../runbooks/adguard-home-health.md) — AGH health
|
||||
@@ -0,0 +1,397 @@
|
||||
# ER-X → RB5009 网关升级:网络形态与实施计划
|
||||
|
||||
**状态:** 规划文档(未采购、未接线、未改生产配置)。
|
||||
**重要变更(2026-08-09):** **SE5420 已采购**,网络升级改为「保留 ER-X + SE5420 核心」路径——
|
||||
实施与验证以 [lan-se5420-deployment-guide.md](lan-se5420-deployment-guide.md) 为准。
|
||||
本文保留为「ER-X 网关未来替换为 RB5009」的备选方案;其中 PVE 透传调研与 VLAN10 实现方法仍适用。
|
||||
|
||||
---
|
||||
|
||||
## 1. 一句话架构
|
||||
|
||||
```text
|
||||
公网 ← RB5009(主网关:PPPoE / NAT / 防火墙 / DHCP / IPv6 / VLAN 66+55 三层)
|
||||
↑ 全部终端 / AP / PVE 直连其 8×1G 端口
|
||||
(SFP+ 空槽,为未来 10G 核心预留)
|
||||
```
|
||||
|
||||
- **RB5009** 兼任路由与 LAN 二层交换:所有接入设备直插 RB5009 端口。
|
||||
- 客户端默认网关仍是 **`.254`**(RB5009 沿用 ER-X 地址,客户端零感知)。
|
||||
- **`gfw`** 仍为旁路由(默认网关 `.254`);OpenClash 行为不变。
|
||||
- **ER-X 升级后下电闲置,作为已配置的备件保存**(回滚路径)。
|
||||
- 2.5G/10G 同 VLAN 交换目标**放弃**(无 SE5420);LAN 二层为 1G。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已确认的设计决策
|
||||
|
||||
| 决策 | 结论 |
|
||||
|---|---|
|
||||
| 角色 | RB5009 单机:路由 + LAN 交换 + 双 VLAN 三层 |
|
||||
| WAN | **PPPoE**,走 **`ether1`(2.5G 铜口接 ONT/光猫)**,MTU 1492 |
|
||||
| LAN 模型 | **单 bridge + VLAN 过滤**:VLAN 66(多数端口)+ VLAN 55;VLAN 10 预留 |
|
||||
| 网关地址 | **沿用 `192.168.66.254` / `192.168.55.254`**;重编址另立项目 |
|
||||
| IPv6 | **保留对等**:PPPoE 上 DHCPv6-PD `/60` + 双 LAN SLAAC `/64` + IPv6 防火墙 |
|
||||
| 端口转发 | 4 条 **原样迁入**(hass / transmission / ssh / openvpn)+ hairpin NAT |
|
||||
| LAN55 成员 | **保持现状**(AC-Lite、Aqara 及原 LAN55 设备)不迁到 VLAN66 |
|
||||
| 管理面 | SSH **仅密钥**(`zhiqiang`);禁用默认 `admin`;**禁 Winbox/API/WebFig** 或仅限 LAN |
|
||||
| 迁移方式 | 台面预配置(临时 `.253`)→ 维护窗(30–60 分钟)→ 验证 → ER-X 留作备件 |
|
||||
| 固件/备份 | 当前稳定 RouterOS 7.x;上线前 `.backup` + 文本导出存离线 |
|
||||
| 时区/NTP | `Asia/Shanghai` + 现有 NTP 服务器 |
|
||||
| 安全 | PPPoE 口令、SSH 私钥、`zhiqiang` 口令**只存设备上**,不落本仓库 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 升级后的网络形态(拓扑图)
|
||||
|
||||
### 3.1 目标物理拓扑(升级完成后)
|
||||
|
||||
```text
|
||||
Internet
|
||||
│ PPPoE (ether1 2.5G, MTU 1492, IPv6 PD /60)
|
||||
▼
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ RB5009 (gw) RouterOS 7.x │
|
||||
│ ether1 = WAN (PPPoE) │
|
||||
│ bridge + vlan-filtering=yes │
|
||||
│ vlan66 = 192.168.66.254/24 (pvid 66) │
|
||||
│ vlan55 = 192.168.55.254/24 (pvid 55) │
|
||||
│ SFP+ = 空槽(未来 10G) │
|
||||
│ SSH-only 管理(仅 LAN) │
|
||||
└─────┬─────────┬──────────┬──────────┬────────┘
|
||||
│ pvid66 │ pvid66 │ pvid66 │ pvid55
|
||||
│ │ │ │
|
||||
gfw (.1) dns (.36) ubnt (.46) UAP-AC-Lite (.55.5)
|
||||
PVE (.26) NAS/PC… U6 Lite Aqara (.55.248)
|
||||
(.66.6)
|
||||
```
|
||||
|
||||
端口角色以面板为准;下表是建议预留:
|
||||
|
||||
| RB5009 端口 | 对端 | 模式 |
|
||||
|---|---|---|
|
||||
| `ether1` | ONT/光猫 | WAN,PPPoE |
|
||||
| `ether2`–`ether8` | PVE/gfw、dns、ubnt、U6 Lite、NAS、PC、杂项 | access,pvid 66 |
|
||||
| `ether9` | 原 LAN55 成员(AC-Lite、Aqara…) | access,pvid 55 |
|
||||
| SFP+ `sfp-sfpplus1` | 未来 10G 核心 | 空槽 |
|
||||
|
||||
> **禁止双上行:** 任一设备不得同时接旧 ER-X 与 RB5009(环路 / 双默认网关)。
|
||||
|
||||
### 3.2 逻辑网络
|
||||
|
||||
| VLAN | 子网 | 网关 / DHCP | 形态 | 成员 |
|
||||
|---|---|---|---|---|
|
||||
| **66** | `192.168.66.0/24` | RB5009 `vlan66` `.254` | 多数端口 access | gfw、dns、ubnt、U6、PVE、NAS、PC… |
|
||||
| **55** | `192.168.55.0/24` | RB5009 `vlan55` `.254` | `ether9` access | AC-Lite、Aqara…(原 LAN55 全量) |
|
||||
| 10(预留) | `192.168.10.0/24` | 未来(`gfw`) | 暂不配业务 | 升级专用 SSID(后续项目) |
|
||||
|
||||
- 客户端 DNS:DHCP 仍下发 **`192.168.66.36`**(AdGuard Home)。
|
||||
- UniFi Inform 仍为 `http://192.168.66.46:9080/inform`。
|
||||
- LAN66 ↔ LAN55 互通由 RB5009 三层转发(沿用现网默认可达语义,防火墙不设隔离边界)。
|
||||
|
||||
### 3.3 流量怎么走
|
||||
|
||||
```text
|
||||
同 VLAN(NAS ↔ PC): 客户端 → RB5009 端口 → bridge(二层交换,1G)
|
||||
跨 VLAN(66 ↔ 55): 客户端 → RB5009 路由(vlan66 ↔ vlan55,1G)
|
||||
上网: 客户端 → RB5009 → PPPoE → Internet(限速 = 线路;RB5009 fasttrack 去掉 ER-X ~900M 天花板)
|
||||
旁路由: 默认网关仍 `.254`;显式代理走 gfw(不变)
|
||||
未来 VLAN10: U6 trunk → RB5009 →(转发 VLAN10)→ gfw(后续项目)
|
||||
```
|
||||
|
||||
### 3.4 性能预期(诚实边界)
|
||||
|
||||
| 场景 | 预期 |
|
||||
|---|---|
|
||||
| WAN / PPPoE | 线路速率以内;若线路 >1G,RB5009 fasttrack 可跑满(ER-X 受 900M 限制) |
|
||||
| 同 VLAN 交换 | **1G**(无 SE5420;RB5009 8×1G) |
|
||||
| 跨 VLAN | 1G 级三层 |
|
||||
| SFP+ 空槽 | 不等于已是 10G |
|
||||
|
||||
---
|
||||
|
||||
## 4. 分阶段实施计划
|
||||
|
||||
### 阶段 0:采购前核验
|
||||
|
||||
1. 确认 RB5009 revision、保修、零售渠道、RouterOS 当前稳定版本号(建议 7.x 最新 stable)。
|
||||
2. 确认硬件:1× 2.5G PoE-in(ether1)、8× 1G、1× SFP+、1GB RAM。SFP+ 模块本次不买。
|
||||
3. 盘点线缆与对端:ONT ↔ ether1;原 LAN55 设备清单(AC-Lite、Aqara…)及原 ER-X `switch0` 接线。
|
||||
|
||||
### 阶段 1:离线初始化(台面)
|
||||
|
||||
1. 仅电源 + 隔离管理本。设置身份、时区 `Asia/Shanghai`、NTP。
|
||||
2. 建 `zhiqiang` 管理用户(**密钥登录,禁密码**);**删除/禁用默认 `admin`**。
|
||||
3. 建 bridge + VLAN 66/55 + 端口模板(`ether2–8` pvid66,`ether9` pvid55);**临时地址 `.253`**(不与 ER-X `.254` 冲突)。
|
||||
4. 配 WAN PPPoE(ether1)、默认路由、NAT masquerade、4 条端口转发 + hairpin、防火墙(input/forward)、MSS clamp、IPv6 PD + SLAAC + IPv6 防火墙。
|
||||
5. 配 DHCP 池 `.38–.243`、24h 租约、DNS `.36`、UniFi Inform 选项、**静态映射全量照搬**。
|
||||
6. 管理面收口:**禁 Winbox(8291)/ API(8728)/ WebFig(80/443)**;SSH 仅限 LAN66/55。
|
||||
7. `system backup save` + `/export`,导出到离线存储(**不落仓库**)。
|
||||
|
||||
**参考 RouterOS 配置骨架**(口令/密钥用占位符;落地前逐一核对):
|
||||
|
||||
```text
|
||||
# 身份 / 时区 / NTP
|
||||
/system identity set name=gw
|
||||
/system clock set time-zone-name=Asia/Shanghai
|
||||
|
||||
# WAN
|
||||
/interface ethernet set ether1 name=wan
|
||||
/interface pppoe-client add name=pppoe0 interface=wan user=<PPPoE_USER> \
|
||||
password=<PPPoE_PASS> add-default-route=yes use-peer-dns=no \
|
||||
mtu=1492 mru=1492
|
||||
|
||||
# bridge + VLAN
|
||||
/interface bridge add name=bridge66 vlan-filtering=yes
|
||||
/interface bridge port add bridge=bridge66 interface=ether2 pvid=66
|
||||
... # ether3..ether8 同 pvid 66
|
||||
/interface bridge port add bridge=bridge66 interface=ether9 pvid=55
|
||||
/interface bridge vlan add bridge=bridge66 vlan-ids=66 tagged=bridge66
|
||||
/interface bridge vlan add bridge=bridge66 vlan-ids=55 tagged=bridge66
|
||||
/interface vlan add name=vlan66 interface=bridge66 vlan-id=66
|
||||
/interface vlan add name=vlan55 interface=bridge66 vlan-id=55
|
||||
|
||||
# 地址(台面先用 .253,维护窗切 .254)
|
||||
/ip address add address=192.168.66.254/24 interface=vlan66
|
||||
/ip address add address=192.168.55.254/24 interface=vlan55
|
||||
|
||||
# NAT masquerade + 端口转发 + hairpin
|
||||
/ip firewall nat add chain=srcnat out-interface=pppoe0 action=masquerade
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=tcp dst-port=8123 \
|
||||
action=dst-nat to-addresses=192.168.55.11 to-ports=8123
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=tcp dst-port=51413 \
|
||||
action=dst-nat to-addresses=192.168.66.51 to-ports=51413
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=udp dst-port=51413 \
|
||||
action=dst-nat to-addresses=192.168.66.51 to-ports=51413
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=tcp dst-port=5822 \
|
||||
action=dst-nat to-addresses=192.168.66.36 to-ports=22
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=udp dst-port=5822 \
|
||||
action=dst-nat to-addresses=192.168.66.36 to-ports=22
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=tcp dst-port=1194 \
|
||||
action=dst-nat to-addresses=192.168.66.32 to-ports=1194
|
||||
/ip firewall nat add chain=dstnat in-interface=pppoe0 protocol=udp dst-port=1194 \
|
||||
action=dst-nat to-addresses=192.168.66.32 to-ports=1194
|
||||
/ip firewall nat add chain=srcnat connection-nat-state=dstnat action=masquerade # hairpin
|
||||
|
||||
# 防火墙(input / forward),顺序很重要:SSH 放行必须早于默认 drop
|
||||
/ip firewall filter add chain=input connection-state=established,related action=accept
|
||||
/ip firewall filter add chain=input connection-state=invalid action=drop
|
||||
/ip firewall filter add chain=input protocol=icmp action=accept
|
||||
/ip firewall filter add chain=input in-interface=pppoe0 action=drop comment="drop WAN in"
|
||||
/ip firewall filter add chain=input protocol=tcp dst-port=22 \
|
||||
src-address=192.168.66.0/24,192.168.55.0/24 action=accept comment="mgmt SSH"
|
||||
/ip firewall filter add chain=input action=drop comment="drop other input"
|
||||
/ip firewall filter add chain=forward connection-state=established,related action=fasttrack-connection \
|
||||
hw-offload=yes
|
||||
/ip firewall filter add chain=forward connection-state=established,related action=accept
|
||||
/ip firewall filter add chain=forward connection-state=invalid action=drop
|
||||
/ip firewall filter add chain=forward in-interface=pppoe0 action=drop comment="drop WAN fwd"
|
||||
/ip firewall filter add chain=forward action=accept comment="accept LAN fwd"
|
||||
|
||||
# MSS clamp(PPPoE MTU 1492 → 1452;ER-X 旧值 1412 偏小,验证后按标准值收敛)
|
||||
/ip firewall mangle add chain=forward protocol=tcp tcp-flags=syn \
|
||||
tcp-mss=1400-65535 action=change-mss new-mss=1452 passthrough=yes
|
||||
|
||||
# IPv6(PD /60 + SLAAC /64)
|
||||
/ipv6 dhcp-client add interface=pppoe0 request=prefix pool-name=pd6 \
|
||||
pool-prefix-length=60 add-default-route=yes
|
||||
/ipv6 address add from-pool=pd6 interface=vlan66 address=::1 adverts=yes
|
||||
/ipv6 address add from-pool=pd6 interface=vlan55 address=::1 adverts=yes
|
||||
# /ipv6 firewall filter 参照 IPv4:established/related accept、invalid drop、
|
||||
# icmpv6 accept、pppoe0 in drop、默认 drop,forward 同构
|
||||
|
||||
# DHCP(池 / DNS / Inform / 静态映射全量照搬 ER-X)
|
||||
/ip pool add name=pool66 ranges=192.168.66.38-192.168.66.243
|
||||
/ip pool add name=pool55 ranges=192.168.55.38-192.168.55.243
|
||||
/ip dhcp-server add name=dhcp66 interface=vlan66 address-pool=pool66 lease-time=1d
|
||||
/ip dhcp-server add name=dhcp55 interface=vlan55 address-pool=pool55 lease-time=1d
|
||||
/ip dhcp-server network add address=192.168.66.0/24 gateway=192.168.66.254 \
|
||||
dns-server=192.168.66.36
|
||||
/ip dhcp-server network add address=192.168.55.0/24 gateway=192.168.55.254 \
|
||||
dns-server=192.168.66.36
|
||||
# 静态映射:从 ER-X 导出后逐条 /ip dhcp-server lease add ...
|
||||
# UniFi Inform:DHCP option 43(hex 编码为 http://192.168.66.46:9080/inform),
|
||||
# 或依赖已 adopt AP 的 set-inform;与现网 ER-X 行为保持一致。
|
||||
```
|
||||
|
||||
### 阶段 2:上台预验证(ER-X 仍在线)
|
||||
|
||||
1. 将 RB5009 `ether2` 接入现有 LAN66 网段(管理本同网段),SSH 登录 `.253`。
|
||||
2. 全配置复查:VLAN 表、防火墙规则顺序(SSH 在 drop 前)、NAT、路由、IPv6。
|
||||
3. **暂不启用 RA/DHCP**(避免与 ER-X 冲突)。预验证 `.253` 可达、SSH 密钥生效、`zhiqiang` 无密码。
|
||||
4. 确认 ER-X 当前配置**已备份并保存**(作为回滚依据;`show configuration commands` 过滤敏感行)。
|
||||
|
||||
### 阶段 3:维护窗切换(约 30–60 分钟)
|
||||
|
||||
1. 变更 RB5009 `vlan66`/`vlan55` 地址 `.253 → .254`。
|
||||
2. **下电 ER-X**(保留原接线与配置,不作任何改动)。
|
||||
3. ONT 线从 ER-X `eth4` 移到 RB5009 `ether1`。
|
||||
4. 观察 PPPoE 拨号:`/interface pppoe-client monitor pppoe0` 直至 `status=established`;确认 WAN IP 与默认路由。
|
||||
5. 验证清单(见第 5 节),全绿才算完成。
|
||||
|
||||
### 阶段 4:上线收口
|
||||
|
||||
1. 再确认管理面:禁 Winbox/API/WebFig、SSH 仅密钥、默认 `admin` 已除。
|
||||
2. 更新固件/补丁至已核定的稳定版;重新导出备份(`.backup` + 文本)存离线。
|
||||
3. ER-X 下电收纳为**已配置备件**,把其接线与角色记录到 `hosts/gw.md` 备份节。
|
||||
|
||||
### 阶段 5(后续,另立项目)
|
||||
|
||||
**时序确认:** VLAN10 升级专用 Wi-Fi 是 RB5009 上线**稳定之后**的独立项目,不并入本次维护窗。
|
||||
|
||||
- 4 条端口转发是否仍需的审计。
|
||||
- 网关重编址(`.254 → 其它`)决策。
|
||||
- **VLAN10 升级专用 SSID → `gfw`(可行性审查,2026-08-09)**。
|
||||
- 迁移后同步本仓库事实文档:`hosts/gw.md`、`docs/lan-overview.md`、`inventory/hosts.md`、`AGENTS.md` 快速地图。
|
||||
|
||||
### 阶段 5 附:VLAN10 可行性审查
|
||||
|
||||
**结论:** 换 RB5009 后**物理上可行**,但 RB5009 只解决「交换机侧」路径;PVE 宿主机到 `gfw` 虚拟机这一段(此前判定为不可行的关键缺口)仍必须单独打通。
|
||||
|
||||
**为什么当前不能在 AP 上启用 VLAN10:**
|
||||
|
||||
U6 接的是 ER-X `eth0` 的普通 untagged LAN66 路径,尚无已验证的 VLAN10 端到端二层通道。给 SSID 选择 VLAN10 后,客户端不会获得可由 gfw 服务的预期 VLAN10 网络;帧究竟被丢弃、被设备错误处理,还是 SSID 实际没有打 tag,必须以 AP/交换机/gfw 抓包和配置核验判断。**不得**将“tagged 帧必然自动去标签并泄漏到 LAN66”作为实施前提。
|
||||
|
||||
**RB5009 下实现需要 3 个前提:**
|
||||
|
||||
1. **RB5009 bridge 增加 VLAN10 转发:**
|
||||
`/interface bridge vlan add bridge=bridge66 vlan-ids=10 tagged=<U6口>,<PVE口>`
|
||||
只在这两个口 tagged。**不要**在 RB5009 上给 VLAN10 配 IP / DHCP(gfw 才是网关与唯一 DHCP)。
|
||||
2. **PVE 宿主机路径(必须做,RB5009 解决不了这一段):**
|
||||
PVE 物理上联口透传 tagged VLAN10;`gfw` 虚拟机有一块 VLAN10 可达的网卡
|
||||
(如 vmbr0 开 `vlan_filtering` + 给 gfw 加第二块 pvid 10 的 NIC,或 VM 内 `eth0.10`)。
|
||||
这是此前「无法实现」的同一处缺口,需在 PVE/vSwitch 层单独验证。
|
||||
3. **`gfw` 侧:** VLAN10 网卡 `192.168.10.1/24` + DHCP(`192.168.10.0/24`)+ 出 66 口
|
||||
masquerade。RB5009 无需到 `192.168.10.0/24` 的路由(gfw SNAT 后源地址即 66 网段)。
|
||||
|
||||
**两个注意点:**
|
||||
|
||||
- **单 DHCP 原则:** VLAN10 上唯一 DHCP 是 gfw;RB5009 不得在 VLAN10 提供 DHCP。
|
||||
- **设计确认:** 升级 SSID 客户端走 gfw 网关后,DNS 为 gfw 的 dnsmasq→clash(7874),
|
||||
**不是** AdGuard `.36`——这是「被代理网络」的预期行为,需接受。
|
||||
|
||||
### 阶段 5 附:PVE 上 VLAN10 透传实现(调研 2026-08-09)
|
||||
|
||||
官方 wiki + 多个社区案例支持两种模型,核心原则是 **「一层只拥有一个 tag」**——要么 PVE 拥有
|
||||
access VLAN,要么 OpenWrt 拥有 trunk,**不能在同一张 NIC 上两层都做**。
|
||||
|
||||
**方案 A(推荐):PVE 拥有 access VLAN,gfw 加第二块 virtio 网卡**
|
||||
|
||||
1. `vmbr0` 开 vlan-aware(`/etc/network/interfaces`):
|
||||
|
||||
```text
|
||||
auto vmbr0
|
||||
iface vmbr0 inet static
|
||||
address 192.168.66.26/24
|
||||
gateway 192.168.66.254
|
||||
bridge-ports eno1
|
||||
bridge-stp off
|
||||
bridge-fd 0
|
||||
bridge-vlan-aware yes
|
||||
bridge-vids 10 # 至少含 10;常见默认 2-4094
|
||||
```
|
||||
|
||||
2. gfw VM 添加 `net1: virtio,bridge=vmbr0,tag=10`。VM 内该网卡是**无标记**接口
|
||||
(已落在 VLAN10 广播域),**不要再建同名 8021q 子接口**。
|
||||
3. 现有 `eth0`(native/untagged = VLAN66)不动,gfw 原有角色不变。
|
||||
4. PVE 物理上联口(PVE→RB5009)改为 **trunk:native 66 + tagged 10**。
|
||||
5. OpenWrt 内:新网卡 `192.168.10.1/24` + DHCP(`192.168.10.0/24`)+ 独立 firewall zone→wan masq。
|
||||
|
||||
**方案 B:OpenWrt 拥有 trunk(单 NIC 多 VLAN)**
|
||||
|
||||
- `vmbr0` vlan-aware;VM NIC **不加 tag**;trunk 原样进 VM;OpenWrt 内建 `8021q` 设备
|
||||
(`eth0.10`,x86/virtio 用 `option type '8021q'`,不要套用 DSA 教程)。物理上联 trunk。
|
||||
- 更灵活(一块网卡多 VLAN),是 router VM 的常见做法,但需要 OpenWrt 8021q 配置,
|
||||
且方案 A 对现有单网卡 gfw 改动更小。
|
||||
|
||||
**RB5009 侧配套(U6 口 + PVE 口都做成 trunk):**
|
||||
|
||||
```text
|
||||
/interface bridge port add bridge=bridge66 interface=<U6口> pvid=66
|
||||
/interface bridge port add bridge=bridge66 interface=<PVE口> pvid=66
|
||||
/interface bridge vlan add bridge=bridge66 vlan-ids=66 tagged=bridge66 untagged=<U6口>,<PVE口>,<其余66口>
|
||||
/interface bridge vlan add bridge=bridge66 vlan-ids=55 tagged=bridge66 untagged=<ether9>
|
||||
/interface bridge vlan add bridge=bridge66 vlan-ids=10 tagged=<U6口>,<PVE口>
|
||||
```
|
||||
|
||||
VLAN10 在 RB5009 上**纯二层桥接**(U6 ↔ PVE),三层由 gfw 承担;RB5009 无 VLAN10 IP/DHCP。
|
||||
|
||||
**常见坑(社区高复发):**
|
||||
|
||||
- **双标签:** PVE 设了 `tag=10` 又在 OpenWrt 里建 `eth1.10` → 一帧被两层改两次。
|
||||
- **native VLAN 不一致:** trunk 上无标记帧被两端当成不同 VLAN → DHCP 消失 / 拿到错网段
|
||||
(正是你之前在 AP 上打 VLAN10 坏 66 网的同类故障)。
|
||||
- **bridge 未 vlan-aware:** tagged 帧进 host 后在 bridge 过滤层消失。
|
||||
|
||||
**诊断命令:**
|
||||
|
||||
```bash
|
||||
# PVE host
|
||||
bridge vlan show
|
||||
ip -br link
|
||||
tcpdump -eni <上联口> # 帧是否到物理口
|
||||
tcpdump -eni vmtapXXXXXX # 帧是否到 VM tap
|
||||
# OpenWrt guest
|
||||
ip -d link show
|
||||
logread -e netifd
|
||||
```
|
||||
|
||||
**参考案例:**
|
||||
|
||||
- PVE 官方 wiki — Network Configuration / VLAN 802.1Q(三种模式 + vlan-aware bridge):
|
||||
<https://pve.proxmox.com/wiki/Network_Configuration>
|
||||
- 「OpenWrt VM on Proxmox」设计(trunk vs access 谁拥有 tag、双标签坑):
|
||||
<https://phb-crystal-ball.org/run-openwrt-in-proxmox/>
|
||||
- PVE 论坛「Tagged and Untagged VLAN」(`bridge-vlan-aware yes` + `bridge-vids` 解法):
|
||||
<https://forum.proxmox.com/threads/tagged-and-untagged-vlan-configuration.144421/>
|
||||
- OpenWrt 论坛 guest WiFi tagged VLAN 案例(`vmbr0.3`→VM 第三网卡;guest 拿到错误网段的
|
||||
同型故障,最终归因在 PVE/host 侧):<https://forum.openwrt.org/t/continued-x86-openwrt-proxmox-vlan-issues/182623>
|
||||
|
||||
> **PVE host 改动风险:** 给 `vmbr0` 开 vlan-aware 是对宿主机网络栈的修改,有管理面断连风险;
|
||||
> 需在维护窗内用控制台/独立带外通道进行,先 `ifreload -a`(PVE7+ 的 ifupdown2 支持热应用),
|
||||
> 保留原配置作回滚。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收清单(「网络算正常」的样子)
|
||||
|
||||
1. 管理面:`zhiqiang` 密钥 SSH 可从 LAN66/55 登录;默认 `admin` 禁用;Winbox/API 不可达;`.254` 管理可达。
|
||||
2. VLAN:客户端取得正确网段(`.66.x` / `.55.x`),默认网关 `.254`,DNS `.36` 可用。
|
||||
3. 互联网:PPPoE 已建立;IPv4 外网通;端口转发逐条从公网验证(hass 8123、transmission 51413、ssh 5822→.36:22、openvpn 1194)。
|
||||
4. IPv6:两 VLAN 拿到 SLAAC `/64`,默认路由存在,外部 IPv6 可达;IPv6 防火墙未阻断必要 ICMPv6/DHCPv6。
|
||||
5. 本地服务:跨 VLAN(`.55.x` ↔ `.66.x`)互通;AdGuard Home、UniFi 控制器、`gfw` 旁路由行为与升级前一致。
|
||||
6. UniFi:U6 Lite(`.66.6`)与 UAP-AC-Lite(`.55.5`)在控制器显示 **Connected**;Inform 未变 `:9080`。
|
||||
7. 无环路、无双默认网关;端口协商与错误计数正常。
|
||||
8. 备份(`.backup` + 文本导出)已离线保存;ER-X 已下电收纳。
|
||||
|
||||
---
|
||||
|
||||
## 6. 回滚语义
|
||||
|
||||
任一阶段失败:**停手**。
|
||||
|
||||
- **维护窗内失败(PPPoE 未起 / 客户端不通 / 防火墙锁死):**
|
||||
1. 下电 RB5009。
|
||||
2. ONT 线插回 ER-X `eth4`。
|
||||
3. 给 ER-X 上电 → 服务在数分钟内恢复。
|
||||
4. 不要在故障中改 ER-X 的 WAN、DHCP、网关地址或 SSH 策略。
|
||||
- **维护窗成功后** ER-X 只是备件;后续 VLAN10 失败只撤 SSID/VLAN 绑定,主 SSID 与 `.254` 路径不动。
|
||||
|
||||
---
|
||||
|
||||
## 7. 安全与记录
|
||||
|
||||
- **本仓库永不记录**:PPPoE 口令、`zhiqiang` 口令、SSH 私钥、RouterOS 备份(含口令/密钥)。
|
||||
- 每次实质性变更(切换、回滚、加固)完成后,在 Linear **`vps` 项目**记录 scope / action / verification / 遗留 follow-up(本次先不建 issue,待执行时补)。
|
||||
- SSH 与访问策略变更遵循仓库「SSH access safety」流程:ER-X 会话保持为回滚路径,新密钥登录验证成功前不关闭旧通道。
|
||||
|
||||
---
|
||||
|
||||
## 8. 参考
|
||||
|
||||
- 现网地图:[lan-overview.md](lan-overview.md)
|
||||
- ER-X 现状:[edgerouter-x-configuration.md](edgerouter-x-configuration.md)、[hosts/gw.md](../hosts/gw.md)
|
||||
- UniFi:[unifi-network.md](unifi-network.md)、[hosts/ubnt.md](../hosts/ubnt.md)
|
||||
- `gfw`:[hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md)
|
||||
- 作废方案(**不再实施**):[lan-erx-se5420-network.md](lan-erx-se5420-network.md)、[lan-core-switch-upgrade-plan.md](lan-core-switch-upgrade-plan.md)
|
||||
- MikroTik RB5009 官方:<https://mikrotik.com/product/rb5009ug_s_in>、RouterOS v7 手册
|
||||
@@ -0,0 +1,495 @@
|
||||
# SE5420 核心交换机升级操作指导(细粒度版)
|
||||
|
||||
**目标:** 把 LAN66/LAN55 有线接入从 ER-X 内置交换迁到 TL-SE5420;稳定后再启用 VLAN10 升级专用 SSID。
|
||||
|
||||
**全程三条(不再重复):**
|
||||
1. 先断旧、后接新,一次一根线;同一设备不得同时接旧路径与新核心。
|
||||
2. 全程保持 SE5420 Type-C Console(38400 8N1)连接——SSH 会话移动其承载链路即断,不是回滚保障。
|
||||
3. 任一步「预期」不符 → 停止,执行 §16 对应回滚;故障中不动 ER-X 的 WAN/DHCP/SSH。
|
||||
|
||||
**关于命令的真实性:** SE5420 的 GUI 菜单路径来自官方 Omada VLAN/管理指南;CLI 语法仅采用官方手册出现过的(`enable`、`telnet disable`、`copy running-config startup-config`、`show vlan`)。PVE/gfw 的每步先有只读快照命令,改动均以你实际看到的输出为准,不套用写死的模板。
|
||||
|
||||
---
|
||||
|
||||
# 第一阶段:主网 55/66 迁移
|
||||
|
||||
## 1. 开工前准备
|
||||
|
||||
### 1.1 备份(4 个动作,全部留档到 `/tmp/opencode/baseline-YYYYMMDD/`,不进仓库)
|
||||
|
||||
1. ER-X 配置快照(滤敏感行):
|
||||
```bash
|
||||
mkdir -p /tmp/opencode/baseline-$(date +%Y%m%d)
|
||||
ssh -4 zhiqiang@192.168.66.254 'show configuration commands' \
|
||||
| grep -Ev 'pppoe|password|secret|key|pre-shared' \
|
||||
> /tmp/opencode/baseline-$(date +%Y%m%d)/gw-sanitized.txt
|
||||
```
|
||||
- 预期:文件非空,`grep -i 'password\|pppoe' gw-sanitized.txt` 无输出。
|
||||
2. PVE 配置:
|
||||
```bash
|
||||
ssh -4 root@192.168.66.26 'cat /etc/network/interfaces; echo ---; qm config 140' \
|
||||
> /tmp/opencode/baseline-$(date +%Y%m%d)/pve-interfaces.txt
|
||||
```
|
||||
- 预期:能看到 `vmbr0`/`vmbr1` 定义与 VM140 的 `net0`/`net1`。
|
||||
3. gfw 配置:
|
||||
```bash
|
||||
ssh -4 root@192.168.66.1 'cat /etc/config/network /etc/config/dhcp /etc/config/firewall' \
|
||||
> /tmp/opencode/baseline-$(date +%Y%m%d)/gfw-uci.txt
|
||||
```
|
||||
- 预期:三个文件内容都在,且能看到 `ubunt_upg` zone、`ubunt_upg_nat`、`ubunt_upg_to_lan`。
|
||||
4. 检查备份文件存在且可读:`ls -la /tmp/opencode/baseline-$(date +%Y%m%d)/`。
|
||||
|
||||
### 1.2 记录现网状态(迁移后比对用)
|
||||
|
||||
1. ER-X 状态:
|
||||
```bash
|
||||
ssh -4 zhiqiang@192.168.66.254 'show interfaces; show ip route; show dhcp server leases'
|
||||
```
|
||||
- 预期:eth0/switch0 up;两条直连路由 + pppoe0 默认路由;DHCP lease 正常。
|
||||
2. PVE 地址:
|
||||
```bash
|
||||
ssh -4 root@192.168.66.26 'ip -br addr'
|
||||
```
|
||||
- 预期:`vmbr0`=192.168.66.26、`vmbr1`=192.168.55.26。
|
||||
3. gfw 关键状态:
|
||||
```bash
|
||||
ssh -4 root@192.168.66.1 'uci show firewall | grep -E "ubunt_upg|wan.*masq"; ip -4 route'
|
||||
```
|
||||
- 预期:`wan.masq=1`、`ubunt_upg_nat`、`ubunt_upg_to_lan` 在位;默认路由 `default via 192.168.66.254`。
|
||||
4. 两台 AP:打开 UniFi 控制台 → Devices,记录 U6 Lite 与 UAP-AC-Lite 的 IP、Connected 状态、Inform URL。
|
||||
|
||||
### 1.3 SSID 与标签
|
||||
|
||||
1. 禁用不可用 SSID:UniFi → WiFi → 找到 `ubnt-upg` → 关闭 Enabled 开关。
|
||||
- 预期:开关变灰/显示 Disabled。
|
||||
- 记录原设置(第二步回滚要用)。
|
||||
2. 给现网每根线缆贴标签:`旧设备端接口名` + `目标 SE5420 口`,如 `NAS→SE5420口4`、`ERX-eth0→SE5420口1`。
|
||||
3. 打印 §2 端口表放在操作位。
|
||||
|
||||
### 1.4 物料与带外
|
||||
|
||||
1. 确认有:SE5420(核对 V1.0 型号)、原装电源线、Type-C 串口线、Cat5e 以上网线数根、标签贴。
|
||||
2. 管理本装「TP-LINK USB 串口驱动」(官网下载或随机光盘)。
|
||||
3. 终端仿真软件准备(PuTTY/串口助手/Windows Terminal),预填参数 **38400 / 8 / 无校验 / 1 / 无流控**。
|
||||
|
||||
## 2. 端口表(SE5420)
|
||||
|
||||
> 面板端口为**上下交错**排列:下排 = 1、3、5、7、9、11、13、15;上排 = 2、4、6、8、10、12、14、16(口 1 最左下,口 2 在其正上方)。
|
||||
> **分组原则:上排全 = LAN66,下排全 = LAN55**(55 在 66 下一排);ER-X 两条上联在最左相邻(口 2=66、口 1=55)。
|
||||
|
||||
### 上排 = VLAN66(口 2、4、6、8、10、12、14、16)
|
||||
|
||||
| SE5420 口 | 对端 | 模式 | PVID | 成员 VLAN |
|
||||
|---|---|---|---|---|
|
||||
| **2** | ER-X `eth0`(66 上联) | access | 66 | untagged 66 |
|
||||
| **4** | PVE `enp1s0` | access | 66 | untagged 66 |
|
||||
| **6** | U6 Lite | access | 66 | untagged 66 |
|
||||
| **8** | NAS/FreeNAS 第 1 口 | access | 66 | untagged 66 |
|
||||
| **10** | windy-pc(`.99`)或 hp-nas(`.32`)二选一 | access | 66 | untagged 66 |
|
||||
| **12** | NAS 第 2 口(后续 LACP 窗用) | 物理断开 | — | — |
|
||||
| **14** | LAN66 预留 | admin down | — | — |
|
||||
| **16** | LAN66 预留 | admin down | — | — |
|
||||
|
||||
### 下排 = VLAN55(口 1、3、5、7、9、11、13、15)
|
||||
|
||||
| SE5420 口 | 对端 | 模式 | PVID | 成员 VLAN |
|
||||
|---|---|---|---|---|
|
||||
| **1** | ER-X `switch0` 成员口(55 上联) | access | 55 | untagged 55 |
|
||||
| **3** | PVE `enp2s0` | access | 55 | untagged 55 |
|
||||
| **5** | UAP-AC-Lite | access | 55 | untagged 55 |
|
||||
| **7** | 隔壁房间上联(IoT) | access | 55 | untagged 55 |
|
||||
| **9** | 打印机(未来) | admin down | — | — |
|
||||
| **11** | LAN55 预留 | admin down | — | — |
|
||||
| **13** | LAN55 预留 | admin down | — | — |
|
||||
| **15** | LAN55 预留 | admin down | — | — |
|
||||
| SFP+ 1–4 | 最右侧 4 个 | 空槽 | — | — |
|
||||
|
||||
- 第一步不建 VLAN10、不向 ER-X 送任何 tag、口 4/6(PVE、U6)不做 trunk。
|
||||
- NAS 只接口 8,口 12 断开(LACP 是独立维护窗)。
|
||||
- 不占口的 VM:dns(.36=VM120)、ubnt(.46=VM160)、gfw(.1=VM140)、haos(.55.11=VM180);transmission(.51) 是 NAS jail。
|
||||
|
||||
## 3. 开箱与固件升级
|
||||
|
||||
1. 核对包装型号 `TL-SE5420`;看设备底部标签硬件版本 `V1.0`。
|
||||
2. 浏览器打开 `https://www.tp-link.com.cn/product_2899.html?v=download`。
|
||||
3. 页面「升级软件」分类下下载最新固件(已知 `20230908_1.0.0`,以页面为准);解压得到 `.bin` 文件。
|
||||
4. 管理本网线接 SE5420 任意 RJ45 口;管理本设静态 IP `10.18.18.10/24`(网关留空)。
|
||||
5. 浏览器 `http://10.18.18.251`,首次登录创建用户名 + 强密码。
|
||||
6. WEB 里 `系统管理 → 系统工具 → 软件升级 → 选择文件` 选固件 → 升级 → 等待重启,**期间勿断电**。
|
||||
7. **验证:** 升级后 `系统管理 → 系统配置 → 系统信息` 版本号正确;面板 SYS 灯绿色闪烁、Link/Act 闪一下后熄灭。
|
||||
|
||||
## 4. 台面初始化 SE5420(不接生产)
|
||||
|
||||
### 4.1 登录通道确认
|
||||
|
||||
1. Console 线接 SE5420 Type-C Console 口 → 管理本 USB。
|
||||
2. 打开串口终端(38400/8/无/1/无流控),连按回车。
|
||||
3. **预期:** 出现 `TL-SE5420>` 提示符。
|
||||
4. 输入 `enable` 回车,进特权模式(提示符变 `#` 或类似)。
|
||||
5. 若 WEB 也通(第 3 步已创建账号),记录:WEB + Console 双通道可用。
|
||||
|
||||
### 4.2 基础安全
|
||||
|
||||
1. WEB 登录 → 改强口令:`系统管理 → 用户管理 → 用户配置` → 修改用户名/密码 → 保存。
|
||||
2. 设时区与 NTP:`系统管理 → 系统配置 → 系统时间` → 时区 `Asia/Shanghai` → NTP 服务器填 `ntp.aliyun.com`(或 `pool.ntp.org`)→ 保存。
|
||||
3. 关 Telnet:`系统管理 → 安全管理 → Telnet配置` → 关闭 Enable。
|
||||
- CLI 替代:`telnet disable`。
|
||||
4. 关 HTTP:`系统管理 → 安全管理 → HTTP配置` → 关闭 HTTP 服务(保留 HTTPS)。
|
||||
5. 开 SSH:`系统管理 → 安全管理 → SSH配置` → 启用 SSH(端口 22)。
|
||||
6. 关云管理:`系统管理 → 云管理 → 全局配置` → 全局开关保持关闭(不绑定 `smbcloud.tp-link.com.cn`)。
|
||||
7. **保存:** 顶部/右上角保存按钮,或 `配置保存 → 保存配置`。
|
||||
8. **验证:** CLI `show running-config` 中 Telnet/HTTP 为 disable;管理本 `https://10.18.18.251` 能开、SSH 22 能连(此时管理 IP 仍是默认 10.18.18.251,66.253 的 HTTPS 验证在 §4.3 第 7 步做)。
|
||||
|
||||
### 4.3 VLAN 与管理地址(本步只建 66/55)
|
||||
|
||||
**步骤顺序依据:先建二层 VLAN(勾选端口成员)→ 再建 SVI → 验证管理路径 → 才清理 VLAN1。**
|
||||
> **端口成员在 VLAN 配置界面的 Untagged/Tagged 选择器里直接勾选**,无独立端口配置页。Untagged = 该 VLAN 的 access 口;PVID 随之对应(以界面为准)。
|
||||
|
||||
1. 建 VLAN66:`VLAN → 802.1Q VLAN → VLAN配置` → 新建 → VLAN ID 填 `66` → **Untagged 勾选:口 2、4、6、8、10**(Tagged 不选)→ 保存。
|
||||
2. 建 VLAN55:新建 → VLAN ID 填 `55` → **Untagged 勾选:口 1、3、5、7**(Tagged 不选)→ 保存。
|
||||
3. **验证:** VLAN 列表出现 66、55;VLAN1 仍在(暂不动)。
|
||||
4. 建管理 SVI:`路由功能 → 接口 → 接口设置` → 创建接口 → 接口 ID `VLAN 66` → IP 地址模式 `Static` → IP `192.168.66.253`、掩码 `255.255.255.0` → 管理状态开启 → 保存。默认网关:`路由功能 → 静态路由 → IPv4静态路由条目` → 新建:目的 `0.0.0.0`、掩码 `0.0.0.0`、下一跳 `192.168.66.254`、管理距离 `1`。
|
||||
5. 管理本改静态 IP `192.168.66.10/24`,网线接 SE5420 **口 6**(口 6 已在 VLAN66)。
|
||||
6. **验证管理路径:**
|
||||
- `ping 192.168.66.253` → 通;
|
||||
- 浏览器 `https://192.168.66.253` → 登录成功;
|
||||
- (可选)SSH:`ssh -o KexAlgorithms=+diffie-hellman-group1-sha1 -o HostKeyAlgorithms=+ssh-rsa admin@192.168.66.253` → 成功。
|
||||
- 全通后再进行下一步;否则停手排查(查 Untagged 勾选/PVID/接线),保持 Console 兜底。
|
||||
7. **保存配置**(右上角保存或 `配置保存 → 保存配置`)。
|
||||
8. **验证:** `show vlan`:口 2/4/6/8/10 属 VLAN66,口 1/3/5/7 属 VLAN55,口 9/11/12/13/14/15/16 无成员;VLAN1 暂时还有全口成员(下一步清理)。
|
||||
9. 清理 VLAN1:`VLAN → 802.1Q VLAN → VLAN配置` → 编辑 VLAN1 → 把口 1–16 从 **Untagged 勾选**中移除(保留 66 或 55 的勾选不变)→ 保存。**改 PVID 不等于移除成员。**
|
||||
> **VLAN1 不可删除属正常**(TP-Link 默认 VLAN),且 **access 口必须属于某 untagged VLAN,GUI 会拒绝把口从 VLAN1 剔除**——不必强求。做法改为:**先把未用口 Disabled(第 13 步)再清 VLAN1 IP(第 10 步)**;disabled 口不转发流量,VLAN1 剩下无管理 IP + 全 disabled 成员 = 无管理面、无转发路径,目标达成。
|
||||
10. 删 VLAN1 管理 IP:`路由功能 → 接口 → 接口设置` → VLAN1 → 清空 IPv4。
|
||||
11. **保存配置**。
|
||||
12. **验证:** `show vlan` VLAN1 无任何成员、无管理 IP;VLAN66 管理路径仍通(SSH/HTTPS 不断)。
|
||||
13. 关未用端口:`二层交换 → 端口管理 → 端口配置` → 口 9、11、12、13、14、15、16 **及 SFP+ 17–20** → 端口状态 `Disabled`;并确认这些口不在任何 VLAN 的 Untagged/Tagged 勾选中。**口 12 是 NAS LACP 预留口,未来 LACP 维护窗需在此重新启用。**
|
||||
14. 配管理面源限制:`系统管理 → 安全管理 → 安全配置`(管理主机/管理访问,以界面为准)→ 只允许源 `192.168.66.0/24` 访问 HTTPS/SSH(启用 SNMP 则一并限制)→ 其余拒绝 → 保存。
|
||||
> ⚠️ **不要**在 `访问控制` 菜单配——那是数据面 ACL(MAC/IP ACL),管不到管理面;管理面限制在 `安全管理 → 安全配置`。
|
||||
15. 配生成树:
|
||||
- 全局配置(`生成树 → 基本配置`):功能`启用`、模式`RSTP`、**CIST优先级 `4096`**(让本机当根桥);联络时间 2 / 老化 20 / 时延 15 / 流量限制 5 / 最大跳数 20 保持默认。
|
||||
- 端口配置(`生成树 → 端口配置`):**终端口 5、6、8、10 的「边缘端口」设`启用`**(= PortFast);上联口 1、2、3、4、7 保持`禁用`。状态列显示`禁用`是链路未接/未启用,属正常。
|
||||
- 端口保护(`生成树 → 安全配置 → 端口保护`):**终端口 5、6、8、10 的「BPDU保护」设`启用`**;上联口 1、2、3、4、7 保持`禁用`。环路保护/根桥保护/TC保护/BPDU过滤全部保持`禁用`。
|
||||
- 保存。
|
||||
16. **L3 明确边界检查:** `路由功能` 确认只有 VLAN66 有接口(`路由功能 → 接口 → 接口设置` 仅 VLAN66 有 IPv4);`路由功能 → 静态路由 → IPv4静态路由条目` 只有默认路由一条(0.0.0.0→.254);`路由功能 → DHCP服务器` 与 `DHCP中继` 未启用;`路由功能 → RIP` 未启用。
|
||||
17. **保存 + 导出备份:** `配置保存 → 保存配置`;`系统管理 → 系统工具 → 配置导出` → 导出配置文件到管理本(离线保存,不进仓库)。
|
||||
18. **断电重启验证:** 拔电源 10 秒 → 插回 → 重启完成后 `ping 192.168.66.253` 通、HTTPS/SSH 可登录 → 证明保存生效。
|
||||
|
||||
### 4.4 台面完成判定
|
||||
|
||||
- `show vlan` 结果与 §2 端口表一致;VLAN1 空;管理仅 LAN66 可达;配置已导出。
|
||||
|
||||
## 5. 建立上联(维护窗开始)
|
||||
|
||||
1. 找到 ER-X `eth0` 当前所连的旧设备/旧交换线,**先拔掉旧线**(避免 eth0 同时连两条路径形成环路)。
|
||||
2. 新线一端插 SE5420 **口 2**,另一端插 ER-X `eth0`。
|
||||
3. **验证:**
|
||||
- 管理本 `ping 192.168.66.253` → 通(SE5420);
|
||||
- `ping 192.168.66.254` → 通(ER-X);
|
||||
- `ping 223.5.5.5` → 通(外网);
|
||||
- SE5420 `show interface` 口 2:Link up、速率协商正常、错误计数 0。
|
||||
4. 找到 ER-X `switch0` 某成员口(eth1–eth3 任一口)的旧接线,先拔。
|
||||
5. 新线一端插 SE5420 **口 1**,另一端插该 switch0 成员口。
|
||||
6. **验证:**
|
||||
- 从 55 网段设备(或临时给管理本加 `.55.x` 地址)`ping 192.168.55.254` → 通;
|
||||
- `ping 192.168.66.46`(控制器,跨网段)→ 通;
|
||||
- SE5420 `show interface` 口 1:Link up、错误计数 0。
|
||||
|
||||
## 6. 迁移 PVE
|
||||
|
||||
1. 拔 PVE `enp1s0` 旧线 → 插 SE5420 **口 4**。
|
||||
2. **验证:**
|
||||
```bash
|
||||
ssh -4 root@192.168.66.26 'ip -br addr'
|
||||
```
|
||||
- 预期:`enp1s0`/`vmbr0` = `192.168.66.26/24`;
|
||||
- `ssh -4 root@192.168.66.26 'ping -c3 192.168.66.254'` → 通。
|
||||
3. 拔 PVE `enp2s0` 旧线 → 插 SE5420 **口 3**。
|
||||
4. **验证:**
|
||||
- `ip -br addr`:`vmbr1` = `192.168.55.26/24`;
|
||||
- `ping -c3 192.168.55.254` → 通。
|
||||
5. 逐台验证 VM(顺序:gfw → dns → ubnt → haos):
|
||||
```bash
|
||||
ssh -4 root@192.168.66.26 'qm list'
|
||||
```
|
||||
- gfw:`ping -c3 192.168.66.1` → 通;
|
||||
- dns:`ping -c3 192.168.66.36` → 通;
|
||||
- ubnt:`ping -c3 192.168.66.46` → 通;
|
||||
- haos:`ping -c3 192.168.55.11` → 通(注意是 55 网段)。
|
||||
6. 每个 VM 再验业务:gfw 的 OpenClash 面板/DNS 正常、dns 的 AdGuard UI 能开、ubnt 控制器 Connected、haos 界面能开。不以"宿主开机"代替。
|
||||
|
||||
## 7. 迁移 AP 与接入设备
|
||||
|
||||
1. U6 Lite:先拔旧线 → 插 SE5420 **口 6**(本地 PoE 注入器不动,网线只走数据)。
|
||||
2. **验证:** U6 重连后拿回 `192.168.66.6`;UniFi 控制台显示 U6 Connected;Inform 与 §1.2 记录一致。
|
||||
3. UAP-AC-Lite:拔旧线 → 插 SE5420 **口 5**。
|
||||
4. **验证:** `192.168.55.5`;UniFi 显示 Connected。
|
||||
5. 隔壁房间上联:拔旧线 → 插 SE5420 **口 7**。
|
||||
- 插之前确认对端:若对端是小交换机,须满足——只有一条上联、只承载 untagged LAN55、无 VLAN trunk、无第二条路径回 ER-X/SE5420。任一项不满足则不迁移。
|
||||
6. **验证:** 该网段设备拿到 `.55.x`、网关 `192.168.55.254`、DNS `192.168.66.36` 可达。
|
||||
7. NAS:拔旧线 → 插 SE5420 **仅口 8**(口 12 保持断开)。
|
||||
8. **验证:** NAS 拿到 `.66.x`(应为原静态 `.51` 或 reservation),`ping 192.168.66.36` 通,transmission jail 可访问。
|
||||
9. 口 10 只接一台明确设备(windy-pc `.99` 或 hp-nas `.32`):拔旧线 → 插口 10。
|
||||
10. **验证:** 该设备拿 `.66.x`、上网正常。
|
||||
11. 确认没有并行二层路径后,旧交换/旧接线下电、收线。
|
||||
|
||||
## 8. 第一阶段验收(全绿才进第二阶段)
|
||||
|
||||
1. SE5420:`ping 192.168.66.253` 通,且从 55 网段**不通**(依赖 §4.3 第 14 步管理面源限制已配;若 ACL 未生效,从 55 网段经 ER-X 路由仍可达 .253——此条不通过则回查 ACL);默认口令已改;配置已离线备份。
|
||||
2. LAN66 客户端:`ip addr` 是 `.66.x`、路由 `default via 192.168.66.254`、DNS `192.168.66.36`;`ping 223.5.5.5` 通。
|
||||
3. LAN55 客户端:`.55.x`、`default via 192.168.55.254`;LAN55↔LAN66 互通(如 `ping 192.168.66.46`)。
|
||||
4. 端口转发:从公网(手机流量)逐条验——hass `:8123`、transmission `:51413`、ssh `:5822→.36:22`、openvpn `:1194`。
|
||||
5. UniFi:U6、AC-Lite 均 Connected,Inform 与基线一致。
|
||||
6. SE5420 `show interface`:所有在用口无 CRC/FCS/错误计数增长;ER-X 无广播风暴/ MAC 漂移日志;旧交换已断电。
|
||||
7. gfw:主网默认网关仍 `.254`(`ip route` 里没有默认走 `.1`),OpenClash 行为与升级前一致。
|
||||
|
||||
> 可选:两台 2.5G 终端同 VLAN 跑 `iperf3 -s` / `iperf3 -c <对端>`,记结果。同 VLAN 才可能超 1G;跨 55/66 与 Internet 受 ER-X/宽带限制。
|
||||
|
||||
---
|
||||
|
||||
# 第二阶段:VLAN10 升级专用 SSID(第一步稳定 ≥1 天后再做)
|
||||
|
||||
> 放行门槛:§8 全绿、稳定 ≥1 天、每步先只读核实现状。SSID 最后才启用。任一步失败只回滚该步。
|
||||
|
||||
## 9. PVE VLAN10 边界
|
||||
|
||||
### 9.1 快照与备份(在 PVE 上执行:SSH `ssh -4 root@192.168.66.26` 进入或本地终端)
|
||||
|
||||
1. 取只读快照(逐条执行并保存输出):
|
||||
```sh
|
||||
cat /etc/network/interfaces
|
||||
qm config 140
|
||||
bridge vlan show
|
||||
bridge link
|
||||
ip -d link show vmbr0
|
||||
pveversion -v
|
||||
```
|
||||
2. 备份(同 §1.1 目录):PVE `/etc/network/interfaces`、`/etc/pve/qemu-server/140.conf`;gfw `/etc/config/{network,dhcp,firewall}`。
|
||||
3. 确认本地 Console/带外路径可用;**无完整备份和回滚能力则停止。**
|
||||
|
||||
### 9.2 改造 vmbr0 为 VLAN-aware(以 §9.1 快照为准)
|
||||
|
||||
1. 编辑 `/etc/network/interfaces`(PVE 本机 `nano /etc/network/interfaces`)。
|
||||
2. 在 **vmbr0 定义段**加两行:
|
||||
```text
|
||||
bridge-vlan-aware yes
|
||||
bridge-vids 10 66
|
||||
```
|
||||
> `bridge-vids` 只放行需要的 tagged VLAN:`10`(gfw VLAN10 帧)+ `66`(显式声明 native)。不要写 `2-4094` 全放行;将来加 VLAN 再扩展此列表。
|
||||
> ⚠️ **`bridge-vids` 必须加在 vmbr0 段**,不是 `enp1s0` 段——enp1s0 只是 `bridge-ports` 成员;加错位置 VLAN10 无法上 bridge。
|
||||
3. **不要**改动 `bridge-ports enp1s0`、现有 tap 口或 `vmbr1`;现有 VM 的无标签语义(untagged/PVID)保持不变。
|
||||
4. 应用:**优先 `ifreload -a`**(PVE 用 ifupdown2,只应用变更);不要用 `systemctl restart networking`(会重载全部接口)。
|
||||
> ⚠️ `ifreload` 会断当前 SSH 连接(管理本重连即可),`ip -br addr` 重新验证。
|
||||
5. **验证(关键):** `ssh -4 root@192.168.66.26 'ip -br addr'` 显示 `.66.26` 仍在;`ping 192.168.66.254` 通;dns(.36)/ubnt(.46)/gfw(.1) 仍通。任一断 → 立即恢复备份的 interfaces 文件并 `ifreload -a`。
|
||||
6. `bridge vlan show` 确认现有 tap 口、物理口的 PVID/untagged 与迁移前一致,只是多出 tag 10 的允许。
|
||||
|
||||
### 9.3 为 gfw 加 tag=10 专用 vNIC
|
||||
|
||||
1. PVE 上给 VM140 加网卡(从管理本执行,bus 用下一个空闲值,记下 MAC):
|
||||
```sh
|
||||
ssh -4 root@192.168.66.26 'qm set 140 -net2 virtio,bridge=vmbr0,tag=10'
|
||||
```
|
||||
2. **验证:** `qm config 140` 出现 `net2 ... tag=10`。
|
||||
3. gfw 内确认新接口名:
|
||||
```sh
|
||||
ssh -4 root@192.168.66.1 'ip -br link'
|
||||
```
|
||||
- 预期出现新接口(如 `eth2`),状态 DOWN 正常。
|
||||
|
||||
### 9.4 迁移 gfw 的 ubunt_upg 到新接口
|
||||
|
||||
1. gfw 先只读确认当前绑定与 DHCP 段名:
|
||||
```sh
|
||||
ssh -4 root@192.168.66.1 'uci show network; uci show dhcp'
|
||||
```
|
||||
预期:`network.ubunt_upg.device='eth0.10'`、`dhcp.ubunt_upg.interface='ubunt_upg'`(按接口名绑定)。
|
||||
2. **最小迁移——只改 device 一行**(gfw 上执行):
|
||||
```sh
|
||||
ifdown ubunt_upg
|
||||
uci set network.ubunt_upg.device='eth2'
|
||||
uci commit network
|
||||
ifup ubunt_upg
|
||||
```
|
||||
> `eth0.10` 不再被引用会自动消失,**无需手动删段**;DHCP 池 `dhcp.ubunt_upg.interface='ubunt_upg'` 按接口名绑定,自动跟着走。
|
||||
3. **验证:** gfw 内 `ip -br addr` → `eth2` 拿 `192.168.10.1/24` UP、`eth0.10` 消失、`eth0` 仍 `.66.1`。
|
||||
4. 确认 firewall `ubunt_upg` zone 按 `network='ubunt_upg'` 绑定(改 device 后 zone/DHCP/NAT/规则自动跟随)。
|
||||
5. **验证:**
|
||||
- PVE 管理 IP 仍在 LAN66;
|
||||
- `bridge vlan show` 确认其他 VM 无 VLAN10 可达性(`tap140i2` 显示 `10 PVID Egress Untagged`;dns/ubnt 等 tap 仍只有 PVID 1)。
|
||||
6. 未通过 → 按 §16 回滚顺序停止 VLAN10 项目。
|
||||
|
||||
## 10. SE5420 增加 VLAN10
|
||||
|
||||
> **关键:口 4/6 是 access 口时,VLAN10 的 Tagged 选择器里会显示不可选**——必须先在本步第 1 步把口 4/6 改成 trunk,才能勾 Tagged。改 trunk 后口 4/6 会从 VLAN66 untagged 成员中被移除,需在第 3 步恢复。
|
||||
|
||||
1. 改口 4/6 为 trunk:`VLAN → 802.1Q VLAN → 端口配置` → 口 4、口 6 → 端口类型 `ACCESS`→`TRUNK` → **PVID 改 `66`**(改 TRUNK 后默认 PVID 变 1,必须改回 66)→ 保存。
|
||||
2. **验证:** 端口配置页口 4/6 显示 `TRUNK` + PVID `66`。
|
||||
3. **恢复 VLAN66 成员:** 改 trunk 后口 4/6 从 VLAN66 untagged 被移除 → `VLAN → 802.1Q VLAN → VLAN配置` → 编辑 VLAN66 → **Untagged 重新勾选口 4、口 6** → 保存。确认 VLAN66 = 口 2、4、6、8、10。
|
||||
4. 建 VLAN10:`VLAN → 802.1Q VLAN → VLAN配置` → 新建 VLAN ID `10` → **Tagged 勾选:口 4、口 6**(Untagged 不勾)→ 保存。口 4/6 即 trunk:untagged 66 + tagged 10。
|
||||
5. 确认其余口不允许 VLAN10(口 4/6 之外不勾选)。
|
||||
6. 确认 VLAN10 不建 SVI/DHCP/路由:`路由功能 → 接口 → 接口设置` 无 VLAN10 接口;`路由功能 → DHCP服务器/DHCP中继` 无 10 段。
|
||||
7. **保存配置:** `配置保存 → 保存配置`。
|
||||
8. **验证(静态链路):**
|
||||
- SE5420 `show vlan`:VLAN10 仅在口 4/6;VLAN66 仍是口 2、4、6、8、10。
|
||||
- PVE `bridge vlan show`:`enp1s0` 允许 10 + 66;`tap140i2` 显示 `10 PVID Egress Untagged`。
|
||||
- `ping 192.168.66.253`、`ssh` 均通(VLAN66 管理流量不受影响)。
|
||||
- > 注:此时抓不到 VLAN10 帧是正常的——还没有客户端(SSID 未启用);真正流量验证在 §12 启用 SSID 后。
|
||||
|
||||
## 11. gfw 防火墙、NAT 与 IPv6
|
||||
|
||||
### 11.1 先只读确认现场
|
||||
|
||||
```sh
|
||||
ssh -4 root@192.168.66.1 'uci show network; uci show firewall; uci show dhcp; ip -4 route; ip -6 addr; ip -6 route; fw4 print; nft list ruleset'
|
||||
```
|
||||
- 记录 `wan` zone 的 masq、`ubunt_upg` zone 段名、`ubunt_upg_nat`、`ubunt_upg_to_lan`、DHCP 段名。以下命令全部用具名段,段名以本次输出为准。
|
||||
|
||||
### 11.1b 现场已核实规则(2026-08 实施时确认存在,无需重建)
|
||||
|
||||
| 段名 | 类型 | 作用 |
|
||||
|---|---|---|
|
||||
| `firewall.ubunt_upg` | zone | `network='ubunt_upg'`,input/forward=`REJECT`、output=`ACCEPT` |
|
||||
| `ubunt_upg_dhcp` | rule | VLAN10 → UDP 67 允许(DHCP) |
|
||||
| `ubunt_upg_dns` | rule | VLAN10 → TCP/UDP 53 允许(DNS) |
|
||||
| `ubunt_upg_block_lan66` | rule | VLAN10 → `192.168.66.0/24` REJECT |
|
||||
| `ubunt_upg_to_lan` | forwarding | `src=ubunt_upg → dest=lan` |
|
||||
| `ubunt_upg_nat` | nat | `src=lan`、`src_ip=192.168.10.0/24`、target `MASQUERADE`(出口走 lan 即 eth0/LAN66) |
|
||||
|
||||
> **待补缺口:** 只有 `192.168.66.0/24` 被拒,**LAN55 和其余 RFC1918 无显式拒绝**——VLAN10 经 ER-X 仍可达 `192.168.55.0/24`(haos/IoT/AP)等内网,必须在 §11.3 补齐。
|
||||
|
||||
### 11.2 IPv4 策略(目标)
|
||||
|
||||
| 流量 | 动作 |
|
||||
|---|---|
|
||||
| VLAN10 → gfw DHCP、DNS | 允许 |
|
||||
| VLAN10 → gfw SSH/LuCI/SNMP/其他管理服务 | 拒绝 |
|
||||
| VLAN10 → LAN66、LAN55、其他 RFC1918 内网 | 拒绝 |
|
||||
| VLAN10 → Internet | 允许 |
|
||||
| established/related 回包 | 允许 |
|
||||
|
||||
### 11.3 执行(段名替换为实际值)
|
||||
|
||||
1. 确认 `ubunt_upg` zone 的 `forward` 保持 `REJECT`(不改 ACCEPT)。
|
||||
2. **NAT 已存在,勿动:** 现场已有 `wan` zone `masq=1` + `ubunt_upg_nat`(`src=lan`、`src_ip=192.168.10.0/24` masquerade)。**不要**给 `ubunt_upg` zone 加 masq,也不要新增 NAT 规则。核对 `nft list ruleset` 该规则在列。
|
||||
3. 不新增宽泛的 `ubunt_upg→wan` zone forwarding(会经 ER-X 放行 LAN55/其它内网)。
|
||||
4. **补内网前缀拒绝**(LAN55、10/8、172.16/12;`192.168.0.0/16` 已被 66/55/10 三段覆盖,无需再加)。⚠️ **每条必须写 `dest='lan'`**——否则 fw4 会把规则放进 `input_ubunt_upg`(进 gfw 本机方向)而非 `forward_ubunt_upg`(VLAN10→其他网段转发),`dest_ip` 永远不命中、等于没配:
|
||||
```sh
|
||||
# LAN55
|
||||
uci add firewall rule
|
||||
uci set firewall.@rule[-1].name='v10_deny_lan55'
|
||||
uci set firewall.@rule[-1].src='ubunt_upg'
|
||||
uci set firewall.@rule[-1].dest='lan'
|
||||
uci set firewall.@rule[-1].dest_ip='192.168.55.0/24'
|
||||
uci set firewall.@rule[-1].target='REJECT'
|
||||
uci set firewall.@rule[-1].proto='all'
|
||||
uci set firewall.@rule[-1].family='ipv4'
|
||||
# 10/8
|
||||
uci add firewall rule
|
||||
uci set firewall.@rule[-1].name='v10_deny_10net'
|
||||
uci set firewall.@rule[-1].src='ubunt_upg'
|
||||
uci set firewall.@rule[-1].dest='lan'
|
||||
uci set firewall.@rule[-1].dest_ip='10.0.0.0/8'
|
||||
uci set firewall.@rule[-1].target='REJECT'
|
||||
uci set firewall.@rule[-1].proto='all'
|
||||
uci set firewall.@rule[-1].family='ipv4'
|
||||
# 172.16/12
|
||||
uci add firewall rule
|
||||
uci set firewall.@rule[-1].name='v10_deny_172net'
|
||||
uci set firewall.@rule[-1].src='ubunt_upg'
|
||||
uci set firewall.@rule[-1].dest='lan'
|
||||
uci set firewall.@rule[-1].dest_ip='172.16.0.0/12'
|
||||
uci set firewall.@rule[-1].target='REJECT'
|
||||
uci set firewall.@rule[-1].proto='all'
|
||||
uci set firewall.@rule[-1].family='ipv4'
|
||||
uci commit firewall
|
||||
fw4 reload
|
||||
```
|
||||
5. **验证(看 nft 而非 UCI):** `nft list ruleset | grep -A6 "chain forward_ubunt_upg"` 应显示拒绝规则**全部在 `forward_ubunt_upg` 链**且先于 `accept_to_lan`;`wan` masq 仍在。若拒绝规则出现在 `input_ubunt_upg` 链 → 说明漏了 `dest='lan'`,回去补。
|
||||
6. **IPv6 核验:** `ip -6 addr show eth2` 只有 `fe80::`;`dhcp.ubunt_upg` 的 `ra='disabled'`、`dhcpv6='disabled'` → VLAN10 无 GUA/ULA、无 IPv6 DNS/路由/出口。
|
||||
|
||||
### 11.4 IPv6:本阶段不提供 VLAN10 IPv6
|
||||
|
||||
1. 显式关闭并核验(不能靠"没配"):RA、DHCPv6、NDP relay/delegation、IPv6 forwarding 对 `ubunt_upg` 相关接口、前缀委派。
|
||||
2. 现场已知:DHCPv6/RA disabled、无 IPv6 默认路由,但全局 `IPv6 forwarding=1`——启用 VLAN10 前复查 `ip -6 addr`、`sysctl net.ipv6.conf.all.forwarding`。
|
||||
3. 客户端可留 link-local;不得获得 GUA/ULA、IPv6 DNS 或 IPv6 默认路由。
|
||||
|
||||
### 11.5 部署后验证
|
||||
|
||||
1. **链归属(已实测):** `nft list ruleset | grep -A6 "chain forward_ubunt_upg"` 确认 66/55/10/172 四条拒绝在 forward 链、先于 `accept_to_lan`;`192.168.10.0/24 masquerade` 仍在(§11.3 第 5 步已验)。
|
||||
2. **IPv6(已实测):** `ip -6 addr show eth2` 仅 `fe80::`;`dhcp.ubunt_upg.ra='disabled'` + `dhcpv6='disabled'`;odhcpd 不服务 eth2(§11.4 已验)。
|
||||
3. **DHCP 租约(已实测):** `cat /tmp/dhcp.leases` 出现 VLAN10 客户端(实测 OnePlus-12 → `192.168.10.168`)。
|
||||
4. **数据面(已实测):** `nft list ruleset` 中 `192.168.10.0/24 masquerade` counter 增长(实测 215 pkts / 42KB)、`accept_to_ubunt_upg` 回包增长、四条 deny 保持 0 → VLAN10 上网走 gfw NAT、隔离生效。
|
||||
5. **拒绝规则命中(SSID 启用后验):** 从测试客户端 `ping 192.168.66.1/.26/.36/.46/.254`、`192.168.55.254`、LAN55 主机 → 全不通;访问 gfw 的 22/80/443 管理口 → 拒绝。
|
||||
6. **客户端上网(SSID 启用后验):** 拿 `192.168.10.x`、网关 `.10.1`、DNS 正常;`curl -4` 国内/海外均通、`curl -6` 失败。
|
||||
7. 抓包工具:`apk add tcpdump`(ImmortalWrt 25.12.0;勿用 opkg)。
|
||||
|
||||
## 12. UniFi 改网络 + 启用 SSID(最后做)
|
||||
|
||||
1. 在 AP 上核实 Inform:SSH 进 U6(`ssh -4 zhiqiangf@192.168.66.6`)→ `info` → 记录 `inform_url`。不凭文档假定。
|
||||
2. UniFi → Networks → 编辑 `ubnt-upg`:
|
||||
- Router/网关设置 → 选 **Third-party Gateway**;
|
||||
- VLAN ID:`10`;
|
||||
- 子网:`192.168.10.0/24`;网关:`192.168.10.1`;
|
||||
- DHCP:关闭(dhcp 由 gfw 提供)。
|
||||
3. **验证:** 保存后网络列表 `ubnt-upg` 显示 Third-party Gateway、VLAN 10。
|
||||
4. UniFi → WiFi → `ubnt-upg` SSID → 分配设备只勾 **U6 Lite**(确保 UAP-AC-Lite 不被勾选)→ 保存。
|
||||
5. 启用 `ubnt-upg` SSID(最后一步才开)。
|
||||
6. 单台测试客户端连上后逐项验证:
|
||||
- `ip addr`:`192.168.10.100~250`;`ip route` 默认网关 `192.168.10.1`;DNS 为预期 gfw 地址;
|
||||
- `nslookup` 普通/国内/海外域名均按预期解析(DNS 走 gfw/OpenClash 路径,不能直连 LAN66 `.36` DNS,除非书面允许);
|
||||
- gfw 出现该客户端 DHCP 租约与 VLAN10 帧;
|
||||
- `curl -4 https://www.baidu.com` 与 `curl -4 https://www.google.com` 均成功;`curl -6` 失败;
|
||||
- `ping 192.168.66.1/.26/.36/.46/.254`、`192.168.55.254`、LAN55 主机 → 全不通;gfw 管理口不通;
|
||||
- 代理路径:在 gfw/OpenClash 日志或按客户端源地址/五元组确认流量走代理,**不以全局 `7892` 计数增长为证明**。
|
||||
7. 任一验证失败 → 先关 `ubnt-upg` SSID;主 SSID、`.254` 路径、LAN55/LAN66 不受影响。
|
||||
|
||||
> **实测记录(2026-08-11):** OnePlus-12 连 `ubnt-upg` 拿到 `192.168.10.168`(DHCP 由 gfw 下发,租约见 `/tmp/dhcp.leases`);gfw `192.168.10.0/24 masquerade` counter 增长(215 pkts / 42KB)、`accept_to_ubunt_upg` 回包 5 pkts、四条内网 deny 保持 0 → 上网走 gfw NAT、VLAN10→内网隔离生效。手机 Wi-Fi 详情不显示网关,以 DHCP 租约 + NAT counter 为证。
|
||||
|
||||
## 13. 第二阶段验收
|
||||
|
||||
1. `ubnt-upg` 客户端满足 §12 第 6 步全部项。
|
||||
2. gfw 侧 DHCP、拒绝规则、NAT、DNS、代理路径均关联到该测试客户端。
|
||||
3. LAN55/LAN66 的 IPv6 行为与迁移前基线一致;VLAN10 无 GUA/ULA、IPv6 DNS、默认路由或 IPv6 出口(除 link-local)。
|
||||
|
||||
> **状态(2026-08-11):** 第 2 条 DHCP+NAT+隔离已实测通过(见 §12 实测记录);第 1、3 条待补(浏览器 curl 国内/海外、拒绝规则从客户端实测命中、IPv6 `curl -6` 失败)。
|
||||
|
||||
## 14. 性能实测(可选但建议)
|
||||
|
||||
1. 两台 2.5G 终端(如 NAS 与 windy-pc,均接 SE5420):一端 `iperf3 -s`,另一端 `iperf3 -c <对端IP>`。
|
||||
2. 记录:单流、4 流结果;预期单流约 2.3–2.5Gbps(受 NIC/线材限制)。
|
||||
3. 跨网段(66↔55)与 Internet 结果如实记录——预期被 ER-X 上联/宽带限制,不要按 2.5G 承诺。
|
||||
|
||||
## 15. 记录
|
||||
|
||||
- 每次实质变更后在 Linear `vps` 项目记录 scope / action / verification / 遗留 follow-up。
|
||||
- 本仓库不记录 SE5420 口令、ER-X 配置快照(含 PPPoE/口令)、gfw 凭据。
|
||||
- 实施前先读 `se5420-review-claim-verification-2026-08.md` 的现场只读复核结论。
|
||||
|
||||
## 16. 回滚
|
||||
|
||||
| 失败位置 | 回滚动作 |
|
||||
|---|---|
|
||||
| §4 台面 | 还原台面配置或恢复出厂(见下) |
|
||||
| §5–7 上联/迁移 | 停手 → 刚迁的线逐根插回 ER-X 原口 → 逐根验证(IP/服务恢复、AP Connected、MAC 表回旧拓扑) |
|
||||
| §9 PVE 边界 | 关 `ubnt-upg` SSID → gfw `ubunt_upg` 恢复绑 `eth0.10` → 恢复 gfw network/DHCP/firewall → 移除 VM140 新增 vNIC(`qm set 140 -delete net2`)→ 恢复 VM140 配置 → **最后**恢复 PVE `/etc/network/interfaces` 并 `ifreload -a` → 分别验证 PVE/dns/ubnt/gfw 的 LAN66 |
|
||||
| §10 SE5420 VLAN10 | 删 VLAN10(`VLAN → 802.1Q VLAN → VLAN配置` 删除 VLAN10),口 4/6 恢复 Untagged 仅 VLAN66、Tagged 清空 |
|
||||
| §11 gfw | 恢复已备份命名规则/NAT;不触碰 LAN55/LAN66 主网规则 |
|
||||
| §12 SSID | 只关 `ubnt-upg` SSID;主 SSID、`.254`、gfw 66 路径不受影响 |
|
||||
|
||||
**SE5420 恢复出厂(忘口令/重配):**
|
||||
- **WEB 方式:** `系统管理 → 系统工具 → 软件复位`(若还能登录 GUI)。
|
||||
- **Console 方式(WEB 不可用/忘口令):**
|
||||
1. 管理本连 Type-C Console(38400 8N1)。
|
||||
2. 交换机断电重启;串口出现 **`Press CTRL-B to enter the bootUtil`** 时按 `Ctrl+B`。
|
||||
3. 输入 **`2`**(软件复位)→ 恢复出厂;此后首次 WEB 登录创建新用户名密码(初始管理 IP 恢复 `10.18.18.251`)。
|
||||
4. 恢复后按 §4 重配并保存。
|
||||
|
||||
## 参考
|
||||
|
||||
- 设计说明:[lan-erx-se5420-network.md](lan-erx-se5420-network.md)
|
||||
- 评审核实:[se5420-review-claim-verification-2026-08.md](se5420-review-claim-verification-2026-08.md)
|
||||
- 现网地图:[lan-overview.md](lan-overview.md)
|
||||
- 官方安装手册(Markdown 版):[se5420-official-manuals/tl-se5420-install-manual.md](se5420-official-manuals/tl-se5420-install-manual.md)
|
||||
- 官方 PDF:<https://service.tp-link.com.cn/download/202310/TL-SE5420%20V1.0安装手册%201.0.2.pdf>
|
||||
- 规格 / 固件:<https://www.tp-link.com.cn/product_2899.html?v=specification> · <https://www.tp-link.com.cn/product_2899.html?v=download>
|
||||
- Omada VLAN 指南:<https://support.omadanetworks.com/en/document/12981/> · <https://support.omadanetworks.com/en/document/13135/>
|
||||
- ER-X:[edgerouter-x-configuration.md](edgerouter-x-configuration.md);gfw:[hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md);PVE VLAN10:[lan-rb5009-upgrade.md#阶段-5-附pve-上-vlan10-透传实现-调研-2026-08-09](lan-rb5009-upgrade.md#阶段-5-附pve-上-vlan10-透传实现-调研-2026-08-09)
|
||||
@@ -0,0 +1,262 @@
|
||||
# TL-SE5420 官方安装手册(整理版)
|
||||
|
||||
> **来源:** TP-LINK《TL-SE5420 V1.0 安装手册 1.0.2》(PDF),已整理为 Markdown。
|
||||
> 官方 PDF:<https://service.tp-link.com.cn/download/202310/TL-SE5420%20V1.0安装手册%201.0.2.pdf>
|
||||
> 配套实施文档:[../lan-se5420-deployment-guide.md](../lan-se5420-deployment-guide.md)
|
||||
> 说明:PDF 中示意图已省略(仅保留文字);正文以官方 PDF 为准。
|
||||
|
||||
---
|
||||
|
||||
## 声明
|
||||
|
||||
- Copyright © 2023 普联技术有限公司,版权所有,保留所有权利。
|
||||
- 产品规格和资讯仅供参考,如有内容更新,恕不另行通知。本手册仅作为使用指导,所作陈述均不构成任何形式的担保。
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档名称 | 用途 |
|
||||
|---|---|
|
||||
| 《安装手册》 | 交换机的安装过程、启动以及常见故障处理 |
|
||||
| 《用户手册》 | 交换机的各个功能应用以及常见功能使用问题处理 |
|
||||
| 《命令行手册》 | 交换机 CLI(Command Line Interface,命令行界面)参考信息 |
|
||||
| 《防雷安装手册》 | 安装中如何做好防雷措施以避免设备雷击损坏 |
|
||||
|
||||
技术支持:服务热线 `400-8863-400` · 邮箱 `fae@tp-link.com.cn` · 微信公众号「TP-LINK商用网络」/「TP-LINK商云APP」
|
||||
|
||||
## 阅读对象与约定
|
||||
|
||||
- 适合阅读对象:网络工程师、网络管理员。
|
||||
- 本手册如无特别说明,均以 **TL-SE5420** 机型为例介绍,所指交换机为 2.5G 三层网管交换机。
|
||||
- 产品规格标贴位于设备底部,扫描二维码可查看安规符号释义。
|
||||
|
||||
---
|
||||
|
||||
## 第 1 章 产品介绍
|
||||
|
||||
### 1.1 产品简介
|
||||
|
||||
2.5G 三层网管交换机,支持静态路由和动态路由 RIP 协议,提供完备的安全防护机制、完善的 QoS 策略和丰富实用的 VLAN 功能。适用于中小型企业网、校园网络、酒店、网吧等使用场景。
|
||||
|
||||
### 1.2 产品外观
|
||||
|
||||
**前面板组成:** 1 模式切换按键 · 2 指示灯 · 3 1 个 Type-C Console 端口 · 4 16 个 2.5Gbps RJ45 端口 · 5 4 个 SFP+ 端口 · 6 1 个 USB 端口
|
||||
|
||||
**模式切换按键:** 用于切换指示灯的指示状态,支持「端口模式」和「堆叠模式」切换。任意模式下超过 45s 不按动按钮,自动恢复为端口模式。(模式切换功能软件后续升级支持,以实际软件功能为准。)
|
||||
|
||||
**指示灯状态:**
|
||||
|
||||
| 指示灯 | 名称 | 工作状态 | 工作说明 |
|
||||
|---|---|---|---|
|
||||
| PWR | 电源指示灯 | 常亮 / 熄灭 / 闪烁 | 供电正常 / 未通电或供电异常 / 供电异常 |
|
||||
| SYS | 系统指示灯 | 绿色闪烁 / 红色闪烁 | 系统正常 / 系统异常 |
|
||||
| Master | 堆叠指示灯 | 绿色常亮 / 黄色常亮 / 熄灭 | 堆叠主设备 / 堆叠错误 / 非主设备 |
|
||||
| Stack | 模式指示灯 | 绿色常亮 / 熄灭 | 端口指示灯工作在堆叠模式 / 其它模式 |
|
||||
| 1-16(端口模式) | 端口指示灯 | 绿色常亮 / 黄色常亮 / 闪烁 / 熄灭 | 2.5Gbps / 10/100/1000Mbps / 传输中 / 未连接 |
|
||||
| 17-20(端口模式) | 端口指示灯 | 绿色常亮 / 黄色常亮 / 闪烁 / 熄灭 | 10Gbps / 2.5Gbps 或 1000Mbps / 传输中 / 未连接 |
|
||||
| USB | 端口指示灯 | 绿色常亮 / 熄灭 | USB 连接正常 / 未连接或异常 |
|
||||
|
||||
**Console 端口:** 用于和计算机或其他终端的串口相连以管理或配置交换机。TL-SE5420 提供 **1 个 Type-C Console 端口**。
|
||||
|
||||
**2.5Gbps RJ45 端口:** 16 个,支持 10M/100M/1000M/2.5Gbps 速率,每个端口对应一个端口指示灯。
|
||||
|
||||
**10Gbps SFP+ 端口:** 支持接入 1Gbps/2.5Gbps SFP 光/电模块或 10Gbps SFP+ 光/电模块,每个端口对应 1 个 Link/Act 指示灯。
|
||||
|
||||
**USB 端口:** 标准 USB2.0,480Mbps。可和交换机 Flash 文件系统交互(上传/下载应用程序、配置文件)。支持**交换机 U 盘开局**(详见《用户手册》)。
|
||||
|
||||
> 注意:不同厂商 USB 设备的兼容性和驱动存在差异,TP-LINK 不保证所有 USB 设备都能正常使用;如不能使用不属于交换机故障。
|
||||
|
||||
**后面板组成:** 1 防雷接线柱 · 2 电源插座(三相,220V~50Hz 交流电源)。
|
||||
|
||||
> 注意:请使用原装电源线;请通过电源线插头连接到有接地连接的输出插座;请远离水、火、潮湿或炎热环境;不要自行拆卸、修理、修改设备或更换电池(有爆炸危险)。
|
||||
|
||||
---
|
||||
|
||||
## 第 2 章 产品安装
|
||||
|
||||
### 2.1 物品清单
|
||||
|
||||
- 交换机 ×1 · 电源线 ×1 · 机架 L 型支架 ×2 · 安装手册 ×1 · 接地线 ×1 · 其他配件
|
||||
|
||||
### 2.2 安装注意事项
|
||||
|
||||
**安装安全:**
|
||||
- 安装过程中电源保持关闭,佩戴防静电手环并保证接地良好;
|
||||
- 确认供电电压与交换机标示电压相符;
|
||||
- 通电前确认不会引起电源电路超负荷;
|
||||
- 工作时不要打开外壳;
|
||||
- 清洁前先拔电源插头,勿用湿润面料擦拭或液体清洗;
|
||||
- 保持交换机底部朝下水平放置。
|
||||
|
||||
**环境要求:**
|
||||
|
||||
| 环境描述 | 温度 | 相对湿度 |
|
||||
|---|---|---|
|
||||
| 工作环境 | 0℃ ~ 40℃ | 10% ~ 90%RH 无凝结 |
|
||||
| 存储环境 | -40℃ ~ 70℃ | 5% ~ 90%RH 无凝结 |
|
||||
|
||||
- 海拔:仅适用于 2000m 以下地区。
|
||||
- 室内防尘、防电磁干扰、防雷:确认设备与大地接地良好;远离高频大功率设备;室外布线建议使用信号防雷器。
|
||||
- 安装台:机架或工作台需承重至少 **5.5Kg**;供电插座与交换机距离不超过 1.5 米。
|
||||
|
||||
### 2.3 安装工具准备
|
||||
|
||||
十字螺丝刀 · 防静电手环 · 网线、光纤
|
||||
|
||||
### 2.4 产品安装
|
||||
|
||||
**桌面安装:** 底部朝上放置于稳定桌面 → 粘贴 4 个脚垫到机壳底部四角凹槽 → 翻转放平。机壳周围预留 5~10cm 空隙确保散热。
|
||||
|
||||
**19 英寸机架安装:** 检查机架接地与稳定性 → 两侧安装 L 型角铁并固定 → 安放到机架内由托架支撑 → 螺钉固定 L 型角铁到机架导槽。机架内一般由下至上安装。
|
||||
|
||||
---
|
||||
|
||||
## 第 3 章 硬件连接
|
||||
|
||||
### 3.1 连接 RJ45 端口
|
||||
通过 RJ45 端口连接交换机和对端设备。
|
||||
|
||||
### 3.2 连接 SFP+ 端口
|
||||
连接 SFP/SFP+ 光/电模块或 SFP+ 电缆。**SFP+ 端口默认支持 10Gbps;若使用千兆 SFP 光模块,需先将 SFP+ 端口速率设置为 1000Mbps。** 使用 SFP+ 电缆时确保最小弯曲半径符合要求。
|
||||
|
||||
### 3.3 连接 Console 端口
|
||||
Type-C Console 端口,连接后可通过命令行管理交换机(登录方法见 4.2)。
|
||||
|
||||
### 3.4 连接电源线
|
||||
接入 220V~50Hz 交流电源;确认供电电源与大地良好接触,确认电源开关位置以便及时断电。
|
||||
|
||||
### 3.5 设备初始化
|
||||
接通电源后自动初始化:
|
||||
- PWR 指示灯亮 = 电源连接正确,灭 = 供电异常;
|
||||
- SYS 指示灯闪烁 = 系统初始化成功,灭或常亮 = 初始化失败;
|
||||
- Link/Act 指示灯会闪烁一下后熄灭,指示系统已复位。
|
||||
|
||||
### 3.6 安装后检查
|
||||
- 周围散热空间与空气流通;
|
||||
- 电源插座供电符合规格;
|
||||
- 电源、交换机、机架正确接地;
|
||||
- 与其它网络设备连接正常。
|
||||
|
||||
---
|
||||
|
||||
## 第 4 章 配置指南
|
||||
|
||||
### 4.1 WEB 登录
|
||||
|
||||
> **TL-SE5420 业务口固定 IP 为 `10.18.18.251`**;电脑连接业务口时需设 IP `10.18.18.x`(x 为 2-250 任意值),子网掩码 `255.255.255.0`。
|
||||
|
||||
1. 普通 PC 用网线连接交换机任意一个 RJ45 业务端口;
|
||||
2. 浏览器输入 `http://10.18.18.251` 回车;
|
||||
3. **设置用户名和密码**,点击<确认>登录 WEB 管理界面;
|
||||
4. 首页左上角商标可访问 TP-LINK 官网;左侧主菜单进行功能配置。
|
||||
|
||||
### 4.2 本地登录(Console)
|
||||
|
||||
1. 连接 Type-C Console 口到计算机;
|
||||
2. 安装「**TP-LINK USB 串口驱动程序**」(官网下载或随机光盘);
|
||||
3. 终端仿真程序参数:
|
||||
- 波特率 **38400bps** · 数据位 **8** · 奇偶校验 **无** · 停止位 **1** · 数据流控制 **无**
|
||||
4. 输入回车,出现 **`TL-SE5420>`** 提示符即登录成功。
|
||||
|
||||
### 4.3 远程登录(Telnet)
|
||||
|
||||
1. cmd 输入 `telnet 10.18.18.251`;
|
||||
2. 输入登录用户名和密码;
|
||||
3. 输入 `enable` 回车进入特权模式。
|
||||
|
||||
### 4.4 云管理
|
||||
|
||||
1. Web 界面 →<云管理> → 开启<全局开关>;
|
||||
2. 访问 `https://smbcloud.tp-link.com.cn/login.html`,登录 TP-LINK ID;
|
||||
3. 项目集中管理 → 设备列表 → 添加设备(输入 MAC、名称、用户名、密码、分组;MAC 可扫设备底部二维码获取);
|
||||
4. 设备信息页找到对应条目 →<远程配置> → 云平台远程管理。
|
||||
|
||||
---
|
||||
|
||||
## 附录 A 常见故障处理
|
||||
|
||||
**问题1:忘记用户名和密码(恢复出厂设置)**
|
||||
1. 串口连 Console 口,打开终端;
|
||||
2. 交换机断电重启,终端出现 **`Press CTRL-B to enter the bootUtil`** 时按 `Ctrl+B` 进入 bootUtil 菜单;
|
||||
3. 输入 **`2`** 进行软件复位,恢复到出厂默认设置,登录用户名和密码均为 **admin**。
|
||||
|
||||
**问题2:电源指示灯显示不正常**
|
||||
- 电源线连接是否正确;供电电源是否匹配(100-240V~ 50/60Hz)。
|
||||
|
||||
**问题3:无法登录 WEB 管理界面**
|
||||
- 是否达到 HTTP 管理人数上限;
|
||||
- 端口线缆连接与端口状态;
|
||||
- 电脑 IP 与交换机 IP 是否同网段;
|
||||
- `ping 127.0.0.1`(本机 TCP/IP)与 `ping 10.18.18.251`(交换机)。
|
||||
|
||||
**问题4:断电后配置消失**
|
||||
- 配置实时生效,但**断电前必须点保存配置**。
|
||||
|
||||
---
|
||||
|
||||
## 附录 B 技术参数规格
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 产品型号 | TL-SE5420 |
|
||||
| 技术标准 | IEEE 802.3 / 802.3i / 802.3u / 802.3ab / 802.3z / 802.3bz(2.5G/5G) / 802.3ae(10G) / 802.3ad(链路聚合) / 802.3x(流控) / 802.1p(QoS) / 802.1q(VLAN) / 802.1x / 802.1d(STP) / 802.1s(MSTP) / 802.1w(RSTP) / 802.1ab(LLDP) |
|
||||
| 端口 | 16× 10/100/1000/2.5G RJ45 + 4× 10G SFP+ |
|
||||
| 传输方式 | 存储转发 |
|
||||
| 数据传输速率 | 10M半双工/20M全双工 · 100M半/200M全 · 千兆2000M全 · 2.5G 5000M全 · 10G 20000M全 |
|
||||
| 包转发速率 | 10BASE-T:14881pps · 100BASE-TX:148810pps · 1000BASE:1488095pps · 2.5GBASE-T:3720238pps · 10GBASE:14880952pps(每端口) |
|
||||
| 电源输入 | 220V~50Hz |
|
||||
| 工作温度 | 0℃~40℃ |
|
||||
| 存储温度 | -40℃~70℃ |
|
||||
| 工作湿度 | 10%~90%RH 无凝结 |
|
||||
| 存储湿度 | 5%~90%RH 无凝结 |
|
||||
|
||||
网络介质(部分):10BASE-T 3类线≤100m · 100BASE-TX 5类线≤100m · 1000BASE-T 超5类≤100m · 2.5GBASE-T 超5类≤100m · 10GBASE-SR OM1/OM2/OM3 MMF 2m~300m · 10GBASE-LR SMF 2m~10000m。
|
||||
|
||||
---
|
||||
|
||||
## 附录 C 连接 SFP+ 端口补充说明
|
||||
|
||||
- **光纤**:单模(芯径 9/10μm,模间色散小,适合远程);多模(芯径 50/62.5μm,传输距离较近,一般几公里)。
|
||||
- **光模块**:按性质分单模/多模;按速率分百兆/千兆/万兆。SFP/SFP+ 端口兼容单模、多模光模块,推荐使用 TP-LINK 光模块。
|
||||
|
||||
**TP-LINK 光模块参考表:**
|
||||
|
||||
| 模块类型 | 产品型号 | 速率 | 距离 | 传输介质 |
|
||||
|---|---|---|---|---|
|
||||
| 光模块 | TL-SM512LS-10KM | 10Gbps | 10km | SMF 双纤 LC |
|
||||
| 光模块 | TL-SM512LM-300m | 10Gbps | 300m | OM3 MMF 双纤 LC |
|
||||
| 光模块 | TL-SM411LSA/B-5KM | 2.5Gbps | 5km | SMF 单纤 LC |
|
||||
| 光模块 | TL-SM411LSA/B-500m | 2.5Gbps | 500m | SMF 单纤 LC |
|
||||
| 光模块 | TL-SM411SSA/B-500m | 2.5Gbps | 500m | SMF 单纤 SC |
|
||||
| 光模块 | TL-SM412LS-5KM | 2.5Gbps | 5km | SMF 双纤 LC |
|
||||
| 光模块 | TL-SM311LSA/B-20KM | 1Gbps | 20km | SMF 单纤 LC |
|
||||
| 光模块 | TL-SM311LSA/B-2KM | 1Gbps | 2km | SMF 单纤 LC |
|
||||
| 光模块 | TL-SM311SSA/B-2KM | 1Gbps | 2km | SMF 单纤 SC |
|
||||
| 光模块 | TL-SM312LS-40KM | 1Gbps | 40km | SMF 双纤 LC |
|
||||
| 光模块 | TL-SM312LS-20KM | 1Gbps | 20km | SMF 双纤 LC |
|
||||
| 光模块 | TL-SM312LS-20KM工业级 | 1Gbps | 20km | SMF 双纤 LC |
|
||||
| 光模块 | TL-SM312LM-550m | 1Gbps | 550m | OM2 MMF 双纤 LC |
|
||||
| 电口模块 | TL-SM510U | 10Gbps | 30m | 超六类网线 |
|
||||
| 电口模块 | TL-SM410U | 2.5Gbps | 100m | 超五类网线 |
|
||||
| 电口模块 | TL-SM310U | 1Gbps | 100m | 超五类网线 |
|
||||
|
||||
---
|
||||
|
||||
## 附录 D 产品保修卡(要点)
|
||||
|
||||
- 免费保修范围外的情况:超出保换/保修期限、未按要求安装使用、擅自改动设置或拆机、意外或人为损坏、运输损坏、不可抗力等。
|
||||
- 技术支持热线:`400-8863-400` · 邮箱 `fae@tp-link.com.cn` · 官网 `www.tp-link.com.cn`。
|
||||
- **售后服务承诺:三年保修。**
|
||||
- 外置电源保换 1 年,电池 6 个月,鼠标 3 个月;无线外置天线及光纤头 3 个月。
|
||||
- 购机 15 天内出现性能问题且外观无划伤可换新。
|
||||
|
||||
---
|
||||
|
||||
## 附录 E 有毒有害物质含量声明
|
||||
|
||||
(详见官方 PDF 附表。)
|
||||
|
||||
---
|
||||
|
||||
公司地址:深圳市南山区深南路科技园工业厂房 24 栋南段 1 层、3-5 层、28 栋北段 1-4 层
|
||||
公司网址:<http://www.tp-link.com.cn> · 技术支持 E-mail:`smb@tp-link.com.cn` · 技术支持热线:`400-8863-400`
|
||||
文档版本:REV1.0.2
|
||||
@@ -0,0 +1,158 @@
|
||||
# 希力威视 SR-S25G3218F 调查(2026-08-08)
|
||||
|
||||
**结论:** 若需求是大量 2.5G 终端、少量 10G 光上联,`SR-S25G3218F` 的端口密度
|
||||
更合适;厂商已公开该型号的固件页,但仍缺少完整规格书、管理手册与兼容矩阵。若需求是 8 条全部可协商
|
||||
1/2.5/5/10G 的铜缆链路,且希望有可查的 L3 能力和固件入口,兮克
|
||||
`SKS8300-8T` 是资料更完整、风险更低的选择;它的代价是主动风扇、外置 12 V 电源、
|
||||
无 SFP+ 光口,且仍不应把消费级/SMB 设备当作安全边界或唯一核心。两者都应在
|
||||
到货可退换期内完成实机验收。
|
||||
|
||||
本页为采购前资料调查,不代表已接入本地网络;检索日期为 2026-08-08。
|
||||
|
||||
## 已能核实的事项
|
||||
|
||||
| 项目 | 结论与证据强度 |
|
||||
|---|---|
|
||||
| 型号/端口 | 京东的希力威视商品标题称该 SKU 为 `SR-S25G3218F`,有 16 个 2.5G 电口和 2 个万兆光口,并宣传 VLAN、端口隔离与 LACP。该店铺被厂商官网列为可购买的「京东旗舰店」,因此可作为销售规格,非技术手册。[京东商品页](https://item.jd.com/100165071727.html);[厂商购买渠道说明](https://en.sirivision.com/contactus/) |
|
||||
| 厂商身份 | 厂商官网为 Shenzhen/Guangdong Sirivision Communication;英文官网说明其自 2016 年起提供接入、汇聚和核心交换机方案。[厂商首页](https://en.sirivision.com/) |
|
||||
| 公开的二手厂家资料 | 同一制造商名义的 Alibaba 出口页将精确型号写成 `16*2.5G+2*10G`、`120Gbps`,并列出 QoS、VLAN、SNMP、L3 与 stackable。这是制造商发布在平台上的销售资料,**不是**官网数据表;其中后五项不能据此视为已验收的功能承诺。[制造商平台页](https://www.alibaba.com/pla/SR-S25G3218F-QoS-Managed-SFP-Switch-1625G210G_1601494946214.html) |
|
||||
| 固件入口 | 厂商已发布此精确型号的[固件页](https://www.sirivision.com/sr-s25g3218f%E5%9B%BA%E4%BB%B6/)。公开变更记录提到“光口自适应”和“增加 DAC 配置”;这证明厂商维护过该路径,**不**代表任意 SFP+/DAC/铜模块均兼容。 |
|
||||
| 本机可计算的带宽 | 端口线速相加为单向 60 Gb/s(16 × 2.5 + 2 × 10);若厂商所谓 `120Gbps` 是全双工交换容量,则数学上吻合。它**不**证明缓冲、PPS、表项规模或实际无阻塞性能。 |
|
||||
|
||||
## 网管/L2/L3 能力边界
|
||||
|
||||
京东标题足以支持把 VLAN、端口隔离、LACP 作为「卖家声称提供」的功能;不得由此推导出
|
||||
ACL、IPv4/IPv6 静态路由、SVI 数量、DHCP relay、OSPF/RIP、VRRP、IGMP、ERPS、
|
||||
802.1X、RADIUS/TACACS+、SSH/HTTPS 管理、SNMP 版本、日志/审计、配置备份或固件
|
||||
安全维护一定存在。
|
||||
|
||||
尤其要注意:厂商官网把真正列出的 2.5G L3 产品标为
|
||||
`SR-S25G3412F (8 × 2.5G + 4 × 10G SFP+)`;其 2.5G 类目只显示 7 个型号,
|
||||
不含 `SR-S25G3218F`。官网也把 L2+、Web Smart、L3 分成不同产品类别。这个目录
|
||||
差异**不是**证明 3218F 没有 L3,而是说明「三层」无法通过官网的精确型号文档确认。
|
||||
[2.5G 产品目录](https://en.sirivision.com/product-category/products/2-5g-switches/);
|
||||
[官网的 10G L3 目录](https://en.sirivision.com/product-category/products/10g-switches/10g-layer3-managed-switches/);
|
||||
[官网的 L2+ 分类示例](https://en.sirivision.com/product-category/products/gigabit-switches/gigabit-layer2-managed-switches/)。
|
||||
|
||||
采购前请向京东/厂商索取**与机身 SKU、硬件 revision 和固件版本对应**的 PDF
|
||||
数据表、管理手册和 release notes,并要求书面回答至少以下问题:
|
||||
|
||||
1. L3 是只有 VLAN Interface/IPv4 静态路由,还是另有 IPv6、ACL、动态路由、DHCP relay
|
||||
等;每项的最大 VLAN、MAC、ARP、路由、ACL、LAG 数量分别是多少?
|
||||
2. LACP 是否符合 802.3ad、一个 LAG 最多多少成员、能否跨两台设备(若销售页的
|
||||
`stackable` 属实,堆叠的线缆/模块、最大成员、控制面和软件版本为何)?
|
||||
3. 管理面是否支持 HTTPS/SSH、禁用 HTTP/Telnet、独立管理 VLAN、SNMPv3、syslog、NTP、
|
||||
配置导出/回滚和已签名或可校验的固件;默认凭据首次登录是否强制修改?
|
||||
|
||||
## 供电、散热和光口:当前不能确认
|
||||
|
||||
针对该精确 SKU,厂商官网目录与公开搜索未找到说明书/数据表,所以以下均为**待确认,
|
||||
不能猜测**:
|
||||
|
||||
- 是否为内置 AC 电源、额定输入范围/最大功耗、是否带电源开关和接地端子;是否完全
|
||||
不提供 PoE(本型号名和京东标题均未写 PoE,但这不足以替代规格书)。
|
||||
- 风扇数量、常态/满载噪声、风向、环境温湿度、机架深度与安装耳;不要将「金属壳」
|
||||
或产品照片等同于无风扇/静音。
|
||||
- 两个槽是否均为 **10G SFP+**,是否可协商 1G SFP;支持的 SR/LR/BiDi 波长距离、
|
||||
DAC/AOC 长度、第三方模块/EERPOM 兼容策略、10GBASE-T SFP+ 模块的功耗/温度限制,
|
||||
以及是否支持 GPON/XPON ONU「猫棒」。
|
||||
|
||||
厂商确实单列「SFP Optical Modules」产品分类,但这不构成 3218F 的兼容清单。
|
||||
[厂商产品导航](https://en.sirivision.com/)。购买光模块/直连线时,应要求厂商按这台
|
||||
设备的硬件/固件 revision 出具兼容型号清单;没有书面清单时,先在可退换期实测两端的
|
||||
链路、重启恢复、热插拔与长时间满载错误计数。
|
||||
|
||||
## 风险与建议验收
|
||||
|
||||
- **文档/生命周期风险(中到高):** 精确型号不在厂商当前官网 2.5G 目录,虽有固件下载页,
|
||||
但未公开完整型号手册、明确 release notes 或兼容矩阵。官网的售后条款也要求按具体产品查询保修期,配件(含光纤头)
|
||||
的保修条款与主机不同;不要把平台页的「3 年」当作中国零售 SKU 的已确认保修。
|
||||
[厂商售后条款](https://en.sirivision.com/after-sale-protection/)
|
||||
- **功能表述风险(高):** 页面将 L2 特性和「三层网管」并列;在命令/网页菜单、
|
||||
手册和测试证明之前,将其当作 L2 VLAN/LACP 设备部署,跨 VLAN 路由仍由现有网关承担。
|
||||
- **双 10G 上联约束(中):** 两个 SFP+ 可作双上联或一个二成员 LAG,但 LAG 增加的是
|
||||
多流量总吞吐,单一 TCP/UDP 流通常仍受一条 10G 链路限制;上级设备也必须匹配 LACP
|
||||
配置。
|
||||
- **管理面风险(中到高):** 家用/低价网管设备常见明文管理、弱默认口令或不透明的固件
|
||||
更新周期;采购后先置于受限管理 VLAN,改口令、升级已验证固件,且不将管理界面暴露
|
||||
到 WAN/访客网。
|
||||
|
||||
最低验收应包括:逐口协商 100M/1G/2.5G、两只不同厂家 SFP+/DAC(仅在卖家承诺支持的
|
||||
范围内)、VLAN trunk/access/PVID、STP/环路保护、LACP 故障切换、端口隔离、满载
|
||||
双向 iperf3 与错误计数、冷启动后的配置保留,以及管理面的 HTTPS/SSH/SNMPv3/配置备份。
|
||||
如无法提供与型号匹配的正式资料或其中任一关键项失败,应在退换期内退货,并选择公开
|
||||
数据表、固件与兼容矩阵更完整的型号。
|
||||
|
||||
## 备选:兮克 SKS8300-8T 对比
|
||||
|
||||
### 已核实的厂商规格
|
||||
|
||||
兮克官网的精确型号页明确将 `SKS8300-8T` 定位为三层管理型 10G 全电口交换机,并列出:
|
||||
|
||||
- 8 × 1/2.5/5/10GBASE-T RJ45;160 Gb/s 交换容量、119.05 Mpps、12 Mbit 缓存、
|
||||
16K MAC、12 KB 巨帧、512 MB DRAM、32 MB Flash,尺寸 207 × 136 × 35 mm;
|
||||
- QoS、ACL、IP+MAC+端口绑定、流分类/优先级标记、多端口镜像、静态/灵活 QinQ、
|
||||
sFlow,以及「基于策略的 IPv4/IPv6 单播路由」。
|
||||
|
||||
这些是厂商能力声明,并非对每一种路由协议或表项上限的承诺;但相对 3218F 的仅有
|
||||
销售标题,它给出了精确型号、转发性能和 L3 范围。[兮克 SKS8300-8T
|
||||
产品页](https://seekswan.com/user/custom-pages/SKS8300-8T.html)
|
||||
|
||||
独立的 OpenWrt 设备资料将其识别为 Realtek RTL9303、512 MB RAM,记录了原厂固件
|
||||
下载入口和串口/TFTP 恢复路径;其硬件数据页列为 12 V / 4 A。这支持「可恢复、可替换
|
||||
系统」的可操作性,但**不是**兮克对原厂功能的支持承诺。
|
||||
[OpenWrt 设备页](https://openwrt.org/toh/xikestor/sks8300-8t);
|
||||
[OpenWrt 硬件数据](https://openwrt.org/toh/hwdata/xikestor/xikestor_sks8300-8t)。
|
||||
|
||||
### 能力、物理与运维比较
|
||||
|
||||
| 维度 | 希力威视 SR-S25G3218F | 兮克 SKS8300-8T |
|
||||
|---|---|---|
|
||||
| 接口/典型用途 | 16 × 2.5G 电口 + 2 × 10G SFP+(销售规格);适合很多 2.5G 终端/NAS,以 10G 光或 DAC 上联。 | 8 × 1/2.5/5/10GBASE-T;适合 10G 铜缆设备、2.5/5G 多速率 NAS/主机。没有 SFP+,光纤上联必须经媒体转换或选另一型号。 |
|
||||
| 可确认的三层范围 | 仅销售/平台资料称 L3;没有精确型号官方手册,不能确认静态路由以外的功能。 | 官网明确写策略型 IPv4/IPv6 单播路由、ACL/QoS/sFlow/QinQ;动态路由、VRRP、IPv6 ACL/SNMP/认证等仍须按当前固件手册确认。 |
|
||||
| 冗余/二层 | 卖家声称 VLAN、端口隔离、LACP;STP/环网的实现与规格未知。 | 官网声明 L3 和多项转发特性,但未在产品页给出 STP/LACP/ERPS 的精确限制;购买前仍索取手册。 |
|
||||
| 散热/噪声 | 无可核实的精确型号风扇、噪声、功耗或风向数据。 | 独立手册镜像和产品图均称智能温控风扇,但厂商产品页未给 dBA;应按「有风扇、可能听得见」规划,不能承诺静音。 |
|
||||
| 供电 | 未找到精确型号官方输入/功耗资料。 | OpenWrt 硬件数据记录 12 V / 4 A;确认随附电源适配器的插头、余量和地区认证。官方产品页未给满载功耗。 |
|
||||
| 固件/恢复 | 有精确型号官方固件页;公开记录包含光口自适应与 DAC 配置改动,但未找到完整 release notes、恢复步骤或兼容矩阵。 | 厂商产品页提供「相关下载」区,OpenWrt 还记录原厂固件入口、RJ45 串口和 U-Boot/TFTP 恢复;原厂镜像是否签名、漏洞修复 SLA、配置回退仍未知。 |
|
||||
|
||||
关于 8T 的风扇、满载功耗(常见转述为 ≤36 W)、温度范围、芯片型号等,本次未找到
|
||||
相应的**厂商原始数据表**;不将第三方手册转录当作已核实规格。若噪声、UPS 容量或
|
||||
机柜散热是购买约束,请先让卖家提供产品铭牌照片、适配器铭牌照片、额定/实测功耗和
|
||||
dBA 测试条件。
|
||||
|
||||
### 选择与验收建议
|
||||
|
||||
- 选 **3218F**:必须有 ≥12 个 2.5G 接入端、10G 光/DAC 上联、且 L3 留给现有路由器。
|
||||
下单前先取得精确型号手册和 SFP+/DAC 兼容承诺;否则端口数量优势不足以抵消资料风险。
|
||||
- 选 **8T**:最多 8 个设备但需要多速率 10G RJ45、明确的 IPv4/IPv6 静态/策略路由和
|
||||
以后自行维护/恢复的余地。不要把其 160 Gb/s 标称交换容量误解为 8 端口同时 10G
|
||||
全双工的性能保证——该标称与端口总线速数学相等,但仍须以实测和厂商 PPS/缓冲说明为准。
|
||||
- 两台都不应单独承担防火墙、访客/IoT 安全隔离或 WAN 暴露;VLAN 的跨网段策略和公网
|
||||
边界留在受支持的网关/防火墙上。先为管理面创建专用 VLAN,仅从管理主机访问,禁用
|
||||
未使用的远程管理协议,备份配置和原厂固件后再接入生产网络。
|
||||
|
||||
## 低功耗核心备选(8 × 2.5G + 2 × SFP+)
|
||||
|
||||
如果核心只需接最多 8 台铜缆终端、上联/连接 NAS 使用 DAC 或光纤 10G,优先考虑没有
|
||||
PoE 的以下两款。它们都满足 VLAN trunk、LACP 和至少两个 10G SFP+ 的需求;不要为
|
||||
AP 选 PoE 版来承担核心,因为 PoE 预算、风扇和待机损耗都会明显增加。
|
||||
|
||||
| 型号 | 端口与管理能力(厂商声明) | 厂商功耗 / 噪声资料 | 对当前 LAN 的判断 |
|
||||
|---|---|---|---|
|
||||
| **TP-Link Omada SG3210X-M2** | 8 × 100M/1G/2.5G RJ45、2 × 10G SFP+,并有 RJ45 和 Micro-USB console。厂商规格列出 802.1Q VLAN、STP/RSTP/MSTP、静态 LAG 和 802.3ad LACP(最多 8 个聚合组、每组最多 8 端口);L3 是 32 个 IPv4/IPv6 接口、48 条静态路由。 | **无风扇**;100–240 V AC 内置电源。`UN 1.20` 数据表:待机最高 **6.0 W**(220 V/50 Hz、25 °C),最高 **15.3 W**(220 V)或 **15.0 W**(110 V)。 | **首选低功耗方案。** 足以做 LAN66 核心、给 PVE/gfw 与 U6 Lite 做 VLAN 10 trunk,并以 SFP+ DAC/光口连接 10G NAS/主机;它不提供 5G/10G RJ45,10G 铜缆需外置转换或 SFP+ 10GBASE-T 模块。 |
|
||||
| **MikroTik CRS310-8G+2S+IN** | 8 × 2.5G RJ45、2 × 10G SFP+;SFP+ 笼支持 1G/2.5G/10G。RouterOS v7(也可选 SwOS)支持 VLAN、链路聚合与 ACL。 | 18–57 V DC 外置供电;官方给出“无附件”最高 **21 W**、总体最高 **34 W**,且机内 **1 个风扇**。厂商没有在该页给出 dBA。 | 可用且软件/文档/恢复路径成熟,但不是本题的静音低功耗优先项:官方最大功耗显著高于 TP-Link,且有风扇。适合明确偏好 RouterOS/SwOS 与其可维护性时选。 |
|
||||
|
||||
功耗数字是各厂商的**上限/待机测试条件**,不是你实际墙插读数;SFP+ 光模块、DAC/AOC,尤其
|
||||
10GBASE-T SFP+ 模块,会另增功耗和热量。对于本网络,用被动 DAC 或短距光模块连接 10G
|
||||
设备,通常比全 RJ45 10G 核心更容易保持低温、低噪。
|
||||
|
||||
`SG3210X-M2` 的上表数据对应 TP-Link 的 `UN 1.20` 数据表;不同地区/硬件版本的包装、
|
||||
认证和功耗标注可能不同,购买中国零售版本前应让卖家确认**准确硬件版本、保修渠道和固件地区**。
|
||||
本次未找到 TP-Link 中国官网的该精确型号页,因此不能把海外官方页面当作大陆现货/售后承诺。
|
||||
MikroTik 同样应通过其官方零售商查询渠道确认本地库存和保修。两台购买前还应确认所选
|
||||
SFP+/DAC 的兼容清单。
|
||||
|
||||
来源:[TP-Link 产品规格](https://www.tp-link.com/uk/business-networking/omada-switch-access-pro/sg3210x-m2/);
|
||||
[TP-Link `UN 1.20` 数据表](https://static.tp-link.com/upload/product-overview/2025/202512/20251224/SG3210X-M2%28UN%29%201.20_datasheet.pdf);
|
||||
[MikroTik 产品页](https://mikrotik.com/product/crs310_8g_2s_in);
|
||||
[MikroTik 用户手册](https://help.mikrotik.com/docs/spaces/UM/pages/214630429/CRS310-8G%2B2S%2BIN)。
|
||||
@@ -0,0 +1,211 @@
|
||||
# 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.
|
||||
|
||||
## Login and MFA
|
||||
|
||||
On 2026-08-08, **"Sync Local Admin with Ubiquiti SSO"** was disabled
|
||||
(`super_sdn.sso_login_enabled: false`). Previously every login was forced
|
||||
through Ubiquiti SSO (sso.ui.com) and required MFA, which blocked programmatic
|
||||
access: the `unifi-network-mcp` write operations failed with
|
||||
`api.err.Ubic2faTokenRequired`.
|
||||
|
||||
Now local accounts (`windy`, `zhiqiangf`) sign in with their local passwords
|
||||
and no MFA. The `unifi-network-mcp` tool uses the `zhiqiangf` local account and
|
||||
works for both read and write operations (verified 2026-08-08 with a reversible
|
||||
client rename).
|
||||
|
||||
Impact: unifi.ui.com remote access (Site Manager) no longer works.
|
||||
|
||||
Manage the setting idempotently via Ansible:
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook playbooks/unifi-sso.yml --limit unifi --check # read-only
|
||||
ansible-playbook playbooks/unifi-sso.yml --limit unifi \
|
||||
-e '{"unifi_sso_confirm": true, "unifi_sso_target_value": false}' # apply
|
||||
```
|
||||
|
||||
See [runbooks/ansible-operations.md](../runbooks/ansible-operations.md).
|
||||
|
||||
## 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()
|
||||
```
|
||||
|
||||
## Dedicated Wi-Fi through a third-party gateway
|
||||
|
||||
### Architecture boundary discovered on 2026-08-08
|
||||
|
||||
`gfw` is a virtual machine connected only to the native LAN 66 network; the
|
||||
physical Wi-Fi uplink is also an ER-X LAN 66 path. The ER-X provides DHCP for
|
||||
that LAN. There is no verified 802.1Q-capable path from the U6 Lite to `gfw`.
|
||||
|
||||
Consequently, the proposed `ubnt-upg` design is **not implementable on the
|
||||
current topology without an additional VLAN-capable L2 path**. Giving the VM
|
||||
an `eth0.10` interface alone does not create VLAN 10 on the physical switch or
|
||||
deliver tagged frames to it.
|
||||
|
||||
UniFi APs associate clients and bridge an SSID into a selected 802.1Q VLAN;
|
||||
they do not allocate DHCP addresses. The controller configures that AP
|
||||
behavior, but it is not a DHCP service for a third-party-gateway VLAN. The
|
||||
gateway on the corresponding VLAN provides DHCP. In the existing topology, the
|
||||
ER-X DHCP service legitimately answers untagged LAN 66 requests.
|
||||
|
||||
The observed `192.168.66.91` lease on `ubnt-upg` is therefore expected when
|
||||
traffic reaches the existing untagged LAN 66. It is not a failure that can be
|
||||
fixed by changing the controller or by adding another DHCP server to the same
|
||||
broadcast domain.
|
||||
|
||||
### Conditions for a future implementation
|
||||
|
||||
The design becomes viable only after adding and proving one of these:
|
||||
|
||||
1. A VLAN-capable switch/trunk from the U6 Lite to a VLAN-10 interface on the
|
||||
actual gateway, with native LAN 66 retained for AP management; or
|
||||
2. A hypervisor/vSwitch and physical uplink configured to pass VLAN 10 end to
|
||||
end to the `gfw` VM, plus the corresponding tagged port towards the U6.
|
||||
|
||||
Then configure one UniFi virtual network with **Router: Third-party Gateway**
|
||||
and VLAN ID `10`, map only `ubnt-upg` to it, and have the VLAN 10 gateway be
|
||||
the sole DHCP server for `192.168.10.0/24`. A test client must obtain
|
||||
`192.168.10.x`, with traffic and its lease visible on that gateway, before the
|
||||
SSID is considered working.
|
||||
|
||||
### Protecting existing Wi-Fi
|
||||
|
||||
`ubnt-windy` is the existing 5 GHz SSID and has been restored on the U6 Lite.
|
||||
Keep its network and broadcast/device assignment unchanged. If the dedicated
|
||||
SSID is ever retried, use the controller's normal **Specific -> U6 Lite**
|
||||
broadcast selection for `ubnt-upg` only; do not reuse, move, or manually edit
|
||||
an existing SSID's AP group. An AP configuration push can still briefly
|
||||
reassociate clients, so zero-impact work on the same AP cannot be guaranteed.
|
||||
|
||||
### References
|
||||
|
||||
- [Feasibility and DHCP responsibility](unifi-third-party-vlan10-dhcp.md)
|
||||
- [Supported topology and implementation examples](unifi-openwrt-vlan10-implementation-examples.md)
|
||||
- Ubiquiti: [Creating Virtual Networks (VLANs)](https://help.ui.com/hc/en-us/articles/9761080275607-Creating-Virtual-Networks-VLANs),
|
||||
[Creating WiFi SSIDs](https://help.ui.com/hc/en-us/articles/26136823938583-Creating-UniFi-WiFi-SSIDs), and
|
||||
[Switch Port VLAN Assignment](https://help.ui.com/hc/en-us/articles/26136855808919-Switch-Port-VLAN-Assignment-Trunk-Access-Ports).
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,120 @@
|
||||
# UniFi AP + OpenWrt VLAN-backed Wi-Fi: implementation evidence
|
||||
|
||||
**Research date:** 2026-08-08
|
||||
**Scope:** research only. This file does not assert that the local physical
|
||||
path currently carries VLAN 10, and no controller, AP, ERX, or OpenWrt setting
|
||||
was changed while preparing it.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Yes. This is a normal, supported deployment pattern: UniFi APs and Network
|
||||
Application manage the SSID-to-VLAN mapping, while a non-UniFi gateway such as
|
||||
OpenWrt owns the tagged VLAN interface, its subnet, DHCP, routing, and
|
||||
firewall/policy routing. The existing untagged LAN DHCP server remains in
|
||||
place; it serves only the native LAN, while OpenWrt serves the tagged VLAN.
|
||||
|
||||
Ubiquiti's current third-party-gateway recipe specifically says to configure
|
||||
the subnet, VLAN ID, and DHCP **on the third-party gateway first**, then create
|
||||
a UniFi virtual network with **Router = Third-party Gateway** and the same VLAN
|
||||
ID. [Ubiquiti: Creating Virtual Networks (VLANs)](https://help.ui.com/hc/en-us/articles/9761080275607-Creating-Virtual-Networks-VLANs)
|
||||
|
||||
That supported pattern requires an end-to-end tagged path. It is not feasible
|
||||
on the current local topology: `gfw` is a VM connected only to untagged LAN 66
|
||||
and there is no verified physical or virtual switch trunk carrying VLAN 10 from
|
||||
the U6 Lite to that VM. Creating `eth0.10` inside the VM cannot supply the
|
||||
missing L2 transport.
|
||||
|
||||
## The reusable topology
|
||||
|
||||
```text
|
||||
main SSID ── untagged/native LAN66 ── ERX DHCP ── 192.168.66.0/24
|
||||
│
|
||||
U6 Lite ── AP uplink trunk (native LAN66 + tagged VLAN 10) ── gfw/OpenWrt
|
||||
└── ubnt-upg SSID ── VLAN 10 ── gfw.10: 192.168.10.1/24 + DHCP + OpenClash
|
||||
```
|
||||
|
||||
This is not two DHCP servers answering one broadcast domain. They serve two
|
||||
separate L2 broadcast domains. An AP bridges a station's DHCP request into the
|
||||
VLAN selected for its SSID; it does not allocate the address itself. Ubiquiti
|
||||
states that SSID clients become members of the selected VLAN and that every
|
||||
port between AP and gateway must allow that VLAN. [Ubiquiti: Creating UniFi
|
||||
WiFi SSIDs](https://help.ui.com/hc/en-us/articles/26136823938583-Creating-UniFi-WiFi-SSIDs)
|
||||
|
||||
OpenWrt's own VLAN documentation shows the same mixed-link model: an untagged
|
||||
LAN plus tagged VLAN interfaces on a common link, and explicitly notes that a
|
||||
separate DHCP server can serve each segment. [OpenWrt: VLAN / switch
|
||||
configuration](https://openwrt.org/docs/guide-user/network/vlan/switch_configuration)
|
||||
|
||||
## Evidence of existing deployments
|
||||
|
||||
### Protocol and product documentation (authoritative)
|
||||
|
||||
* Ubiquiti's third-party-gateway workflow is an exact product-supported recipe
|
||||
for the UniFi half: gateway first, then matching VLAN in UniFi. It does not
|
||||
require a UniFi gateway or switch. [Creating Virtual Networks
|
||||
(VLANs)](https://help.ui.com/hc/en-us/articles/9761080275607-Creating-Virtual-Networks-VLANs)
|
||||
* Ubiquiti specifies the exact Wi-Fi half: choose the network in the SSID's
|
||||
**Network** dropdown, select the APs that should broadcast it, and permit the
|
||||
VLAN on all intervening ports. [Creating UniFi WiFi
|
||||
SSIDs](https://help.ui.com/hc/en-us/articles/26136823938583-Creating-UniFi-WiFi-SSIDs)
|
||||
* Ubiquiti's troubleshooting topology specifically calls an SSID VLAN made
|
||||
native on the AP port, or absent as a tagged VLAN upstream, a broken setup;
|
||||
it directs operators to trace every AP-to-gateway port. [Virtual Network
|
||||
(VLAN) Troubleshooting](https://help.ui.com/hc/en-us/articles/9592924981911-Virtual-Network-VLAN-Troubleshooting)
|
||||
* OpenWrt documents Linux 802.1Q interfaces and bridge VLAN membership. Its
|
||||
example calls out a trunk carrying untagged traffic and tagged VLANs, which
|
||||
is the link gfw needs. [OpenWrt: VLAN / switch
|
||||
configuration](https://openwrt.org/docs/guide-user/network/vlan/switch_configuration)
|
||||
|
||||
### User-reported working deployments (official Ubiquiti Community; anecdotal)
|
||||
|
||||
These are not Ubiquiti support guarantees, but they demonstrate that the exact
|
||||
vendor split is in real use:
|
||||
|
||||
* A user reports a UniFi site whose upstream router/DHCP server is **OpenWrt**;
|
||||
they create a Third-Party Gateway network, assign its VLAN tag, then bind it
|
||||
to the Wi-Fi SSID. The report says it works without a UniFi gateway or switch.
|
||||
[UniFi Network Application 7.4.156 discussion](https://community.ui.com/releases/UniFi-Network-Application-7-4-156/15ac6260-9cd1-4ac3-a91c-4880c1c87882?page=36)
|
||||
* A second user reports two SSID VLANs over an **OpenWrt Linksys WRT32X 802.1Q
|
||||
trunk**, with no USG or UniFi switch; the controller network is selected in
|
||||
the SSID's Network dropdown. [Assigning VLANs to SSIDs on Controller
|
||||
6](https://community.ui.com/questions/Assigning-VLANs-to-SSIDs-on-Controller-6/3e656047-685f-470e-954c-703077e89aaa)
|
||||
* A Ubiquiti Community post describes an existing multi-VLAN UniFi deployment
|
||||
with an OpenWrt NanoPi R6S gateway and all UniFi networks configured as
|
||||
Third-party Gateway. [OpenWrt third-party-gateway
|
||||
deployment](https://community.ui.com/questions/Replacing-an-existing-third-party-router-firewall-with-a-UXG-Lite-on-an-existing-UniFi-network/5de2504a-fac2-437a-b893-495a6be2f3c0)
|
||||
|
||||
## Exact mapping for this network
|
||||
|
||||
| Layer | Required configuration |
|
||||
|---|---|
|
||||
| `gfw` / OpenWrt | Only after a tagged path reaches the VM: create/retain VLAN-10 interface, address `192.168.10.1/24`, run the **only** DHCP server for that segment, and apply the intended OpenClash/policy routing. |
|
||||
| U6-to-gfw path | Native/untagged LAN66 for U6 management; tagged VLAN 10 allowed at every switch/link. Do not replace the native LAN with VLAN 10. |
|
||||
| UniFi network | One VLAN 10 virtual network with **Router: Third-party Gateway**. This is controller metadata that makes the AP tag the selected SSID; it is not a DHCP service. |
|
||||
| `ubnt-upg` Wi-Fi | Select that VLAN 10 network. Do not enable UniFi Hotspot/guest portal unless deliberately required. |
|
||||
| Existing Wi-Fi | Leave `ubnt-windy`, `ubnt-windy-2`, and `ubnt-haas` network/VLAN choices untouched; they remain on their existing LANs and DHCP servers. |
|
||||
|
||||
The acceptance test is therefore unambiguous: after reconnecting, a device on
|
||||
`ubnt-upg` must receive `192.168.10.x` from the VLAN 10 gateway, while a device
|
||||
on existing Wi-Fi continues to receive its established LAN address. A
|
||||
`192.168.66.x` lease on the dedicated SSID means VLAN 10 was not selected or
|
||||
did not pass the L2 path; it is not evidence that the U6 should run DHCP.
|
||||
|
||||
## Protecting the existing Wi-Fi during a retry
|
||||
|
||||
The current UniFi UI supports **All**, **Specific**, and **Groups** independently
|
||||
for each SSID's Broadcasting APs selection. [UniFi WiFi SSID and AP Settings
|
||||
Overview](https://help.ui.com/hc/en-us/articles/32065480092951-UniFi-WiFi-SSID-and-AP-Settings-Overview)
|
||||
|
||||
Given the earlier incident where changing a virtual/AP group removed
|
||||
`ubnt-windy` from the U6 Lite, the low-risk control is to leave every existing
|
||||
SSID's Broadcasting APs selection unchanged and set **only `ubnt-upg`** to
|
||||
**Specific → U6 Lite**. Do not reuse or move the default SSID into a temporary
|
||||
group. This does not eliminate the short re-provision/reassociation caused by
|
||||
an AP config push, but it prevents the configuration-level loss of the default
|
||||
SSID that was observed.
|
||||
|
||||
Before enabling the dedicated SSID, inspect the saved settings and AP's
|
||||
effective configuration, then test with one client. Only the dedicated SSID
|
||||
should be affected by a failure; LAN66 DHCP and the existing SSIDs require no
|
||||
change.
|
||||
@@ -0,0 +1,104 @@
|
||||
# UniFi Wi-Fi VLAN 10 with a third-party gateway
|
||||
|
||||
**Research date:** 2026-08-08
|
||||
**Scope:** evidence-backed design only; no controller, AP, switch, EdgeRouter,
|
||||
or OpenWrt settings were changed by this research.
|
||||
|
||||
## Answer: who assigns client addresses?
|
||||
|
||||
Neither a UniFi Network controller nor a UniFi AP is the DHCP server for a
|
||||
normal third-party-gateway deployment. The controller configures the AP; the
|
||||
AP maps a selected SSID to an 802.1Q VLAN and bridges the client's DHCP
|
||||
broadcast into that VLAN. A UniFi **Gateway** can provide DHCP, but Ubiquiti's
|
||||
DHCP documentation expressly describes the DHCP server as a service of UniFi
|
||||
Gateways, not APs or the Network Application. [UniFi DHCP Server](https://help.ui.com/hc/en-us/articles/360012097513-UniFi-DHCP-Server)
|
||||
|
||||
Accordingly, an existing DHCP server on the native/untagged LAN (here, the
|
||||
EdgeRouter X for `192.168.66.0/24`) is expected to answer an SSID that is not
|
||||
actually VLAN-tagged. It must **not** provide the lease for a genuinely
|
||||
separate dedicated VLAN. That VLAN's gateway would provide its DHCP service.
|
||||
|
||||
## Required architecture
|
||||
|
||||
```text
|
||||
client → SSID ubnt-upg → U6 Lite
|
||||
│ VLAN 10 (802.1Q tagged)
|
||||
▼
|
||||
every intervening L2 link permits VLAN 10
|
||||
▼
|
||||
gateway VLAN-10 interface: 192.168.10.1/24 + DHCP + routing/OpenClash
|
||||
```
|
||||
|
||||
The AP's management traffic may remain untagged on LAN66. VLAN 10 is only
|
||||
the client traffic emitted by `ubnt-upg`; it must be carried, tagged, from the
|
||||
U6 Lite through every intervening switch/link to `gfw`. Ubiquiti explicitly
|
||||
states that selecting a network for an SSID makes connected clients members of
|
||||
that VLAN and that all ports between AP and gateway must permit its VLAN ID;
|
||||
it recommends AP uplinks be trunked for the relevant VLANs. [Creating UniFi
|
||||
WiFi SSIDs](https://help.ui.com/hc/en-us/articles/26136823938583-Creating-UniFi-WiFi-SSIDs)
|
||||
|
||||
On a managed UniFi switch, this means the AP uplink and every upstream port in
|
||||
the path have native LAN66 (for AP management) plus tagged VLAN 10. Ubiquiti
|
||||
defines that as a trunk, and warns that an AP uplink must not restrict VLANs
|
||||
needed downstream. [Switch Port VLAN Assignment](https://help.ui.com/hc/en-us/articles/26136855808919-Switch-Port-VLAN-Assignment-Trunk-Access-Ports)
|
||||
|
||||
If a non-UniFi switch is in the path, its 802.1Q settings must be configured
|
||||
equivalently (or it must be verified to transparently forward VLAN 10); UniFi
|
||||
does not manage that switch. This is a physical/L2 requirement, so merely
|
||||
creating a controller network cannot make a VLAN reach `gfw`.
|
||||
|
||||
## Local feasibility result
|
||||
|
||||
The local `gfw` instance is a VM attached only to untagged LAN 66, and the U6
|
||||
Lite's physical path is the same ER-X LAN 66 network. No VLAN-capable physical
|
||||
trunk or verified hypervisor/vSwitch VLAN handoff exists between them. The
|
||||
diagram above is therefore a valid design pattern but **not a description of
|
||||
the current deployable topology**. An `eth0.10` interface inside the VM cannot
|
||||
create that missing L2 path.
|
||||
|
||||
To make this architecture feasible, add a VLAN-capable switch/trunk from AP to
|
||||
gateway, or configure an end-to-end tagged VLAN handoff through the hypervisor
|
||||
and its physical uplink. Until then, leave the ER-X LAN 66 DHCP design in place
|
||||
and do not expect `gfw` to serve an SSID-only subnet.
|
||||
|
||||
## Correct controller model (after the L2 prerequisite exists)
|
||||
|
||||
Use one UniFi virtual network for VLAN 10 whose **Router** is **Third-party
|
||||
Gateway** and whose VLAN ID is `10`; assign that network to Wi-Fi
|
||||
`ubnt-upg`. Configure the subnet, VLAN and DHCP on the VLAN gateway first.
|
||||
This follows Ubiquiti's documented third-party-gateway workflow: create the
|
||||
VLAN on the gateway, then add a UniFi virtual network with Router =
|
||||
Third-party Gateway and the matching VLAN ID. [Creating Virtual Networks
|
||||
(VLANs)](https://help.ui.com/hc/en-us/articles/9761080275607-Creating-Virtual-Networks-VLANs)
|
||||
|
||||
Do not enable a second DHCP service in UniFi for this VLAN. The controller
|
||||
entry is the AP/switch VLAN definition; the gateway on VLAN 10 remains the
|
||||
sole DHCP server and default gateway. The EdgeRouter X's LAN66 DHCP service
|
||||
can remain unchanged because it is for untagged LAN66, not VLAN 10.
|
||||
|
||||
## Safe verification sequence
|
||||
|
||||
1. Read-only confirm on `gfw` that its VLAN-10 interface has
|
||||
`192.168.10.1/24`, DHCP is enabled for `192.168.10.0/24`, and no other
|
||||
DHCP daemon serves that VLAN.
|
||||
2. In the controller, verify `ubnt-upg` selects the Third-party Gateway VLAN
|
||||
10 network, not `Default`/LAN66 and not an unassigned VLAN-only placeholder.
|
||||
3. Trace the U6 Lite-to-`gfw` L2 path and permit tagged VLAN 10 on every link
|
||||
while retaining LAN66 as the AP's native/management network.
|
||||
4. Reconnect a test client (forget/rejoin or renew lease). The expected lease
|
||||
is `192.168.10.x`, gateway/DNS `192.168.10.1` (or the DNS option configured
|
||||
by `gfw`), never `192.168.66.x`.
|
||||
5. Confirm a lease appears on `gfw` and that the VLAN-10 interface's RX/TX
|
||||
counters advance. Ubiquiti's VLAN troubleshooting guide identifies
|
||||
upstream ports not allowing the VLAN as the common cause when Wi-Fi clients
|
||||
cannot receive a correct IP address. [Virtual Network (VLAN)
|
||||
Troubleshooting](https://help.ui.com/hc/en-us/articles/9592924981911-Virtual-Network-VLAN-Troubleshooting)
|
||||
|
||||
## What the observed `192.168.66.91` proves
|
||||
|
||||
It is evidence that the client's DHCP discover reached the LAN66 broadcast
|
||||
domain and was answered by the EdgeRouter X. It does not show a DHCP failure
|
||||
on `gfw`; it shows that the client was bridged untagged (or VLAN 10 was not
|
||||
carried end-to-end) at that attempt. The corrective work is therefore to make
|
||||
the SSID-to-VLAN association and L2 trunk path real, then test the lease—not to
|
||||
make the AP/controller compete with the LAN DHCP server.
|
||||
@@ -0,0 +1,83 @@
|
||||
# dns.windy.lan — AdGuard Home LAN DNS
|
||||
|
||||
## Role and access
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| IPv4 | `192.168.66.36` |
|
||||
| SSH | `ssh -4 windy@192.168.66.36` |
|
||||
| OS | Debian 12 |
|
||||
| **Host** | **PVE VM 120 (`pihole`)** — not a separate physical host (verified 2026-08-09) |
|
||||
| Roles | AdGuard Home LAN DNS; Mihomo explicit-proxy service |
|
||||
|
||||
Use `windy` for routine inspection. Do not store credentials, AdGuard Home
|
||||
user hashes, proxy subscriptions, or query-log contents in this repository.
|
||||
|
||||
## AdGuard Home
|
||||
|
||||
AdGuard Home runs as the root-owned `adguardhome` Docker container in host
|
||||
network mode. The persistent deployment is `/opt/adguardhome`:
|
||||
|
||||
| Item | Path / setting |
|
||||
|---|---|
|
||||
| Compose file | `/opt/adguardhome/docker-compose.yml` |
|
||||
| Configuration | `/opt/adguardhome/conf/AdGuardHome.yaml` |
|
||||
| Mutable data and query logs | `/opt/adguardhome/work` |
|
||||
| DNS | TCP/UDP `53`; intentionally no source ACL so both LAN55 and LAN66 work |
|
||||
| HTTP UI | `192.168.66.36:80`; LAN66 only |
|
||||
| Image policy | Pin the reviewed image digest; do not use `latest` for a rebuild |
|
||||
|
||||
The EdgeRouter DHCP service advertises this host as DNS for LAN55 and LAN66.
|
||||
Do not enable AdGuard Home DHCP unless the existing EdgeRouter DHCP service is
|
||||
explicitly migrated and disabled first.
|
||||
|
||||
`agh-ui-access.service` loads `/etc/nftables-agh-ui-access.nft`. It permits
|
||||
`192.168.66.0/24` (LAN66) and `192.168.55.0/24` (LAN55, for Home Assistant
|
||||
integration) to TCP/80 and drops other TCP/80 input. It deliberately
|
||||
does **not** restrict DNS, SSH, Docker, or Mihomo ports. Keep it isolated from
|
||||
Docker-managed nftables tables.
|
||||
|
||||
> 2026-08-12: added `192.168.55.0/24` allow so HAOS (`192.168.55.11`) can reach
|
||||
the HTTP API on `:80` for the Home Assistant AdGuard Home integration; applied
|
||||
via `sudo systemctl restart agh-ui-access.service` (file edited first, then
|
||||
reloaded; syntax verified with `nft -c`). Other firewalls (EdgeRouter LAN_IN/
|
||||
LAN_OUT inactive, PVE zero rules) were already open for LAN55->LAN66.
|
||||
|
||||
Current query-log policy is 14 days with anonymized client IPs. Check free
|
||||
space before increasing retention. DNSSEC is disabled because the selected
|
||||
upstream path did not pass the known-bad-signature validation check; do not
|
||||
enable it without re-testing validated upstreams.
|
||||
|
||||
The compatible names `hass.windy.lan` and legacy `hass.local` currently point
|
||||
to the same Home Assistant address. Migrate clients to `hass.windy.lan`; keep
|
||||
the legacy rewrite until its planned retirement.
|
||||
|
||||
## Mihomo and routing boundary
|
||||
|
||||
Mihomo listens for explicit proxy traffic on this host. Read-only checks found
|
||||
no host TUN policy routing or local nftables/iptables transparent-redirection
|
||||
rules. A Mihomo listener alone does not mean it intercepts AdGuard Home
|
||||
traffic. The default gateway (`gfw.windy.lan`) can still affect outbound
|
||||
traffic, so inspect its OpenClash rules separately before changing upstream
|
||||
routing.
|
||||
|
||||
## Safety
|
||||
|
||||
- Back up `/opt/adguardhome/conf/AdGuardHome.yaml` and `docker-compose.yml`
|
||||
before changing either, then run the official `--check-config` command.
|
||||
- Recreate only the AdGuard Home container and verify DNS A/AAAA, local names,
|
||||
the UI from LAN66, and the UI denial from LAN55 afterwards.
|
||||
- Private PTR currently has no local authoritative source: direct EdgeRouter
|
||||
PTR queries return NXDOMAIN. Do not point `local_ptr_upstreams` at the
|
||||
router until that source exists.
|
||||
|
||||
See [the health runbook](../runbooks/adguard-home-health.md) and
|
||||
[the official configuration review](../docs/adguard-home-official-review-2026-08.md).
|
||||
|
||||
Routine health through Ansible (`--limit adguardhome`), with a daily timer +
|
||||
email alerting like the other active hosts:
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook playbooks/health-report.yml --limit adguardhome
|
||||
```
|
||||
+39
-6
@@ -6,17 +6,33 @@
|
||||
|---|---|
|
||||
| Role | LAN gateway and OpenClash transparent/explicit-proxy host |
|
||||
| IPv4 | `192.168.66.1` |
|
||||
| SSH | `ssh -4 root@192.168.66.1` |
|
||||
| OS | OpenWrt, Linux `6.12.87` |
|
||||
| SSH | `ssh -4 root@192.168.66.1` (key-only, verified 2026-08-06) |
|
||||
| OS | ImmortalWrt 25.12.0 (r37854), Linux `6.12.87`, x86/64 |
|
||||
| **Host** | **PVE VM 140 (`gfw`)** — 3 NICs: `net0`→vmbr0(LAN66/eth0), `net1`→vmbr1(LAN55/eth1, up but unaddressed), `net2`→VLAN10/`ubunt_upg`(eth2, `192.168.10.1/24`) (topology 2026-08-09; eth2/VLAN10 live verified 2026-08-11) |
|
||||
|
||||
Do not store the root password in this repository. Prefer a verified SSH key for
|
||||
future management access.
|
||||
Do not store the root password in this repository.
|
||||
|
||||
## OpenClash
|
||||
|
||||
OpenClash runs `/etc/openclash/clash` with configuration
|
||||
OpenClash runs `/etc/openclash/clash` (clash_meta core) with configuration
|
||||
`/etc/openclash/pass-cat.yaml`.
|
||||
|
||||
- Core: **Mihomo Meta v1.19.29** (linux-amd64, with_gvisor; upgraded from
|
||||
`alpha-g9ae1cc2` on 2026-08-06, backup `clash_meta.bak.alpha-g9ae1cc2`) —
|
||||
tracked in Linear W1N-50
|
||||
- OpenClash app: **luci-app-openclash 0.47.133** (GitHub latest)
|
||||
|
||||
- Mode: **fake-ip + TPROXY transparent proxy** (`operation_mode=fake-ip`,
|
||||
`en_mode=fake-ip`, `proxy_mode=rule`); fake-ip network `198.18.0.0/16`
|
||||
- DNS path: dnsmasq → clash DNS `127.0.0.1#7874` (`server=127.0.0.1#7874` in
|
||||
dnsmasq config); OpenClash custom DNS uses `mosdns` on `127.0.0.1:6052` as its
|
||||
`nameserver`/`default-nameserver` for DIRECT-rule real-IP resolution
|
||||
(`/etc/mosdns/config.yaml`): domestic domains → AGH `.36:53`, foreign →
|
||||
`223.5.5.5`/`119.29.29.29` (Chinese public DNS). mosdns is **not** in the
|
||||
client query path — LAN/VLAN10 clients receive fake-ip from clash :7874.
|
||||
- nft: OpenClash injects TPROXY/redirect + DNS-hijack rules into
|
||||
`table inet fw4`; a residual `table inet passwall` exists with 0 packets (unused)
|
||||
|
||||
| Listener | Port |
|
||||
|---|---:|
|
||||
| HTTP | `7890` |
|
||||
@@ -25,10 +41,28 @@ OpenClash runs `/etc/openclash/clash` with configuration
|
||||
| Mixed | `7893` |
|
||||
| TPROXY | `7895` |
|
||||
| DNS | `7874` |
|
||||
| Dashboard | `9090` |
|
||||
|
||||
`8443` is not an OpenClash listener and has no runtime nftables forwarding rule.
|
||||
It is included only in OpenClash's common TLS-sniffing port list.
|
||||
|
||||
## VLAN 10 Wi-Fi
|
||||
|
||||
`gfw`'s third NIC `eth2` hosts the `ubunt_upg` interface at `192.168.10.1/24`,
|
||||
serving the dedicated `ubnt-upg` SSID VLAN 10 (untagged access path from a
|
||||
VLAN-capable switch/trunk; AP management stays untagged on LAN66). The
|
||||
`ubunt_upg` zone runs the **only** DHCP server for `192.168.10.0/24` (UDP/67),
|
||||
allows DNS (53), and applies `192.168.10.0/24 → eth0 masquerade` (NAT) for
|
||||
Internet egress. `forward_ubunt_upg` isolates VLAN10 from LAN66/55 and RFC1918
|
||||
(deny counters 0, `accept_to_lan` passes).
|
||||
|
||||
Live-verified 2026-08-11: an `ubnt-upg` client received `192.168.10.168` (lease
|
||||
in `/tmp/dhcp.leases`), the `192.168.10.0/24 masquerade` counter climbed
|
||||
(215 pkts/42KB), and the LAN55/LAN66 deny counters stayed 0 → VLAN10→LAN
|
||||
isolation holds. See
|
||||
[docs/lan-se5420-deployment-guide.md](../docs/lan-se5420-deployment-guide.md),
|
||||
[docs/unifi-openwrt-vlan10-implementation-examples.md](../docs/unifi-openwrt-vlan10-implementation-examples.md)
|
||||
|
||||
## Operational note
|
||||
|
||||
When OpenClash logs a LAN client request such as
|
||||
@@ -37,4 +71,3 @@ an explicit-proxy client sending its own loopback request to the gateway. Fix
|
||||
the originating host's `NO_PROXY` / `no_proxy` configuration; do not create a
|
||||
listener on this router to mask the problem. See
|
||||
[the UniFi localhost proxy note](../docs/unifi-openclash-localhost.md).
|
||||
|
||||
|
||||
+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.
|
||||
@@ -50,8 +50,9 @@ See [mailcow-smtp-client](../runbooks/mailcow-smtp-client.md).
|
||||
- [mailcow-health](../runbooks/mailcow-health.md)
|
||||
- [mailcow-update](../runbooks/mailcow-update.md)
|
||||
- [mailcow-smtp-client](../runbooks/mailcow-smtp-client.md)
|
||||
- Script: [`../scripts/check-mx2.sh`](../scripts/check-mx2.sh)
|
||||
|
||||
Routine health: `cd ansible && ansible-playbook playbooks/health-report.yml --limit mailcow`.
|
||||
|
||||
## Verified
|
||||
|
||||
Last checked: 2026-08-01 — `./scripts/check-mx2.sh` exit 0 (compose up, watchdog 100%, empty queue, TLS LE OK, SMTP Postcow, A/AAAA/PTR/MX/SPF OK).
|
||||
Last checked: 2026-08-01 — Ansible mailcow health profile OK (compose up, watchdog 100%, empty queue, TLS LE OK, SMTP Postcow, A/AAAA/PTR/MX/SPF OK).
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
| IPv4 | `192.168.66.46` |
|
||||
| SSH | `ssh -4 windy@192.168.66.46` |
|
||||
| Hostname | `ubnt` |
|
||||
| **Host** | **PVE VM 160 (`ubnt`)** — not a separate physical host (verified 2026-08-09) |
|
||||
| Privilege | `windy` has passwordless sudo and is in the `docker` group |
|
||||
|
||||
The current SSH ED25519 host-key fingerprint is
|
||||
@@ -25,6 +26,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 +45,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
|
||||
@@ -56,3 +66,15 @@ 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.
|
||||
|
||||
## Ansible management
|
||||
|
||||
The host is in the Ansible `unifi` group (inventory `ansible/inventory/hosts.yml`),
|
||||
deliberately outside `managed` so the generic health report does not run here.
|
||||
|
||||
- SSO login setting (idempotent, gated): `cd ansible && ansible-playbook
|
||||
playbooks/unifi-sso.yml --limit unifi` — see
|
||||
[runbooks/ansible-operations.md](../runbooks/ansible-operations.md).
|
||||
- Login/MFA state: SSO login disabled 2026-08-08; local accounts
|
||||
(`windy`, `zhiqiangf`) use local passwords, no MFA. `unifi-network-mcp`
|
||||
works for read and write operations. See
|
||||
[docs/unifi-network.md](../docs/unifi-network.md).
|
||||
|
||||
@@ -76,9 +76,15 @@ ansible-playbook playbooks/compose-reconcile.yml --limit vaultwarden \
|
||||
-e '{"service_reconcile_confirm": true, "service_reconcile_targets": ["vaultwarden"], "service_reconcile_restart_traefik": true}'
|
||||
```
|
||||
|
||||
## Other software on this host (stubs)
|
||||
## Other running services on this host
|
||||
|
||||
`/opt/traefik`, `soft-serve`, `authelia`, `dendrite`/`conduit`, `mastodon`, `rustdesk`, `zitadel`, etc.
|
||||
| Container | Status | Image / notes |
|
||||
|-----------|--------|---------------|
|
||||
| `soft-serve` | Up | `ghcr.io/charmbracelet/soft-serve:latest` (`repo.windy.me:2222`) |
|
||||
| `traefik` | Up | `traefik:v3.6.2` (`/opt/traefik`, public `:80`/`:443`) |
|
||||
| `nghttpx-proxy` + `squid-backend` | Up | HTTP forward-proxy stack (`/opt/nghttpx`), network `nghttpx_internal-net`; details TBD |
|
||||
|
||||
Directories for `authelia`, `conduit`, `dendrite`, `mastodon`, `rustdesk`, `zitadel`, etc. exist under `/opt` but have no running containers; treat them as dormant, not documented services.
|
||||
|
||||
## Verified
|
||||
|
||||
|
||||
@@ -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.
|
||||
+8
-3
@@ -5,6 +5,9 @@ run through Ansible. SSH endpoints below remain the access path for focused
|
||||
diagnosis and procedures that are deliberately interactive or destructive; see
|
||||
[the Ansible operations runbook](../runbooks/ansible-operations.md).
|
||||
|
||||
For a live-verified map of the **internal LAN** (gw, gfw, dns, ubnt, APs) and
|
||||
the software deployed there, see [the LAN overview](../docs/lan-overview.md).
|
||||
|
||||
| Host | Role | SSH | IPv4 | Status | Facts |
|
||||
|------|------|-----|------|--------|-------|
|
||||
| mx2.windy.me | mailcow (primary MX prio 20) | `ssh -4 windy@mx2.windy.me` | 194.163.160.244 | active | [hosts/mx2.windy.me.md](../hosts/mx2.windy.me.md) |
|
||||
@@ -13,15 +16,17 @@ 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 |
|
||||
| pdns.wsvc.info | Poweradmin 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) |
|
||||
| **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) |
|
||||
| **ubnt** | UniFi Network Controller | `ssh -4 windy@192.168.66.46` | `192.168.66.46` | **active** | [hosts/ubnt.md](../hosts/ubnt.md) |
|
||||
| **gfw.windy.lan** | OpenWrt (ImmortalWrt) LAN gateway / OpenClash (PVE VM 140) | `ssh -4 root@192.168.66.1` | `192.168.66.1` | **active** | [hosts/gfw.windy.lan.md](../hosts/gfw.windy.lan.md) |
|
||||
| **dns.windy.lan** | AdGuard Home LAN DNS + Mihomo explicit proxy (PVE VM 120) | `ssh -4 windy@192.168.66.36` | `192.168.66.36` | **active** | [hosts/dns.windy.lan.md](../hosts/dns.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 (PVE VM 160) | `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.
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
version: 1
|
||||
profile: minimal
|
||||
@@ -1,70 +0,0 @@
|
||||
# VPS 維護專案重構:Ansible + systemd timers + Restic
|
||||
|
||||
## Objective
|
||||
|
||||
將現有 VPS 維運知識庫漸進重構為一個以 Ansible 管理一致性、以 systemd timers 執行每日唯讀檢查、以 Restic 建立加密異機備份的個人 SRE 維運專案。現納管 mx2、us2、hk2 與 Matrix K3s 主機。自動化預設只能觀測與告警;受 allowlist 和顯式確認保護的 Compose reconciliation 可處理已審查的服務重建,DNS、機密與破壞性資料操作仍需人工確認。
|
||||
|
||||
## Confirmed Decisions
|
||||
|
||||
- [x] 定位為個人 SRE/維運手冊庫,而非全自動修復平台。
|
||||
- [x] 已納管 mx2、us2、hk2 與 Matrix K3s;mx、us1、us4 待完成盤點與緊急存取驗證後才納管。
|
||||
- [x] 每日自動做唯讀檢查與 Email 通知;每週人工審查、每月人工維護、每季復原演練與權限盤點。
|
||||
- [x] 以 Email 作為唯一告警與摘要通道。
|
||||
- [x] 採用本機快速復原加密異機副本的 3-2-1 最小可行策略。
|
||||
- [x] 真實機密不進 Git;repo 只保存去敏設定結構與操作/驗證紀錄。
|
||||
- [x] 日常存取使用非 root SSH key;禁止 root/password SSH,維護 provider/recovery console 緊急存取。
|
||||
- [x] 採用 Ansible + systemd timers + Restic 作為技術基線。
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
- [x] **Status: Done — 建立去敏 Ansible inventory。** 為 mx2、us2、hk2 與 Matrix 建立主機與服務群組,將非機密主機資料與現有人類可讀的 `inventory/hosts.md` 對應;理由是提供可審查的機器可讀編排層,且不取代既有事實來源。
|
||||
- [x] **Status: Done — 實作 audit-only Ansible playbook。** 只收集連線、OS、磁碟、systemd、Docker Compose 和依賴命令狀態,不做設定或服務改動;理由是先確認控制面與真實現況。
|
||||
- [x] **Status: Done — 定義跨服務健康檢查結果合約。** 統一去敏結構化結果、日誌位置、嚴重度及 exit code,讓人工執行、systemd 和 Ansible audit 可共同消費;理由是避免各服務告警語意漂移。
|
||||
- [x] **Status: Done — 保留並模組化 Mailcow 健康檢查。** 持續檢查 Compose、watchdog、queue、listeners、HTTP/HTTPS、TLS、SMTP、DNS/PTR/MX/SPF;理由是現有腳本已覆蓋關鍵郵件服務面向。
|
||||
- [x] **Status: Done — 實作 Vaultwarden 唯讀健康檢查。** 驗證 Compose/Postgres、HTTPS、有效設定、SMTP AUTH、備份新鮮度及機密指紋一致性,禁止輸出機密;理由是 `config.json` 優先於 `.env`,且 SMTP 漂移已有已知風險。
|
||||
- [x] **Status: Done — 實作 PowerDNS 唯讀健康檢查。** 驗證容器、版本與安全公告、ns1/ns2 served SOA、API、Web UI、備份新鮮度與必要設定;理由是同時覆蓋公開 DNS 與 secondary 同步。
|
||||
- [x] **Status: Done — 使用 Ansible 部署 systemd healthcheck service/timer。** 每台主機本機執行每日檢查,具 persistent 排程、權限、logrotate 和一致錯誤處理;理由是控制端離線不應阻止巡檢。
|
||||
- [x] **Status: Done — 部署 Email 告警與每日摘要。** Critical/unknown 立即通知、健康狀態每日摘要、重複失敗抑制;SMTP 真實認證僅在各主機受限路徑保存;理由是已選定 Email 為唯一通知通道。
|
||||
- [!] **Status: Blocked — 決定 Restic 異機 repository 與存取隔離。** 選擇具加密傳輸、權限隔離與可承受保留需求的 S3/B2/SFTP 或等價目的地;理由是無異機 repository 即無法滿足已確認的 3-2-1 政策。阻塞:本地實作依要求未虛構 backend、repository 或 credentials。
|
||||
- [x] **Status: Done (templates gated) — 部署 Vaultwarden 與 PowerDNS 的 Restic 備份。** 將既有一致性資料庫 dump、必要資料目錄及復原元資料加密同步,並加入 snapshot 年齡和 `restic check` 驗證;理由是兩者已有本機備份但缺少異機保護。啟用被 repository 決策和主機端受限設定檔阻擋。
|
||||
- [!] **Status: Blocked — 完成 Mailcow 備份設計審查。** 官方流程已確認:以 `/opt/mail/helper-scripts/backup_and_restore.sh backup all`(或經明確核准的元件集)先產生一致性備份,再由 Restic 同步該輸出;不得直接複製 Docker volumes。阻塞:仍需決定本機備份位置、保留期、排程及異機 Restic repository。
|
||||
- [x] **Status: Done (templates gated) — 部署 Restic backup、retention、forget/prune 與 check timers。** 與現有資料庫 dump 時段錯開,所有 repository 認證與密碼留在伺服器端受限檔案;理由是保持備份可用與成本可控。啟用被 repository 決策和主機端受限設定檔阻擋。
|
||||
- [!] **Status: Blocked — 補齊服務級復原 runbook。** 阻塞:需在選定 Restic repository、建立實際 snapshot 並確認各服務的實際備份輸出後,才能編寫可驗證的 restore 前置條件、順序與 rollback;禁止臆造 backend/credentials 或未驗證還原命令。
|
||||
- [!] **Status: Blocked — 執行隔離式復原演練。** 阻塞:尚未選定/配置 Restic 異機 repository,亦尚無可供還原的異機 snapshot;演練不得對現有生產資料執行。
|
||||
- [x] **Status: Done — 實作受控 common baseline 與 maintenance playbook。** 僅在 audit 穩定後納入 SSH 稽核、時間同步、logrotate、更新預覽與人工確認的維護操作;理由是避免工具導入期間同時改變服務狀態。
|
||||
- [x] **Status: Done (2026-08-03) — 將例行操作收斂為 Ansible 入口。** 新增 on-demand health report 與 allowlisted Compose reconciliation;health、maintenance preview、baseline 現可涵蓋 Matrix。互動式 Mailcow 更新、資料遷移、DNS 與機密操作保留人工程序。
|
||||
- [x] **Status: Done (separate change defined) — 將 PowerDNS API key、DB password 與 TSIG 輪替列為分離變更。** 已明確保持 API key、DB password 與 TSIG 為三項獨立、需人工核准的變更,並要求逐步驗證 Auth、Poweradmin、AXFR/NOTIFY、DNSSEC 與 ns2 同步;未輪替任何機密。
|
||||
- [!] **Status: Blocked — 盤點 mx、us1、us4。** 阻塞:inventory 僅記錄 `TBD` SSH/角色或缺少完整事實,尚未提供可驗證的存取方式與 provider/recovery console 資訊;不得猜測或嘗試未授權存取。
|
||||
|
||||
## Verification Criteria
|
||||
|
||||
- [ ] 四台 active 主機能被 Ansible 正確解析;Compose 主機可執行 audit,且 audit 不產生主機變更。
|
||||
- [ ] 四台主機每日產生去敏健康結果;服務、容量、TLS、公開端點和備份新鮮度異常均能被偵測。
|
||||
- [ ] 正常狀態寄送 Email 摘要;受控測試異常能觸發一次可讀且不含機密的 Email 告警。
|
||||
- [ ] 每個核心服務至少有一份加密、異機、可列出且通過完整性檢查的 Restic snapshot。
|
||||
- [ ] 至少完成一次隔離式實際還原,並確認其結果符合或明確量化偏離服務 RPO/RTO。
|
||||
- [ ] 自動化排程不包含更新、重啟、修復、秘密輪替或 DNS 變更。
|
||||
- [ ] Repo、Ansible vars、產出日誌及 Email 均不包含任何真實機密。
|
||||
|
||||
## Potential Risks and Mitigations
|
||||
|
||||
1. **Mailcow 備份未保持資料一致性。**
|
||||
Mitigation: 先依官方方式完成備份範圍與還原設計,再上線排程;以隔離還原驗證作為完成門檻。
|
||||
|
||||
2. **Ansible 設定錯誤改動生產系統。**
|
||||
Mitigation: 先導入 audit-only;變更 playbook 必須使用 dry-run/diff、明確 tag 與人工確認,且先限制單一 host。
|
||||
|
||||
3. **機密出現在 Git、Ansible output 或 Email。**
|
||||
Mitigation: 真實值只存主機受限檔案;去敏輸出、no-log 機制和測試用機密掃描為強制要求。
|
||||
|
||||
4. **Timer 成功但公開服務實際不可用。**
|
||||
Mitigation: 同時執行本機 Compose/DB 檢查與外部 HTTP、SMTP、TLS、DNS 檢查。
|
||||
|
||||
5. **PowerDNS 密鑰輪替中斷 DNS 管理或 zone transfer。**
|
||||
Mitigation: API key、DB password、TSIG 分批處理;每步都驗證 primary/secondary 同步、DNSSEC 和管理 UI。
|
||||
|
||||
## Alternative Approaches
|
||||
|
||||
1. **純 shell + systemd,不使用 Ansible**:初期較快,但設定與排程容易跨主機漂移,且難以審查或擴充;不建議作為長期方案。
|
||||
2. **Ansible + 集中式監控平台**:可增加儀表板與趨勢,但平台本身需額外維護;等 Email 摘要無法滿足需求時再評估。
|
||||
3. **立即導入 SOPS + age**:可使加密設定進 Git,但需先完成密鑰生命週期與緊急存取設計;建議列為後續階段。
|
||||
@@ -0,0 +1,60 @@
|
||||
# AdGuard Home health — dns.windy.lan
|
||||
|
||||
This runbook is read-only. It does not expose query-log contents or secrets.
|
||||
|
||||
Routine checks run through Ansible on demand:
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook playbooks/health-report.yml --limit adguardhome
|
||||
```
|
||||
|
||||
The same sanitized profile is installed as a daily server-local timer
|
||||
(`vps-healthcheck.timer`, output at `/var/lib/vps-health/latest.json`) via
|
||||
`playbooks/healthchecks.yml`; the manual checks below are for focused diagnosis.
|
||||
|
||||
## Verify service and configuration
|
||||
|
||||
```bash
|
||||
ssh -4 -o BatchMode=yes windy@192.168.66.36
|
||||
sudo docker ps --filter name=^/adguardhome$
|
||||
sudo docker exec adguardhome /opt/adguardhome/AdGuardHome --check-config \
|
||||
-c /opt/adguardhome/conf/AdGuardHome.yaml
|
||||
sudo ss -lntup | grep -E '(:53|:80)'
|
||||
```
|
||||
|
||||
Expected: DNS listens on TCP/UDP 53, and the HTTP UI listens only on
|
||||
`192.168.66.36:80`.
|
||||
|
||||
## Verify DNS and storage
|
||||
|
||||
```bash
|
||||
dig +time=5 +tries=1 @192.168.66.36 example.com A +noall +comments +answer
|
||||
dig +time=5 +tries=1 @192.168.66.36 example.com AAAA +noall +comments +answer
|
||||
dig +time=5 +tries=1 @192.168.66.36 hass.windy.lan A +noall +comments +answer
|
||||
sudo docker logs --since 30m adguardhome 2>&1 | grep -Ei '\[(error|warn|fatal)\]|level=(error|warn|fatal)' || true
|
||||
df -h /opt/adguardhome/work
|
||||
```
|
||||
|
||||
The host intentionally accepts DNS from LAN55 and LAN66. Do not infer a
|
||||
broken DNS policy merely because `allowed_clients` is empty.
|
||||
|
||||
## Verify management UI boundary
|
||||
|
||||
From LAN66, the UI should return a login redirect:
|
||||
|
||||
```bash
|
||||
curl -sS -o /dev/null -w '%{http_code}\n' http://192.168.66.36/
|
||||
```
|
||||
|
||||
From a LAN55 source, TCP/80 should time out or be refused by the dedicated
|
||||
`agh-ui-access.service` nftables rule. Do not alter this rule to restrict
|
||||
port 53 without an explicit network-access review.
|
||||
|
||||
## DNSSEC and PTR caveats
|
||||
|
||||
DNSSEC is currently disabled. Before enabling it, select upstreams that pass
|
||||
a known-bad-signature test; an enabled DO bit alone is not validation.
|
||||
|
||||
Private PTR forwarding is intentionally absent because the EdgeRouter does
|
||||
not currently answer private PTR requests.
|
||||
@@ -59,3 +59,26 @@ ansible-playbook playbooks/maintenance-preview.yml
|
||||
# Baseline observations; logrotate management remains opt-in
|
||||
ansible-playbook playbooks/baseline.yml
|
||||
```
|
||||
|
||||
## UniFi SSO login setting (mutating)
|
||||
|
||||
Reconciles `super_sdn.sso_login_enabled` on the UniFi controller (host `ubnt`,
|
||||
group `unifi`). Idempotent and gated: without `unifi_sso_confirm=true` the
|
||||
playbook only reports the current state and refuses to change anything.
|
||||
|
||||
```bash
|
||||
# Read-only status report
|
||||
ansible-playbook playbooks/unifi-sso.yml --limit unifi --check
|
||||
|
||||
# Apply (disable SSO login; local accounts use local passwords, no MFA)
|
||||
ansible-playbook playbooks/unifi-sso.yml --limit unifi \
|
||||
-e '{"unifi_sso_confirm": true, "unifi_sso_target_value": false}'
|
||||
|
||||
# Rollback (re-enable SSO login)
|
||||
ansible-playbook playbooks/unifi-sso.yml --limit unifi \
|
||||
-e '{"unifi_sso_confirm": true, "unifi_sso_target_value": true}'
|
||||
```
|
||||
|
||||
The controller container is not restarted by default; the UI path applies the
|
||||
setting immediately. Set `unifi_sso_restart_controller: true` only when the
|
||||
setting was changed directly in the DB while the controller was running.
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
Target: [mx2.windy.me](../hosts/mx2.windy.me.md)
|
||||
Path: `/opt/mail`
|
||||
Prefer: the Ansible health report (`ansible/playbooks/health-report.yml`), or
|
||||
`./scripts/check-mx2.sh` from this repo. The SSH commands below remain useful
|
||||
for focused, interactive diagnosis after the report identifies a problem.
|
||||
Prefer: the Ansible health report (`ansible/playbooks/health-report.yml --limit mailcow`),
|
||||
or the server-local daily timer result at `/var/lib/vps-health/latest.json`. The
|
||||
SSH commands below remain useful for focused, interactive diagnosis after the
|
||||
report identifies a problem.
|
||||
|
||||
## Focused SSH diagnostics
|
||||
|
||||
@@ -66,7 +67,9 @@ dig @1.1.1.1 +short windy.me TXT | grep -i spf
|
||||
|
||||
## Scheduled local check
|
||||
|
||||
The sanitized Ansible health profile is `mailcow` (`ansible/playbooks/healthchecks.yml`). It preserves the existing standalone `scripts/check-mx2.sh` as the richer operator check, while the server-local timer emits a sanitized result at `/var/lib/vps-health/latest.json`. Neither check changes Mailcow configuration or service state.
|
||||
The sanitized Ansible health profile is `mailcow` (`ansible/playbooks/healthchecks.yml`).
|
||||
The server-local timer emits a sanitized result at `/var/lib/vps-health/latest.json`.
|
||||
It does not change Mailcow configuration or service state.
|
||||
|
||||
## Pass criteria
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Path: `/opt/mail`
|
||||
|
||||
## Before
|
||||
|
||||
1. Run [mailcow-health](mailcow-health.md) (or `./scripts/check-mx2.sh`). Record baseline.
|
||||
1. Run [mailcow-health](mailcow-health.md) (Ansible health report). Record baseline.
|
||||
2. Note config quirks in `mailcow.conf`, especially:
|
||||
- `SKIP_CLAMD=y` — after update, `clamd` may still be running until services are recreated per mailcow docs.
|
||||
- `SKIP_LETS_ENCRYPT`, ports, hostname — should stay `mx2.windy.me`.
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Read-only health check for mx2.windy.me mailcow.
|
||||
# Usage: ./scripts/check-mx2.sh
|
||||
# Env:
|
||||
# MX2_HOST default mx2.windy.me
|
||||
# MX2_SSH_USER default windy
|
||||
# MX2_ALLOW_IPV6 set to 1 to allow IPv6 (default forces IPv4; WSL often lacks v6 route)
|
||||
set -euo pipefail
|
||||
|
||||
HOST="${MX2_HOST:-mx2.windy.me}"
|
||||
SSH_USER="${MX2_SSH_USER:-windy}"
|
||||
|
||||
# Default -4: host has AAAA but many clients (WSL) have no IPv6 route → "Network is unreachable"
|
||||
SSH_AF_OPTS=(-4)
|
||||
CURL_AF_OPTS=(-4)
|
||||
OPENSSL_AF_OPTS=(-4)
|
||||
if [[ "${MX2_ALLOW_IPV6:-0}" == "1" ]]; then
|
||||
SSH_AF_OPTS=()
|
||||
CURL_AF_OPTS=()
|
||||
OPENSSL_AF_OPTS=()
|
||||
fi
|
||||
|
||||
SSH=(ssh "${SSH_AF_OPTS[@]}" -o BatchMode=yes -o ConnectTimeout=10 "${SSH_USER}@${HOST}")
|
||||
# Prefer public resolvers: local stub (127.0.0.53) is flaky from some WSL setups
|
||||
DIG_OPTS=(+time=3 +tries=2 +short)
|
||||
DIG_SERVERS=(1.1.1.1 8.8.8.8)
|
||||
FAILURES=0
|
||||
|
||||
section() { printf '\n===== %s =====\n' "$1"; }
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
FAILURES=$((FAILURES + 1))
|
||||
}
|
||||
|
||||
# Strip dig commentary (timeouts are printed on stdout as ";; ...")
|
||||
dig_answers_only() {
|
||||
grep -v '^;;' | grep -v '^$' || true
|
||||
}
|
||||
|
||||
# dig_short <name> [type] — tries each public resolver; skips timeout noise
|
||||
dig_short() {
|
||||
local name="$1"
|
||||
local typ="${2:-A}"
|
||||
local server out
|
||||
for server in "${DIG_SERVERS[@]}"; do
|
||||
out="$(dig @"${server}" "${DIG_OPTS[@]}" "$name" "$typ" 2>/dev/null | dig_answers_only)" || out=""
|
||||
if [[ -n "$out" ]]; then
|
||||
printf '%s\n' "$out"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# dig_ptr <ip>
|
||||
dig_ptr() {
|
||||
local ip="$1"
|
||||
local server out
|
||||
for server in "${DIG_SERVERS[@]}"; do
|
||||
out="$(dig @"${server}" "${DIG_OPTS[@]}" -x "$ip" 2>/dev/null | dig_answers_only)" || out=""
|
||||
if [[ -n "$out" ]]; then
|
||||
printf '%s\n' "$out"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
check_ptrs() {
|
||||
local ip ptr
|
||||
while read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
ptr="$(dig_ptr "$ip" || true)"
|
||||
printf 'PTR %s -> %s\n' "$ip" "${ptr:-<none>}"
|
||||
grep -Fiq "${HOST}" <<<"$ptr" || fail "PTR for ${ip} does not mention ${HOST}"
|
||||
done
|
||||
}
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "missing required command: $1" >&2
|
||||
exit 127
|
||||
}
|
||||
}
|
||||
|
||||
need_cmd ssh
|
||||
need_cmd curl
|
||||
need_cmd openssl
|
||||
need_cmd dig
|
||||
need_cmd timeout
|
||||
|
||||
section "SSH + compose ps"
|
||||
PS_OUT="$("${SSH[@]}" 'cd /opt/mail && docker compose ps -a')" || {
|
||||
fail "ssh or docker compose ps"
|
||||
echo "Cannot continue without SSH/compose." >&2
|
||||
exit 1
|
||||
}
|
||||
printf '%s\n' "$PS_OUT"
|
||||
if grep -qiE 'Exited|Restarting|[[:space:]]Dead[[:space:]]' <<<"$PS_OUT"; then
|
||||
fail "compose has Exited/Restarting/Dead containers"
|
||||
fi
|
||||
for svc in nginx-mailcow postfix-mailcow dovecot-mailcow mysql-mailcow; do
|
||||
grep -Fq "$svc" <<<"$PS_OUT" || fail "missing service ${svc}"
|
||||
done
|
||||
|
||||
section "Watchdog (tail)"
|
||||
if ! "${SSH[@]}" 'cd /opt/mail && docker compose logs --tail=40 watchdog-mailcow'; then
|
||||
fail "watchdog logs"
|
||||
fi
|
||||
|
||||
section "Mail queue"
|
||||
QUEUE_OUT="$("${SSH[@]}" 'cd /opt/mail && docker compose exec -T postfix-mailcow postqueue -p' 2>&1)" || {
|
||||
fail "postqueue"
|
||||
QUEUE_OUT=""
|
||||
}
|
||||
printf '%s\n' "$QUEUE_OUT"
|
||||
if [[ -n "$QUEUE_OUT" ]] && ! grep -Fiq 'Mail queue is empty' <<<"$QUEUE_OUT"; then
|
||||
fail "mail queue not empty"
|
||||
fi
|
||||
|
||||
section "Listeners"
|
||||
LISTEN_OUT="$("${SSH[@]}" 'ss -tlnp 2>/dev/null | grep -E ":(25|465|587|110|143|993|995|80|443|4190)[[:space:]]" || true')"
|
||||
printf '%s\n' "$LISTEN_OUT"
|
||||
for p in 25 465 587 110 143 993 995 80 443 4190; do
|
||||
if ! grep -qE ":${p}[[:space:]]" <<<"$LISTEN_OUT"; then
|
||||
fail "port ${p} not listening on host"
|
||||
fi
|
||||
done
|
||||
|
||||
section "HTTP / HTTPS"
|
||||
HTTP_OUT="$(curl "${CURL_AF_OPTS[@]}" -sS -I --max-time 10 "http://${HOST}/" 2>&1 | head -8)" || true
|
||||
printf '%s\n' "$HTTP_OUT"
|
||||
echo "---"
|
||||
# No -k: surface TLS trust problems
|
||||
HTTPS_OUT="$(curl "${CURL_AF_OPTS[@]}" -sS -I --max-time 10 "https://${HOST}/" 2>&1 | head -15)" || true
|
||||
printf '%s\n' "$HTTPS_OUT"
|
||||
grep -qE '^HTTP/[0-9.]+ 301' <<<"$HTTP_OUT" || fail "HTTP did not redirect (expect 301)"
|
||||
grep -qE '^HTTP/[0-9.]+ 200' <<<"$HTTPS_OUT" || fail "HTTPS did not return 200"
|
||||
|
||||
section "TLS cert (443)"
|
||||
CERT_OUT="$(
|
||||
timeout 15 openssl s_client "${OPENSSL_AF_OPTS[@]}" -connect "${HOST}:443" -servername "${HOST}" </dev/null 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates 2>/dev/null
|
||||
)" || true
|
||||
printf '%s\n' "$CERT_OUT"
|
||||
if ! grep -Fq "CN=${HOST}" <<<"$CERT_OUT" && ! grep -Fq "CN = ${HOST}" <<<"$CERT_OUT"; then
|
||||
fail "TLS subject missing ${HOST}"
|
||||
fi
|
||||
grep -Fiq "Let's Encrypt" <<<"$CERT_OUT" || fail "TLS issuer not Let's Encrypt"
|
||||
|
||||
section "SMTP banner"
|
||||
# Connect via A record so /dev/tcp does not pick unreachable AAAA first
|
||||
SMTP_IP="$(dig_short "${HOST}" A 2>/dev/null | head -n1 || true)"
|
||||
if [[ -z "$SMTP_IP" ]]; then
|
||||
fail "cannot resolve A for SMTP check"
|
||||
SMTP_OUT=""
|
||||
else
|
||||
SMTP_OUT="$(
|
||||
timeout 8 bash -c "exec 3<>/dev/tcp/${SMTP_IP}/25; printf 'EHLO test.local\r\nQUIT\r\n' >&3; cat <&3" 2>/dev/null \
|
||||
| head -20
|
||||
)" || true
|
||||
fi
|
||||
printf 'connect %s:25\n' "${SMTP_IP:-?}"
|
||||
printf '%s\n' "$SMTP_OUT"
|
||||
grep -qiE 'Postcow|ESMTP' <<<"$SMTP_OUT" || fail "SMTP banner unexpected"
|
||||
|
||||
section "DNS"
|
||||
A_REC="$(dig_short "${HOST}" A || true)"
|
||||
AAAA_REC="$(dig_short "${HOST}" AAAA || true)"
|
||||
printf 'A: %s\n' "${A_REC:-<none>}"
|
||||
printf 'AAAA: %s\n' "${AAAA_REC:-<none>}"
|
||||
[[ -n "$A_REC" ]] || fail "missing A record"
|
||||
[[ -n "$AAAA_REC" ]] || fail "missing AAAA record"
|
||||
|
||||
check_ptrs <<<"$A_REC"
|
||||
check_ptrs <<<"$AAAA_REC"
|
||||
|
||||
MX_REC="$(dig_short windy.me MX || true)"
|
||||
printf 'MX:\n%s\n' "${MX_REC:-<none>}"
|
||||
grep -Fiq "${HOST}" <<<"$MX_REC" || fail "windy.me MX missing ${HOST}"
|
||||
|
||||
SPF="$(dig_short windy.me TXT || true)"
|
||||
printf 'SPF:\n'
|
||||
if ! grep -Fi 'v=spf1' <<<"$SPF"; then
|
||||
fail "no SPF TXT on windy.me"
|
||||
fi
|
||||
|
||||
section "Done"
|
||||
if [[ "$FAILURES" -gt 0 ]]; then
|
||||
echo "Health check finished with ${FAILURES} failure(s)."
|
||||
exit 1
|
||||
fi
|
||||
echo "Health check OK. Update hosts/mx2.windy.me.md Verified line if desired."
|
||||
exit 0
|
||||
Reference in New Issue
Block a user