Andy Huo's Blog

重磅原创:Use Apps Like a Pro.

Created: Last update:


使用软件的另一种方式:self hosted

百毒网盘?🚮 👉 nextcloud 🥂
网抑云音乐?🚮 👉 Navidrome 🍻
密码记在小本本上?🙈 👉 vaultwarden 🔒
自带静默删除功能之遥遥领先系统相册?🚮 👉 immich 🏄‍♂️

其实你完全没有必要给百毒网盘之流的流氓网盘软件交月租。也没有必要冒照片被“遥遥领先”的手机系统给“智能”筛选和默默删除的风险。也没有必要点开那个集市般嘈杂的音乐软件,你只是想单纯的听首歌,结果它给你网抑云,给你女主播,给你跳转到购物页。总之,你没有必要忍受那些商业软件对你明目张胆地剥削,它没有丝毫尊重你的用户体验,它只是把你当作能产出收益的一个…生产单位。你只是换算过后的它们收益表里的一个冷冰冰的数字。所以,和这些丑陋的东西说拜拜吧👋。把它们赶出你的手机,你的电脑,你的世界。让我们拥抱自由的开源世界,那里满是宝藏,足以用来打造你的专属数字空间。这样一个系统有如下特点:

效果图如下: CleanShot 2025-10-07 at 14.45.30@2x.png * 该面板由homepage 强力驱动

就像图片里展示的面板,从笔记,链接管理,阅读,网盘,AI聊天,音乐软件,播客,图片管理,聊天,视频管理,文档协作,密码管理,记账,等等,主打一个全面。
你可以通过搜索图片中的软件名称(reading 例外,它是 calibre 这款强大而优秀的个人书籍管理系统的web server),去它的官网查看介绍,或者有的软件没有官网,但是至少会有 github 仓库。它们的共同点是源代码都是开源的,由开源社区的大佬们积极维护,感谢他们的付出🙏。如果你以后写了自己的软件,觉得别人可能也会有需求,可以慷慨地开源出来,为开源社区贡献自己的力量。

面板中那些软件都是为了满足我的需求搭建的,你可以在这些地方发现更多可以满足你个性化需求的开源软件:

看到这里,如果你之前完全没有自己部署过软件,或者说不了解 self hosted 领域,希望上面的介绍为你打开了新世界的大门。或者至少,你可以知道,原来网络世界不是只有喧闹的,不在乎用户体验的,只在乎利益的商业软件。还存在另外一个,开放的,自由的,包容的,开源软件世界。或者你已经是 self hosted 爱好者,也希望你能收获一两个之前没留意过的优秀软件。

我们说完了事情的一方面,事物往往是两面的。当我们选择拥抱开源社区,坚持自己的审美,对某些糟糕的商业软件弃如敝履。同时我们也需要承担搭建整个系统的成本,探索期的时间投入,还有维护期的少量精力投入。最主要的是,对于数据安全,我们选择了自己握紧方向盘。「自由的代价是永恒的警惕」。数据安全是重中之重,这一块如何强调都不为过。后续我们会有专门的说明。此外,真的要玩转这套体系,是需要有一定技术基础和动手能力的。网络世界的麻瓜可能驾驭不了,他们很大可能不具有数据安全意识和对软件的要求和审美,他们也是某度创始人口中的用隐私换便利的群体,多说一句,他们只是想要便利而且对差劲的用户体验容忍度也比较高,但是你司别TM给人家喂💩啊,What’s your problem?🤬。市面上其实有很多成熟的硬件产品提供整体方案,比如各种 NAS 和家庭服务器可能更适合…想要便利的群体。

OK,铺垫就到这里,如果你对搭建这样一套系统有兴趣,那么可以接着往下看。

系统概述

组成上并不复杂,核心只有两点:

  1. 运行软件服务。绝大部分服务将以 docker container 的形式运行;
  2. 提供(暴露)软件服务到公网。根据你的网络环境,选择不同的方式:
    • 路由器端口映射(有固定的公网IP);
    • 路由器端口映射 + DDNS(有动态的公网IP);
    • cloudflare tunnel(没有公网IP);

需要的物料:

搭建流程

1. 硬件及网络准备

