GTS 的安装#

因为开到了甲骨文,所以决定搭建一个自己的 Fediverse 实例,思来想去选了 GoToSocial, 比较轻量级,也可以使用各种兼容 Mastodon API 的客户端,美中不足就是比较 bareminimum,像表情回应和引用之类的功能是没有的。

GTS 官方的 docker-compose.yaml 长这样:

wget https://codeberg.org/superseriousbusiness/gotosocial/raw/branch/main/example/docker-compose/docker-compose.yaml
services:
  gotosocial:
    image: docker.io/superseriousbusiness/gotosocial:latest
    container_name: gotosocial
    user: 1000:1000
    networks:
      - gotosocial
    environment:
      # Change this to your actual host value.
      GTS_HOST: example.org
      GTS_DB_TYPE: sqlite
      # Path in the GtS Docker container where
      # the sqlite.db file will be stored.
      GTS_DB_ADDRESS: /gotosocial/storage/sqlite.db
      # Change this to true if you're not running
      # GoToSocial behind a reverse proxy.
      GTS_LETSENCRYPT_ENABLED: "false"
      # Set your email address here if you
      # want to receive letsencrypt notices.
      GTS_LETSENCRYPT_EMAIL_ADDRESS: ""
      # Path in the GtS Docker container where the
      # Wazero compilation cache will be stored.
      GTS_WAZERO_COMPILATION_CACHE: /gotosocial/.cache
      ## For reverse proxy setups:
      GTS_TRUSTED_PROXIES: "172.18.0.1/16"
      ## Set the timezone of your server:
      #TZ: UTC
    ports:
      - "443:8080"
      ## For letsencrypt:
      #- "80:80"
      ## For reverse proxy setups:
      #- "127.0.0.1:8080:8080"
    volumes:
      # Your data volume, for your
      # sqlite.db file and media files.
      - ~/gotosocial/data:/gotosocial/storage
      # OPTIONAL: To mount volume for the WAZERO
      # compilation cache, for speedier restart
      # times, uncomment the below line:
      #- ~/gotosocial/.cache:/gotosocial/.cache
    restart: "always"

networks:
  gotosocial:
    ipam:
      driver: default
      config:
        - subnet: "172.18.0.0/16"
          gateway: "172.18.0.1"

我自己用的时候,因为机器上已经有运行着的 Caddy,就把关于反向代理的部分都注释掉了。

然后在 environment 底下加了几行(GTS 有个单独的 config 文件用来调整环境变量,但是你不想挂载出来的话可以直接在 compose.yml 里面写,只要把变量名全部大写, - 换成 _,在前面加上 GTS_ 就好了,参考此处):

      GTS_ACCOUNTS_ALLOW_CUSTOM_CSS: true # 允许自定义 CSS
      TZ: Europe/Berlin
      GTS_INSTANCE_LANGUAGES: "zh,en,nl,de,fr,ja"

在 volumes 下面挂载了自定义字体:

      - /home/ubuntu/gotosocial/fonts/GeistPixel-Square.woff2:/gotosocial/web/assets/fonts/GeistPixel-Square.woff2:ro

这样做之后,就可以在 CSS 中调用啦。

CSS 美化#

GTS 用户资料主题的来源有两个,一个是管理员上传主题作为备选项,另一个是用户自行编辑 CSS。这两个效果会叠加。

全站的 CSS 也是可以自定义的,我用了 Catppuccin FrappéGeist Pixel Square, Fusion Pixel 的组合。

数据库和媒体的备份#

说实话我对自建实例这件事一直很犹豫,因为我对自己的备份没什么信心,如果数据库损坏,签署信息的密钥会丢失,想要从这个域名重新加入联邦会非常非常麻烦。

GTS 文档里面有关于备份和恢复的详细教程,其中推荐的数据库备份法是 Borgmatic不过我比较懒,在写这篇之前没有看这部分所以我没用

备份媒体和数据库我用的是 S3 桶,通过 rclone 挂载。

当然可以把 docker 映射到本机的文件夹里的内容一股脑全部上传,不过这样不太优雅,因为媒体文件包含远端实例的缓存,这些东西吃掉的空间比较多,但并没有备份的必要,只需要保存本实例的媒体即可。

GTS CLI tools 有 gotosocial admin media list-attachmentsgotosocial admin media list-emojis, 只要在运行的时候加上 --local-only 的 flag 即可只列出本地媒体文件。

输出类似于这样:

/gotosocial/062G5WYKY35KKD12EMSM3F8PJ8/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3R.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg
/gotosocial/01F8MH5ZK5VRH73AKHQM6Y9VNX/attachment/original/01FVW7RXPQ8YJHTEXYPE7Q8ZY0.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH8RMYQ6MSNY3JM2XT1CQ5.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH7TDVANYKWVE8VVKFPJTJ.gif
/gotosocial/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01F8MH58A357CV5K7R7TJMSH6S.jpg
/gotosocial/01F8MH1H7YV1Z7D2C8K2730QBF/attachment/original/01CDR64G398ADCHXK08WWTHEZ5.gif

