23.6K Star,告别Notion、飞书,开源在线协作云文档平台 Outline 部署指南

冰岩作坊 March 23, 2024

★全文约 4300 字,阅读约需 8 分钟

背景介绍

由于经常折腾,扬系统跟吃饭喝水一样频繁,而我之前的 Obsidian 配合 Git 同步使用,虽然不用担心数据丢失,但是每次扬完系统后都需要拉取仓库 & 下载 Obsidian 客户端,次数一多就开始厌烦了,特别是想记点什么东西的时候发现还要去拉仓库,下客户端,就立马失去了动力,于是最近一直在寻找好用的 Obsidian/Notion/飞书 替代品,既要有基础的功能,又能够支持 Web 即开即用,同时最好能够自托管,还能快速通过链接分享一些内容。找来找去,找到了三个候选项目:Appflowy[1], Affine[2], Outline[3]

Appflowy

在准备部署 Appflowy 的时候发现 Appflowy Docker部署居然需要使用 X Server,我完全不理解为什么一个笔记软件需要用到 X Server,文档中也没有解释为什么需要这个,在社区逛了一遍后看到了这个

一个笔记软件需要使用 X Server 明显超出了合理的范畴,我不想去解决一系列权限和依赖问题,纯属徒增部署难度,果断 Pass

AFFiNE

而 Affine 是我最先部署成功并尝试的项目,但是部署后发现以下提示:

★The server is not gonna save you note unless the cloud is enabled. All data will be saved in the browser’s local storage.

这意味着我自托管后还要登录使用他们官方 AFFiNE Cloud?使用也就罢了,确实存在部分产品是支持自托管登录后使用他们的云服务的,但是问题是,那都是基于登录账号提供增值服务的前提下。AFFiNE 的意思是,哪怕你是自托管也必须使用它们的云服务,不然你的数据只会保存在你的浏览器本地,而不会保存到托管服务器中。

Pass

Outline

现在,三款产品已经排除了两款,已经没有可以选择的了

先介绍下 Outline 有哪些特性:

但是在尝试部署 Outline 的时候也是吃尽了苦头,整个部署会非常的麻烦,不过最终体验确实能够值回部署付出的精力,长达两百多行的环境变量配置文件(虽然大多是注释,但若只是想尽快尝试看到效果来说配置起来也着实痛苦,并且还要不断根据错误调整修改配置文件),只支持 SSO 登录而不支持账号密码登录,而直接适配的 SSO 登录只有 Slack, Google (Workspace), Azure 等少数团队协作平台,同时很呆的OIDC配置也是导致也是踩了不少坑。本篇记录一下我在部署 Outline 途中踩的坑和问题,给后续想要自托管 Outline 的小伙伴提供一个参考。

Docker Compose部署

这里贴上我的Dockers Compose部署配置文件,是根据官方提供的 docker-compose.yml[5] 文件修改而来,主要修改是将 默认的反向代理服务替换为了我一直使用的 traefik 反向代理需要的配置,同时将所有的 volumes 修改为了相对路径,如果你之前没有配置过 traefik 的话,我建议直接使用官方提供的配置文件

1
version: "3.2"services:  outline:    image: outlinewiki/outline:latest    env_file: ./docker.env    # labels 用于配置 traefik 的路由,如果你使用别的反向代理服务可以直接忽略    labels:      - "traefik.http.routers.outline.rule=Host(`outline.xeu.life`)"      - "traefik.enable=true"      - "traefik.http.routers.outline.entrypoints=https"      - "traefik.http.routers.outline.tls=true"      - "traefik.http.routers.outline.tls.certresolver=mresolver"    expose:      # traefik 不关心服务对外暴露的端口,所以这里使用了 expose,如果没有反向代理可以改成 ports 进行端口映射      - "3000"    networks:      # traefik 需要服务处于同一网络下,如果不使用 taefik 可删除,后面两个服务的 networks 也是一样      - nets    volumes:      - ./storage-data:/var/lib/outline/data    depends_on:      - postgres      - redis  redis:    image: redis    env_file: ./docker.env    ports:      - "6379:6379"    volumes:      - ./redis.conf:/redis.conf    networks:      - nets    command: ["redis-server""/redis.conf"]    healthcheck:      test: ["CMD""redis-cli""ping"]      interval10s      timeout30s      retries3  postgres:    image: postgres    env_file: ./docker.env    ports:      - "5432:5432"    volumes:      - ./database-data:/var/lib/postgresql/data    healthcheck:      test: ["CMD""pg_isready -d outline -U user"]      interval30s      timeout20s      retries3    networks:      - nets    environment:      POSTGRES_USER'user'      POSTGRES_PASSWORD'pass'      POSTGRES_DB'outline'networks:  nets:    external:      # 外部预创建的 docker network       name: nets

