看到了糖喵的前端速成教程用了 outline,确实好看,又有点想自己搞一个出来,不由得回忆起以前刚开始看 outline 部署的时候。

当时傻不拉叽没想到直接配 SMTP, 也没想到 gitea(forgejo) 是个 OpenID Provider, 可以对接 OIDC. 这些都是后来配熟了才意识到的。

除此之外,最难的就是 S3 了。当时是指定要求使用 S3 存储,但是也没有我能负担得起的 S3 服务商,真的难倒我。当时再加上一些人发的劝退言论,令我很是绝望,就几乎忘记了这玩意的存在。不过这次看到文档说 0.72 以后他们支持了本地文件存储^1,就省事很多了。那我觉得还是有些能做出来的希望的。

最近看 notion 替代品的时候,经常有人提到 outline. 虽然肯定不及 notion 和飞书那么强大,但我觉得它姑且算开源,而且我正好缺一个好用的在线 WYSIWYG markdown 编辑器当作草稿箱,而它正好合适,看起来比 standard notes 和 joplin 顺手多了,虽然没有 e2ee. 哦对了,似乎占用也不大,刚起来的时候加起来吃掉 1GB RAM 吧。

添加 OAuth App

要拿 forgejo 当作 OpenID Provider, 就要在 forgejo 里面添加一个 application, 然后拿走 client id 和 client secret.

Callback URL 文档里没写,是等到第一次调用 OIDC 进到授权界面的时候,看到地址栏里有个 redirect 然后得知的,path 是 /auth/oidc.callback ,太奇妙了。

部署

按照文档,看起来就是一个 docker compose 的事情,那再把 TLS 部分交给 nginx 就行,并不困难。

上 compose file

文档中提供了 compose 配置的示例。看到暴露端口的时候就绷不住了,拿下来加以修改。

  • 添加 container_name 作为内部通信用的 hostname,无需再对外暴露端口。

  • 将 volume 改为直接 bind mount 文件夹。

    注意:需要随后将 storage-data 文件夹的所有权改为 1001:65533,否则无法正常上传文件或导出(可使用 docker exec -it outline_app sh 进入容器内,执行 id -uid -g 自行验证)

  • 将 image tag 的版本锁定住。

  • 不需要使用单独的反代了,我自己有 nginx.

  • 把 forgejo 的地址解析到它容器所在的网关,那个地址有一个 nginx 监听,不必绕一圈公网了。

# docker-compose.yml

version: "3.2"