利用这个功能,我们可以在定时备份的时候对媒体文件根据本地账号进行筛选。

以 rclone 备份 docker 容器映射到宿主机的文件夹为例:

/usr/local/bin/rclone-backup-optimized.sh

#!/usr/bin/env bash
set -uo pipefail


SRC="/home/ubuntu/gotosocial/data"
MEDIA_DST="remote:bucket/media"
DB_DST="remote:bucket/db"
CONF="/home/ubuntu/.config/rclone/rclone.conf"
COMPOSE_FILE="/home/ubuntu/gotosocial/docker-compose.yaml"
SERVICE="gotosocial"     # 容器名称
GTS_BIN="/gotosocial/gotosocial"            
DB_FILE="/home/ubuntu/gotosocial/data/sqlite.db"   


#  SQLite (vacuum into snapshot)
DB_OK=1; DB_OUT=""
SNAPDIR=$(mktemp -d); SNAP="$SNAPDIR/sqlite.db"
if DB_OUT=$(sqlite3 "$DB_FILE" "PRAGMA busy_timeout=10000; VACUUM INTO '$SNAP'" 2>&1); then
  CHK=$(sqlite3 "$SNAP" "PRAGMA integrity_check" 2>&1)
  if [ "$CHK" = "ok" ]; then
    if DB_OUT=$(rclone copy "$SNAP" "$DB_DST/" --config "$CONF" 2>&1); then
      DB_OK=0
    fi
  else
    DB_OUT="Snapshot integrity failed: $CHK"
  fi
fi
rm -rf "$SNAPDIR"

# list local account id 
get_ids() {
  sudo docker  exec  "$SERVICE" \
    "$GTS_BIN" admin media "$1" --local-only 2>/dev/null \
    | grep -oE '/[0-9A-HJKMNP-TV-Z]{26}/(attachment|emoji)/' \
    | grep -oE '[0-9A-HJKMNP-TV-Z]{26}'
}
IDS=$( { get_ids list-attachments; get_ids list-emojis; } | sort -u )

# sync media
FILTER=$(mktemp)
while IFS= read -r id; do printf '+ /%s/**\n' "$id"; done <<< "$IDS" >> "$FILTER"
printf -- '- **\n' >> "$FILTER"
MEDIA_OUT=$(rclone sync "$SRC" "$MEDIA_DST" --filter-from "$FILTER" --config "$CONF" 2>&1)
MEDIA_CODE=$?
rm -f "$FILTER"

每次备份完后,也可以用 Discord Webhook 发送提醒,防止备份失败自己不知道。

所以我在脚本中加上了:

WEBHOOK="https://discord.com/api/webhooks/.../..." 

notify() {  # $1=title  $2=color  $3=description
  local payload
  payload=$(jq -n --arg t "$1" --arg d "$3" --arg h "$(hostname)" --argjson c "$2" \
    '{embeds:[{title:$t, description:$d, color:$c, footer:{text:$h}, timestamp:(now|todate)}]}')
  curl -sf -H "Content-Type: application/json" -d "$payload" "$WEBHOOK" >/dev/null
}

NUM=$(printf '%s\n' "$IDS" | wc -l | tr -d ' ')
[ "$DB_OK" -eq 0 ] && DB_LINE="Database: success" || DB_LINE="Database: failed ($(printf '%s' "$DB_OUT" | tail -c 300))"
[ "$MEDIA_CODE" -eq 0 ] && MEDIA_LINE="Media: success (${NUM} local account directory/ies)" || MEDIA_LINE="Media: failed (exit $MEDIA_CODE)"

if [ "$DB_OK" -eq 0 ] && [ "$MEDIA_CODE" -eq 0 ]; then
  TITLE="GtS backup success"; COLOR=3066993
else
  TITLE="GtS backup failed"; COLOR=15158332
fi
TAIL=$(printf '%s' "$MEDIA_OUT" | tail -c 1000)
DESC=$(printf '%s\n%s\n```\n%s\n```' "$DB_LINE" "$MEDIA_LINE" "${TAIL:-(Media no output)}")
notify "$TITLE" "$COLOR" "$DESC"

好看好用的前端 Phanpy#

Phanpy 的安装很懒人,因为它是纯静态的,可以直接下载 release 之后用 web server 反代,不过官方并不推荐这么做,推荐的做法是 custom build。

克隆仓库之后类似这样:

PHANPY_DEFAULT_INSTANCE=social.obsp.de \
    PHANPY_CLIENT_NAME="Observer's Space Social" \
	    PHANPY_WEBSITE="https://phanpy.obsp.de" 
    PHANPY_PRIVACY_POLICY_URL=https://social.obsp.de/about \
    npm run build

dist 文件夹里就会有我们想要的东西。

目前 Phanpy 还不支持实例白名单,所以我稍微修改了一下 src/pages/login.jsx,只能从一个特定实例登录。