跳转到内容

约 4318 字

docker

前言

最近在海鲜市场淘了个矿渣,配置和性能都比我那软路由高多了,价钱却便宜了很多,虽然也还是 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文件中。
创建网络
docker network create --driver bridge home

2. 部署前置条件

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

域名区别:

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

个人推荐:公网非子域名。

3. hodev

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
hodev/gitea.env
# echo $(id -u)
USER_UID=1000
# echo $(id -g)
USER_GID=1000
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
hodev/gitea/data/...
hodev/dbgate/data/...
hodev/mysql/data/...
hodev/mysql/my.cnf
# 从容器中拷贝出来
docker cp mysql:/etc/my.cnf .
hodev/redis/data/...
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
hodev/.env
TZ=Asia/Shanghai

3.1 mysql

直接部署,创建的用户开启远程登录。

3.2 redis

直接部署即可。

3.3 gitea

直接部署即可,配置参考官方文档。对于 gitea 的 SSH 端口,我没有做麻烦的直通,直接修改了宿主机的 SSH 端口。

注意

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

3.4 dbgate

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

4. homedia

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
homedia/jellyfin.env
JELLYFIN_PublishedServerUrl=https://meida.mayee.dpdns.org
homedia/libretv.env
# 公开部署设置密码,避免可能法律风险
PASSWORD=''
ADMINPASSWORD=''
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
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
homedia/openlist.env
# echo $(id -u)
PUID=1000
# echo $(id -g)
PGID=1000
UMASK=022
homedia/qinglong.env
QlBaseUrl=/
homedia/vaultwarden.env
# 绑定域名
DOMAIN=https://vw.mayee.dpdns.org
# 内部启动端口
ROCKET_PORT=8137
# 禁止注册
SIGNUPS_ALLOWED=false
# 图片服务器地址
ICON_SERVICE=https://icons.bitwarden.net/{}/icon.png
homedia/watchtower.env
WATCHTOWER_LABEL_ENABLE=true
WATCHTOWER_ROLLING_RESTART=true
WATCHTOWER_CLEANUP=true
WATCHTOWER_POLL_INTERVAL=7200
homedia/jellyfin/config/...
homedia/jellyfin/cache/...
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资源"
    }
  }
}
homedia/navidrome/data/...
homedia/openlist/data/...
homedia/portainer/data/...
homedia/qinglong/data/...
homedia/vaultwarden/data/...
homedia/.env
TZ=Asia/Shanghai

4.1 allinone

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

4.2 navidrome

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

注意

# 挂载硬盘
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、bitwarden、google。

注意

vaultwarden 部署成功后,可以方便的将其他平台,如:lastpass、1password、Keepass 等导入进来。但对于 *.kdbx 文件则无法用内置功能迁移,需要借助kp2bw工具。

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

部署参考:带有 DNS 挑战的 Caddy、使用 Let's Encrypt 证书运行私有 Vaultwarden 实例

4.9 jellyfin

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

4.10 watchtower

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

4.11 portainer

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

5. honet

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
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"
honet/caddy.env
CLOUDFLARE_API_TOKEN=xxxxxxx
DOMAIN=mayee.dpdns.org
honet/sub-store.env
SUB_STORE_FRONTEND_BACKEND_PATH=/xxxxxxxxxx
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
}
honet/caddy/config/...
honet/caddy/data/...
honet/caddy/site/...
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
honet/homelan/...
honet/lucky/data/...
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"
    }
  ]
}
honet/sub-store/data/...
honet/.env
TZ=Asia/Shanghai

5.1 homelan

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

注意

挂载进去的 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时勾选上 DNS 模块,但这里我用的是容器部署,所以需要自行编译,好在官方早就提供了xcaddy方便编译。

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。

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

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"}

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

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 是一个家用软硬路由公神器。它集成了很多功能,但对我来说最有用的功能就是提供 STUN 内网穿透,只要是 NAT1 类型的网络都可以穿透。穿透之后就好处多多了,在外也可以访问到内网。