首先,我们不会选择云服务器。因为我们选择自己掌握数据,而且长远来看云服务器的成本也更高。一般来说,我们不需要很高配置的硬件,甚至过时的笔记本或者台式机都可以。我之前使用一个树莓派,去年才换成最低配的 Mac mini,对我来说现阶段完全够用。除了电脑,还需要一个足够容量的固态硬盘,如果电脑本身的存储空间不够的话,那么得两个,为什么是两个?因为需要做数据备份,我们将遵守3-2-1备份策略 ,关于数据远端备份,后面还会具体说明。

除了硬件部分,网络环境也是基础设施的一部分。如果你处在一个很不幸的网络环境中,表现为:这也访问不了,那也访问不了,这也拉取不到,那也拉取不到(我们将拉取大量的 docker镜像,当然也可以配置一些镜像站凑合一下,但是总感觉差点意思)。那么可能你需要一些魔法来对抗网络世界的魔鬼。先去弄个临时魔法 ,有时间一定要再去弄个专属魔法 ,因为,几乎可以说魔法是我们系统的基础。记住,魔法越多,自由越大,往往是这样。

此外,在保证了网络畅通的前提下,需要区分你的网络环境特点:有没有固定的IP,指IPv4。这一点很重要。最好的情况下,你有一个固定的公网IP,其次,你是动态的公网IP,最坏的情况下,你没有公网IP,不过即使这种情况也有解。

