# outline 部署
## 什麼是 Outline
---
<https://www.getoutline.com/>
想找一個能夠替代 Confluence 管理文檔 ,既開源可本地部署,layout 又不會過於年代感的 wiki,找到了這個與 notion 87 分像的開源項目,於是著手開始進行部署設定。

\
## 環境建立
本篇將使用 docker 完成部署,參考 outline 的官方文件有直接建議 hosting outline 的做法
[Docker](https://app.getoutline.com/share/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t)
### 資料設定
1. Install [Docker Compose](https://docs.docker.com/compose/install/)
2. 建立 `docker-compose.yml` 檔案,並參考以下設定:
```yaml
version: "3"
services:
outline:
image: outlinewiki/outline
env_file: ./docker.env
ports:
- "3000:3000"
depends_on:
- postgres
- redis
- storage
redis:
image: redis
env_file: ./docker.env
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:14-alpine
env_file: ./docker.env
ports:
- "5432:5432"
volumes:
- database-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready -U user"]
interval: 30s
timeout: 20s
retries: 3
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: outline
storage:
image: minio/minio
env_file: ./docker.env
ports:
- "9000:9000"
- "9001:9001"
entrypoint: sh
volumes:
- https-portal-data:/var/lib/https-portal
healthcheck:
test: ["CMD", "service", "nginx", "status"]
interval: 30s
timeout: 20s
retries: 3
command: -c 'minio server /data --console-address ":9001"'
deploy:
restart_policy:
condition: on-failure
volumes:
- storage-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
https-portal:
image: steveltn/https-portal
env_file: ./docker.env
ports:
- '80:80'
- '443:443'
links:
- outline
- storage
restart: always
volumes:
- https-portal-data:/var/lib/https-portal
healthcheck:
test: ["CMD", "service", "nginx", "status"]
interval: 30s
timeout: 20s
retries: 3
volumes:
https-portal-data:
storage-data:
database-data:
```
與官方建議不同,需要注意的地方:
* 建議將 storage( minio )的兩個 port 輸出出來,`:9000` 指向 api,`:9001` 指向 console,承如 minIO 官方建議那樣,稍後也比較好設定其他配置。
3. 建立環境參數 `docker.env`
```bash
# –––––––––––––––– REQUIRED ––––––––––––––––
# Generate a hex-encoded 32-byte random key. You should use `openssl rand -hex 32`
# in your terminal to generate a random value.
SECRET_KEY=generate_a_new_key
# Generate a unique random key. The format is not important but you could still use
# `openssl rand -hex 32` in your terminal to produce this.
UTILS_SECRET=generate_a_new_key
# For production point these at your databases, in development the default
# should work out of the box.
# DATABASE_URL=postgres://user:pass@postgres:5432/outline
# DATABASE_URL_TEST=postgres://user:pass@postgres:5432/outline-test
# DATABASE_CONNECTION_POOL_MIN=
# DATABASE_CONNECTION_POOL_MAX=
# Uncomment this to disable SSL for connecting to Postgres
# PGSSLMODE=disable
# For redis you can either specify an ioredis compatible url like this
REDIS_URL=redis://redis:6379
# or alternatively, if you would like to provide addtional connection options,
# use a base64 encoded JSON connection option object. Refer to the ioredis documentation
# for a list of available options.
# Example: Use Redis Sentinel for high availability
# {"sentinels":[{"host":"sentinel-0","port":26379},{"host":"sentinel-1","port":26379}],"name":"mymaster"}
# REDIS_URL=ioredis://eyJzZW50aW5lbHMiOlt7Imhvc3QiOiJzZW50aW5lbC0wIiwicG9ydCI6MjYzNzl9LHsiaG9zdCI6InNlbnRpbmVsLTEiLCJwb3J0IjoyNjM3OX1dLCJuYW1lIjoibXltYXN0ZXIifQ==
# URL should point to the fully qualified, publicly accessible URL. If using a
# proxy the port in URL and PORT may be different.
URL=<your-outline-url>
PORT=3002
# See [documentation](docs/SERVICES.md) on running a separate collaborationNGUAGE
# server, for normal operation this does not need to be set.
COLLABORATION_URL=
# To support uploading of images for avatars and document attachments an
# s3-compatible storage must be provided. AWS S3 is recommended for redundency
# however if you want to keep all file storage local an alternative such as
# minio (https://github.com/minio/minio) can be used.
## 此段落設定給 storage - minio
MINIO_SERVER_URL=<your-minio-api-url>
MINIO_BROWSER_REDIRECT_URL=<your-minio-admin-url>
MINIO_ROOT_USER=<your-minio-username>
MINIO_ROOT_PASSWORD=<your-minio-password>
##MinIO 自己可以當 Object Storage,如果要 MinIO 去連 S3 需要加上 MINIO_REGION_NAME
#MINIO_REGION_NAME=
# A more detailed guide on setting up S3 is available here:
# => https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f
AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}
AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
#AWS_REGION=${MINIO_REGION_NAME}
AWS_S3_UPLOAD_BUCKET_URL=${MINIO_SERVER_URL}
AWS_S3_UPLOAD_BUCKET_NAME=<your-minio-bucket-name>
AWS_S3_UPLOAD_MAX_SIZE=26214400
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
# –––––––––––––– AUTHENTICATION ––––––––––––––
# Third party signin credentials, at least ONE OF EITHER Google, Slack,
# or Microsoft is required for a working installation or you'll have no sign-in
# options.
# To configure Slack auth, you'll need to create an Application at
# => https://api.slack.com/apps
#
# When configuring the Client ID, add a redirect URL under "OAuth & Permissions":
# https://<URL>/auth/slack.callback
SLACK_CLIENT_ID=get_a_key_from_slack
SLACK_CLIENT_SECRET=get_the_secret_of_above_key
# To configure Google auth, you'll need to create an OAuth Client ID at
# => https://console.cloud.google.com/apis/credentials
#
# When configuring the Client ID, add an Authorized redirect URI:
# https://<URL>/auth/google.callback
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# To configure Microsoft/Azure auth, you'll need to create an OAuth Client. See
# the guide for details on setting up your Azure App:
# => https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4
AZURE_CLIENT_ID=
AZURE_CLIENT_SECRET=
AZURE_RESOURCE_APP_ID=
# 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.callback
# OIDC_CLIENT_ID=75fe43a9d5b8cdd0f154f7ac8e95d668
# OIDC_CLIENT_SECRET=1029d3ca78ac322d495fbaa59afd394f
OIDC_AUTH_URI=
OIDC_TOKEN_URI=
OIDC_USERINFO_URI=
# Specify which claims to derive user information from
# Supports any valid JSON path with the JWT payload
OIDC_USERNAME_CLAIM=preferred_username
# Display name for OIDC authentication
OIDC_DISPLAY_NAME=OpenID
# Space separated auth scopes.
OIDC_SCOPES=openid profile email
# –––––––––––––––– OPTIONAL ––––––––––––––––
# Base64 encoded private key and certificate for HTTPS termination. This is only
# required if you do not use an external reverse proxy. See documentation:
# https://wiki.generaloutline.com/share/1c922644-40d8-41fe-98f9-df2b67239d45
SSL_KEY=
SSL_CERT=
# If using a Cloudfront/Cloudflare distribution or similar it can be set below.
# This will cause paths to javascript, stylesheets, and images to be updated to
# the hostname defined in CDN_URL. In your CDN configuration the origin server
# should be set to the same as URL.
CDN_URL=
# Auto-redirect to https in production. The default is true but you may set to
# false if you can be sure that SSL is terminated at an external loadbalancer.
FORCE_HTTPS=false
# Have the installation check for updates by sending anonymized statistics to
# the maintainers
ENABLE_UPDATES=true
# How many processes should be spawned. As a reasonable rule divide your servers
# available memory by 512 for a rough estimate
WEB_CONCURRENCY=1
# Override the maxium size of document imports, could be required if you have
# especially large Word documents with embedded imagery
MAXIMUM_IMPORT_SIZE=5120000
# You can remove this line if your reverse proxy already logs incoming http
# requests and this ends up being duplicative
DEBUG=http
# For a complete Slack integration with search and posting to channels the
# following configs are also needed, some more details
# => https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
#
SLACK_VERIFICATION_TOKEN=your_token
SLACK_APP_ID=A0XXXXXXX
SLACK_MESSAGE_ACTIONS=true
# Optionally enable google analytics to track pageviews in the knowledge base
GOOGLE_ANALYTICS_ID=
# Optionally enable Sentry (sentry.io) to track errors and performance
SENTRY_DSN=
# To support sending outgoing transactional emails such as "document updated" or
# "you've been invited" you'll need to provide authentication for an SMTP server
SMTP_HOST=
SMTP_PORT=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_FROM_EMAIL=
SMTP_REPLY_EMAIL=
SMTP_TLS_CIPHERS=
SMTP_SECURE=true
# Custom logo that displays on the authentication screen, scaled to height: 60px
# TEAM_LOGO=https://example.com/images/logo.png
# The default interface language. See translate.getoutline.com for a list of
# available language codes and their rough percentage translated.
DEFAULT_LANGUAGE=zh_TW
```
### 取得 Oauth
outline 較麻煩得是需要經過第三方認證,在註解 **AUTHENTICATION** Block 的部分,設定幾個項目就有幾個進入方式,但進入後,上方的 `.env` 註解的部分也有介紹,以下提供幾個方案
#### Google Oauth
* 在[ https://console.cloud.google.com/apis ]( https://console.cloud.google.com/apis ) 底下建立相關資訊,並取得 `GOOGLE_CLIENT_ID `與 `GOOGLE_CLIENT_SECRET`
* 需要注意的是使用 Google Oauth 被授權的 URL 要帶有 **https**

#### Slack OAuth
* 在 **Settings** → **Base Information** 取得 `SLACK_CLIENT_ID` 與 `SLACK_CLIENT_SECRET`
* 與 Google oauth 相同,在設定重新導向一樣要帶有 **https**
再到 **Features** → **Base Information** 設定 `https://<URL>/auth/slack.callback`

#### Custom OIDC
另外找了兩個可以自架第三方授權的項目,可以到此文章最下方的參考文件中的『其他人部署方法』,參考其他人部署 SSO 的方法。
* [Keycloak](https://www.keycloak.org/)
* [docker-sso-server](https://github.com/soulteary/docker-sso-server)
\
### Database
接下來繼續按照官方的方法繼續把資料庫,資料表建立起來:
```bash
docker-compose run --rm outline yarn sequelize db:create --env=production-ssl-disabled
```
Migrate the new database to add needed tables, indexes, etc:
```bash
docker-compose run --rm outline yarn sequelize db:migrate --env=production-ssl-disabled
```
### Running
建立好 `docker-compose.yml` 與 `docker.env` 後,在此目錄底下執行
```javascript
docker-compose up -d
```
就可以看到畫面了!但在看到畫面後,又遇到一些問題需要修正,再繼續往下看⋯⋯
## 解決問題
### 解決無法編輯文章的問題
部署完畢成功進到 outline 畫面後,會發現右下角出現斷線的 icon,並從 dev tool 中看到錯誤訊息

原因是因為 WebSocket 服務的 Nginx 配置出現問題,WebSocket 有連接的要求,因為要告訴服務端從 http 協議更換到 ws 協議,request / response header 都必須設置: `Upgrade: webSocket`、`Connection: Upgrade`。
### 為什麼要在 Nginx 配置以上兩項 header?
由於 `Upgrade`、`Connection` 都是 hop-by-hop header,在 domain 使用 nginx 做反向代理時,由於以上兩項 header 不會被帶到代理伺服器上,所以無法成功建立 ws 連線。
> hop-by-hop header:only for a single transport-level connection and must not be retransmitted by proxies or cached <Connection、Keep-Alive、Proxy-Authenticate、Trailer、TE、Transfer-Encoding、Upgrade>)
為了解決這個問題,Nginx 提供了 `proxy_set_header` 的配置,將資訊帶至代理伺服器上。
Nginx 配置以下:
```
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
add_header Service-Worker-Allowed /;
}
```
## 上傳圖片
反向代理設定 minIO ,一樣也需要進行 Nginx 設定,參考[==官網文件==](https://docs.min.io/docs/setup-nginx-proxy-with-minio.html)配置以下:
```
location / {
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_set_header Host $http_host;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://sit-minio-api;
}
```
### minIO 管理介面
storage(minIO)中的 `:9001` 指向 console,即是 minIO 的管理介面。
1. 使用配置中 `MINIO_ROOT_USER` 、 `MINIO_ROOT_PASSWORD` 內容進行登入之後,能夠看到以下畫面
2. 創建一個新的 bucket
* 名稱為配置中的 `AWS_S3_UPLOAD_BUCKET_NAME`
* 再進入 **Manage** → **Access Rules** 寫入以下設定

* 完成後回到 **Summary** 把 **Access Policy** 從 private 改為 custom,並寫入以下設定
```
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:GetBucketLocation",
"s3:ListBucketMultipartUploads"
],
"Resource": [
"arn:aws:s3:::outline"
]
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject",
"s3:AbortMultipartUpload"
],
"Resource": [
"arn:aws:s3:::outline/avatars*",
"arn:aws:s3:::outline/public*",
"arn:aws:s3:::outline/uploads*"
]
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"*"
]
},
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::outline"
],
"Condition": {
"StringEquals": {
"s3:prefix": [
"avatars",
"public",
"uploads"
]
}
}
}
]
}
```
以上設定完成後就可以成功上傳圖片與附加檔案!
:::tip
在 outline 中移除圖片並不會徹底刪除,如需徹底刪除圖片需要進到 minIO 的管理頁面,找到上傳的名稱後進行刪除!
:::
## 參考文件
* outline 官方部署:
<https://app.getoutline.com/share/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t>
* outline github&Discussions:<https://github.com/outline/outline>
* minIO 官方文件:<https://docs.min.io/docs/>
* outline 討論串 <https://www.reddit.com/r/selfhosted/comments/hq6m6b/how_to_self_host_outline_wiki/>
* 其他人的部署方法:
* <https://blog.gurucomputing.com.au/doing-more-with-docker/deploying-outline-wiki/>
* <https://www.luckzym.com/posts/a239536c/>
* <https://biteax.com/2022/05/07/Outline/%E3%80%90Outline%E3%80%91%E7%BA%AFDocker%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97/>
* <https://soulteary.com/2021/09/05/opensource-documentation-wiki-software-outline-part-1.html>