services:
  outline:
    image: docker.getoutline.com/outlinewiki/outline:0.74.0
    env_file: ./docker.env
    container_name: outline_app
    ports:
      - "127.0.0.1:3000:3000"
    volumes:
      - ./storage-data:/var/lib/outline/data
    depends_on:
      - postgres
      - redis
    extra_hosts: 
      - "forgejo.example.com:172.30.0.1"

  redis:
    image: redis:7.0-alpine
    env_file: ./docker.env
    container_name: outline_redis
    # ports:
      # - "6379:6379"
    volumes:
      - ./redis.conf:/redis.conf
    command: ["redis-server", "/redis.conf"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres:13-alpine
    env_file: ./docker.env
    container_name: outline_postgres
    # ports:
      # - "5432:5432"
    volumes:
      - ./database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      POSTGRES_USER: 'user'
      POSTGRES_PASSWORD: 'pass'
      POSTGRES_DB: 'outline'

改环境变量

文档里提及了 github 上面托管的示例,拿下来重命名为 docker.env 提供给 docker compose 用。

按照自己的需求修改。

Line Comment
7, 11 自己生成去
15, 16, 23 更改 postgres 和 redis 的地址,就可以在内部 bridge 通信,不用对容器外暴露端口。
20 内部通信,postgres 不开 SSL 了。
33 改成你自己拿来暴露到公网的地址
48-55, 59 使用本地存储
71-96 删去不使用的外部认证配置
101-106 照 forgejo 文档的 OIDC Endpoints 猛抄一遍
134 顺便禁用一些奇妙的 TLS 配置
138 禁用遥测(虽然感觉自欺欺人)
142 进程数我开了 4, 看个人需求。
160-162 删去 slack 集成
175-182 考虑到我那个域名邮箱可怜的 quota, 就不配 SMTP 了(后来一看那个邮件通知设置,一堆开关,真能把 quota 用爆,然后塞满我的收件箱。)
7c7
-SECRET_KEY=generate_a_new_key
+SECRET_KEY=<GENERATED_SECRET_1>

11c11
-UTILS_SECRET=generate_a_new_key
+UTILS_SECRET=<GENERATED_SECRET_2>

15,16c15,16
-DATABASE_URL=postgres://user:pass@localhost:5432/outline
-DATABASE_URL_TEST=postgres://user:pass@localhost:5432/outline-test
+DATABASE_URL=postgres://user:pass@outline_postgres:5432/outline
+DATABASE_URL_TEST=postgres://user:pass@outline_postgres:5432/outline-test

20c20
-# PGSSLMODE=disable
+PGSSLMODE=disable

23c23
-REDIS_URL=redis://localhost:6379
+REDIS_URL=redis://outline_redis:6379

33c33
-URL=https://app.outline.dev:3000
+URL=https://outline.example.com

48,55c48,55
-AWS_ACCESS_KEY_ID=get_a_key_from_aws
-AWS_SECRET_ACCESS_KEY=get_the_secret_of_above_key
-AWS_REGION=xx-xxxx-x
-AWS_S3_ACCELERATE_URL=
-AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
-AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
-AWS_S3_FORCE_PATH_STYLE=true
-AWS_S3_ACL=private
+# AWS_ACCESS_KEY_ID=get_a_key_from_aws
+# AWS_SECRET_ACCESS_KEY=get_the_secret_of_above_key
+# AWS_REGION=xx-xxxx-x
+# AWS_S3_ACCELERATE_URL=
+# AWS_S3_UPLOAD_BUCKET_URL=http://s3:4569
+# AWS_S3_UPLOAD_BUCKET_NAME=bucket_name_here
+# AWS_S3_FORCE_PATH_STYLE=true
+# AWS_S3_ACL=private

80,81c80,81
-SLACK_CLIENT_ID=get_a_key_from_slack
-SLACK_CLIENT_SECRET=get_the_secret_of_above_key
+# SLACK_CLIENT_ID=get_a_key_from_slack
+# SLACK_CLIENT_SECRET=get_the_secret_of_above_key

88,89c88,89
-GOOGLE_CLIENT_ID=
-GOOGLE_CLIENT_SECRET=
+# GOOGLE_CLIENT_ID=
+# GOOGLE_CLIENT_SECRET=

94,96c94,96
-AZURE_CLIENT_ID=
-AZURE_CLIENT_SECRET=
-AZURE_RESOURCE_APP_ID=
+# AZURE_CLIENT_ID=
+# AZURE_CLIENT_SECRET=
+# AZURE_RESOURCE_APP_ID=

101,105c101,105
-OIDC_CLIENT_ID=
-OIDC_CLIENT_SECRET=
-OIDC_AUTH_URI=
-OIDC_TOKEN_URI=
-OIDC_USERINFO_URI=
+OIDC_CLIENT_ID=<YOUR_CLIENT_ID>
+OIDC_CLIENT_SECRET=<YOUR_CLIENT_SECRET>
+OIDC_AUTH_URI=https://forgejo.example.com/login/oauth/authorize
+OIDC_TOKEN_URI=https://forgejo.example.com/login/oauth/access_token
+OIDC_USERINFO_URI=https://forgejo.example.com/login/oauth/userinfo

134c134
-FORCE_HTTPS=true
+FORCE_HTTPS=false

138c138
-ENABLE_UPDATES=true
+ENABLE_UPDATES=false

142c142
-WEB_CONCURRENCY=1
+WEB_CONCURRENCY=4

160,162c160,162
-SLACK_VERIFICATION_TOKEN=your_token
-SLACK_APP_ID=A0XXXXXXX
-SLACK_MESSAGE_ACTIONS=true
+# SLACK_VERIFICATION_TOKEN=your_token
+# SLACK_APP_ID=A0XXXXXXX
+# SLACK_MESSAGE_ACTIONS=true

165c165
-GOOGLE_ANALYTICS_ID=
+# GOOGLE_ANALYTICS_ID=

170,171c170,171
-SENTRY_DSN=
-SENTRY_TUNNEL=
+# SENTRY_DSN=
+# SENTRY_TUNNEL=

175,182c175,182
-SMTP_HOST=
-SMTP_PORT=
-SMTP_USERNAME=
-SMTP_PASSWORD=
-SMTP_FROM_EMAIL=hello@example.com
-SMTP_REPLY_EMAIL=hello@example.com
-SMTP_TLS_CIPHERS=
-SMTP_SECURE=true
+# SMTP_HOST=
+# SMTP_PORT=
+# SMTP_USERNAME=
+# SMTP_PASSWORD=
+# SMTP_FROM_EMAIL=hello@example.com
+# SMTP_REPLY_EMAIL=hello@example.com
+# SMTP_TLS_CIPHERS=
+# SMTP_SECURE=true

添加 nginx 配置

server stream 都少不了

stream {
    map $ssl_preread_server_name $name {
...
+       outline.example.com outline;
    }
...
...
+   upstream outline {
+       server 127.0.0.3001;
+   }

顺便要加 TLS 配置,不要忘记。我用了 wildcard, 复用分离出来的 TLS 配置文件就行了。

server {
    listen 127.0.0.1:3001 ssl; http2 on;
    server_name  outline.example.com;

    # TLS settings
    include /etc/nginx/conf.d/xx-tls.conf;
    # enforce authenticated pull
    include /etc/nginx/conf.d/xx-verify-client.conf;

    location / {
        proxy_pass http://127.0.0.1:3000/;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $host;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Scheme $scheme;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}

其他的一些就不再提了。有坑慢慢填。

初次启动

nginx -t 检查一下配置,然后 systemctl reload nginx 重启 nginx.

容器部分,先启动 redis 和 postgres, 再启动 app 本体。

docker compose up redis postgres

然后另外开个 tty(用 tmux/screen 更快)

docker compose up outline

观察一下启动好了以后,访问 outline 实例,用 OIDC 登录。

跳转到授权界面,权限要得属实有些多了,forgejo 什么时候修

然后就能进来了,尽情玩耍吧。