这是一款适用于个人或团队场景使用的开源文档/Wiki软件,Outline。
我在这两篇文章(https://soulteary.com/2021/09/05/opensource-documentation-wiki-software-outline-part-1.html、https://soulteary.com/2021/09/11/opensource-documentation-wiki-software-outline-part-2.html)看到了这个工具,打算试一试,结合那篇文章中罗列的信息,加上我自己的理解,基本上可以把这款软件的特点罗列如下:
- 能够将数据完全自托管管理,不涉及私有格式,而且可以随时导出成开源格式(例如 PDF、Markdown)
- Markdown 语法、所见即所得,可以直接上传附件和图片,也支持代码片段、数学公式
- 类 Notion,允许插入富文本内容、卡片式渲染
- 个人使用和管理文档,并在需要时可以邀请用户协同编辑、分享文档
- 层级嵌套,方便分类和整理
- 历史版本记录,并在文档被改动/编辑后有邮件提醒
缺点也不少:
- 不支持本地登录,只支持 OAuth 登录
- 不支持本地存储,只能使用 AWS S3 或者兼容 S3 协议的存储,例如 Minio
- 从文档中删除图片,未必能清理后端存储中的文件
- 没有评论功能,权限管理的层级不够丰富
- 很多设置项不能在网页端修改,只能重启 docker-compose
- 极度简陋的自托管支持,只能靠社区成员的零碎的讨论来解决问题
官方提供的 docker-compose 安装教程(https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t)非常简陋,有很多细节都没有解释,所以并不是开箱即用的。根据官方提供的模板,做了很多尝试,我终于搭建起来了。我把缺少的细节都记录一下。
我的 docker-compose 采用 env_file 读取环境参数,然后把环境参数都写在 .env 文件里面,这样我就也可以在 docker-compose.yml 里面用环境变量,方便些。
我把 https-portal 删掉了,我打算直接开放 outline 的 3000 端口,之后用 NGINX 转发;Redis、Postgres 都只用容器内网络通讯,不开放端口;Minio 我开放了 9000 端口,也绑定了一个控制台,因为这样也可以方便后面管理(虽然理论上它也只需要容器内通讯)。
outline:
image: ${DOCKER_OUTLINE_IMAGE_NAME}
container_name: outline
env_file: ./.env
ports:
- "9303:3000"
restart: always
networks:
- outline
extra_hosts:
- "${DOCKER_OUTLINE_HOSTNAME}:0.0.0.0"
depends_on:
- postgres
- redis
- storage
image: ${DOCKER_REDIS_IMAGE_NAME}
env_file: ./.env
volumes:
- ./redis.conf:/redis.conf
container_name: ${DOCKER_REDIS_HOSTNAME}
restart: always
networks:
- outline
command: ["redis-server", "/redis.conf"]
postgres:
image: ${DOCKER_POSTGRES_IMAGE_NAME}
env_file: ./.env
volumes:
- ./database-data:/var/lib/postgresql/data
container_name: ${DOCKER_POSTGRES_HOST}
restart: always
networks:
- outline
image: ${DOCKER_MINIO_IMAGE_NAME}
container_name: ${OUTLINE_MINIO}
env_file: ./.env
ports:
- "${DOCKER_OUTLINE_MINIO_PORT}:9000"
- "${DOCKER_OUTLINE_MINIO_ADMIN_PORT}:9001"
command: "minio server /data --console-address 0.0.0.0:${DOCKER_OUTLINE_MINIO_ADMIN_PORT}"
restart: always
extra_hosts:
- "${DOCKER_MINIO_HOSTNAME}:0.0.0.0"
- "${DOCKER_MINIO_ADMIN_DOMAIN}:0.0.0.0"
volumes:
- ./storage-data:/data
networks:
- outline
networks:
outline:
external: true
.env 文件保留了环境变量,根据自己的情况修改。
所有的 IMAGE_NAME 我都用了此刻(2023-03-23)最新的。
DOCKER_OUTLINE_IMAGE_NAME=outlinewiki/outline:0.68.1
DOCKER_POSTGRES_IMAGE_NAME=postgres:15.2
DOCKER_REDIS_IMAGE_NAME=redis:7.2-rc1
DOCKER_MINIO_IMAGE_NAME=minio/minio:RELEASE.2023-03-22T06-36-24Z
Outline 相关的参数如下:
DOCKER_OUTLINE_HOSTNAME=outline.example.com
OUTLINE_URL=https://${DOCKER_OUTLINE_HOSTNAME}
URL=${OUTLINE_URL}
PORT=3000
Postgres 和 Redis 的参数没有特殊的地方,只要注意容器内地址通讯即可,例如:
DATABASE_URL=postgres://${DOCKER_POSTGRES_USER}:${DOCKER_POSTGRES_PASS}@${DOCKER_POSTGRES_HOST}:5432/${DOCKER_POSTGRES_DBNAME}
当然也要注意把改过名字的参数类型映射回 docker 的环境变量会用的名字,以及我 disable 了 PostgreSQL 的 SSL。
POSTGRES_USER=${DOCKER_POSTGRES_USER}
PGSSLMODE=disable
Outline 不支持本地存储,他只开放了 AWS S3 存储,但是也可以使用兼容 S3 协议的其他存储(比如 Minio)。
Minio 是一个兼容 S3 协议的存储,简单说就是启动了一个服务之后,它把 S3 处理请求解析后,把文件存放到本地。docker-compose 中我们启动了这个 Minio 的 docker 镜像,并把 /data
目录挂载到了本地持久存储。
初始化 Minio 的时候,我提供了 MINIO_ROOT_USER
和 MINIO_ROOT_PASSWORD
,这两个后面会当作 S3 的 Access ID 和 Secret Key 来使用。它们都只由小写 a-z 和数字组成,前者 16 位,后者 64 位,我使用 https://onlinerandomtools.com/generate-random-string 生成。
因为是自己的 Minio,所以 MINIO_REGION_NAME
就可以随便写了,这个后面会当作 S3 的 Region 来使用。
DOCKER_MINIO_HOSTNAME=minio.example.com
DOCKER_MINIO_ADMIN_DOMAIN=minio-admin.example.com
为了管理方便,docker-compose 还启动了 9001 管理界面,这里我们做一下重定向。
MINIO_BROWSER=on
MINIO_BROWSER_REDIRECT_URL=https://${DOCKER_MINIO_ADMIN_DOMAIN}
DOCKER_OUTLINE_MINIO_PORT=9000
DOCKER_OUTLINE_MINIO_ADMIN_PORT=9001
后面大部分参数都可以按照官方示例(https://github.com/outline/outline/blob/main/.env.sample)中的说明来操作,比如使用 openssl rand -hex 32
来生成 SECRET KEY 等等。
AWS 存储就使用 Minio。
AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}
AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
AWS_REGION=${MINIO_REGION_NAME}
AWS_S3_UPLOAD_BUCKET_URL=https://${DOCKER_MINIO_HOSTNAME}
AWS_S3_UPLOAD_BUCKET_NAME=outline
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
登录是个大麻烦,Outline 不支持本地用户登录,它目前只支持 Slack、Azure、Google 以及 OIDC,所以需要去各平台生成 OAuth Token,还挺麻烦的。好在 GitLab 支持标准 OIDC 协议,而我有一个私有部署的 GitLab 实例,就直接接入了。
首先填写通用的信息,如下:
OIDC_AUTH_URI=https://gitlab.example.com/oauth/authorize
OIDC_TOKEN_URI=https://gitlab.example.com/oauth/token
OIDC_USERINFO_URI=https://gitlab.example.com/oauth/userinfo
OIDC_USERNAME_CLAIM=username
OIDC_DISPLAY_NAME=GitLab
OIDC_SCOPES=openid email
接着去管理中心 - 应用 - 实例 OAuth 应用程序 (https://gitlab.example.com/admin/applications),新建一个应用。
回调 URI 写 https://outline.example.com/auth/oidc.callback
,范围勾选 openid
和 email
,视情况选择是否可信和是否私密。
点击保存应用之后,把 CLIENT_ID
和 CLIENT_SECRET
填写到 .env 文件中。
还有剩下一些杂项,根据情况修改。
我把 FORCE_HTTPS
改成了 false
、DEFAULT_LANGUAGE
改成了 zh_CN
。因为我不打算使用 Slack,所以我还把 Slack 的默认数据都删掉了。我启用了 SMTP,我用的是 mailgun 的服务,所以修改了 TLS_CIPHERS 以支持 587 TLS。
SMTP_TLS_CIPHERS=TLSv1.2
SMTP_SECURE=false
接下去还要修改 NGINX 配置。
首先是 Outline。
Outline 的核心是 proxy_pass 的时候要加上一些 Header。
location / {
proxy_pass http://localhost:9303;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header Access-Control-Allow-Origin "*";
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;
}
如果遇到跨域的问题还要加 Allow-Origin。
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS,PUT;
add_header Access-Control-Allow-Headers Origin,X-Requested-With,Content-Type,Accept,Authorization;
if ($request_method = 'OPTIONS') {
return 204;
}
接着是 Minio。
Minio 直接转发就可以,不要带 Allow-Origin,也不要带 proxy_set_header,不然可能会出现奇怪的 CORS 错误(因为 Minio 有默认的 Allow *
的配置),或者可能出现管理界面 400 错误。
# minio-admin.example.com
location / {
proxy_pass http://localhost:9001;
}
# minio.example.com
location / {
proxy_pass http://localhost:9000;
}
接下来要新建一个 Minio 存储桶。
这一步也可以使用命令行完成,例如运行一个 minio/mc 的客户端,使用 /usr/bin/mc mb
来创建一个桶,并设置访问权限。
我访问了 minio.example.com,这会被自动重定向到 minio-admin.example.com,我使用刚才上面的 Access ID 和 Secret Key 作为用户名和密码登录,然后选择左侧 Buckets,新建一个存储桶,名称就用 docker-compose 或者 .env 文件中设置的。上例是 outline。
回到 docker-compose 和 .env 的目录,启动 docker-compose up -d
。
首次运行还需要创建数据库以及执行迁移。
docker-compose run --rm outline yarn db:create --env=production-ssl-disabled
docker-compose run --rm outline yarn db:migrate --env=production-ssl-disabled
运行之后可能还要根据自己的情况执行下面这个命令。这个命令是解决在内存不足的情况下后台保存可能会失败的问题。这个值是在主机级别,而不是容器级别。Redis 推荐 1 的原因是他们的后台保存机制(https://redis.io/topics/faq#background-saving-fails-with-a-fork-error-under-linux-even-if-i-have-a-lot-of-free-ram)。
sysctl vm.overcommit_memory=1
此时回到 outline.example.com 应该一切正常了。