DuetG Blog

Keep It Simple, Stupid

标签: tutorial

  • 给WordPress双重身份验证增加应用程序密码支持

    给WordPress双重身份验证增加应用程序密码支持

    2020年底发布的Wordpress5.6增加了应用程序密码(Application Passwords)功能,这个功能是建立在整合Application Passwords插件代码的基础上的。

    事实上Wordpress官方GitHub账号上还有一款双重身份验证插件,也就是Two-Factor插件。尽管在Wordpress.org的插件库里,这款插件的作者既没有写作官方商业插件的Automattic,也没写作开源插件的WordPress Contributors,而是写为Plugin Contributors。但鉴于它的代码在官方GitHub账号上托管的身份,它无疑算是官方的两步验证插件。只是这款插件已经发布了这么久,并没有见官方像应用程序密码一样把它的代码整合进核心代码。也不知道是不是为了给其他第三方商业两步验证插件留条生路。

    本着简单免费的原则,我还是选择了官方身份的这款免费插件。不过用了之后发现了些小问题——这款插件直接对已经开启了两步验证的账号禁用了API登录的请求。这导致我的IFTTT没有办法再和Wordpress互动。这是Two-Factor默认的设置,倒也不是bug,只不过它并没有给一个能处理的用户界面来修改。

    最终的解决方案是Application Passwords的作者在GitHub上给出来的,不过也只是一段代码,而非是增加一个用户界面选项。

    add_filter( 'two_factor_user_api_login_enable', function( $enable ) {
        // Allow API login when using an application password even with 2fa enabled.
        if ( did_action( 'application_password_did_authenticate' ) ) {
            return true;
        }
    
        return $enable;
    } );

    在Wordpress.org上插件库里,Application Passwords的介绍中也有这段代码的描述,只不过任何人都没有将它做成一个用户界面选项。然而Application Passwords的代码已经整合进了Wordpress核心代码里,以后恐怕也不太有可能看到有这个选项了,毕竟它本来就是Two-Factor的功能。

    把这段代码粘到主题的functions.php里就可以了。不过如果嫌麻烦,我也做了一个简单的插件,直接下载安装激活就可以了(本身就还是这段代码)。

  • 在家部署MinIO实现S3自由

    在家部署MinIO实现S3自由

    这是一篇与随后准备写的部署Mastodon实例相关的文章。

    S3是Simple Storage Service的缩写,是亚马逊提供的一种云存储模式,目前已是一种主流的云存储,大多数前端服务都支持S3接入,大多数的商业云存储服务也都兼容S3。

    商业云存储目前大多数都是付费模式,个别限时或限量的免费体验版整体来说使用起来都不很自由。

    借助开源软件的免费优势,我们今天来尝试在家庭服务器上部署自托管的MinIO对象存储服务。MinIO本身是兼容S3的,作为一种低成本解决方案,一旦部署好就可以基本替代商业云存储。要注意的是,低成本不代表无成本,基本替代也很难成为完美替代。

    首先我们家中需要一台服务器(或者是服务器上的虚拟机)和一个域名。另外家庭宽带也需要公网IP,宽带上下行速度会直接影响文件传输速度。如果没有公网IP想用frp穿透的话,其数据传输速度自然会更加受影响,所以建议要有公网IP。后面会以安装好Docker的Debian/Ubuntu系统举例,当然你要用Podman也没问题。

    先使用路由器的动态DNS(DDNS)功能把家庭宽带的公网IP自动更新到域名托管的DNS服务。然后再用路由器防火墙的端口映射功能把WAN口的9000端口映射到你服务器的9000端口。

    然后再服务器里建立工作文件夹,并生成MinIO的配置文件

    mkdir minio
    cd minio
    vim minioconfig

    将如下内容按实际修改后粘贴进去。MinIO更多的环境变量可以参见这里

    # MINIO_ROOT_USER 和 MINIO_ROOT_PASSWORD 设置 MinIO 服务器的 root 帐户。 
    # 该用户拥有对部署中的任何资源执行 S3 和管理 API 操作的不受限制的权限。 
    # 如将其备注掉不设置则使用默认值“minioadmin:minioadmin”。 
    # MinIO 建议设置非默认值作为最佳实践,无论环境如何
    
    MINIO_ROOT_USER=myminioadmin
    MINIO_ROOT_PASSWORD=minio-secret-key-change-me
    
    # MINIO_VOLUMES 设置用于 MinIO 服务器的存储卷或路径。
    
    MINIO_VOLUMES="/mnt/data"
    
    # MINIO_SERVER_URL 设置与 MinIO 服务器一起使用的本地计算机的主机名 
    # MinIO 假设您的网络控制界面可以正确将此主机名解析到本地计算机 
    # 取消注释以下行,并将该值替换为本地计算机的正确主机名和 MinIO 服务器的端口(默认为 9000)。
    # 如https://你的域名:9000
    
    #MINIO_SERVER_URL="https://minio.example.net:9000"

    填入之后保存退出vim,之后来准备Docker Compose文件

    vim docker-compose.yml

    我们部署一个单节点单驱动的实例,进入编辑界面后把下面的内容按实际修改后填入

    version: '2'
    
    services:
      minio:
        container_name: minio
        command: server --console-address ":9090"
        environment:
          - MINIO_CONFIG_ENV_FILE=/etc/config.env
        image: quay.io/minio/minio:latest
        volumes:
          - ./data:/mnt/data
          - ./minioconfig:/etc/config.env
        restart: always
        ports:
          - "9001:9000"
          - "9090:9090"

    这里设定工作文件夹下的data文件夹是整个存储的目录,你可以按需求把第11行冒号前面的./data修改成你想要的目录。或者你可以在工作目录下建立一个名为data的链接到你想要的目标目录。也可以用nfs协议把内网其他位置的硬盘目录映射到这个data上,总之按你的需要来处理存储位置的设置。同时为了后面使用NGINX来实现反向代理端口,容器的9000端口先映射到主机的9001端口了。

    保存退出后就可以下载镜像生成容器了

    docker compose up -d

    容器顺利生成并启动后,我们来配置NGINX,首先设置站点配置文件

    vim /etc/nginx/sites-available/minio

    将下面的内容贴入编辑界面

    server {
            listen 9000 ssl default_server;
            listen [::]:9000 ssl default_server;
    
            include snippets/snakeoil.conf;
    
            ssl_session_cache shared:le_nginx_SSL:10m;
            ssl_session_timeout 1440m;
            ssl_session_tickets off;
    
            ssl_protocols TLSv1.2 TLSv1.3;
            ssl_prefer_server_ciphers off;
    
            ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
    
            server_name _;
    
            # To allow special characters in headers
            ignore_invalid_headers off;
            # Allow any size file to be uploaded.
            # Set to a value such as 1000m; to restrict file size to a specific value
            client_max_body_size 1000m;
            # To disable buffering
            proxy_buffering off;
            proxy_request_buffering off;
    
            location / {
                    # First attempt to serve request as file, then
                    # as directory, then fall back to displaying a 404.
                    # try_files $uri $uri/ =404;
                    proxy_set_header Host $http_host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
    
                    proxy_connect_timeout 300;
                    # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
                    proxy_http_version 1.1;
                    proxy_set_header Connection "";
                    chunked_transfer_encoding off;
    
                    proxy_pass http://127.0.0.1:9001;
            }
    }

    这里我们假定了证书的储存位置是/etc/nginx/snippets/snakeoil.conf写的

    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

    如果你希望是别的位置请修改相应的内容。保存退出后使用ln命令将其链接到enabled文件夹,并删掉其中的default配置

    ln -s /etc/nginx/sites-available/minio /etc/nginx/sites-enabled/
    rm /etc/nginx/sites-enabled/default

    然后我们使用acme.sh的DNS API功能来申请证书,使用DNS API申请证书不需要80端口,非常适合国内家庭宽带申请证书,而且acme.sh也支持大量的DNS服务商。具体的申请方法可参见这篇文章的前半部分。

    证书设置完毕后就可以重启NGINX了,也可以先用nginx -t来检查一下配置是否正确。这里我只把MinIO的服务端口9000映射到了公网,而网页控制台的9090端口依然还在内网,从而确保服务的安全。如果你需要在外网访问的话可以考虑使用Tailscale之类的服务来远程访问这台主机的端口。

    最后我们用这台服务器的内网IP加9090端口在浏览器上就可以访问MinIO的网页控制台,使用minioconfig中设置的用户名和密码登录即可。

  • 使用WebP Server Go优化网站图片

    使用WebP Server Go优化网站图片

    之前介绍了使用WebP Cloud Services的公共服务做代理让Gravatar的头像能在大陆正常访问的方法。

    设置好之后就决定想尝试一下他们的图片服务。但是官方制作的Wordpress插件着实用起来不顺手:只能将正文里本地图片的地址替换成代理地址,但特色图片却不能替换。要知道像2023这种官方主题,如果文章里不怎么插入图片的话,特色图片反而是页面加载数据量的大头。自行修改插件失败之后,我决定寻找替代的方案。

    按照官方插件作者的Blog我找到了WebP Cloud Services后端使用的开源程序WebP Server Go。开源代码和付费服务其实是一个作者。研究了一下GitHub上的README.md以及官网的文档,用Docker很快就部署完成了。简单修改了一下Nginx的配置文件,Blog页面的加载速度果然快了不少。特色图片用的大分辨率的图果然影响了不少速度啊。

    简单说一下部署流程和注意事项,默认你要加速的也是Wordpress,并且服务器里已经装好了Docker。

    首先建立一个工作目录,并且在里面创建一个 docker-compose.yml

    version: '3'
    
    services:
      webp:
        image: webpsh/webp-server-go
        # image: ghcr.io/webp-sh/webp_server_go
        restart: always
        environment:
          - MALLOC_ARENA_MAX=1
        volumes:
          - ./path/to/pics:/opt/pics  #./path/to/pics 改成你的Wordpress目录
          - ./exhaust:/opt/exhaust
          - ./metadata:/opt/metadata
        ports:
          -  127.0.0.1:3333:3333
        deploy:
          resources:
            limits:
              memory: 400M
        memswap_limit: 400M

    假设你的图片和网站路径有着这样的对应模式:

    图片路径

    /var/www/duetg.com/path/demo.jpg

    网站路径

    https://duetg.com/path/demo.jpg

    那么docker-compose.yml里的 ./path/to/pics 就应该改成 /var/www/duetg.com

    exhaust 和 metadata 会自动在工作文件夹中生成,exhaust 文件夹中包含已经转换完毕的图片缓存,以备下次请求的时候直接使用。这两个文件夹也可以自行定义位置。

    Docker Compose文件写好保存退出后,就可以拉取镜像创建容器了

    docker compose up -d

    接下来我们修改Nginx配置文件。原理很简单,在 server 块里新建一个 location,运用反向代理把和图片相关的请求直接转到Docker容器映射的3333端口就好。location 有两种写法,一种是直接指定到Wordpress的图片附件文件夹

    location ^~ /wp-content/uploads/ {
        proxy_pass http://127.0.0.1:3333;
    }

    还有一种是在整个目录里用正则表达式过滤和图片相关的文件扩展名

    location ~* \.(?:jpg|jpeg|gif|png)$ {
        proxy_pass http://127.0.0.1:3333;
    }

    如果你还使用了Cloudflare之类的CDN加速,你需要让CDN服务器知道这些图片不缓存,所以还要在 location 块里再加上下面这行

    location ~* \.(?:jpg|jpeg|gif|png)$ {
        add_header Cache-Control 'private';
        proxy_pass http://127.0.0.1:3333;
    }

    由于WebP Server Go会自动拒绝原本已经是WebP图片的转换请求,导致返回是空值,如果你的网站已经有一部分WebP图片了,建议还是用按扩展名过滤的方法来向服务请求图片转换更好。

    使用自托管的WebP转换服务和使用云服务相比,优点除了有免费且不限量外,其实更重要还在于网页里的图片引用地址不会被更改,这点也是我所看中的。

  • 使用Cloudflare Tunnel通过标准http(s)端口访问内网服务器

    使用Cloudflare Tunnel通过标准http(s)端口访问内网服务器

    其实访问内网服务器本身就是隧道的功能之一,这并没有什么可多说的。但Cloudflare Tunnel的优势在于,不但能够内网穿透,还可以为原本不具备使用标准http(s)端口的服务(如国内的家庭宽带)增加标准端口访问。当然还有Cloudflare提供的其他付费免费的安全功能和加速功能就不细说了。

    大多数人用家宽架设服务器的时候都会选择在路由器上用一个非标准的端口映射到内网的机器上。也许你内网的机器是标准的80或者443端口,但映射的时候只能给他随便挑个吉利(冷僻)的端口。再用DDNS把wan口获得的公网IP同步到DNS服务上。然后在浏览器的地址栏里敲上你的域名外加冒号和端口号,就能看到内网开的服务了。

    但是假如电信公司没给你公网IP呢?

    这时候用Cloudflare Tunnel就会非常方便了(其实有公网IP也可以用)。借官网的图来解释一下:

    通过加密隧道,你的内网资源与Cloudflare建立了访问通道,而Cloudflare又把资源映射到你托管在它上面的域名上,无视你是否有公网IP(因为资源是通过隧道访问的),也不管你是否是标准端口(因为域名在Cloudflare上托管,不管实际是什么端口,它只管从标准端口转发过去就好)。这样我们就实现了使用标准端口访问内网资源的目的。

    使用之前要弄明白一点:你的内网只要有一台机器和Cloudflare建立的Tunnel连接,且内网设备间互相访问是没有障碍的。那么你内网的所有机器都可以在Cloudflare上映射一个标准端口的域名,哪怕是一台空调或者一个冰箱。这个功能类似于Tailscale的路由功能。

    先选择一台机器作为与Cloudflare建立Tunnel的客户端。这里有几点建议。尽量不要选需要频繁切换代理的机器,因为Tunnel连接在切换代理的时候会断掉,而是否能够重新连上是不一定的,重启客户端是比较可靠的方法。但有时不一定能物理接触到那台机器,然而Tunnel又断了,很有可能就没办法处理了。其次尽量选择能够一直开机的服务器来部署,因为如果关机了整个内网都会无法访问。最后是把客户端安在路由器里是个不错的选择,但可能没有在某台机器上方便管理和调整。

    具体的安装可以部分参照少数派的《CloudFlare Tunnel 免费内网穿透的简明教程

    由于我的域名目前已经在SNI/HOST黑名单,所以才会出现http协议映射后跳转https连接失败的情况。如果你的域名没问题,按照少数派的教程映射http是可以的。但如果你想用自己申请的证书,可以参考后面没有划线的文字。虽然少数派的教程算是详尽,但最后在“5. 配置域名和转发URL”环节是有一些表述上的问题的。如果是以http协议映射,即域名使用默认80端口访问,内网服务也是http协议,大多数情况是没有问题的,但请不要在域名SSL/TLS设置部分开启“始终使用 HTTPS”,否则会强行自动跳转https。可如果像少数派的教程所讲用https协议映射内网http端口的话,虽然Cloudflare能够给你一个免费的证书,但通过我的测试,在大陆网络环境下这种方式是无法访问到服务的,具体情况是浏览器会报无法获取证书的错。所以如果想要使用https,建议还是在本地服务器上配置证书,再用Nginx开启ssl监听端口(比如443),最后再以https协议在Cloudflare Tunnel中映射内网服务器的该端口,并且务必开启下方“Additional application settings”中“TLS”里的“No TLS Verify”开关,否则Cloudflare Tunnel会拒绝访问,返回502错误。

    至于如何在没有公网IP以及80端口的情况下为主机申请免费证书,建议使用acme.shDNS API功能。

  • 在ARM开发板上自托管Bitwarden

    在ARM开发板上自托管Bitwarden

    结论:如果经济条件允许且对数据托管于别处不敏感,请首先选择1Password,除了数据托管在云上之外,使用体验是最好的。但要是对经济性有要求的话,建议使用完全免费的Vaultwarden

    背景介绍:
    在Lastpass一再发生风险事故后,我决定将密码的管理迁移到1Password。但1Password较高的年费让我又产生了动摇。使用一年后,我决定再寻找一款合适自己的产品。对比过Enpass、KeePass等产品后,基础账号免费、具备多平台客户端且整个系统能够自托管的Bitwarden成为了我的选择。本文简明介绍在ARM开发板上部署Bitwarden服务的步骤。

    技术要求:
    本文需要有一定Docker以及Linux的使用经验,并假定使用root登录Debian/Ubuntu。另需要一条具备公网IP的宽带、一个域名以及一块ARM开发板。

    Bitwarden官方主流安装方法虽然也是在Docker下运行,但镜像并不支持ARM架构。如果是想在ARM开发板上运行Bitwarden,需要使用官方提供的Unified镜像,也就是集成化镜像。通过单一镜像可以大幅降低对系统资源的要求(最少200MB内存,1GB储存空间),理论上非ARM架构主板也能运行Unified镜像,但由于此安装方式目前仍在Beta阶段,官方支持度也只能说一般,大多数遇到的问题还得要自己研究解决。在部署过程中遇到的坑后面文章中会给出解决方法。

    准备SSL证书

    作为密码管理软件,在客户端和服务器端通信的过程如果是明文,会大大降低使用的安全性。Bitwarden需要有SSL连接(即https访问),则首先要在系统内准备SSL证书。虽然Docker镜像内也可以部署SSL证书(可通过Volume参数映射到容器外),但官方建议尽量在容器外使用Nginx配置证书,并通过反向代理将https的端口指向容器开放的非加密访问端口(如80)。可以申请使用免费证书,如Let’s Encrypt

    安装Nginx

    apt update
    apt install nginx

    如果ARM开发板不能通过80端口来访问(国内家庭宽带绝大多数80、443、8080、8443都是被封锁的),而一般申请证书时要用80端口来验证。则需要使用DNS API来申请证书,这样就绕过了通过80端口验证的环节。Let’s Encrypt主推的Certbot是不具备这个验证功能的,这里我们使用acme.sh

    安装acme.sh

    #将[email protected]换成你自己的邮箱,如果没有curl的话先安装curl
    curl https://get.acme.sh | sh -s [email protected]

    安装过程中会有红色字样提示先安装socat,由于我们使用DNS API来申请证书,所以可以不用安装socat。acme.sh安装完成后重新登入一次账号,就可以直接使用acme.sh命令了。接下来我们通过域名托管的DNS服务来申请证书,acme.sh支持大量的DNS服务,我们以Cloudflare来举例。

    先在Cloudflare的个人资料页面左侧选择API令牌,然后再下一个页面点击右侧蓝色的创建令牌按钮。在API令牌模板中选择编辑区域DNS模板会大幅减少你的工作量。点击蓝色使用模板按钮后,在创建令牌中区域资源段最右侧的下拉菜单里选择你先前绑定在Cloudflare上的域名,随后点击下方蓝色的继续以显示摘要按钮,可以看到类似如下的令牌摘要:

    编辑区域 DNS API 令牌摘要

    此 API 令牌将影响以下帐户和区域,以及它们各自的权限

    确认无误后点击蓝色的创建令牌按钮,并在下个页面把虚线框中的字符串完整复制到桌面的文本文件里,这个就是你域名的DNS API令牌。接下来回到Cloudflare的账户主页,点击绑定的域名进入域名概述页面,在页面右侧下方找到区域ID和账户ID也复制到本地。接下来在ARM开发板的命令行中通过如下命令申请证书。

    #先导入令牌和账户ID以及区域ID
    export CF_Token="<token>"
    export CF_Account_ID="<id>"
    export CF_Zone_ID="<zone>"
    
    #再使用命令申请证书
    acme.sh --issue --dns dns_cf -d example.com

    证书申请成功后需要安装才可以使用,这里我们就借用Nginx内置的snakeoil文件所包含的证书位置和名称来安装证书。

    acme.sh --install-cert -d example.com \
    --key-file       /etc/ssl/private/ssl-cert-snakeoil.key  \
    --fullchain-file /etc/ssl/certs/ssl-cert-snakeoil.pem \
    --reloadcmd     "service nginx force-reload"

    证书完成安装后,开始写Nginx的配置文件

    #系统中如果没有vim的话可以先安装vim
    apt install vim
    #编辑Nginx默认配置文件
    vim /etc/nginx/sites-available/default

    请参考以下内容修改default文件,完成后按 :wq 保存并退出

    server {
            listen 443 ssl default_server;
            listen [::]:443 ssl ipv6only=on default_server;
     
            include snippets/snakeoil.conf;
    
            ssl_session_cache shared:le_nginx_SSL:10m;
            ssl_session_timeout 1440m;
            ssl_session_tickets off;
    
            ssl_protocols TLSv1.2 TLSv1.3;
            ssl_prefer_server_ciphers off;
    
            ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
    
            server_name _;
    
            location / {
                    ### Force timeouts if one of backend hosts is dead ###
                    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    
                    ### Set headers ###
                    proxy_set_header          X-Real-IP $remote_addr;
                    proxy_set_header          Accept-Encoding "";
                    proxy_set_header          Host $http_host;
                    proxy_set_header          X-Forwarded-For $proxy_add_x_forwarded_for;
    
                    ### Don't timeout waiting for long queries - timeout is 1 hr ###
                    proxy_read_timeout        3600;
                    proxy_set_header          X-Forwarded-Proto $scheme;
    
                    ### By default we don't want to redirect ###
                    proxy_redirect            off;
                   
                    ### Add Websocket proxy support ###
                    proxy_http_version 1.1;
                    proxy_set_header Upgrade $http_upgrade;
                    proxy_set_header Connection "upgrade";
    
                    proxy_pass http://127.0.0.1:80;
            }
    }

    保存并退出后使用以下命令重启Nginx

    service nginx force-reload

    以上是证书及Nginx反向代理的设置步骤。接下来开始为Bitwarden容器的运行做准备。首先是安装Docker,我们使用Docker官方的安装脚本来安装。

    #首先回到Home文件夹
    cd ~
    #下载安装脚本
    curl -fsSL https://get.docker.com -o install-docker.sh
    #浏览并测试脚本(也可以跳过)
    cat install-docker.sh
    sh install-docker.sh --dry-run
    #执行脚本安装Docker,如果从官方服务器下载速度很差,可以使用阿里云的镜像,安装操作请务必在root权限下进行
    sh install-docker.sh --mirror Aliyun

    安装完成后在Home中生成Bitwarden工作文件夹并进入并添加docker-compose.yml文件

    cd ~
    mkdir bitwarden
    cd bitwarden
    vim docker-compose.yml

    将下面内容按实际编辑后粘贴进vim并用 :wq 保存退出

    version: "3.8"
    
    services:
      bitwarden:
        depends_on:
          - db
        env_file:
          - settings.env
        image: ${REGISTRY:-bitwarden}/self-host:${TAG:-beta}
        restart: always
        ports:
          - "80:8080"
      #   - "443:8443"
        volumes:
          - bitwarden:/etc/bitwarden
          - logs:/var/log/bitwarden
    
      # MariaDB Example
      db:
        environment:
          MARIADB_USER: "bitwarden"
          MARIADB_PASSWORD: "super_strong_password" #建议修改password
          MARIADB_DATABASE: "bitwarden_vault"
          MARIADB_RANDOM_ROOT_PASSWORD: "true"
        image: mariadb:10
        restart: always
        volumes:
          - data:/var/lib/mysql
    
    volumes:
      bitwarden:
      logs:
      data:

    接下来添加Bitwarden的设置文件

    vim settings.env

    将下列内容编辑后粘贴进vim并用 :wq 保存退出

    #####################
    # Required Settings #
    #####################
    
    # Server hostname
    BW_DOMAIN=example.com
    
    # Database
    # Available providers are sqlserver, postgresql, mysql/mariadb, or sqlite
    BW_DB_PROVIDER=mysql
    BW_DB_SERVER=db
    BW_DB_DATABASE=bitwarden_vault
    BW_DB_USERNAME=bitwarden
    BW_DB_PASSWORD=super_strong_password  #和docker-compose.yml中一致
    
    # Installation information
    # Get your ID and key from https://bitwarden.com/host/
    # 请到 https://bitwarden.com/host/ 中输入邮箱并提交后免费获取
    BW_INSTALLATION_ID=xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    BW_INSTALLATION_KEY=xxxxxxxxxxxxxxxxxxxx
    
    #####################
    # Optional Settings #
    #####################
    # Learn more here: https://bitwarden.com/help/environment-variables/
    
    # Container user ID/group ID
    #PUID=1000
    #PGID=1000
    
    # Webserver ports
    #BW_PORT_HTTP=8080
    #BW_PORT_HTTPS=8443
    
    # SSL
    #BW_ENABLE_SSL=true
    #BW_ENABLE_SSL_CA=true
    #BW_SSL_CERT=ssl.crt
    #BW_SSL_KEY=ssl.key
    #BW_SSL_CA_CERT=ca.crt
    
    # Services
    # Some services, namely for enterprise use cases, are disabled by default. Defaults shown below.
    BW_ENABLE_ADMIN=true
    BW_ENABLE_API=true
    BW_ENABLE_EVENTS=false
    BW_ENABLE_ICONS=true
    BW_ENABLE_IDENTITY=true
    BW_ENABLE_NOTIFICATIONS=true
    BW_ENABLE_SCIM=false
    BW_ENABLE_SSO=false
    
    #BW_ICONS_PROXY_TO_CLOUD=false
    
    # Mail
    # 使用Gmail的SMTP服务发信
    globalSettings__mail__replyToEmail=noreply@$BW_DOMAIN
    globalSettings__mail__smtp__host=smtp.gmail.com
    globalSettings__mail__smtp__port=587
    globalSettings__mail__smtp__ssl=false
    [email protected]   #你的Gmail地址
    globalSettings__mail__smtp__password=xxxxxxxxxxxxxxxx   #你的Google账户的应用专用密码
    
    # Yubikey
    #globalSettings__yubico__clientId=REPLACE
    #globalSettings__yubico__key=REPLACE
    
    # Other
    #globalSettings__disableUserRegistration=true    #如想禁止注册请取消本行开头的注释
    #globalSettings__hibpApiKey=REPLACE
    adminSettings__admins="[email protected]"         #使用你的邮箱作为管理员登录的凭据
    #globalSettings__baseServiceUri__vault=https://example.com:1234     #如果使用了非标准端口,请取消本行注释并将完整带端口号的地址填在后面
    #下方BW_REAL_IPS是反向代理过程中可能会影响到获取真实IP的内网IP地址,以及后半段的是Cloudflare的IP地址,请按实际情况进行修改
    BW_REAL_IPS=172.0.0.0/8,127.0.0.1,192.168.2.0/24,173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22,2400:cb00::/32,2606:4700::/32,2803:f800::/32,2405:b500::/32,2405:8100::/32,2a06:98c0::/29,2c0f:f248::/32

    录入完成并保存退出后就可以使用Docker Compose启动Bitwarden

    docker compose up -d

    稍等一会儿,在浏览器中访问 https://{ARM开发板IP} ,应该会先看到证书错误的提示,选择继续访问就可以看到Bitwarden的登录页面。

    如果有公网IP的话,在路由器上利用DDNS服务把公网IP更新到域名A记录上,再用端口映射功能将ARM开发板内网IP的443端口映射到路由器上Bitwarden设置文件里自定义的端口(本例是1234)上,就可以直接用域名加端口号来访问Bitwarden了。在上方的例子中是 https://example.com:1234

    至此Bitwarden在ARM开发板的自托管便设置完成了。但是作为一款密码管理软件,如果不能备份数据库的话,使用风险是很高的。在Bitwarden Unified镜像的官方反馈帖中,官方明确表述了鉴于是自托管运行,所以目前不提供任何备份数据的功能,需要靠自己来完成备份。于是我们需要编写一个脚本,让机器定时完成备份。

    首先我们进入Bitwarden的工作文档新建一个备份文件夹,再新建一个备份脚本

    cd ~/bitwarden
    mkdir backups
    vim backup.sh

    将以下代码按实际情况修改后粘贴进 vim 并用 :wq 保存并退出

    #!/bin/bash
    # Backup database.
    
    docker exec bitwarden-db-1 sh -c 'exec mysqldump --all-databases -ubitwarden "super_strong_password"' > /root/bitwarden/backups/backup.sql
    #将上行命令中的密码修改为之前设置的密码
    
    now=$(date +"%Y-%m-%d-%H_%M")
    new_name=db_backup-${now}
    
    echo "Prepare backup folder from backup.sql and docker volume bitwarden_bitwarden"
    mkdir -p /root/bitwarden/backups/${new_name}
    mv /root/bitwarden/backups/backup.sql /root/bitwarden/backups/${new_name}/backup.sql
    cp -a /var/lib/docker/volumes/bitwarden_bitwarden /root/bitwarden/backups/${new_name}/
    cp -a /root/bitwarden/backup.sh /root/bitwarden/backups/${new_name}/
    cp -a /root/bitwarden/docker-compose.yml /root/bitwarden/backups/${new_name}/
    cp -a /root/bitwarden/restore.sh /root/bitwarden/backups/${new_name}/
    cp -a /root/bitwarden/settings.env /root/bitwarden/backups/${new_name}/
    
    echo "Compressing ${new_name} folder"
    tar -zcpf /root/bitwarden/backups/${new_name}.tar.gz -C /root/bitwarden/backups/${new_name} .
    rm -r /root/bitwarden/backups/${new_name}
    cd /root/bitwarden/backups/
    ls -t | sed -n '151,$p' | xargs -I {} rm -rf {}

    备份脚本会将所有和Bitwarden相关的数据全部备份并打包,以备份时间为文件名将压缩包保存在Bitwarden工作目录下的 backups 文件夹中。如果有支持 samba 协议的网络存储设备(如群晖NAS),还可以通过 smbclient 命令将备份的压缩包传送到别处异地备份。另外脚本最后一行命令确保备份文件夹中始终保持最新的150个备份,这个数字可以按需求自行调整。这里给一下 smbclient 的使用方法,可以添加到备份脚本的末尾。如果备份到群晖等NAS上,还可以使用类似Hyper Backup之类的程序继续将其备份到网络存储,但注意文件安全。

    smbclient -N //{NAS的IP}/home -U {NAS用户名}%{NAS密码} -t 120 -c 'cd /BitwardenBackup/; put /root/bitwarden/backups/'${new_name}'.tar.gz '${new_name}'.tar.gz'

    通过系统 cron 定期执行备份脚本,以达到自动备份的效果

    #编辑crontab
    crontab -e
    #将下行代码粘贴至crontab最后一行并保存退出,即可每6小时执行一次脚本
    0 */6 * * * sh /root/bitwarden/backup.sh > /dev/null

    如果重新部署Bitwarden需要还原数据可以用以下代码

    #!/bin/bash
    # Restore database.
    
    echo "Extract files from archive file"
    mkdir -p /root/bitwarden/backups/temp
    tar -zxpf $1 -C /root/bitwarden/backups/temp
    
    echo "Restore database"
    docker exec -i bitwarden-db-1 sh -c 'exec mysql -ubitwarden "-super_strong_password"' < /root/bitwarden/backups/temp/backup.sql
    #将上行命令中的密码修改为之前设置的密码
    
    echo "Restore docker volume bitwarden_bitwarden"
    rm -rf /var/lib/docker/volumes/bitwarden_bitwarden
    cp -af /root/bitwarden/backups/temp/bitwarden_bitwarden /var/lib/docker/volumes/
    
    echo "Clean work folder"
    rm -rf /root/bitwarden/backups/temp
    
    echo "Stop the running container, please restart manually"
    cd /root/bitwarden
    docker compose down

    将上述代码修改后保存为 restore.sh。先按照本文初的方式部署并运行Bitwarden(包括工作目录及备份文件夹的设置),将备份文件和 restore.sh 一同放入工作目录,保持Bitwarden运行中,执行

    sh restore.sh db_backup-xxxx-xx-xx-xx_xx.tar.gz

    完成还原数据后再执行一次启动Bitwarden的命令即可

    docker compose up -d

    至此Bitwarden Unified的备份还原工作也完成。

    另,acme.sh目前默认的证书服务由ZeroSSL提供,如果还想使用Let’s Encrypt,请在申请证书前先用下方命令切换默认证书服务到Let’s Encrypt,再操作证书申请

    acme.sh --set-default-ca --server letsencrypt

    再另,本文所提方法适用于家庭宽带有公网IP的情形,如家庭宽带不具备公网IP条件,可以使用Cloudflare Tunnel来完成内网穿透,实现通过标准端口访问内网ARM开发板,具体方法之后会再写一篇blog详述。

    Update: Cloudflare Tunnel相关的内容请参见这里