vault backup: 2026-01-05 13:03:55

This commit is contained in:
windyboy
2026-01-05 13:03:55 +08:00
parent 21460fc35d
commit be7c6cdcc9
589 changed files with 396508 additions and 27 deletions
+124
View File
@@ -0,0 +1,124 @@
https://bandwagonhost.com/
**quick-flag-3.localdomain**
SPECIAL 80G KVM PROMO V3 - LOS ANGELES - CN2
23.105.208.126
2023-07-15
Semi-Annually: $100.88
matrix.chans.xyz
VM 1719294 — quick-flag-3.localdomain [23.105.208.126]
root: r90Bai3aQqV0
port: 27919
https://manage.hostdare.com/clientarea.php?action=services
new matrix.chans.xyz :
https://rhinotech.cc/
root: 4eFcoxKoC8Gr
2core/2g/30g
debain 10
**$139.80 USD** /year
us4.wsvc.info
mx2.windy.me
103.99.115.4
47 年
us1.wsvc.info
ns2.wsvc.info
32 /年
323-600-314
**CKVM1**
[us1.wsvc.info](http://us1.wsvc.info/)
$32.19 USD
Annually
Sunday, February 4th, 2024
Active
**CKVM2**
[us2.wsvc.info](http://us2.wsvc.info/)
$76.99 USD
Annually
Saturday, December 16th, 2023
Active
**CKVM2**
[us4.wsvc.info](http://us4.wsvc.info/)
$47.59 USD
Annually
Tuesday, November 21st, 2023
[https://10g.biz/](https://10g.biz/)
https://www.rhinotech.cc
hk2.chans.xyz
remark.windy.me
$43.92 USD/year
reinstall:
code: M26JrovIHtgp
https://clients.zgovps.com/index.php?/clientarea/services/special-offer/10527/
Domain [matrix.chans.xyz](http://matrix.chans.xyz)
Registration Date 2024-12-24
Expiry Date 2025-12-24
---
First Payment Amount $52.00 USD
https://app.dartnode.com/
38.134.41.134
https://new.contabo.com
$4.95
194.163.160.244
2a02:c207:2284:8258:0000:0000:0000:0001/64
+649
View File
@@ -0,0 +1,649 @@
### Powerdns
ns1
hk2.chans.xyz
with docker compose:
好的,按你要的思路来:**仍然用“官方 schema 自动建表”的 Compose 方案**PostgreSQL 16 + PowerDNS Authoritative),**另外提供一个“手动恢复数据”的脚本**,支持恢复纯 SQL dump 和 `pg_dump -Fc` 自定义格式 dump。
---
# 目录结构(建议)
```
pdns/
├─ docker-compose.yml
├─ auth/
│ └─ pdns.conf
├─ db-init/
│ └─ 01-pdns-schema.sql # 官方 gpgsql schema(见下文获取方式)
├─ backup/
│ └─ pdns.dump # 你的备份文件(.sql 或 -Fc)
└─ scripts/
└─ restore-pdns.sh # 手动恢复脚本
```
## 1) 获取 PowerDNS 官方 PostgreSQL schema
> **重要**:请下载与你将使用的 `powerdns/pdns-auth-49` **相同小版本**的 schema(例如 4.9.x 对应的 tag),保存为 `db-init/01-pdns-schema.sql`。
> (官方路径通常在 `modules/gpgsqlbackend/schema.pgsql.sql` 对应版本的 tag 下。)
---
## 2) `docker-compose.yml`(保持“官方 schema 建表”的方案)
```yaml
version: "3.9"
services:
db:
image: postgres:16
container_name: pdns-db
environment:
POSTGRES_DB: powerdns
POSTGRES_USER: powerdns
POSTGRES_PASSWORD: ${PDNS_DB_PASSWORD:-changeme_db}
volumes:
- dbdata:/var/lib/postgresql/data
- ./db-init:/docker-entrypoint-initdb.d:ro # 首启时自动导入 schema
- ./backup:/backup:ro # 供手动恢复脚本读取 dump
healthcheck:
test: ["CMD-SHELL", "pg_isready -U powerdns -d powerdns"]
interval: 10s
timeout: 5s
retries: 10
restart: unless-stopped
networks: [dnsnet]
auth:
# 固定到你要用的 4.9.x 小版本,例如 4.9.4
image: powerdns/pdns-auth-49:4.9.4
container_name: pdns-auth
depends_on:
db:
condition: service_healthy
ports:
- "53:53/udp"
- "53:53/tcp"
- "8081:8081" # REST API / 内置 webserver
volumes:
- ./auth/pdns.conf:/etc/powerdns/pdns.conf:ro
environment:
PDNS_API_KEY: ${PDNS_API_KEY:-changeme_api}
healthcheck:
test: ["CMD", "sh", "-c",
"wget -qO- --header='X-API-Key: ${PDNS_API_KEY:-changeme_api}' http://127.0.0.1:8081/api/v1/servers/localhost | grep -q 'version'"]
interval: 10s
timeout: 5s
retries: 12
restart: unless-stopped
networks: [dnsnet]
networks:
dnsnet:
driver: bridge
volumes:
dbdata: {}
```
## 3) `auth/pdns.conf`PostgreSQL 后端示例)
```ini
local-address=0.0.0.0
local-port=53
launch=gpgsql
gpgsql-host=pdns-db
gpgsql-port=5432
gpgsql-dbname=powerdns
gpgsql-user=powerdns
gpgsql-password=changeme_db
api=yes
api-key=changeme_api
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=127.0.0.1,10.0.0.0/8,192.168.0.0/16
version-string=anonymous
disable-syslog=yes
loglevel=4
```
## 4) 启动
```bash
docker compose up -d
# 等 db 健康检查 OK 后,auth 会启动并可通过 8081 API 访问
```
---
# 手动恢复脚本(支持 .sql 与 -Fc)
`scripts/restore-pdns.sh`:默认**安全模式**是恢复到一个**新数据库**(避免与你现有 schema 冲突),完成后你只需把 `pdns.conf``gpgsql-dbname` 改成新库名并 `docker compose restart auth` 即可。也提供 `--inplace` 选项可“原地覆盖”(会清空原库 `public` 模式)——谨慎使用。
```bash
#!/usr/bin/env bash
set -euo pipefail
# 用法:
# scripts/restore-pdns.sh /absolute/or/relative/path/to/backup/pdns.dump
# 可选: --inplace # 原地覆盖到 powerdns 库(会清空 public schema
#
# 说明:
# - 支持两类 dump:
# 1) 纯 SQL
# 2) pg_dump -Fc 自定义格式
# - 默认行为: 恢复到新库 powerdns_restore_YYYYmmddHHMMSS
# - 容器/数据库参数需与 docker-compose.yml 一致
DB_SVC="db" # Compose 中的服务名
DB_NAME="powerdns"
DB_USER="powerdns"
INPLACE=0
if [[ "${1:-}" == "--inplace" ]]; then
INPLACE=1
shift
fi
if [[ $# -lt 1 ]]; then
echo "Usage: $0 [--inplace] <path-to-dump>"
exit 1
fi
DUMP_PATH="$1"
if [[ ! -f "$DUMP_PATH" ]]; then
echo "Dump file not found: $DUMP_PATH"
exit 1
fi
# 统一让容器内能看到该文件(compose 已挂载 ./backup -> /backup:ro
# 若传入的不是 ./backup 下的文件,临时 cp 进去容器使用
IN_CONTAINER_DUMP=""
if [[ "$DUMP_PATH" == ./backup/* || "$DUMP_PATH" == backup/* ]]; then
# 剥离前缀,映射到 /backup
BN="${DUMP_PATH##*/}"
IN_CONTAINER_DUMP="/backup/${BN}"
else
# 复制到容器临时路径
BN="$(basename "$DUMP_PATH")"
echo "Copying dump into container..."
docker compose cp "$DUMP_PATH" "${DB_SVC}:/tmp/${BN}"
IN_CONTAINER_DUMP="/tmp/${BN}"
fi
# 检测 dump 类型(在容器内使用 'file'
FILE_OUT="$(docker compose exec -T ${DB_SVC} sh -lc "file -b ${IN_CONTAINER_DUMP} || true")"
echo "Detected: ${FILE_OUT}"
IS_FC=0
if echo "$FILE_OUT" | grep -qi 'PostgreSQL custom database dump'; then
IS_FC=1
fi
if [[ $INPLACE -eq 1 ]]; then
echo ">>> INPLACE mode: dropping and recreating schema 'public' in ${DB_NAME}"
docker compose exec -T ${DB_SVC} psql -U "${DB_USER}" -d "${DB_NAME}" -v ON_ERROR_STOP=1 <<'SQL'
DO $$
BEGIN
EXECUTE 'DROP SCHEMA IF EXISTS public CASCADE';
EXECUTE 'CREATE SCHEMA public AUTHORIZATION powerdns';
EXECUTE 'GRANT ALL ON SCHEMA public TO powerdns';
END$$;
SQL
if [[ $IS_FC -eq 1 ]]; then
echo ">>> Restoring (-Fc) into ${DB_NAME} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"pg_restore --clean --if-exists --no-owner -U ${DB_USER} -d ${DB_NAME} ${IN_CONTAINER_DUMP}"
else
echo ">>> Restoring (SQL) into ${DB_NAME} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"psql -v ON_ERROR_STOP=1 -U ${DB_USER} -d ${DB_NAME} -f ${IN_CONTAINER_DUMP}"
fi
echo ">>> Done. INPLACE restore finished for DB: ${DB_NAME}"
else
NEWDB="powerdns_restore_$(date +%Y%m%d%H%M%S)"
echo ">>> Creating new database: ${NEWDB}"
docker compose exec -T ${DB_SVC} sh -lc \
"psql -U ${DB_USER} -d postgres -v ON_ERROR_STOP=1 -c 'CREATE DATABASE ${NEWDB} OWNER ${DB_USER};'"
if [[ $IS_FC -eq 1 ]]; then
echo ">>> Restoring (-Fc) into ${NEWDB} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"pg_restore --clean --if-exists --no-owner -U ${DB_USER} -d ${NEWDB} ${IN_CONTAINER_DUMP}"
else
echo ">>> Restoring (SQL) into ${NEWDB} ..."
docker compose exec -T ${DB_SVC} sh -lc \
"psql -v ON_ERROR_STOP=1 -U ${DB_USER} -d ${NEWDB} -f ${IN_CONTAINER_DUMP}"
fi
echo ">>> Done. Now edit ./auth/pdns.conf:"
echo " gpgsql-dbname=${NEWDB}"
echo ">>> Then restart PDNS:"
echo " docker compose restart auth"
fi
```
给执行权限:
```bash
chmod +x scripts/restore-pdns.sh
```
### 使用示例
1. **默认安全模式(恢复到新库,不影响当前运行):**
```bash
./scripts/restore-pdns.sh ./backup/pdns.dump
# 按提示把 auth/pdns.conf 的 gpgsql-dbname 改为脚本输出的新库名
docker compose restart auth
```
2. **原地覆盖(会清空当前库的 public schema):**
```bash
./scripts/restore-pdns.sh --inplace ./backup/pdns.dump
```
---
## 小结 / 注意点
- **官方 schema 自动建表**:通过 `db-init/01-pdns-schema.sql` 在首次初始化时创建所需表结构,之后你随时可以用上面的脚本**手动恢复**。
- **避免冲突**:若你的 dump 是“包含 schema 的完整备份”,默认脚本会恢复到**新库**,避免与已存在的官方 schema 冲突;你也可以用 `--inplace` 覆盖恢复。
- **版本匹配**:尽量让 dump 的来源 PostgreSQL 版本 ≤ 目标版本(例如从 13/14/15 恢复到 16 一般可行);PowerDNS 版本与 schema 文件要匹配同一小版本。
- **安全回滚**:新库恢复方式便于回滚——只需把 `pdns.conf` 切回旧库名并 `restart auth`
需要我把「下载 schema 的命令」按你的目标 `pdns-auth` 版本具体写死(例如 4.9.4)吗?我可以直接给出一条 `curl` 命令放到 README 里。
## 4.9.8:
#### db init
```
curl -L \
https://raw.githubusercontent.com/PowerDNS/pdns/master/modules/gpgsqlbackend/schema.pgsql.sql \
-o db-init/01-pdns-schema.sql
```
```
docker compose exec -T db \
pg_restore --clean --if-exists --no-owner \
-U pdns -d pdns /backup/pdns.dump
```
```
docker compose exec -e PGPASSWORD=windyboy2006 -T db \
pg_restore --jobs=4 --clean --if-exists --no-owner --no-acl \
-U pdns -d pdns /backup/pdns.dump
```
```
docker compose exec -T db psql -U pdns -d pdns -c "SELECT count(*) FROM domains;"
```
db-init/02-pda.sql
```sql
-- 创建 PowerDNS-Admin 的数据库与用户(与 PDNS 库隔离)
CREATE USER pdnsadmin WITH PASSWORD 'windyboy2006';
CREATE DATABASE pdnsadmin OWNER pdnsadmin ENCODING 'UTF8';
GRANT ALL PRIVILEGES ON DATABASE pdnsadmin TO pdnsadmin;
```
```shell
docker compose exec -T db psql -U ${PDNS_DB_USER:-pdns} -d postgres -v ON_ERROR_STOP=1 \
-c "DO \$\$BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='pdnsadmin') THEN CREATE ROLE pdnsadmin LOGIN PASSWORD 'changeme_pdapass'; END IF; END\$\$;"
docker compose exec -T db psql -U ${PDNS_DB_USER:-pdns} -d postgres -v ON_ERROR_STOP=1 \
-c "CREATE DATABASE pdnsadmin OWNER pdnsadmin;" || true
```
```shell
docker compose exec -T db psql \
-U "${PDNS_DB_USER:-powerdns}" -d postgres -v ON_ERROR_STOP=1 \
-c "ALTER ROLE \"${PDNSADMIN_DB_USER:-pdns}\" WITH PASSWORD '${PDNSADMIN_DB_PASSWORD}';"
```
```
pda:
image: powerdnsadmin/pda-legacy:latest
container_name: powerdns-admin
depends_on:
db:
condition: service_healthy
auth:
condition: service_started
ports:
- "${PDA_HTTP_PORT:-9191}:80"
environment:
SECRET_KEY: ${PDA_SECRET_KEY:-changeme_pda_secret}
SQLALCHEMY_DATABASE_URI: >-
postgresql://${PDNSADMIN_DB_USER:-pdnsadmin}:${PDNSADMIN_DB_PASSWORD:-changeme_pdapass}@db:5432/${PDNSADMIN_DB:-pdnsadmin}
restart: unless-stopped
networks: [dnsnet]
```
```
docker compose exec -T db psql -U pdns -d postgres -v ON_ERROR_STOP=1
-c "ALTER ROLE pdnsadmin WITH PASSWORD 'windyboy2006';"
```
```
docker compose exec -e PGPASSWORD="$PDNSADMIN_DB_PASSWORD" -T db \
psql -U "${PDNSADMIN_DB_USER:-pdnsadmin}" -d "${PDNSADMIN_DB:-pdnsadmin}" \
-c "select current_user, current_database();"
```
```shell
docker compose exec auth pdnsutil list-zone chans.xyz
```
```
docker compose exec auth pdnsutil add-record chans.xyz hk2 A 154.36.174.161
```
```
docker compose exec auth pdnsutil delete-rrset example.test www AAAA
```
# PowerDNS Auth 变更 IP — 简版操作手册(按本次实际操作)
> 场景回顾:主节点(Docker**154.36.174.161**)更换/确认公网 IP;二级节点 **us1.wsvc.info** 为二进制安装(非 Docker)。前端用 **Nginx Proxy Manager (NPM)** 代理 **PowerDNS-Admin (PDA)** 与 **PDNS API**。
---
## 1) 现状与目标
- **主节点(Docker**`pdns-auth-49:4.9.8` + `postgres:16` + `powerdnsadmin/pda-legacy`
- **从节点(us1**:二进制 `pdns-server`,数据库 `domains` 使用老式字段 `type/master`
- **NPM 单独 compose**,与 PDNS/PDA 通过 **共享外部网络 `npm_proxy`** 互通。
- 对外 **只开放 53/tcp, 53/udp**PDA 与 PDNS API 仅在容器网络内,由 NPM 反代并做 IP 白名单。
---
## 2) 主节点(Docker)操作
### 2.1 网络与 Compose
```bash
docker network create npm_proxy || true
```
**pdns compose 关键点:**
- `auth`
- `ports`: 只保留 `53:53/udp``53:53/tcp`
- `expose`: `8081`API 仅在容器网络可见)。
- `networks`: 加入 `default` + `npm_proxy`
- **挂载目录**`./auth:/etc/powerdns:ro`(避免 pdns.conf 丢失)。
- 健康检查可用 `curl``curl -fsS -H 'X-API-Key: ${PDNS_API_KEY}' http://127.0.0.1:${PDNS_API_PORT}/api/v1/servers/localhost`
- `pda`
- **移除** `9191:80` 的对外发布;仅 `expose: 80`,加入 `npm_proxy` 网络。
### 2.2 `auth/pdns.conf` 关键参数
```ini
api=yes
api-key=<你的长随机值>
webserver=yes
webserver-address=0.0.0.0
webserver-port=8081
webserver-allow-from=127.0.0.1,::1,172.16.0.0/12
launch=gpgsql
# gpgsql-* 与 Postgres 账户一致
```
> **提示**:若本机 `curl http://localhost:8081` 空回应,多半命中 IPv6 `::1`;用 `curl -4` 或把 `::1` 加入 `webserver-allow-from`。
### 2.3 通过 NPM 反代
- NPM `app` 服务加入 `npm_proxy`;发布 `80/443`
- 新建 Proxy Host
- **PDA** → `pda:80`(绑定域名,Access List 白名单)。
- **PDNS API** → `auth:8081`(同上)。
> 在 NPM 容器内自检:
```bash
docker compose exec app curl -sI http://pda:80/ | head
docker compose exec app curl -sI http://auth:8081/api/v1/servers/localhost | head
```
### 2.4 记录修改(本次实际)
- **ns1.wsvc.info A** 改为 `154.36.174.161`
```bash
curl -s -X PATCH -H "X-API-Key: $PDNS_API_KEY" -H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/wsvc.info. \
-d '{"rrsets":[{"name":"ns1.wsvc.info.","type":"A","changetype":"REPLACE","ttl":86400,"records":[{"content":"154.36.174.161","disabled":false}]}]}'
```
- **hk2.chans.xyz A** 移除 `.101`,保留 `.161`
```bash
curl -s -X PATCH -H "X-API-Key: $PDNS_API_KEY" -H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/chans.xyz. \
-d '{"rrsets":[{"name":"hk2.chans.xyz.","type":"A","changetype":"REPLACE","ttl":3600,"records":[{"content":"154.36.174.161","disabled":false}]}]}'
```
- 验证:`dig @127.0.0.1 ns1.wsvc.info A +short``dig @127.0.0.1 hk2.chans.xyz A +short`
---
## 3) us1(二进制从节点)操作(本次实际)
> us1 使用二进制 `pdns-server`,数据库 `domains` 为旧 schema`type/master`)。本次已验证以下两种方式皆可;**推荐优先使用 pdnsutil 命令**。
### 3.1 推荐:用 `pdnsutil` 指向新主并拉取
确保允许从区:
```
# /etc/powerdns/pdns.conf
secondary=yes # 旧版本为 slave=yes
```
把各区改为 Secondary,并设置新的主(**154.36.174.161**),然后触发 AXFR
```bash
sudo pdnsutil set-kind wsvc.info secondary
sudo pdnsutil set-kind chans.xyz secondary
sudo pdnsutil set-kind windy.me secondary
# 你的 pdnsutil 支持:change-secondary-zone-primary
sudo pdnsutil change-secondary-zone-primary wsvc.info 154.36.174.161
sudo pdnsutil change-secondary-zone-primary chans.xyz 154.36.174.161
sudo pdnsutil change-secondary-zone-primary windy.me 154.36.174.161
# 立即拉取(若无此命令可重启 pdns 替代)
sudo pdnsutil retrieve-secondary wsvc.info
sudo pdnsutil retrieve-secondary chans.xyz
sudo pdnsutil retrieve-secondary windy.me
```
日志与连通性:
```bash
journalctl -u pdns -e | egrep -i 'SOA|AXFR|IXFR|NOTIFY'
dig @154.36.174.161 wsvc.info SOA +tcp +time=2 +tries=1
```
> 失败多为 **TCP/53 未通** 或主节点未监听 TCP/53。
### 3.2 备选 A:没有该子命令时,用“重建从区”
```bash
sudo pdnsutil delete-zone wsvc.info && sudo pdnsutil create-secondary-zone wsvc.info 154.36.174.161
sudo pdnsutil delete-zone chans.xyz && sudo pdnsutil create-secondary-zone chans.xyz 154.36.174.161
sudo pdnsutil delete-zone windy.me && sudo pdnsutil create-secondary-zone windy.me 154.36.174.161
```
### 3.3 备选 B:直接改数据库后补拉取(你本次已用)
```sql
-- 在 us1 上
UPDATE domains SET type='SLAVE', master='154.36.174.161'
WHERE name IN ('wsvc.info','chans.xyz','windy.me');
```
然后:
```bash
sudo pdnsutil retrieve-secondary wsvc.info || sudo systemctl restart pdns
sudo pdnsutil retrieve-secondary chans.xyz || sudo systemctl restart pdns
sudo pdnsutil retrieve-secondary windy.me || sudo systemctl restart pdns
```
### 3.4 主节点授权(在 161 的容器上执行)
```bash
US1_IP=<us1 公网 IP>
docker compose exec auth pdnsutil set-meta wsvc.info ALLOW-AXFR-FROM $US1_IP
docker compose exec auth pdnsutil set-meta chans.xyz ALLOW-AXFR-FROM $US1_IP
docker compose exec auth pdnsutil set-meta windy.me ALLOW-AXFR-FROM $US1_IP
# 可选:
docker compose exec auth pdnsutil set-meta wsvc.info ALSO-NOTIFY $US1_IP
```
---
## 4) 常见问题(按本次排障)
- **PDA 看不到 zone**PDA 不读数据库,需连 PDNS API。PDA 服务器配置:`API URL=http://auth:8081``API Key``pdns.conf` 一致、`Server ID=localhost`。从 `pda` 容器内 `curl http://auth:8081/...` 验证。
- **`Empty reply from server`**`curl` 命中 IPv6 `::1`;改 `curl -4` 或在 `webserver-allow-from``::1`
- **`auth` Unhealthy**:多数是 API Key 不一致或健康检查命令在镜像中不可用;改用 `curl` 并确保 key 一致。
- **`Received NOTIFY ... not a primary (Refused)`**:从节点仍为 `MASTER`;将其改为 `SLAVE` 并正确设置 `master`
---
## 5) 验收清单(简版)
- 主节点:`db` healthy、`auth` API 可返回版本、PDA 通过 NPM 可访问。
- us1`domains``type=SLAVE``master=154.36.174.161``retrieve-secondary` 成功;日志有 AXFR/IXFR 记录。
- 主节点对 `US1_IP` 设置了 `ALLOW-AXFR-FROM`(必要时 `ALSO-NOTIFY`)。
- 关键记录变更已生效(`dig` 结果正确,考虑 TTL 缓存)。
```
export PGHOST=127.0.0.1
export PGPORT=5432
export PGUSER=pdns
export PGPASSWORD='windyboy2006'
export PGDATABASE=pdns
```
```
pg_dump --no-owner --no-acl --format=p --file=~/pdns_backup_$(date +%F).sql \
--single-transaction
```
```
docker compose exec -T db psql -U pdns -c "DROP DATABASE IF EXISTS pdns;"
docker compose exec -T db psql -U pdns -c "CREATE DATABASE pdns OWNER pdns;"
```
```
docker compose exec -T db psql -U pdns -d template1 -c "DROP DATABASE IF EXISTS pdns;"
docker compose exec -T db psql -U pdns -d template1 -c "CREATE DATABASE pdns OWNER pdns;"
```
```
docker compose exec -T db psql -U pdns -d pdns < backup/pdns_backup_2025-11-03.sql
```
```
docker compose exec -T db psql -U pdns -d pdns -c "UPDATE domains SET type='MASTER';"
```
```
docker compose exec -T db psql -U pdns -d pdns -c "DELETE FROM domainmetadata WHERE kind='AXFR-MASTER-TSIG';"
```
```
docker compose exec -T db psql -U pdns -d pdns -c "TRUNCATE TABLE supermasters;"
```
```
docker compose exec auth pdnsutil list-all-zones
docker compose exec auth pdnsutil check-all-zones
docker compose exec auth pdnsutil list-zone windy.me
```
```
docker compose exec -e PGPASSWORD=windyboy2006 backup psql -h db -U pdns -d pdns -c "\l"
```
@@ -0,0 +1,362 @@
# Install
## debian 13
### vps provider debian 10 install
default root:
```
8txlD7djkkKk
```
Good catch 👍 — youre right.
Debian 11 _Bullseye_ moved from the **main mirrors** to the **Debian archive repository** after its LTS phase ended in 2026. That means:
- You **can still upgrade** from Debian 10 → 11 → 12 → 13,
- But for Debian 11 youll need to point your `sources.list` to the **archive.debian.org** service, since the normal `deb.debian.org` no longer carries Bullseye.
---
## 🔧 How to Handle Missing Debian 11 (Bullseye) Repos
When upgrading **Debian 10 → 11**, change your `/etc/apt/sources.list` to use the archive:
```bash
sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list
sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list
```
Then explicitly set it to Bullseye:
```
deb http://archive.debian.org/debian bullseye main contrib non-free
deb http://archive.debian.org/debian-security bullseye-security main contrib non-free
```
⚠️ Important: you also need to **disable `Valid-Until` checks** (since archive metadata is old). Add this to `/etc/apt/apt.conf.d/99no-check-valid-until`:
```
Acquire::Check-Valid-Until "false";
```
After that, `apt update` will work again for Debian 11 packages.
---
## 🔄 Recommended Path
- **Debian 10 → Debian 11 (Bullseye)**
using `archive.debian.org`
- **Debian 11 → Debian 12 (Bookworm)**
switch back to `deb.debian.org`
- **Debian 12 → Debian 13 (Trixie)**
upgrade normally from main repos
---
## ⚠️ Alternative Approach (Skip Hop?)
Some admins **reinstall instead of multi-hop upgrades** when theyre more than 2 releases behind.
Its often faster and cleaner to install Debian 13 fresh and migrate configs/data.
---
👉 Do you want me to prepare the **exact `sources.list` entries** for each step (10 → 11, 11 → 12, 12 → 13), so you can just copy-paste them before each upgrade?
## traefik
```compose.yml
services: 10:47:35 [24/1240]
traefik:
image: traefik:v3.4
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks: [traefik]
ports:
- "80:80"
- "443:443"
- "8080:8080" # dashboard
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./dynamic:/dynamic:ro
command:
# Entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
# Providers
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=traefik"
- "--providers.file.directory=/dynamic"
- "--providers.file.watch=true"
# Let's Encrypt (ACME)
- "--certificatesresolvers.letsencrypt.acme.email=admin@windy.me"
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
# Dashboard
- "--api.dashboard=true"
- "--api.insecure=false"
# Logging
- "--log.level=INFO"
- "--accesslog=true"
# Metrics (optional)
- "--metrics.prometheus=true"
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`npm.chans.xyz`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth@docker"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$wrhTVUaG$$tcchNFj..."
networks:
traefik:
external: true
```
dashboard user and pass
```
Ahku+eRei_chu3ah
```
```
htpasswd -nb windy "Ahku+eRei_chu3ah"
```
```
windy:$apr1$wrhTVUaG$tcchNFj.yyA3OpK8f9XnA.
```
## 一步改成 MASTER
执行以下命令即可统一切换类型:
```
docker compose exec -T db psql -U pdns -d pdns -c "UPDATE domains SET type='MASTER';"
```
执行完,再确认:
```
docker compose exec -T db psql -U pdns -d pdns -c "SELECT id, name, type FROM domains ORDER BY name;"
```
应输出:
```
id | name | type ----+-----------+--------- 7 | chans.xyz | MASTER 9 | windy.me | MASTER 8 | wsvc.info | MASTER (3 rows)
```
---
## ✅ 一、明确两种元数据的作用
|kind|作用|主节点是否需要|
|---|---|---|
|`PRESIGNED`|表示该 zone 的 DNSSEC 已经签好,不需要 PowerDNS 重新签名|✅ 需要保留|
|`AXFR-MASTER-TSIG`|从节点用来验证上游 master(旧主)的 TSIG 密钥|❌ 主节点不需要|
---
## 🧹 二、删除无用的 `AXFR-MASTER-TSIG` 记录
执行:
```bash
docker compose exec -T db psql -U pdns -d pdns -c "DELETE FROM domainmetadata WHERE kind='AXFR-MASTER-TSIG';"
```
验证删除结果:
```bash
docker compose exec -T db psql -U pdns -d pdns -c "SELECT domain_id, kind, content FROM domainmetadata;"
```
应该只剩:
```
domain_id | kind | content
------------+------------+---------
7 | PRESIGNED | 1
8 | PRESIGNED | 1
9 | PRESIGNED | 1
(3 rows)
```
---
## 🧩 三、保留 `PRESIGNED`(不要删)
这三条 `PRESIGNED` 的存在说明你的 zone 已经是“预签名区”,
也就是在数据库里已经有 DNSKEY、RRSIG、NSEC/NSEC3 等签名记录,
PowerDNS 不会去重新签名,非常正确 ✅
---
## 🧰 四、确保 `supermasters` 已清空(如果还没执行)
```bash
docker compose exec -T db psql -U pdns -d pdns -c "TRUNCATE TABLE supermasters;"
```
---
## 🚀 五、重启 PDNS 并验证主节点状态
```bash
docker compose exec auth pdnsutil list-all-zones
docker compose exec auth pdnsutil check-all-zones
```
预期输出:
```
Zone "chans.xyz." (MASTER)
Zone "windy.me." (MASTER)
Zone "wsvc.info." (MASTER)
All zones OK
```
---
## ✅ 六、总结(当前应保留状态)
|表|应保留内容|已处理状态|
|---|---|---|
|domains|type=MASTER|✅|
|domainmetadata|仅 `PRESIGNED`|🧹 已清理|
|supermasters|空表|✅|
|records / cryptokeys / tsigkeys|原样保留|✅|
---
如果你愿意,我可以帮你生成一个命令脚本,一次性完成:
- 把所有域设为 MASTER
- 删除 AXFR/MASTER 相关 metadata
- 清空 supermasters
- 验证 zone 状态
你只需执行一条命令即可让任何副节点快照变主节点。是否要我生成?
```
docker compose exec auth pdnsutil metadata set wsvc.info ALLOW-AXFR-FROM "202.91.35.141"
docker compose exec auth pdnsutil metadata set windy.me ALLOW-AXFR-FROM "202.91.35.141"
docker compose exec auth pdnsutil metadata set chans.xyz ALLOW-AXFR-FROM "202.91.35.141"
docker compose exec auth pdnsutil metadata set wsvc.info TSIG-ALLOW-AXFR "mykey."
Set 'wsvc.info' meta TSIG-ALLOW-AXFR = mykey.
docker compose exec auth pdnsutil metadata set windy.me TSIG-ALLOW-AXFR "mykey."
Set 'windy.me' meta TSIG-ALLOW-AXFR = mykey.
docker compose exec auth pdnsutil metadata set chans.xyz TSIG-ALLOW-AXFR "mykey."
Set 'chans.xyz' meta TSIG-ALLOW-AXFR = mykey.
docker compose exec auth pdns_control notify windy.me
docker compose exec auth pdns_control notify chans.xyz
docker compose exec auth pdns_control notify wsvc.info
```
db-init/01-init.sh
```bash
#!/bin/bash
set -e
echo "🔧 Creating PowerDNS role and databases..."
psql -v ON_ERROR_STOP=1 --username "$PGUSER" <<-'EOSQL'
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'pdns') THEN
CREATE USER pdns WITH PASSWORD 'windyboy';
END IF;
END
$$;
EOSQL
for dbname in pdns pdnsadmin; do
if ! psql -tAc "SELECT 1 FROM pg_database WHERE datname='${dbname}'" | grep -q 1; then
echo "🆕 Creating database ${dbname} owned by pdns"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -c "CREATE DATABASE ${dbname} OWNER pdns;"
else
echo "✅ Database ${dbname} already exists"
fi
done
echo "✅ Initialization finished."
```
```
for z in windy.me chans.xyz wsvc.info; do
docker compose exec auth pdnsutil zone unset-presigned $z
docker compose exec auth pdnsutil zone secure $z
docker compose exec auth pdnsutil zone rectify $z
done
```
```
docker compose exec auth pdnsutil zone list-all | while read z; do
docker compose exec auth pdnsutil zone list "$z" > "auth/export/$z.zone"
done
```
```
labels:
- "traefik.enable=true"
- "traefik.http.routers.pgweb.rule=Host(`pgweb.wsvc.info`)"
- "traefik.http.routers.pgweb.entrypoints=websecure"
- "traefik.http.routers.pgweb.tls.certresolver=letsencrypt"
- "traefik.http.services.pgweb.loadbalancer.server.port=8081"
```
@@ -0,0 +1,94 @@
```
version: "3.8"
services:
squid:
image: ubuntu/squid:latest
container_name: squid-proxy
restart: unless-stopped
volumes:
- ./config/squid.conf:/etc/squid/squid.conf:ro
- squid_cache:/var/spool/squid
- squid_logs:/var/log/squid
networks:
- proxy-net
- traefik
# 只在本地暴露端口(可选,用于调试)
ports:
# - "127.0.0.1:3128:3128"
healthcheck:
test: ["CMD", "squidclient", "-h", "localhost", "mgr:info", "||", "exit", "1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
# Squid 管理界面路由
#- "traefik.http.routers.squid-mgr.rule=Host(`squid.yourdomain.com`) && PathPrefix(`/squid-internal-mgr`)"
#- "traefik.http.routers.squid-mgr.entrypoints=websecure"
#- "traefik.http.routers.squid-mgr.tls.certresolver=letsencrypt"
#- "traefik.http.routers.squid-mgr.middlewares=squid-auth"
#- "traefik.http.services.squid-mgr.loadbalancer.server.port=3128"
# Basic Auth 中间件
#- "traefik.http.middlewares.squid-auth.basicauth.users=admin:$$apr1$$8EVjn/nj$$GiLUZqcbueTFeD23SuB6x0"
nghttpx:
image: jehrhart/nghttp2docker
container_name: nghttpx-proxy
restart: unless-stopped
volumes:
- ./config/nghttpx.conf:/nghttpx/nghttpx.conf:ro
command: nghttpx --conf /nghttpx/nghttpx.conf
depends_on:
squid:
condition: service_healthy
networks:
- proxy-net
- traefik
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/", "||", "exit", "1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
# HTTP/2 代理主路由
- "traefik.http.routers.nghttpx.rule=Host(`proxy.yourdomain.com`)"
- "traefik.http.routers.nghttpx.entrypoints=websecure"
- "traefik.http.routers.nghttpx.tls.certresolver=letsencrypt"
- "traefik.http.routers.nghttpx.tls.options=modern@file"
- "traefik.http.services.nghttpx.loadbalancer.server.port=8080"
# HTTP 到 HTTPS 重定向
- "traefik.http.routers.nghttpx-http.rule=Host(`proxy.yourdomain.com`)"
- "traefik.http.routers.nghttpx-http.entrypoints=web"
- "traefik.http.routers.nghttpx-http.middlewares=redirect-to-https@docker"
# 中间件
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true"
# 可选:添加速率限制
- "traefik.http.routers.nghttpx.middlewares=rate-limit@docker"
- "traefik.http.middlewares.rate-limit.ratelimit.average=100"
- "traefik.http.middlewares.rate-limit.ratelimit.burst=50"
networks:
traefik:
external: true
volumes:
squid_cache:
driver: local
squid_logs:
driver: local
```
@@ -0,0 +1,67 @@
```
services:
traefik:
image: traefik:v3.4
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs:/certs
- ./dynamic:/dynamic
command:
# Entrypoints
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http.tls=true"
# Providers
- "--providers.file.filename=/dynamic/tls.yaml"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
# API & Dashboard
- "--api.dashboard=true"
- "--api.insecure=false"
# Logs
- "--log.level=INFO"
- "--accesslog=true"
- "--metrics.prometheus=true"
# Let's Encrypt (ACME)
- "--certificatesresolvers.letsencrypt.acme.email=zhiqiang@windy.me"
- "--certificatesresolvers.letsencrypt.acme.storage=/certs/acme.json"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`us4.wsvc.info`)
- traefik.http.routers.dashboard.entrypoints=websecure,web
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.tls.certresolver=letsencrypt
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$aBNLDToX$$nIKWMN41tGMBhtdSaA/Ih/"
- traefik.http.routers.dashboard.middlewares=dashboard-auth@docker,redirect-to-https@file
networks:
proxy:
name: proxy
external: true
```
```
S3cureP@ssw0rd!
```