---
url: /article/etzl6hp9/index.md
---
## 前言

最近在海鲜市场淘了个矿渣，配置和性能都比我那软路由高多了，价钱却便宜了很多，虽然也还是 arm 设备，但是硬件可以支持做更多事，于是将 r2s 上的一些服务尽可能转移过来，用 docker compose 部署。

## 1. 规划分类

**计划分 3 个 compose.yaml 文件**：

1. hodev: home➕develop 的缩写，内置开发相关服务。
2. homeia: home➕media 的缩写，内置媒体相关服务。
3. honet: home➕network 的缩写，内置网络相关的服务。

**部署原则如下**：

1. 对于提供支撑类型的服务，稳定性高于新功能，因此指定版本号。
2. 对于提供消费类型的服务，新功能优先，因此使用`latest`版本号，并且使用 watchtower 监听自动更新。
3. 目录挂载可以使用`数据卷`或`目录绑定`，但数据卷的方式只能映射目录，目录绑定的方式还可以绑定文件，这里个人偏好是目录绑定，方便管理。但要注意，目录绑定时宿主机上的目录会自动创建，但文件绑定时必须先创建好文件，否则会被当成目录名处理。当服务启动时，数据卷和绑定的目录中就会自动填入内容。
4. 手动创建网络或数据卷。
5. 个人暂时用不到的服务用了`ignore`忽略。
6. 公共的环境变量放在`.env`文件中，服务个性化变量放在各服务命名的`*.env`文件中。

```bash title="创建网络" :no-line-numbers
docker network create --driver bridge home
```

## 2. 部署前置条件

