家庭服务AllInDocker
前言
最近在海鲜市场淘了个矿渣,配置和性能都比我那软路由高多了,价钱却便宜了很多,虽然也还是 arm 设备,但是硬件可以支持做更多事,于是将 r2s 上的一些服务尽可能转移过来,用 docker compose 部署。
1. 规划分类
计划分 3 个 compose.yaml 文件:
- hodev: home➕develop 的缩写,内置开发相关服务。
- homeia: home➕media 的缩写,内置媒体相关服务。
- honet: home➕network 的缩写,内置网络相关的服务。
部署原则如下:
- 对于提供支撑类型的服务,稳定性高于新功能,因此指定版本号。
- 对于提供消费类型的服务,新功能优先,因此使用
latest
版本号,并且使用 watchtower 监听自动更新。 - 目录挂载可以使用
数据卷
或目录绑定
,但数据卷的方式只能映射目录,目录绑定的方式还可以绑定文件,这里个人偏好是目录绑定,方便管理。但要注意,目录绑定时宿主机上的目录会自动创建,但文件绑定时必须先创建好文件,否则会被当成目录名处理。当服务启动时,数据卷和绑定的目录中就会自动填入内容。 - 手动创建网络或数据卷。
- 个人暂时用不到的服务用了
ignore
忽略。 - 公共的环境变量放在
.env
文件中,服务个性化变量放在各服务命名的*.env
文件中。
docker network create --driver bridge home
2. 部署前置条件
准备一个非域名,因为有些服务必须要 https 域名访问。
可以是仅内网域名(如: dev.lan),也可上DigitalPlat注册(2个免费域名且可托管至 Cloudflare),或者上Duck DNS免费注册至多5个子域名。
域名区别:
- 内网域名:优点是随意命名,缺点是客户端设备需要要导入根证书并信任。
- 公网域名:优点是不需要设备导入证书,缺点是不一定能注册到心意的域名。
个人推荐:公网非子域名。
3. hodev
hodev
compose.yaml
gitea.env
mysql.env
gitea
data
...
dbgate
data
...
mysql
data
...
my.cnf
redis
data
...
conf
redis.conf
.env
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
# echo $(id -u)
USER_UID=1000
# echo $(id -g)
USER_GID=1000
# 字符集
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
# 从容器中拷贝出来
docker cp mysql:/etc/my.cnf .
# 监听地址
bind 0.0.0.0
# 关闭保护(已经有密码了)
protected-mode no
requirepass 123456
# 开启 AOF 持久化
appendonly yes
appendfsync everysec
# 内存管理
maxmemory 256mb
maxmemory-policy allkeys-lru
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
jellyfin.env
libretv.env
moontv.env
navidrome.env
openlist.env
qinglong.env
vaultwarden.env
watchtower.env
jellyfin
config
...
cache
...
moontv
config.json
navidrome
data
...
openlist
data
...
portainer
data
...
qinglong
data
...
vaultwarden
data
...
.env
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
JELLYFIN_PublishedServerUrl=https://meida.mayee.dpdns.org
# 公开部署设置密码,避免可能法律风险
PASSWORD=''
ADMINPASSWORD=''
# 管理员账号(后台访问: /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
# 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
# echo $(id -u)
PUID=1000
# echo $(id -g)
PGID=1000
UMASK=022
QlBaseUrl=/
# 绑定域名
DOMAIN=https://vw.mayee.dpdns.org
# 内部启动端口
ROCKET_PORT=8137
# 禁止注册
SIGNUPS_ALLOWED=false
# 图片服务器地址
ICON_SERVICE=https://icons.bitwarden.net/{}/icon.png
WATCHTOWER_LABEL_ENABLE=true
WATCHTOWER_ROLLING_RESTART=true
WATCHTOWER_CLEANUP=true
WATCHTOWER_POLL_INTERVAL=7200
{
"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资源"
}
}
}
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
homelan.env
caddy.env
sub-store.env
caddy
conf
Caddyfile
config
...
data
...
site
...
haproxy
haproxy.cfg
homelan
...
lucky
data
...
sing-box
cofnig.json
sub-store
data
...
.env
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
# 需要唤醒的主机 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"
CLOUDFLARE_API_TOKEN=xxxxxxx
DOMAIN=mayee.dpdns.org
SUB_STORE_FRONTEND_BACKEND_PATH=/xxxxxxxxxx
{
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
}
# 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
{
"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"
}
]
}
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方便编译。
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 类型的网络都可以穿透。穿透之后就好处多多了,在外也可以访问到内网。
6. 常用命令
说明 | 命令 | 备注 |
---|---|---|
创建 Stack | docker compose up -d | 会自动创建网络和数据卷(如果有定义),若镜像不存在则自动拉镜像,否则直接用现有镜像创建 |
销毁 Stack | docker compose [-p <stack_name>] down | 会停止并删除定义的容器和网络,但不会删除数据卷。 |
重建容器(不拉新镜像) | docker compose up -d --no-deps --force-recreate <service_name> | 用于 docker-compose 文件配置更改了,重建容器 |
重建容器(拉新镜像) | docker compose up -d --no-deps --force-recreate --pull always <service_name> | 用于镜像更新了,重建容器 |
创建数据卷 | docker compose create volume | |
查看卷 | docker volume inspect <volume_name> | |
删除数据卷 | docker compose rm volume | |
创建网络 | docker compose create network | |
查看网络 | docker volume inspect <network_name> | |
删除网络 | docker compose rm network | |
删除所有无标签镜像 | docker image prune -f | -a 表示删除所有未被任何容器使用的镜像(包括有 tag 的老版本) |
如果要删除多个卷:docker volume ls -q | xargs docker volume rm
,创建多个卷:echo "mysql-conf mysql-data redis" | xargs -n 1 docker volume create
,其他命令同理。
版权所有
版权归属:Mayee