如何确定有没有公网IP?查看路由器管理后台(比如,http://192.168.0.1)显示的IP地址,和访问 网站 显示的是不是一样,如果一样,那么恭喜你,至少你有公网IP。至于是固定的,还是动态的,要看它会不会变化。如果隔个几天,甚至每天,它都会变化,那么就是动态IP。

好了,总结一下。我们需要一台不用很强大,但是得能一直待机的电脑作为服务器,还需要两个足够空间的硬盘,电脑内置的,或者外接的,都行。另外需要畅通的网络,真正畅通的,不是只能访问局域网的那种,你懂我意思。

现在假设你有了硬件,网络也畅通了,而且你也了解了自己的网络环境。那么就可以开始下一步了。

2. 文件系统的创建

我们要搭建合理的文件系统(其实就是一系列良好命名的,秩序井然的文件夹)。首先,先保证硬盘是加密的,这一点至关重要,因为你也不想一旦别人拿到你的硬盘,你的所有数据都直接泄露了。所以一定要加密,然后自动挂载到电脑。Mac环境可以通过将硬盘格式化为加密的格式(格式化前记得转移和备份数据先),然后让系统的钥匙串记住密码,以后开机之后就会自动解密并挂载了。Windows和Linux环境应该都是类似的。

现在我们开始创建文件夹目录,井然有序定义良好的目录结构是方便扩展和迁移的前提。我的目录结构如下图。 CleanShot 2025-10-07 at 16.46.24@2x.png

inbox 文件夹会通过 File Browser 以 web 服务的形式开放出来。这样你在任何客户端,都可以通过浏览器上传文件到这个文件夹,轻松收集和管理文件。不过,我目前的实践中,已经很少用到这个文件夹了。我一般直接将特定类型的文件交给代理该类型文件的软件。换句话说,省去了中转环节。当然如果待处理文件多到一时处理不过来,就可以先堆在这里。

local 文件夹存放需要放在本地的数据,比如我现在使用的 Mac 端的 docker 管理软件 OrbStack , 它需要很大的空间来存放 docker 镜像,和数据卷。这些数据显然不需要备份到远端,因为任何一个容器,我们拿到 Dockerfile 或者 docker-compose.yml 这些配置文件就可以拉取到同样的 docker 镜像。所以放在 local 就好了。此外,就要根据数据需不需要被安全备份。这里的安全备份是指加密备份到远端。判断一个数据需不需要被安全备份,很主观。我觉得主要取决于数据的大小,重要程度和你钱包的厚度,因为云端备份一般来说是要付费的。我所用的 Backblaze 6刀/TB/月。所以蓝光视频之类的超大数据还是算了吧,取舍下来,它只能被放在本地。当然,本地文件也还是要备份,只不过它的备份也在本地,这就是我们为什么要准备两个加密硬盘的原因。

3.本地数据备份

现在假设你有了两块加密硬盘,A 和 B, 它们都挂载到了系统。并且,你在 A 硬盘上已经有了上面步骤建立的文件结构 /data。那么我们要将 /data 文件夹备份到 B 硬盘。

  1. 下载 rclone 软件,它是一个命令行程序,用于管理云存储上的文件,也可以完成本地文件夹的克隆备份。
  2. 在 A 硬盘的 /data 文件夹同层级创建 rclone_exclude.txt 文件,用来排除不需要同步的文件及文件夹,例如 local 文件夹下 OrbStack 创建的 8T 标量的文件夹。
1/local/orb/* 
  1. 命令行执行同步命令 sudo rclone sync /[A]/data /[B]/data_backup --progress --retries 1 --low-level-retries 1 --skip-links --ignore-checksum --size-only --exclude-from /[A]/rclone_exclude.txt 其中的 A, B,替换成你两块硬盘的实际名称。该命令将把 A 硬盘下的 /data 文件夹下除了 rclone_exclude.txt 文件中排除的条目外,都同步到 B 硬盘下的 /data_backup 文件夹。
  2. 为该命令创建 alias, 例如如果你使用 zsh 命令行工具,那么将如下命令添加到 ~/.zshrc 文件中。 alias data_sync_local="sudo rclone sync /[A]/data /[B]/data_backup --progress --retries 1 --low-level-retries 1 --skip-links --ignore-checksum --size-only --exclude-from /[A]/rclone_exclude.txt" 同样的将其中的 A, B,替换成你两块硬盘的实际名称。
  3. 每天,或每周,命令行执行一下:data_sync_local 命令,就完成了本地数据备份。当然,你也可以创建一个自动执行的命令。但是因为权限问题,我没有创建成功。执行一条命令也不麻烦,我就没有深入探索了。

* 推荐使用 Warp 终端工具,集成 AI agent,谁用谁知道。

4. 运行服务

我们的绝大部分软件都将使用 Docker 来运行,所以首先,你要在你的环境下安装好 Docker 和 docker compose。如果是 Mac 平台,我还推荐使用 OrbStack 来管理 Docker 容器,此外它还能快速运行虚拟机,我现在的开发环境就是基于它运行的虚拟机,稳定性很不错。

我们在加密硬盘 A 上创建了文件系统 /data. 假设我们要运行 File Browser 来将 /data/inbox 文件夹暴露出来,方便我们通过网页管理这个文件下的文件。

我们首先来到 /data/sync/core/aux/containers 文件夹下,这个文件夹就是放置所有和我们的数据有关的软件服务的 docker 配置项及软件运行相关数据的。新建一个和软件同名的文件夹 filebrowser, 然后 cd 到该文件夹。

我们 Google filebrowser,搜索结果页的第一项,就是它的官网。然后我们去到它的使用说明关于 使用 Docker 来安装的部分

1docker run \
2    -v /path/to/srv:/srv \
3    -v /path/to/database:/database \
4    -v /path/to/config:/config \
5    -e PUID=$(id -u) \
6    -e PGID=$(id -g) \
7    -p 8080:80 \
8    filebrowser/filebrowser:s6

我们一般会创建, docker-compose.yml 文件,我的实践是直接将上述的 docker 命令贴给 Warp 终端工具,让它基于该命令,创建一个 docker-compose.yml 文件。 然后我们调整一下,内容如下:

 1services:
 2  filebrowser-inbox:
 3    image: filebrowser/filebrowser:s6
 4    restart: always
 5    container_name: filebrowser-inbox
 6    environment:
 7      - PUID:1000
 8      - PGID:1000
 9    ports:
10      - 9801:80
11    volumes:
12      - /[A]/data/inbox:/srv
13      - ./settings.json:/config/settings.json
14      - ./filebrowser.db:/database/filebrowser.db

其中有几点需要注意:

  1. 我们可以调整该容器映射到宿主机的端口,比如,我们将其映射到了 9801, 在此之前,你应该先检查一下, 9801 端口有没有被占用,如果被占用,需要换一个其他的。
  2. 我们将该容器的内容文件夹映射到了 /[A]/data/inbox 也就是我们实际需要管理的文件夹。
  3. settings.json 和 数据库文件 filebrowser.db 我们就近映射到当前文件夹。而该文件夹,是会被备份到远程的(所有 /data/sync 文件夹下的数据都会被备份)。

接下来就可以通过 docker compose 来启动服务了。 docker-compose up -d 它会拉取 docker 镜像,然后启动 docker 容器。 可以通过 docker-compose ps 来查看运行情况,可以通过 docker-compose logs 来查看运行日志。 更多命令, 请自行 google,常用的没几个。

* 如果发现镜像拉取不成功,可以搜索配置替代镜像源。但是为了服务的运行和访问顺畅,我更推荐你彻底解决好网络环境问题。
* 如果镜像拉取成功了,但是运行失败,并且 filebrowser.db 变成了文件夹,而不是文件,那么先删除 filebrowser.db 文件夹,然后 touch filebrowser.db 手动创建该文件,然后再次 docker-compose up -d 启动服务。

好了,顺利的话,现在浏览器访问: http://localhost:9801, 应该就可以看到应用的界面了。

回顾一下我们建立一个服务的过程。首先,我们去到它的文档找到 docker 安装的说明,然后创建它的 docker-compose.yml 配置文件的文件夹,然后创建 docker-compose.yml 文件,并调整配置。

但是一个服务只运行在局域网,用处好像不是很大。所以,我们处理下一个关键步骤:开放服务。

5.开放服务

开放服务到公网,使得服务随时随地可以访问,这是我们的目标。根据你的网络环境,有不同的实现方式。确定你的网络环境的特点,参考前面的硬件及网络准备部分。

1. 如果你有一个公网IP

那么,需要路由器开放并映射特定的端口到服务器,将域名解析到该IP上,申请 TLS 证书,通过 Caddy 反向代理服务。

  1. 路由器开放并映射端口到服务器,需要在路由器后台操作,特定品牌的路由器操作界面有差异。下面是我的路由器操作界面。 CleanShot 2025-10-07 at 19.57.29@2x.png * ⚠️ 需要注意的是,开放路由器端口可能会有安全隐患。所有暴露的服务都要加密,不要轻易泄露链接。
    * 某些地区可能会限制 80, 443, 8080, 8443 等端口。
  2. 解析域名到 IP(如果还没有域名,先买一个), 在 cloudflare 的域名 DNS 页面完成配置 CleanShot 2025-10-07 at 20.06.45@2x.png
  3. 申请 TLS 证书
    我们将使用 acme.sh 的 docker 安装方式来申请证书并后续自动更新证书。
    3.1 在 /data/sync/aux/containers 文件夹下新建 acme 文件夹,进入到该文件夹,新建 docker-compose.yml 文件,内容如下:
 1services:
 2  acme-sh:
 3    image: neilpang/acme.sh
 4    container_name: acme.sh
 5    volumes:
 6      - ./out:/acme.sh
 7    network_mode: host
 8    command: daemon
 9    stdin_open: true
10    tty: true
11    restart: no

它会将申请到的证书放在 ./out文件夹里,并且每天自动检查证书是否即将过期,如果将要过期,自动申请新的证书。每次证书的有效期为3个月。
3.2 docker-compose up -d 启动服务
配置文件 ./out/account.conf 里填入 SAVED_CF_Token,该 token 需要在 cloudflare 创建。 CleanShot 2025-10-07 at 20.24.50@2x.png
3.3 切换默认证书机构: docker exec acme.sh --set-default-ca --server letsencrypt
3.4 申请证书:docker exec acme.sh --issue --dns dns_cf -d [你的域名] -d '*.[你的域名]' 稍等片刻 ./out文件夹下就有证书了。
4. 通过 Caddy 反代应用, 个人感觉,Caddy 要比 nginx 简单好用,当然如果你习惯用 nginx 也可以。
4.1 在 /data/sync/aux/containers 文件夹下新建 caddy 文件夹,进入到该文件夹,新建 docker-compose.yml 文件,内容如下:

 1services:
 2  caddy:
 3    image: caddy:latest
 4    restart: unless-stopped
 5    container_name: caddy
 6    ports:
 7      - "9000:9000" #路由器映射的服务器端口
 8      - "9100:9100" #路由器映射的服务器端口
 9      - "9100:9100/udp"
10    volumes:
11      - ./Caddyfile:/etc/caddy/Caddyfile
12      - /[A]/data/sync/aux/containers/acme/out/pubhub.cc_ecc:/.local/caddy/cert
13      - caddy_data:/data
14      - caddy_config:/config
15volumes:
16  caddy_data:
17  caddy_config:

4.2 新建 Caddyfile 文件

 1{
 2  http_port 9000
 3  https_port 9100
 4
 5  admin off
 6  debug
 7}
 8 
 9(localtls) {
10  tls /.local/caddy/cert/fullchain.cer /.local/caddy/cert/pubhub.cc.key
11}
12
13filebrowser.[你的域名] {
14  import localtls
15  reverse_proxy host.docker.internal:9801
16}

4.3 运行命令 docker-compose up -d
现在浏览器访问 https://filebrowser.[你的域名] 就可以看到服务了。

2. 你有动态的公网IP

一般情况下,普通家庭宽带几乎都是动态公网IP。动态公网IP的特点是,它会隔三岔五,甚至每天都会变化。所以手动记录 DNS 解析就行不通了。一般的解决方案是,运行一个程序,它间隔固定时间,比如说5分钟,检查一下现在的IP,再通过域名托管商提供的 API 拿到域名当前的 DNS 解析的 IP。对比,如果不一样,就更新 DNS 记录。所以,理论上,会有几分钟服务不可用。但是其实网络运营商一般也不会在大白天突然变动你的 IP, 更多是在凌晨时候,比如凌晨 1点,2点。所以影响不大。

理论说完,下面是实操环节。

  1. 路由器开放并映射端口到服务器,需要在路由器后台操作, 同上。
  2. 解析域名到 IP, 在 cloudflare 的域名 DNS 页面完成配置,同上。
  3. 安装 DDNS 程序, 比如 cloudflare-ddns 或者 ddns-updater-lite ,以 ddns-updater-lite 为例。
    3.1. 克隆仓库, 在 data/sync/aux/services 文件夹下运行: git clone https://github.com/andyhuocom/ddns-updater-lite.git
    3.2. cd ddns-updater-lite 然后新建 .env 文件
1HOSTNAME="yourdomain.com,*.yourdomain.com"
2CLOUDFLARE_API_TOKEN="get your token from cloudflare"
3CLOUDFLARE_ZONE_ID="get your zone id from cloudflare"

其中,HOSTNAME 是你的一系列域名,以半角逗号分割开,对应上一步在 cloudflare DNS 页面填入的配置。CLOUDFLARE_API_TOKEN 就是上面申请 TSL 证书时创建的 cloudflare token。CLOUDFLARE_ZONE_ID 在 cloudflare overview 页面的右下角,如下图所示。
CleanShot 2025-10-08 at 00.03.04@2x.png
3.3. docker-compose up -d 运行服务。可以通过 docker-compose logs 查看运行日志。
4. 申请 TLS 证书,同上。
5. 通过 Caddy 反代应用, 同上。

3.没有公网IP

可以通过 cloudflare tunnel 来开放服务。具体来说,使用 DockFlare , 它的 文档 非常详尽,跟着一步步操作就行。

或者其他方案,比较复杂:

  1. Pangolin 相当于自托管的 cloudflare tunnel,需要一台有公网 IP 的 VPS。
  2. frp 是一款高性能的反向代理应用,专注于内网穿透。通过在具有公网 IP 的节点上部署 frp 服务端,您可以轻松地将内网服务穿透到公网。所以,也需要一台有公网 IP 的 VPS。

通过路由器开放端口的方式开放服务。一般来说,只能使用非标准端口,因为标准端口(80,443,8080,8443,一般被封禁了…)。所以 url 会包含一个丑陋的非标准端口号。如果想去掉这个非标准端口,可以选择通过 cloudflare 代理服务。这样做的好处除了 url 更加美观,通过代理,我们还不用暴露自己的真实 IP,而且可以防御网络攻击,所以更加安全。但是也有坏处,那就是强如 cloudflare 可能也会被针对…导致访问服务变慢,甚至访问不到。所以需要取舍。

闭环了,终于闭环了。我们部署了服务,并且开放了服务。
但是还有一个重点就是数据远端备份。我们将遵守3-2-1备份策略 ,创建一个每天都会自动同步的远端备份。这样,即使本地的数据突然全部丢失了,我们也可以通过远端的备份快速恢复数据和服务。

6. 数据远端备份

我们将使用 kopia ,一款优秀的开源的跨平台备份软件,具有快速、增量备份、客户端端到端加密、压缩和重复数据删除功能。它的文档在这里

备份

  1. /data/sync/aux/containers 文件夹下新建 kopia 文件夹,进入到该文件夹,新建 docker-compose.yml 文件,内容如下:
 1services:
 2    kopia:
 3        image: kopia/kopia:latest
 4        hostname: hostname
 5        privileged: true
 6        cap_add:
 7            - SYS_ADMIN
 8        security_opt:
 9            - apparmor:unconfined
10        devices:
11            - /dev/fuse:/dev/fuse:rwm
12        container_name: kopia
13        restart: unless-stopped
14        ports:
15            - 51515:51515
16        # Setup the server that provides the web gui
17        command:
18            - server
19            - start
20            - --disable-csrf-token-checks
21            - --insecure
22            - --address=0.0.0.0:51515
23            - --server-username=admin
24            - --server-password=oD2dfdeMrvC2eCdkfjlsdkfD9
25        environment:
26            # Set repository password
27            KOPIA_PASSWORD: "<a strong password>"
28            USER: "<a user name>"
29            TZ: Asia/Shanghai 
30        volumes:
31            # Mount local folders needed by kopia
32            - ./config:/app/config
33            - /[A]/data/local/kopia/cache:/app/cache #这个文件夹适合放在 local 
34            - /[A]/data/local/kopia/logs:/app/logs   #这个文件夹适合放在 local 
35            - /Volumes/sandi/data/sync:/data:ro      #需要被备份的文件夹
36            # Mount repository location
37            - /[A]/data/local/kopia/repository:/repository #这个文件夹适合放在 local 
38            # Mount path for browsing mounted snaphots
39            - /[A]/data/local/kopia/tmp:/tmp:shared   #这个文件夹适合放在 local 
  1. 运行命令:sudo docker-compose up -d
  2. 浏览器打开 http:localhost:51515 就可以看到页面了,输入 server-username 和 server-password.
  3. 设置远端存储库,我用的是 backblaze , 你可以选择其他方案,查看文档获取相关信息。下面以 backblaze 为例。
  4. 在 backblaze 创建账号,并新建 bucket, 创建 application key。
  5. web UI 设置远端存储库, 选 backblaze, 填入 bucket name, application key name, application key.
  6. 填入 docker-compose.yml 里面 environment 设置的 KOPIA_PASSWORD 和 USER,创建 repository。软件会使用 KOPIA_PASSWORD 将你的数据加密后上传到 bucket.
  7. 创建新的 Snapshot, 文件夹以 /data 开头,我的做法是给 /data 整体创建一个 Snapshot, 并且排除整个 /data/core/0_sources/文件夹。然后再为 /data/core/0_sources/ 文件夹下的每个资源文件夹创建自己的 Snapshot。 之所以这样做,就是考虑到随着时间的推移,每一个资源文件夹可能会变得很大,如果整体备份,可能会比较耗时,也增加了出错的概率。效果图如下: CleanShot 2025-10-08 at 10.46.48@2x.png 推荐的 Snapshot Policy:
    files 可以选择需要排除同步的文件夹和文件;
    compression 算法设置为 zstd-fastest
    scheduling 可以设置自动同步的时间,manual snapshot only 设置为 no;
  8. 点击 Snapshot Now 按钮,开始备份。之后就会根据 scheduling 设置的时间,自动触发备份。

恢复

只有备份,没有恢复肯定是不行的。上一步备份时候的各种设置信息和密码请妥善保存。
恢复步骤:

  1. 下载 kopia 软件, Mac 可以直接下载 UI 版, 或者使用上面的 docker container;
  2. 在 UI 上,连接远程 repository, 输入 bucket name, key id, key
  3. 然后输入 repository password 用来解密数据
  4. 自定义一个 user 和 hostname(区分备份的 hostname)
  5. 挂载文件夹,或者浏览并下载文件;

建议经常验证数据的恢复,防止到了真的需要用的时候,恢复失败,那太杯具了。

关于备份操作的其他细节和更生动的说明,也可以参考这篇优秀的教程

备份这一步做完,真的很让人安心,你以后的绝大部分个人数据(除了 local 文件夹,它采取本地备份,而没有被远端备份)都更大概率不会丢失了。多亏了3-2-1备份策略

这下真的闭环了,从数据的角度,它的产生(或者收集),处理(通过各种软件),备份(和恢复)。

7. 必备软件推荐

推荐几款必备的软件,它们是除了我们要掌握自己数据的核心目的外,辛辛苦苦搭建一套系统的主要理由。

1. Trilium 个人知识管理系统

Trilium 的设计理念就是一个可靠的个人知识管理系统,性能强大,我导入了可以追溯到十年前的各个笔记软件的笔记,上万条,依然很丝滑。这也是它为什么选择将数据存在数据库的原因,而不是像obsidian 那样以 markdown 文本作为数据存储单位。顺便说一下,如果你更偏爱 obsidian, 这里有一个很棒的主题 , 还有自托管的sync插件 。说回到 Trilium, 它还支持插件 , 并且也有桌面端的 APP。其实只用 web 端也够用。因为它太重了,所以在移动端其实体验不是很好。移动端推荐: Memos

2. Memos

它的定位是知识管理和笔记应用。它很轻量,有社区适配的移动端 APP,但是 APP 维护并不是很积极,所以在移动端我一般以 PWA 的方式使用它,同样很丝滑。它有一个特点是,提供 API 的方式创建笔记,所以想象空间就很大。顺便说一下, Trilium 也提供 API。你可以创建一个 iOS 的捷径(安卓端用 HTTP Shortcuts),实现闪念胶囊之类的功能。也可以创建一个浏览器插件,随时把网页的摘录收集起来。也可以创建 Raycast Script Command 保存文本。还有 PopClip 快捷方式。可能性很多。

3. Navidrome 音乐专辑管理平台

非常好用,而且各个端也有 APP,具体可以参考文档 。安卓端的Symfonium 体验很惊艳。iOS 体验了一圈,整体都差点意思。play:Sub 还行。桌面端可以用Feishin 。 其实音乐和视频重要的不是容器,而是资源。可惜某些地区的音乐软件,虽然资源不可谓不多,但是用户体验太欺负人了,恨不得直接把全集团的入口都塞在里面,恨不得直接从用户口袋里掏钱。虽然我理解音乐版权可能已经支出了很多,迫切想要营收,但是也别那么过分哇。如果要推荐一个还不错的音乐软件提供更多音乐资源配合我们自己的音乐软件一起使用,我推荐 Spotify , 财力捉襟见肘的话,可以去拼个家庭会员

所以,音乐资源的话,得自己想办法。但是有一个我偶然发现的宝藏资源 ,还是忍不住想分享一下。这里边儿肯定有版权问题的,所以,请谨慎使用,请支持正版。

4. Jellyfin 媒体资源管理平台

很优秀的媒体资源管理平台,主要用来管理视频资源。

5. immich 图片和视频管理平台

immich 也是优秀到必备的软件。各个端相册里的图片和视频,都同步起来,集中管理。

6. vaultwarden 密码管理

vaultwarden 密码管理器,它有 web 版的 UI, 同时也兼容 Bitwarden 。全平台客户端支持。必备。

7. homepage 面板

随着你在 self holded 领域探索的时间越来越长,你发现并部署的好东西越来越多。就需要有一个地方集中的展示和组织它们的入口。一个优秀的面板,就必不可少。我的实践是安装两个面板,这两个面板将软件的入口分为局域网和公网。就像上面的 kopia 备份软件,从安全的角度考虑,它就不应该在公网访问。还有一些系统监控类的软件,或者为了最大化网络性能,如果能局域网访问到,就尽量通过局域网访问。所有暴露在公网的服务,一定要设置用户验证,这个面板也不例外,但是它没有内建这个功能。有很多方式做这件事。我的选择是通过 caddy 集成简单的 security 插件来提供用户验证功能。

  1. /data/sync/aux/containers/caddy 文件夹新建 Dockerfile 文件,构建自定义的带 security 插件的 caddy 镜像。
1FROM caddy:2.9-builder AS builder
2
3RUN xcaddy build \
4    --with github.com/greenpau/caddy-security
5
6FROM caddy:2.9
7
8COPY --from=builder /usr/bin/caddy /usr/bin/caddy
  1. 调整 docker-compose.yml 以使用该镜像:
 1services:
 2  caddy:
 3    build: .
 4    restart: unless-stopped
 5    container_name: caddy
 6    ports:
 7      - "9000:9000" #路由器映射的服务器端口
 8      - "9100:9100" #路由器映射的服务器端口
 9      - "9100:9100/udp"
10    volumes:
11      - ./Caddyfile:/etc/caddy/Caddyfile
12      - /[你的硬盘路径]/acme/out/[你的证书文件夹]:/.local/caddy/cert
13      - caddy_data:/data
14      - caddy_config:/config
15volumes:
16  caddy_data:
17  caddy_config:
  1. Caddyfile 中增加鉴权功能:
 1{
 2  http_port 9000
 3  https_port 9100
 4
 5  admin off
 6  debug
 7
 8  order authenticate before respond
 9  order authorize before basicauth
10
11  security {
12    local identity store localdb {
13         realm local
14         path /.local/caddy/users.json
15    }
16
17    authentication portal myportal {
18          crypto default token lifetime 604800 # 1 week
19          cookie lifetime 604800 # 1 week
20
21          transform user {
22               match origin local
23               action add role authp/user
24          }
25
26    }
27
28    authorization policy admins_policy {
29           set auth url https://auth-caddy.<your domain>:<port>/
30           allow roles authp/admin authp/user
31           enable js redirect
32     }
33  }
34}
35
36auth-caddy.<your domain> {
37	import localtls
38	route {
39		authenticate with myportal
40	}
41}
  1. 创建 dot_local_caddy/caddy/users.json 用户信息
 1{
 2  "version": "1.1.2",
 3  "policy": {
 4    "password": {
 5      "keep_versions": 10,
 6      "min_length": 8,
 7      "max_length": 128,
 8      "require_uppercase": true,
 9      "require_lowercase": true,
10      "require_number": true,
11      "require_non_alpha_numeric": false,
12      "block_reuse": false,
13      "block_password_change": false
14    },
15    "user": {
16      "min_length": 3,
17      "max_length": 50,
18      "allow_non_alpha_numeric": false,
19      "allow_uppercase": true
20    }
21  },
22  "revision": 2,
23  "last_modified": "2021-10-25T13:04:58.482997492-04:00",
24  "users": [
25    {
26      "id": "395452-454e-4c85-829b-8195a8d81",
27      "username": "admin",
28      "email_address": {
29        "address": "[email protected]",
30        "domain": "gmail.com"
31      },
32      "email_addresses": [
33        {
34          "address": "[email protected]",
35          "domain": "gmail.com"
36        }
37      ],
38      "passwords": [
39        {
40          "purpose": "generic",
41          "algorithm": "bcrypt",
42          "hash": "$2a$14$VtcLPPLS1kENheGXRsIjSedfdssTG/C444IpQycpjuS1bIPb0hEBTEBMA2",
43          "cost": 10,
44          "expired_at": "0001-01-01T00:00:00Z",
45          "created_at": "2025-01-01T12:46:38.918246801Z",
46          "disabled_at": "0001-01-01T00:00:00Z"
47        }
48      ],
49      "created": "2025-01-01T12:46:38.918246801Z",
50      "last_modified": "2025-01-01T12:46:38.918246801Z",
51      "roles": [
52        {
53          "name": "admin",
54          "organization": "authp"
55        }
56      ]
57    }
58  ]
59}

其中的密码 hash, 可以通过 caddy 命令行生成,例如:
caddy hash-password --algorithm bcrypt --plaintext "your-password" --cost 10
输出类似: $2a$14$VtcLPPLS1kENheGXRsIjSeTG/C444IpQycpjuS1bIPb0hEBTEBMA2
将该字符串放入⁠hash字段中。确保⁠算法为⁠“bcrypt”,并将⁠成本设置为您使用的成本(例如10)。时间戳可以保持为零或酌情设置。
5. Caddyfile 中使用

1homepage.<your domain> {
2        import localtls
3
4        route {
5                  authorize with admins_policy
6                  reverse_proxy host.docker.internal:3000
7        }
8}

authorize with admins_policy 就是给 homepage.<your domain> 这个服务增加了用户验证。访问该链接的时候,如果没有用户登录信息,就会先跳转到 https://auth-caddy.<your domain>:<port>,输入正确的用户名和密码,登录成功后,就会再跳转回原链接,这里就是 homepage.<your domain>。你可以给任何没有自带用户验证的服务,通过 authorize with admins_policy 一行配置就添加用户验证。

结语

self hosted 的一种定制含量很高的方式的各个环节就介绍完了。林林总总写下来,有的地方可能很详细,有的地方可能过于概括。任何问题和建议都可以在评论区交流探讨,或者 email 联系我。其实写作前,我对这篇的定位主要是综合概述,重点是介绍理念和范式,展示更大的存在和更多的可能性。你知道得越多,你的选择就越多,换句话说,就越自由。关键的是问题域,就像房间里的大象,很多时候其实我们并不清楚我们面对的是什么。在这个搜索和AI已经如此好用的时代,问题意识和一个好的问题,远远比答案更有价值。这里提供的每个步骤的具体实现,不一定都是最优解。重要的是你要有一颗探索的心,去探索最适合你的方案。过程可能是曲折的,但是回报也绝对是丰厚的。当麻瓜们还在对着短视频傻笑的时候,我们直接就是互联网世界的 Pro 用户。

<< Previous Post

|

Next Post >>