准备一个非域名，因为有些服务必须要 https 域名访问。\
可以是仅内网域名(如: dev.lan)，也可上[DigitalPlat](https://domain.digitalplat.org)注册(2个免费域名且可托管至 Cloudflare)，或者上[Duck DNS](https://www.duckdns.org/)免费注册至多5个子域名。

域名区别：

1. 内网域名：优点是随意命名，缺点是客户端设备需要要导入根证书并信任。
2. 公网域名：优点是不需要设备导入证书，缺点是不一定能注册到心意的域名。

**个人推荐**：==公网非子域名==。

## 3. hodev

::: code-tree title="文件结构和内容" height="600px" entry="hodev/compose.yaml"

```yaml title="hodev/compose.yaml"
services:
  mysql:
    image: mysql:8.0
    container_name: mysql
    ports: [3306:3306]
    networks: [home]
    env_file: [mysql.env]
    restart: unless-stopped
    volumes:
      - ./mysql/data:/var/lib/mysql # 数据目录
      - ./mysql/my.cnf:/etc/my.cnf # 配置文件

  redis:
    image: redis:8.0.3
    container_name: redis
    ports: [6379:6379]
    networks: [home]
    restart: unless-stopped
    volumes:
      # 数据目录
      - ./redis/data:/data
      - ./redis/conf:/usr/local/etc/redis
    command: redis-server /usr/local/etc/redis/redis.conf

  gitea:
    image: gitea/gitea:1.24.3
    container_name: gitea
    ports: [9300:3000, 22:22]
    networks: [home]
    env_file: [gitea.env]
    restart: unless-stopped
    user: root
    volumes:
      - ./gitea/data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

  dbgate:
    image: dbgate/dbgate:6.5.6
    container_name: dbgate
    ports: [3300:3000]
    networks: [home]
    restart: unless-stopped
    volumes:
      - ./dbgate/data:/root/.dbgate


networks:
  home:
    external: true
```

```env title="hodev/gitea.env"
# echo $(id -u)
USER_UID=1000
# echo $(id -g)
USER_GID=1000
```

```env title="hodev/mysql.env"
# 字符集
MYSQL_CHARACTER_SET_SERVER=utf8mb4
# 排序规则
MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci
# root 账户密码
MYSQL_ROOT_PASSWORD=root
# 自动创建的数据库
MYSQL_DATABASE=dev
# 自动创建的非root用户
MYSQL_USER=mayee
# 自动创建的用户密码
MYSQL_PASSWORD=123456
```

```txt title="hodev/gitea/data/..."
```

```txt title="hodev/dbgate/data/..."
```

```txt title="hodev/mysql/data/..."
```

```cnf title="hodev/mysql/my.cnf"
# 从容器中拷贝出来
docker cp mysql:/etc/my.cnf .
```

```txt title="hodev/redis/data/..."
```

```cnf title="hodev/redis/conf/redis.conf"
# 监听地址
bind 0.0.0.0
# 关闭保护(已经有密码了)
protected-mode no
requirepass 123456
# 开启 AOF 持久化
appendonly yes
appendfsync everysec
# 内存管理
maxmemory 256mb
maxmemory-policy allkeys-lru
```

```env title="hodev/.env"
TZ=Asia/Shanghai
```

:::

### 3.1 mysql

直接部署，创建的用户开启[远程登录](/notes/qr0e6712/#mysql)。

### 3.2 redis

直接部署即可。

### 3.3 gitea

直接部署即可，配置参考官方[文档](https://docs.gitea.com/zh-cn/installation/install-with-docker)。对于 gitea 的 SSH 端口，我没有做麻烦的直通，直接修改了宿主机的 SSH 端口。

::: warning
更多设置在`/data/gitea/conf/app.ini`
:::

### 3.4 dbgate

网页端的数据库客户端，直接部署即可。

## 4. homedia

::: code-tree title="文件结构和内容" height="600px" entry="homedia/compose.yaml"

```yaml title="homedia/compose.yaml"
services:
  allinone:
    image: youshandefeiyang/allinone:latest
    container_name: allinone
    ports: [35455:35455]
    networks: [home]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    command: >
      -tv=true
      -aesKey=xxxxxxx
      -userid=789123
      -token=123456

  navidrome:
    image: deluan/navidrome:0.57.0
    container_name: navidrome
    ports: [4533:4533]
    networks: [home]
    env_file: [navidrome.env]
    restart: unless-stopped
    volumes:
      - ./navidrome/data:/data
      - /mnt/app/media/music:/music:ro

  openlist:
    image: openlistteam/openlist:v4.0.8
    container_name: openlist
    ports: [5244:5244]
    networks: [home]
    env_file: [openlist.env]
    restart: unless-stopped
    volumes:
      - ./openlist/data:/opt/openlist/data
      - /mnt/app/media:/mnt/media

  libretv:
    image: bestzwei/libretv:latest
    container_name: libretv
    ports: [8899:8080]
    networks: [home]
    env_file: [libretv.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true

  moontv:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    ports: [8300:3000]
    networks: [home]
    env_file: [moontv.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    volumes:
      - ./moontv/config.json:/app/config.json:ro

  iptvnator:
    profiles: ['ignore']
    image: 4gray/iptvnator:latest
    container_name: iptvnator
    ports: [8480:80]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true

  # 5700
  qinglong:
    profiles: ['ignore']
    image: whyour/qinglong:2.19.2
    container_name: qinglong
    ports: [8300:3000]
    networks: [home]
    env_file: [qinglong.env]
    restart: unless-stopped
    volumes:
      - ./qinglong/data:/ql/data

  vaultwarden:
    image: vaultwarden/server:1.34.1
    container_name: vaultwarden
    ports: [8137:8137]
    networks: [home]
    env_file: [vaultwarden.env]
    restart: unless-stopped
    volumes:
      - ./vaultwarden/data:/data

  jellyfin:
    profiles: ['ignore']
    image: jellyfin/jellyfin:10.10.7
    container_name: jellyfin
    ports: [8096:8096]
    networks: [home]
    env_file: [jellyfin.env]
    user: 1000:1000 # 替换为实际的 UID:GID。也可删掉这行，将使用 root 用户
    restart: unless-stopped
    volumes:
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /usr/share/fonts:/usr/local/share/fonts/custom:ro # 系统字体库
      - /mnt/app/media:/media:ro # 挂载媒体目录
    extra_hosts:
      - host.docker.internal:host-gateway # 相当于在容器内的 hosts 中加这些行。在 host 模式下 docker 健康检查可能会用的

  watchtower:
    image: containrrr/watchtower:1.7.1
    container_name: watchtower
    networks: [home]
    env_file: [watchtower.env]
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      
  portainer:
    image: portainer/portainer-ce:2.31.3
    container_name: portainer
    ports: [9000:9000]
    networks: [home]
    restart: unless-stopped
    volumes:
      - ./portainer/data:/data
      - /var/run/docker.sock:/var/run/docker.sock


networks:
  home:
    external: true
```

```env title="homedia/jellyfin.env"
JELLYFIN_PublishedServerUrl=https://meida.mayee.dpdns.org
```

```env title="homedia/libretv.env"
# 公开部署设置密码，避免可能法律风险
PASSWORD=''
ADMINPASSWORD=''
```

```env title="homedia/moontv.env"
# 管理员账号(后台访问: /admin)
USERNAME: admin
# 管理员密码
PASSWORD: admin
# 应用名称
SITE_NAME: Homovie
# 公告
ANNOUNCEMENT: 家庭影视
NEXT_PUBLIC_STORAGE_TYPE: redis
REDIS_URL: redis://:123456@redis:6379
# 禁止注册，但可以通过管理面版添加用户
NEXT_PUBLIC_ENABLE_REGISTER: false
# 每页条数
NEXT_PUBLIC_SEARCH_MAX_PAGE: 15
```

```env title="homedia/navidrome.env"
# https://www.navidrome.org/docs/usage/configuration-options/#available-options
ND_DEFAULTLANGUAGE=zh-Hans
ND_DEFAULTTHEME=Spotify-ish
ND_SESSIONTIMEOUT=12h
ND_ENABLETRANSCODINGCONFIG=true
ND_ENABLEGRAVATAR=true
ND_COVERARTPRIORITY=embedded
ND_LASTFM_LANGUAGE=zh
ND_SEARCHFULLSTRING=true
ND_LASTFM_APIKEY=xxx
ND_LASTFM_SECRET=xxx
ND_SPOTIFY_ID=xxx
ND_SPOTIFY_SECRET=xxx
ND_SCANNER_SCHEDULE=0
ND_IMAGECACHESIZE=100MB
ND_TRANSCODINGCACHESIZE=100MB
ND_LOGLEVEL=warn
```

```env title="homedia/openlist.env"
# echo $(id -u)
PUID=1000
# echo $(id -g)
PGID=1000
UMASK=022
```

```env title="homedia/qinglong.env"
QlBaseUrl=/
```

```env title="homedia/vaultwarden.env"
# 绑定域名
DOMAIN=https://vw.mayee.dpdns.org
# 内部启动端口
ROCKET_PORT=8137
# 禁止注册
SIGNUPS_ALLOWED=false
# 图片服务器地址
ICON_SERVICE=https://icons.bitwarden.net/{}/icon.png
```

```env title="homedia/watchtower.env"
WATCHTOWER_LABEL_ENABLE=true
WATCHTOWER_ROLLING_RESTART=true
WATCHTOWER_CLEANUP=true
WATCHTOWER_POLL_INTERVAL=7200
```

```txt title="homedia/jellyfin/config/..."
```

```txt title="homedia/jellyfin/cache/..."
```

```json title="homedia/moontv/config.json"
{
  "cache_time": 7200,
  "api_site": {
    "dyttzy": {
      "api": "http://caiji.dyttzyapi.com/api.php/provide/vod",
      "name": "电影天堂资源",
      "detail": "http://caiji.dyttzyapi.com"
    },
    "heimuer": {
      "api": "https://json.heimuer.xyz/api.php/provide/vod",
      "name": "黑木耳",
      "detail": "https://heimuer.tv"
    },
    "ruyi": {
      "api": "https://cj.rycjapi.com/api.php/provide/vod",
      "name": "如意资源"
    },
    "bfzy": {
      "api": "https://bfzyapi.com/api.php/provide/vod",
      "name": "暴风资源"
    },
    "tyyszy": {
      "api": "https://tyyszy.com/api.php/provide/vod",
      "name": "天涯资源"
    },
    "ffzy": {
      "api": "http://ffzy5.tv/api.php/provide/vod",
      "name": "非凡影视",
      "detail": "http://ffzy5.tv"
    },
    "zy360": {
      "api": "https://360zy.com/api.php/provide/vod",
      "name": "360资源"
    },
    "iqiyi": {
      "api": "https://www.iqiyizyapi.com/api.php/provide/vod",
      "name": "iqiyi资源"
    },
    "wolong": {
      "api": "https://wolongzyw.com/api.php/provide/vod",
      "name": "卧龙资源"
    },
    "jisu": {
      "api": "https://jszyapi.com/api.php/provide/vod",
      "name": "极速资源",
      "detail": "https://jszyapi.com"
    },
    "dbzy": {
      "api": "https://dbzy.tv/api.php/provide/vod",
      "name": "豆瓣资源"
    },
    "mozhua": {
      "api": "https://mozhuazy.com/api.php/provide/vod",
      "name": "魔爪资源"
    },
    "mdzy": {
      "api": "https://www.mdzyapi.com/api.php/provide/vod",
      "name": "魔都资源"
    },
    "zuid": {
      "api": "https://api.zuidapi.com/api.php/provide/vod",
      "name": "最大资源"
    },
    "yinghua": {
      "api": "https://m3u8.apiyhzy.com/api.php/provide/vod",
      "name": "樱花资源"
    },
    "wujin": {
      "api": "https://api.wujinapi.me/api.php/provide/vod",
      "name": "无尽资源"
    },
    "wwzy": {
      "api": "https://wwzy.tv/api.php/provide/vod",
      "name": "旺旺短剧"
    },
    "ikun": {
      "api": "https://ikunzyapi.com/api.php/provide/vod",
      "name": "iKun资源"
    }
  }
}
```

```txt title="homedia/navidrome/data/..."
```

```txt title="homedia/openlist/data/..."
```

```txt title="homedia/portainer/data/..."
```

```txt title="homedia/qinglong/data/..."
```

```txt title="homedia/vaultwarden/data/..."
```

```env title="homedia/.env"
TZ=Asia/Shanghai
```

:::

### 4.1 allinone

肥羊的 IPTV 直播源，需要有 token 才能成功部署，获取方式不方便公开讲。但注意，该源纯免费，强烈谴责收费的二道贩子。

### 4.2 navidrome

音乐私服，直接部署即可，其中一些需要 token 的地方自行获取，但没有亦可部署使用。搭配`音流`客户端体验更佳。

::: warning

```bash
# 挂载硬盘
mount /dev/sda2 /srv/share
systemctl daemon-reload
# 拷贝文件(带进度显示)
rsync -r --info=progress2 /srv/share/music /mnt/app/
# 查看磁盘占用
du -sh /mnt/app/share/music
# 查看内存占用
free -h
```

:::

### 4.3 openlist

作为 AList 的替代品，提供网盘整合服务，也可以挂载本地硬盘，提供多种协议访问，很适合做文件仓库。直接部署即可。

### 4.4 libretv

提供 VOD 资源，可以看电影、电视剧、综艺等。直接部署即可。

### 4.5 moontv

同 libretv，只不过这个多了账号体系，但不太成熟，也具有特色。直接部署即可。

### 4.6 iptvnator

IPTV 网页端播放器，但实测无法播放肥羊源，内置播放器不支持格式，等以后可能更新支持更多播放器。直接部署即可。

### 4.7 qinglong

支持定义脚本任务定时执行，可以做一些周期性的任务，比如：签到。

### 4.8 vaultwarden

Bitwarden 的自托管版本，很好用的密码服务器，提供全平台客户端，免费使用。

图标服务器可选：[duckduckgo](https://icons.duckduckgo.com/ip3/{}.ico)、[bitwarden](https://icons.bitwarden.net/{}/icon.png)、[google](https://www.google.com/s2/favicons?domain={}\&sz=64)。

::: warning
vaultwarden 部署成功后，可以方便的将其他平台，如：lastpass、1password、Keepass 等导入进来。但对于 \*.kdbx 文件则无法用内置功能迁移，需要借助[kp2bw](https://github.com/jampe/kp2bw)工具。

```bash
# windows 下请用 powershell 执行
# 注意启动 kp2bw 后根据提示登录自建的 vaultwarden，但由于一开始我用的自签证书， node 服务器找不到根证书。如果用的可信机构证书应该不会有这问题
# 所以将根证书放进来，设置环境变量再执行登录即可(仅当前会话有效)
$env:NODE_EXTRA_CA_CERTS="./root.crt"
# 登录完成后根据说明将 session 设置为环境变量，例如：
$env:BW_SESSION="9TwD+D2hueP56eC1GI1Z6bl5TisRYBH72Z7Hbs8Nk76uTsPTvoO8ePLIPXNERIAfjZwmv9mUCJPDhcGTs09ThA=="
# 之后继续根据说明操作即可
```

部署参考：[带有 DNS 挑战的 Caddy](https://rs.ppgg.in/container-image-usage/using-docker-compose#caddy-with-dns-challenge)、[使用 Let's Encrypt 证书运行私有 Vaultwarden 实例](https://rs.ppgg.in/reverse-proxy/https/running-a-private-vaultwarden-instance-with-lets-encrypt-certs)
:::

### 4.9 jellyfin

一般是 NAS 必备的媒体服务器，可以刮削媒体，比如电影、电视剧、动漫、音乐等，有插件系统提供更多支持。直接部署即可。

### 4.10 watchtower

监控容器，自动更新容器。直接部署即可。

### 4.11 portainer

Docker 的管理面板，可以管理容器、镜像、网络、卷、配置、任务、服务、系统信息等等。直接部署即可。

## 5. honet

::: code-tree title="文件结构和内容" height="600px" entry="honet/compose.yaml"

```yaml title="honet/compose.yaml"
services:
  # 8000  UDP 广播不会穿透 Docker NAT 网络，即使用端口映射， 那也只影响接收，对容器发出的广播无作用。简单点这里还是用 host 模式
  homelan:
    image: mayeee/homelan:latest
    container_name: homelan
    network_mode: host
    env_file: [homelan.env]
    restart: unless-stopped
    volumes:
      - ./sing-box/config.json:/app/ext/sing-box/config.json:ro
      - /root/.ssh/id_ed25519:/app/ext/ssh/id_ed25519:ro
      - ./homelan:/app/ext
      
  # 55305    
  haproxy:
    image: haproxy:3.2.3
    container_name: haproxy
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./haproxy:/usr/local/etc/haproxy/haproxy.cfg:ro

  sing-box:
    image: ghcr.io/sagernet/sing-box:v1.11.15
    container_name: sing-box
    network_mode: host
    restart: unless-stopped
    volumes:
      - ./sing-box:/etc/sing-box:ro
    command: -D /var/lib/sing-box -C /etc/sing-box/ run

  sub-store:
    image: xream/sub-store:latest
    container_name: sub-store
    ports: [3000:3000, 3001:3001]
    networks: [home]
    env_file: [sub-store.env]
    restart: unless-stopped
    labels:
      com.centurylinklabs.watchtower.enable: true
    volumes:
      - ./sub-store/data:/opt/app/data

  caddy:
    image: mayeee/caddy:2.10.0
    container_name: caddy
    env_file: [caddy.env]
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./caddy/conf:/etc/caddy
      - ./caddy/site:/srv
      - ./caddy/data:/data
      - ./caddy/config:/config/caddy

  lucky:
    profiles: ['ignore']
    image: gdy666/lucky:2.18.1
    container_name: lucky
    ports: [16601:16601]
    restart: always
    networks: [home]
    volumes:
      - ./lucky/data:/goodluck

networks:
  home:
    external: true
```

```env title="honet/homelan.env"
# 需要唤醒的主机 MAC
MAC="xx:xx:xx:xx:xx:xx"
# 需要唤醒的主机 IP
IP="192.168.0.1"
# 唤醒主机的用户
USER="mayee"
# bark 通知，不配置则不通知
DEVICE_KEY="xxxxx"
# aes 密钥, 必须 16 位
AES_KEY="xxxxx"
# aes 向量
AES_IV="xxx"
```

```env title="honet/caddy.env"
CLOUDFLARE_API_TOKEN=xxxxxxx
DOMAIN=mayee.dpdns.org
```

```env title="honet/sub-store.env"
SUB_STORE_FRONTEND_BACKEND_PATH=/xxxxxxxxxx
```

```text title="honet/caddy/conf/Caddyfile"
{
        auto_https disable_redirects
        email maye_e@qq.com
        ocsp_stapling off
}
(cloudflare_tls) {
        tls {
                dns cloudflare {$CLOUDFLARE_API_TOKEN}
                propagation_timeout 300s
        }
}
{$DOMAIN}, *.{$DOMAIN} {
    import cloudflare_tls

    @root host {$DOMAIN}
    reverse_proxy @root http://127.0.0.1:2151

    @code host code.{$DOMAIN}
    reverse_proxy @code http://192.168.0.75:2333

    @git host git.{$DOMAIN}
    reverse_proxy @git http://127.0.0.1:9300

    @db host db.{$DOMAIN}
    reverse_proxy @db http://127.0.0.1:3300

    @vw host vw.{$DOMAIN}
    reverse_proxy @vw http://127.0.0.1:8137
}
```

```text title="honet/caddy/config/..."
```

```text title="honet/caddy/data/..."
```

```text title="honet/caddy/site/..."
```

```text title="honet/haproxy/haproxy.cfg"
# Example configuration file for HAProxy 2.0, refer to the url below for
# a full documentation and examples for configuration:
# https://cbonte.github.io/haproxy-dconv/2.0/configuration.html


# Global parameters
global

	# Log events to a remote syslog server at given address using the
	# specified facility and verbosity level. Multiple log options 
	# are allowed.
	#log 10.0.0.1 daemon info

	# Specifiy the maximum number of allowed connections.
	maxconn 32000

	# Raise the ulimit for the maximum allowed number of open socket
	# descriptors per process. This is usually at least twice the
	# number of allowed connections (maxconn * 2 + nb_servers + 1) .
	ulimit-n 65535

	# Drop privileges (setuid, setgid), default is "root" on OpenWrt.
	uid 0
	gid 0

	# Perform chroot into the specified directory.
	#chroot /var/run/haproxy/

	# Daemonize on startup
	daemon

	nosplice
	# Enable debugging
	#debug

	# Spawn given number of processes and distribute load among them,
	# used for multi-core environments or to circumvent per-process
	# limits like number of open file descriptors. Default is 1.
	#nbproc 2

# Default parameters
defaults
	# Default timeouts
	timeout connect 5000ms
	timeout client 50000ms
	timeout server 50000ms

# 自定义 sni 分流
frontend shadowsocks_sni_in
    bind 0.0.0.0:55305
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req.ssl_hello_type 1 }

    # shadow-tls
    acl sni_is_st_1 req.ssl_sni -i toutiao.com
    acl sni_is_st_2 req.ssl_sni -i coding.net
	  acl sni_is_st_3 req.ssl_sni -i mp.weixin.qq.com
	  acl sni_is_st_4 req.ssl_sni -i cdn.bootcdn.net
	  acl sni_is_st_5 req.ssl_sni -i feishu.cn

    # shadow-tls
    use_backend st_ss_1 if sni_is_st_1
    use_backend st_ss_2 if sni_is_st_2
	  use_backend st_ss_3 if sni_is_st_3
	  use_backend st_ss_4 if sni_is_st_4
	  use_backend st_ss_5 if sni_is_st_5

# shadow-tls
backend st_ss_1
    mode tcp
    server shadow_tls_1 127.0.0.1:55315

backend st_ss_2
    mode tcp
    server shadow_tls_2 127.0.0.1:55325

backend st_ss_3
    mode tcp
    server shadow_tls_3 127.0.0.1:55335

backend st_ss_4
    mode tcp
    server shadow_tls_4 127.0.0.1:55345

backend st_ss_5
    mode tcp
    server shadow_tls_5 127.0.0.1:55355

# Special health check listener for integration with external load
# balancers.
listen local_health_check

	# Listen on port 60000
	bind :60000

	# This is a health check
	mode http

	http-request return status 200 content-type "text/plain" string "OK"
	# Enable HTTP-style responses: "HTTP/1.0 200 OK"
	# else just print "OK".
	#option httpchk
```

```text title="honet/homelan/..."
```

```text title="honet/lucky/data/..."
```

```json title="honet/sing-box/cofnig.json"
{
  "dns": {
    "servers": [
      {
        "tag": "tencent",
        "address": "https://doh.pub/dns-query",
        "address_resolver": "dns_resolver",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      },
      {
        "tag": "alibaba",
        "address": "https://dns.alidns.com/dns-query",
        "address_resolver": "dns_resolver",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      },
      {
        "tag": "dns_resolver",
        "address": "223.5.5.5",
        "strategy": "prefer_ipv4",
        "detour": "direct"
      }
    ],
    "rules": [
      {
        "outbound": "any",
        "server": "tencent"
      }
    ]
  },
  "inbounds": [
    {
      "tag": "st-1",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55315,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "toutiao.com",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-2",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55325,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "coding.net",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-3",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55335,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "mp.weixin.qq.com",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-4",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55345,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "cdn.bootcdn.net",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "st-5",
      "type": "shadowtls",
      "listen": "127.0.0.1",
      "listen_port": 55355,
      "version": 3,
      "users": [
        {
          "name": "mayee",
          "password": "xxxx"
        }
      ],
      "handshake": {
        "server": "feishu.cn",
        "server_port": 443
      },
      "strict_mode": true,
      "detour": "ss"
    },
    {
      "tag": "ss",
      "type": "shadowsocks",
      "listen": "127.0.0.1",
      "listen_port": 5505,
      "network": "tcp",
      "method": "2022-blake3-aes-128-gcm",
      "password": "xxxx",
      "tcp_fast_open": true,
      "multiplex": {
        "enabled": true,
        "padding": true
      },
      "detour": "direct"
    }
  ],
  "outbounds": [
    {
      "tag": "direct",
      "type": "direct"
    }
  ]
}
```

```text title="honet/sub-store/data/..."
```

```env title="honet/.env"
TZ=Asia/Shanghai
```

:::

### 5.1 homelan

我写的服务，持续集成自己需要的功能，目前提供的功能有：生成订阅节点用于内网穿透、支撑 WOL 方便手机管理电脑 开机/关机/休眠/唤醒。直接部署即可。

::: warning
挂载进去的 ssh key 必须只读，确保当前用户有目录权限`chown -R mayee:mayee data`，且限制文件权限`chmod 600 ./id_ed25519`，SSH 客户端要求很严格，权限过于宽松的 key 不会使用。
:::

### 5.2 haproxy

haproxy 是一个负载均衡软件，用于将流量转发到多个后端服务器，这里是用来根据 SNI 分流到 sing-box 的不同端口。直接部署即可。

### 5.3 sing-box

sing-box 是一个代理软件，用于将流量转发到后端服务器，这里是提供了 5 个 Sahdow-TLS 入口 和 1 个 Shadowsocks 服务端，用作内网穿透服务器。直接部署即可。

### 5.4 sub-store

sub-store 是一个订阅转换软件，也可以托管订阅节点或者配置文件，还可以对配置进行复写，并输出多种订阅格式。直接部署即可。

### 5.5 caddy

caddy 是一个反向代理软件，对于一些需要 https 访问的服务，在这里配置反代。直接部署即可。

由于我的内网服务并不想暴露到公网上，在内网时直接用，在外时通过内网穿透用，安全可靠。\
使用域名访问就会存在一个问题，如果用自签域名，则每个访问设备都要导入根证书，不安全且麻烦。那就要用可信机构的证书，这个前提必须得有个公网域名，可是问题又来了，申请证书时，我纯内网的服务器如何验证呢？\
通常证书发行机构主动验证用的是 HTTP 挑战，而对于纯内网服务器，可以用 DNS 挑战，原理就是把 token 和域名绑定的 txt 内容写入到 DNS 记录，发行机构在一定时间内去验证，如果验证成功就证明你是这个服务器的拥有者，可以给你发证书。

最简单的方式是下载[Caddy](https://caddyserver.com/download)时勾选上 DNS 模块，但这里我用的是容器部署，所以需要自行编译，好在官方早就提供了[xcaddy](https://caddyserver.com/docs/build#xcaddy)方便编译。

```txt title="Dockerfile"
FROM caddy:2.10.0-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare \
    --with github.com/caddy-dns/duckdns

FROM caddy:2.10.0

COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```

然后运行`docker build -t mayeee/caddy:2.10.0 --push .`编译并推送到[Docker Hub](https://hub.docker.com/r/mayeee/caddy)。

当发起证书申请时，在 Caddy 的后台可能看到如下日志：

```log :no-line-numbers
stderr: {"level":"info","ts":1753150377.8356597,"msg":"trying to solve challenge","identifier":"mv.mayee.dpdns.org","challenge_type":"dns-01","ca":"https://acme-v02.api.letsencrypt.org/directory"}
```

看到如下日志说明证书申请成功了：

```log :no-line-numbers
stderr: {"level":"info","ts":1753150394.7948797,"msg":"authorization finalized","identifier":"mv.mayee.dpdns.org","authz_status":"valid"}
stderr: {"level":"info","ts":1753150394.794989,"msg":"validations succeeded; finalizing order","order":"https://acme-v02.api.letsencrypt.org/acme/order/2540810831/409490037471"}
stderr: {"level":"info","ts":1753150395.8999088,"msg":"got renewal info","names":["mv.mayee.dpdns.org"],"window_start":1758254328,"window_end":1758409778,"selected_time":1758359327,"recheck_after":1753171995.8998837,"explanation_url":""}
stderr: {"level":"info","ts":1753150396.3523867,"msg":"got renewal info","names":["mv.mayee.dpdns.org"],"window_start":1758254328,"window_end":1758409778,"selected_time":1758352057,"recheck_after":1753171996.3523502,"explanation_url":""}
stderr: {"level":"info","ts":1753150396.352736,"msg":"successfully downloaded available certificate chains","count":2,"first_url":"https://acme-v02.api.letsencrypt.org/acme/cert/06fc056c35ccb80c775af08e52af41b0c704"}
stderr: {"level":"info","ts":1753150396.3627076,"logger":"tls.obtain","msg":"certificate obtained successfully","identifier":"mv.mayee.dpdns.org","issuer":"acme-v02.api.letsencrypt.org-directory"}
stderr: {"level":"info","ts":1753150396.3632228,"logger":"tls.obtain","msg":"releasing lock","identifier":"mv.mayee.dpdns.org"}
stderr: {"level":"warn","ts":1753150396.3652065,"logger":"tls","msg":"stapling OCSP","error":"no OCSP stapling for [mv.mayee.dpdns.org]: no OCSP server specified in certificate","identifiers":["mv.mayee.dpdns.org"]}
```

至于提示`no OCSP stapling`不用管，这只是个警告，不影响证书使用和续签。OCSP 就是让客户端可以实时验证证书状态，是否 still valid 或 revoked，Let's Encrypt 的证书通常不会包含 OCSP URL。申请 Let's Encrypt 证书时必须要有邮箱，并且限制一个邮箱 7 天内最多申请 50 个证书。

### 5.6 lucky

[lucky](https://lucky666.cn/) 是一个家用软硬路由公神器。它集成了很多功能，但对我来说最有用的功能就是提供 STUN 内网穿透，只要是 NAT1 类型的网络都可以穿透。穿透之后就好处多多了，在外也可以访问到内网。