环境变量配置

环境变量直接从官方仓库中的 .env.sample[6] 复制,并根据自己的情况进行修改,里面的注释已经比较详细了,这里不再赘述,下文重点提一下 Github OIDC 接入和文件存储接入。

接入 SSO

由于 Outline 提供的几个平台我都不怎么使用,唯一尝试了常使用的 Google 登录以后才知道 Outline 不支持个人账号使用 Google 登录,必须是 Google Workspace 的用户,而这个是 Google 的一个团队协同办公服务,开通需要花钱,只能放弃,选择唯一的 OIDC 接入

自建 SSO

对于其他 SSO 无法正常登录的情况下可以尝试自己部署一个 SSO 服务接入,具体可以参考以下博客

https://www.luckzym.com/posts/a239536c/,这里就不再赘述了

OIDC 理论上来说是 OAuth 2.0 的超集,支持所有 OAuth 的服务,于是我开始尝试接入最方便也是最常用的 Github OAuth

接入 Github OAuth

打开 https://github.com/settings/developers,选择 New OAuth App 创建一个新的 Oauth App,填入自己的应用名称与主页地址(带http://或https://),Authorization callback URL 填写

1
https://<你的Outline地址>/auth/oidc.callback

这里附上我的参数

随后配置 docker.env 中 OIDC 部分

以下是具体的配置,OIDC_CLIENT_ID填写 Github OAuth App 中的Client ID,OIDC_CLIENT_SECRET填写在 Github OAuth App 点击 Generate a new client secret 后的 Client secret,注意每次创建后只展示一次,后续无法查看,如果不慎丢失重新生成一个新的即可

1
To configure generic OIDC auth, you'll need some kind of identity provider.# See documentation for whichever IdP you use to acquire the following info:# Redirect URI is https://<URL>/auth/oidc.callbackOIDC_CLIENT_ID=<Client ID> OIDC_CLIENT_SECRET=<Client secret>OIDC_AUTH_URI=https://github.com/login/oauth/authorizeOIDC_TOKEN_URI=https://github.com/login/oauth/access_tokenOIDC_USERINFO_URI=https://api.github.com/userOIDC_LOGOUT_URI=# Specify which claims to derive user information from# Supports any valid JSON path with the JWT payloadOIDC_USERNAME_CLAIM=name# Display name for OIDC authenticationOIDC_DISPLAY_NAME=Github# Space separated auth scopes.OIDC_SCOPES=read:user user:email

到这里按理说就基本上可用了,但是我在使用 Github OAuth 时登录失败,网页只出现一个!,并没有错误信息,在日志中查看发现出现An email field was not returned in the profile parameter, but is required.错误

这个问题困扰了我好久,通过不断翻 issue 和 discussion 终于找到一个相关的讨论:https://github.com/outline/outline/pull/2399#issuecomment-916036880

简单来说就是因为 Outline 使用 OIDC 登录时必须要获取到 email 字段,但是 Github 的隐私邮箱功能会隐藏邮箱导致 Outline 只能获取到 null,解决方案也很简单:关闭隐私邮箱即可(虽然不是很优雅,但是这是目前为止唯一的解决方案,自用的话足够了)

Github 关闭隐私邮箱

打开 https://github.com/settings/emails,取消勾选 Keep my email addresses private。我最初以为这样就足够了,因为邮箱列表中已经有了如下提示:但是问题仍然存在,实际上还需要在 Profile[7] 中第二项 Public email 选择你的邮箱,并 Update Profile 之后 Outline 才能够获取到你的邮箱。

存储服务

存储服务我最初使用的腾讯云对象存储服务,腾讯云对象存储支持 AWS S3 协议接入,但是在实际配置好后发现上传失败,在经过排查后发现服务器能够正常与 COS 通信并创建上传文件的申请,但是浏览器发送的上传请求被拒绝了,返回了 403,错误信息为:

1
Condition key x-cos-algorithm doesn't match the value AWS4-HMAC-SHA256

经过一番查找,最终找到这样一篇讨论:https://github.com/outline/outline/discussions/3159,简单来说应该是腾讯云 COS 的问题(也可能是我的配置问题,但我没有继续研究下去),虽然讨论中提到腾讯云应该会在未来解决这个问题,但讨论是在 2022 年,而现在是 2024 …,所以只能考虑别的 S3 兼容云服务或者直接使用 local 本地存储

而我在使用本地存储的时候也发现上传失败,查看日志发现是没有权限,将存储文件夹权限修改为 777 解决了这个问题。

使用阿里云 OSS

