362 lines
9.0 KiB
Markdown
362 lines
9.0 KiB
Markdown
|
||||
|
|
|
|||
|
|
# Install
|
|||
|
|
|
|||
|
|
|
|||
|
|
## debian 13
|
|||
|
|
### vps provider debian 10 install
|
|||
|
|
|
|||
|
|
default root:
|
|||
|
|
```
|
|||
|
|
8txlD7djkkKk
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Good catch 👍 — you’re 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 you’ll 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 they’re more than 2 releases behind.
|
|||
|
|
It’s 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"
|
|||
|
|
|
|||
|
|
```
|