考虑到备份的问题,最终还是选择将存储服务迁移到对象存储,而已知阿里云是支持,于是开始设置使用阿里云 OSS

阿里云正常创建一个对象存储桶,然后在配置文件中填入以下内容

1
AWS_ACCESS_KEY_ID=AWS_SECRET_ACCESS_KEY=# 存储桶区域,如:oss-cn-shanghaiAWS_REGION= AWS_S3_ACCELERATE_URL=https://..aliyuncs.comAWS_S3_UPLOAD_BUCKET_URL=https://..aliyuncs.comAWS_S3_UPLOAD_BUCKET_NAME=AWS_S3_FORCE_PATH_STYLE=falseAWS_S3_ACL=private# Specify what storage system to use. Possible value is one of "s3" or "local".# For "local", the avatar images and document attachments will be saved on local disk.FILE_STORAGE=s3

Access Key 和 Secret Access Key 在 https://ram.console.aliyun.com/users 创建一个新的子用户,然后创建 AccessKey 即可获取。

接下来在对象存储中配置 CORS ,存储桶 > 数据安全 > 跨域设置,创建规则,来源处填写你的 outline 地址,然后允许 Methods 全选确定即可

### 文件迁移

从本地存储迁移到阿里云后,原先的文件都将无法打开,需要前往服务器的本地存储文件夹,即 outline 映射的文件夹(参见上文的 docker-compose.yml 文件定义),在本文中为./storage-data ,将其中的内容打包

1
cd ./storage-datazip -r image.zip *

然后通过 scp 或者别的方法导出/下载到本地,随便解压到一个文件夹,前往阿里云对象存储的文件列表,点击上传文件,点击扫描文件夹,选中你解压出来的uploads或public文件夹(不要直接选中根目录,会导致上传后多一层文件夹),然后上传即可,上传成功后对象存储中的根目录文件列表应该长这样:

如果你的 public 和 uploads (可能其中有的文件夹不存在,这取决于你之前上传的文件类型)文件夹没有在根目录,而在二级目录(如 image/uploads )那么说明上传的时候选中的是 image文件夹,而不是uploads 文件夹,删除重新上传即可,上传完成之后原来的图片理论上就能够正常访问了

配置 CDN 加速

★CDN 加速为可选项,可以加速访问 & 一定程度节省分发的流量费用

阿里云创建一个新的 CDN 域名,回源选择OSS回源,选择自己的 outline 存储桶,随后就是常规的 CDN 配置:验证域名、设置 CNAME、申请证书、设置 HTTPS,除此以外有几处需要特别设置的地方:

  1. CDN 回源配置中不需要开启阿里云OSS私有Bucket回源
  2. 如果要配置访问控制中的 Referrer 过滤,需要把允许空 Referrer 勾选,因为 Outline 在实际展示图片时使用的是 no-referrer
  3. CDN 回源 HOST 选择加速域名
    1. 对象存储域名管理中绑定 CDN 的加速域名(不需要设置 CNAME)(这一步主要是统一回源域名)
    1. 配置文件中的域名配置为 CDN 的域名
    1
    AWS_S3_ACCELERATE_URL=https://<你的 CDN 加速域名>#AWS_S3_ACCELERATE_URL=https://..aliyuncs.comAWS_S3_UPLOAD_BUCKET_URL=https:/<你的 CDN 加速域名>#AWS_S3_UPLOAD_BUCKET_URL=https://..aliyuncs.com
    配置完成后重启容器即可

第3步与第5步主要目的是统一域名,鉴权时域名也被包含在计算范围内,如果域名不一致会导致鉴权不通过无法访问资源

第4步目的是允许使用指定的域名访问存储桶,如果不绑定回源域名的话也会被禁止访问

其他操作

关闭新用户注册

至此已经能够成功使用 Github OAuth 登录了,但是如果有其它用户获取到你的 Outline 链接,他也能够直接使用 OAuth 成功登录,如果不想要其他用户注册登录的话,在 Outline 设置中 > 安全性 > 域名白名单这里添加一个不存在的域名或者自己的域名即可,其他用户使用 OAuth 登录时只要 Outline 获取到用户的邮件地址没有在该域名白名单中就会禁止登录,已注册用户不受影响(注意白名单为空时会禁用白名单,而不是禁止所有用户注册)

参考资料[1]Appflowy: https://github.com/AppFlowy-IO/AppFlowy

[2]Affine: https://github.com/toeverything/AFFiNE

[3]Outline: https://github.com/outline/outline

[4]桌面客户端: https://www.getoutline.com/download

[5]docker-compose.yml: https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t

[6].env.sample: https://github.com/outline/outline/blob/main/.env.sample

[7]Profile: https://github.com/settings/profile