Очередная статья про настройку Outline + KeyCloak. С одной стороны, статьи в сети есть. С другой стороны - начинаешь по ним делать и выплывают подводные камни, которые в статьях почему то не приведены. После определенного периодического мучения с установкой данного продукта решил собрать в кучу все мысли и действия.

Первоисточник: Основной алгоритм был взят из статьи.

Первичная подготовка системы

Выбираем, что нам по душе - Debian 12 или Ubuntu Server. Алгоритм в этих вариантах один и тотже.

Устанавливаем Docker согласно документации: Docker_on_Debian или Docker_on_Ubuntu.

Придумываем и регистрируем во внешних DNS-серверах три DNS-записи для трех решений:

  • wiki.<наш домен>

  • kk.<наш домен>

  • minio.<наш домен>

ВАЖНО: Если на Linux машине IP-адрес серый (но в нас есть DNAT портов 80/443 либо Reverse Proxy), обязательно нужно в файле /etc/hosts прописать соответствие серого IP-адреса и созданным трем DNS-записям. Вообще в любом варианте (белый IP на интерфейсе) можно это сделать, лишним не будет. Если этот шаг упустить - KeyCloak аутентификация у меня не работает с Outline.

Установка KeyCloak

Создаем каталог для размещения сценария для Docker:

mkdir -pv /opt/keycloak
nano /opt/keycloak/docker-compose.yml

Заполняем файл, меняем WEB-имя сервера и пароль доступа к админской части:

version: '3'
 
volumes:
  keycloak-db:
 
services:
  keycloak:
      image: quay.io/keycloak/keycloak:latest
      environment:
        KC_PROXY: edge
        KEYCLOAK_ADMIN: admin
        KEYCLOAK_ADMIN_PASSWORD: XXXXXXXXXX
        KC_HOSTNAME: kk.vdsina.vershininegor.site
        KC_HOSTNAME_STRICT_HTTPS: 1
        KC_HOSTNAME_STRICT: 1
      volumes:
        - keycloak-db:/opt/keycloak/data/h2
      ports:
        - 7214:8080
      command: "start --optimized"

Сохраняем, далее, переходим в каталог /opt/keycloak/ и запускаем установку контейнера:

cd /opt/keycloak/
docker compose up -d

Ожидаемый вывод, если у нас есть Интернет, и мы правильно установили Docker:

root@ubuntu:/opt/keycloak# docker compose up -d
[+] Running 5/5
 ✔ keycloak 4 layers [⣿⣿⣿⣿]      0B/0B      Pulled                       15.3s
   ✔ da5b6ed7dedb Pull complete                                            2.2s
   ✔ 235cc15c2bca Pull complete                                            6.5s
   ✔ 33a5d0f592f8 Pull complete                                            8.5s
   ✔ 881ee45db5ad Pull complete                                            8.5s
[+] Running 3/3
 ✔ Network keycloak_default       Created                                  0.1s
 ✔ Volume "keycloak_keycloak-db"  Created                                  0.0s
 ✔ Container keycloak-keycloak-1  Started 

После запуска смотрим статус контейнера командой:

docker compose ps -a

Через некоторое время видим, что контейнер не запущен, статус остановки Exited:

NAME                  IMAGE                              COMMAND                  SERVICE             CREATED             STATUS                      PORTS
keycloak-keycloak-1   quay.io/keycloak/keycloak:latest   "/opt/keycloak/bin/k…"   keycloak            17 minutes ago      Exited (1) 17 minutes ago

Смотрим журнал контейнера (docker logs <имя контейнера>):

2023-08-15 09:14:58,626 WARN  [io.agroal.pool] (agroal-11) Datasource '<default>': Error opening database: "Could not save properties /opt/keycloak/data/h2/keycloakdb.lock.db" [8000-220]
2023-08-15 09:14:58,653 INFO  [org.infinispan.CLUSTER] (main) ISPN000080: Disconnecting JGroups channel `ISPN`
2023-08-15 09:14:58,695 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Failed to start server in (production) mode
2023-08-15 09:14:58,696 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Failed to obtain JDBC connection
2023-08-15 09:14:58,696 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: Error opening database: "Could not save properties /opt/keycloak/data/h2/keycloakdb.lock.db" [8000-220]
2023-08-15 09:14:58,696 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) ERROR: /opt/keycloak/data/h2/keycloakdb.lock.db
2023-08-15 09:14:58,698 ERROR [org.keycloak.quarkus.runtime.cli.ExecutionExceptionHandler] (main) For more details run the same command passing the '--verbose' option. Also you can use '--help' to see the details about the usage of the particular command.

Исправляется следующей командой:

chmod 777 /var/lib/docker/volumes/keycloak_keycloak-db/_data/

Останавливаем и снова запускаем контейнер:

cd /opt/keycloak/
docker compose down
docker compose up -d

Теперь команды docker compose ps -a и lsof -i -nP должны показывать, что контейнер с KeyCloak работает и порты доступа открыты.

SSL/TLS сертификаты для Nginx

KeyCloak и Outline по умолчанию настроены на работу только по HTTPS. При этом они также не доверяют самоподписанным сертификатам и заставить их работать у меня не получилось до конца (хотя попытки были и кое какой прогресс был).

Поэтому нам необходим белый IP, публичные DNS-записи и утилита python3-certbot. Устанавливаем, запускаем (certbot certonly), получаем на 3 месяца сертификат на три публичных DNS-записи.

Установка Nginx

Во многих статьях используют Docker контейнер с Nginx Proxy Manager, который дает GUI для настройки и сам способен автоматически продлять Les't Encrypt сертификаты. В прошлом я его использовал, в последний раз решил от него отказаться в пользу установки Nginx на базовую систему (потому что захотелось).

apt install nginx
cd /etc/nginx/sites-enabled

Настройка Nginx для Outline, KeyCloak и MinIO

Создаем следующие конфигурации - /etc/nginx/sites-enabled/keycloak.conf:

server {
    listen 80;
    server_name kk.vdsina.vershininegor.site;
    rewrite ^ https://$server_name$request_uri? permanent;
}
 
server {
    listen 443 ssl;
 
    server_name kk.vdsina.vershininegor.site;
 
    # Logging (optional, but probably want it for debugging)
    access_log /var/log/nginx/keycloak-access.log;
    error_log /var/log/nginx/keycloak-error.log info;
 
    ## Keep alive timeout set to a greater value for SSL/TLS.
    keepalive_timeout 75 75;
 
    # Locations of certs using Certbot
    ssl_certificate /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/privkey.pem;
    ssl_session_timeout 5m;
 
    # Forces browsers to redirect to HTTPS themselves for 31536000 seconds (1 year).
    # includeSubDomains will cause this to upgrade HTTP on all subdomains of the given domain.
    # preload adds your domain to a list of domains for browsers to automatically upgrade to HTTPS, no mattter what.
    # always will append the header no matter the return code.
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
 
    # SSO requests tend to be very long, will fail on default settings.
    proxy_buffer_size 12k;
 
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-Proto https;
 
    location /js/ {
        proxy_pass http://127.0.0.1:7214;
    }
 
    location /realms/ {
        proxy_pass http://127.0.0.1:7214;
    }
 
    location /auth/ {
        proxy_pass http://127.0.0.1:7214;
    }
 
    location /resources/ {
        proxy_pass http://127.0.0.1:7214;
    }
 
    location /robots.txt {
        proxy_pass http://127.0.0.1:7214;
    }
 
    # We don't really care about anything else.
    location / {
        return 404;
    }
 
    # -------------------
    # I recommend deleting the /admin/ endpoint once done for security reasons.
    # -------------------
 
    location /admin/ {
        proxy_pass http://127.0.0.1:7214;
    }
 
}

/etc/nginx/sites-enabled/minio.conf

server {
    listen 80;
    server_name minio.vdsina.vershininegor.site;
    rewrite ^ https://$server_name$request_uri? permanent;
}

server {
    listen 443 ssl;

    server_name minio.vdsina.vershininegor.site;

    # Logging (optional, but probably want it for debugging)
    access_log /var/log/nginx/minio-access.log;
    error_log /var/log/nginx/minio-error.log info;

    ## Keep alive timeout set to a greater value for SSL/TLS.
    keepalive_timeout 75 75;

    # Locations of certs using Certbot
    ssl_certificate /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/privkey.pem;
    ssl_session_timeout 5m;

    # Forces browsers to redirect to HTTPS themselves for 31536000 seconds (1 year).
    # includeSubDomains will cause this to upgrade HTTP on all subdomains of the given domain.
    # preload adds your domain to a list of domains for browsers to automatically upgrade to HTTPS, no mattter what.
    # always will append the header no matter the return code.
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # SSO requests tend to be very long, will fail on default settings.
    proxy_buffer_size 12k;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-Proto https;

    ignore_invalid_headers off;
    client_max_body_size 0;
    proxy_buffering off;
    proxy_request_buffering off;

    location / {
        proxy_set_header Host $http_host;
        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_connect_timeout 300;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        chunked_transfer_encoding off;

        proxy_pass http://127.0.0.1:7216;
    }

}

/etc/nginx/sites-enabled/outline.conf

server {
    listen 80;
    server_name wiki.vdsina.vershininegor.site;
    rewrite ^ https://$server_name$request_uri? permanent;
}

server {
    listen 443 ssl;

    server_name wiki.vdsina.vershininegor.site;

    # Logging (optional, but probably want it for debugging)
    access_log /var/log/nginx/outline-access.log;
    error_log /var/log/nginx/outline-error.log info;

    ## Keep alive timeout set to a greater value for SSL/TLS.
    keepalive_timeout 75 75;

    # Locations of certs using Certbot
    ssl_certificate /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/wiki.vdsina.vershininegor.site/privkey.pem;
    ssl_session_timeout 5m;

    # Forces browsers to redirect to HTTPS themselves for 31536000 seconds (1 year).
    # includeSubDomains will cause this to upgrade HTTP on all subdomains of the given domain.
    # preload adds your domain to a list of domains for browsers to automatically upgrade to HTTPS, no mattter what.
    # always will append the header no matter the return code.
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # SSO requests tend to be very long, will fail on default settings.
    proxy_buffer_size 12k;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-Proto https;


    location / {
        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;
        # Set to the correct ports for Outline
        proxy_pass http://127.0.0.1:7215;
    }

}

Перезапускаем Nginx:

systemctl restart nginx

Настройка KeyCloak

Входим по адресу: https://kk.<наш домен>/admin , авторизуемся учеткой admin с паролем, который задан в docker-compose.yml файле:

Создаем новый Realm с именем outline:

Находясь уже в новом созданном Realm с именем outline, создаем нового клиента (это наш сайт с Outline):

Указываем имя, описание и ID - outline-wiki :

Включаем клиентскую аутентификацию и отключаем прямой вход:

Указываем ряд URL-адресов нашей WiKi:

Настройка KeyCloak - вторая часть

Теперь нам нужно сохранить себе мастер-ключ для взаймодействия с этим клиентом. Переходим в раздел Credentials и копируем себе ключ, который сгенерировался при создании клиента KeyCloak:

Теперь необходимо создать пользователя в KeyCloak - с его помощью мы будем авторизовываться и работать в Outline Wiki.

В левом фрейме переходим в раздел: Users и добавляем нового пользователя: Add User. Логин и email должны совпадать.

После создания пользователя у него не задан пароль - необходимо это исправить в разделе Credentials через кнопку Set password:

На этом работа с KeyCloak закончена. Других пользователей Outline создавать аналогично.

Запуск контейнеров с PostgreSQL, Redis, MinIO и Outline

Создаем нужный каталог и файлы:

mkdir -pv /opt/outline
touch /opt/outline/docker-compose.yml
touch /opt/outline/docker.env

Содержимое файла docker-compose.yml:

version: "3"
services:
 
  outline:
    image: outlinewiki/outline:latest
    env_file: ./docker.env
    networks:
      - outline-network
    ports:
      - "7215:3000"
    depends_on:
      - postgres
      - redis
      - minio
    command: sh -c "yarn db:migrate --env production-ssl-disabled && yarn start"
 
  redis:
    image: redis
    env_file: ./docker.env
    networks:
      - outline-network
    volumes:
      - ./redis.conf:/redis.conf
    command: ["redis-server", "/redis.conf"]
 
  postgres:
    image: postgres
    env_file: ./docker.env
    networks:
      - outline-network
    volumes:
      - outline-postgres-data:/var/lib/postgresql/data
 
  minio:
    image: minio/minio:latest
    env_file: ./docker.env
    networks:
      - outline-network
    ports:
      - "7216:9000"
      - "9001:9001"
    deploy:
      restart_policy:
        condition: on-failure
    volumes:
      - outline-minio-data:/data
    entrypoint: sh
    command: -c 'minio server --console-address ":9001" /data'
 
volumes:
  outline-minio-data:
  outline-postgres-data:
 
networks:
  outline-network:
    name: outlinewiki-network

Содержимое с настройками (будем дальше несколько раз менять) - docker.env :

# Fill these out with "pwgen 64 1" or "openssl rand -hex 32".
SECRET_KEY=2787d5895f5a61235eb73e8dc1712e57bbfa4a4d7fc3a8bf10d2ed4570c0869b
UTILS_SECRET=d679896a4576c0887862b72b7bee1ae3c68d050d7f40118678d089675fefd367
 
# Please see "Enviroment Variables" for more information.
# Make sure to change "user" and "pass".
POSTGRES_USER=admin
POSTGRES_PASSWORD=XXXXXXXXXX
POSTGRES_DB=outline
 
# Please see "Enviroment Variables" for more information.
# Make sure to change "user" and "pass".
# If you changed POSTGRES_DB, change "outline", too.
DATABASE_URL=postgres://admin:XXXXXXXXXX@postgres:5432/outline
 
# Was in the official Docker.env. Probably not needed?
DATABASE_URL_TEST=postgres://admin:XXXXXXXXXX@postgres:5432/outline-test
 
# This is the domain name to access Outline. Include the "https://"
URL=https://wiki.vdsina.vershininegor.site
 
# Please see "Enviroment Variables" for more information.
# Make sure to change "user" and "pass".
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=XXXXXXXXXX
 
# You will need to get this from the Minio admin panel.
AWS_ACCESS_KEY_ID=DpzbsYrmOnAcbSGq
AWS_SECRET_ACCESS_KEY=ESMxba21uVn0NXK7UzzXaJhEmfvYJT9l
AWS_REGION=us-east-1
 
AWS_S3_UPLOAD_BUCKET_URL=https://minio.vdsina.vershininegor.site/
AWS_S3_UPLOAD_BUCKET_NAME=datawiki
AWS_S3_UPLOAD_MAX_SIZE=26214400
## Probably shouldn't change these
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private
 
# You will need to get this from the Keycloak admin panel. Please see "Keycloak OIDC Setup".
OIDC_CLIENT_ID=outline-wiki
OIDC_CLIENT_SECRET=Kw4NvTB1nEB89iTSzQ91IHRk4k3smrqh
OIDC_AUTH_URI=https://kk.vdsina.vershininegor.site/realms/outline/protocol/openid-connect/auth
OIDC_TOKEN_URI=https://kk.vdsina.vershininegor.site/realms/outline/protocol/openid-connect/token
OIDC_USERINFO_URI=https://kk.vdsina.vershininegor.site/realms/outline/protocol/openid-connect/userinfo
## Changes what shows up on the login button.
OIDC_DISPLAY_NAME=Keycloak
## Probably shouldn't change
OIDC_USERNAME_CLAIM=preferred_username
OIDC_SCOPES=openid profile email
 
# Telemtry?!?!! :O
# Enable if you want.
ENABLE_UPDATES=false
 
# Check official docs for this. Has to be with processes or whatever.
WEB_CONCURRENCY=1
 
# Limits size of documents (esp. with images) in (I think) bytes,
MAXIMUM_IMPORT_SIZE=5120000
 
# Default langauage. Supported codes at translate.getoutline.com.
DEFAULT_LANGUAGE=en_US
 
# ----------------------------------
# STUFF YOU PROBABLY SHOULD NOT CHANGE.
# ----------------------------------
 
# Self-explanatory.
NODE_ENV=production
 
# Docker will forward this specific port to the host as the one specified in the compose file.
PORT=3000
 
# Accessing self-hosetd redis through the Docker network.
REDIS_URL=redis://redis:6379
 
# We will be using Postgres through the Docker network. So no HTTPS.
PGSSLMODE=disable
 
# We will terminate SSL on nginx.
FORCE_HTTPS=false

В параметр OIDC_CLIENT_SECRET впишите ключ, который мы сохраняли сразу после создания клиента в KeyCloak (outline-wiki).

Параметры AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_S3_UPLOAD_BUCKET_NAME пока оставляем как есть, мы их исправим позже, когда установим, запустим и настроим параметры контейнера с MinIO (S3-хранилища).

Теперь необходимо запустить загрузку и запуск контейнеров:

cd /opt/outline/
docker compose up -d

Настройка MinIO

Подключаемся к серверу по адресу: http://<IP>:9001

Входим учеткой admin и паролем, который мы указали в docker.env файле.

ПЕРВЫЙ ШАГ - сменить регион расположения S3 хранилища и перезапустить сервис:

Далее жмем кнопку: Restart и дал должно из сервиса выкинуть (снова на страницу входа):

Снова входим и настраиваем.

Настройка MinIO - часть 2

Теперь создаем Bucket для хранения картинок и другого контента wiki:

Выбираем имя - datawiki. Тут есть тонкости - если выбирать имена, пересекающиеся с именами URL-адресов, будут проблемы.

В созданном Bucket создаем Access Rule:

Создаем Access Key для параметров AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY в файле docker.env:

Записываем в блокнот данные для AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY (больше их посмотреть не удастся).

Настройка MinIO закончена.

Донастройка контейнеров

Теперь нам нужно изменить содержимое файла /opt/outline/docker.env и указать параметры:

AWS_ACCESS_KEY_ID=Zk27jPsxqE0ro9B2
AWS_SECRET_ACCESS_KEY=MmqmnkUcvONVwhm8sBtTpa6wW1n2KsCj
AWS_REGION=us-east-1
 
AWS_S3_UPLOAD_BUCKET_URL=https://minio.vdsina.vershininegor.site/
AWS_S3_UPLOAD_BUCKET_NAME=datawiki
AWS_S3_UPLOAD_MAX_SIZE=26214400
## Probably shouldn't change these
AWS_S3_FORCE_PATH_STYLE=true
AWS_S3_ACL=private

Теперь перезапускаем контейнеры с этими параметрами:

cd /opt/outline/
docker compose down
docker dompose up -d

Проверка работы

Входим по адресу: https://wiki.vdsina.vershininegor.site.

При входе появляется кнопка - продолжить вход с помощью KeyCloak.

Авторизуемся в нем и нас пускают в Outline Wiki.

Потребление ресурсов

На самой дешевой виртуалке за 15 рублей в день (1 ядро, 2 Гб ОЗУ) это способно жить и работать при 2-3 пользователях. На холостом ходу Active ОЗУ чуть больше 1,2 Гб.

Личное мнение автора

С 2006 года пользуюсь сам DokuWiki, накопил более 900 заметок и статей для своих нужд. С тех под много что другого настраивал и пробовал (MediaWiki, BookStack, WikiJS, etc). Но видимо я уже динозавр, так как мне Dokuwiki проще и привычнее. Однако молодежь требует более простых инструментов. Outline действительно прост в использовании, красив и удобен. Еще бы поднимался одной кнопкой и кушал 50 Мб ОЗУ - было бы вообще идеально )))

Комментарии (3)


  1. AlexGluck
    18.08.2023 21:21

    А где xwiki в голосовалке?


    1. Dorlas Автор
      18.08.2023 21:21

      Движков более 100 видов, я про такой не слышал)))

      http://m.traditio.wiki/Список_вики-движков


      1. Dorlas Автор
        18.08.2023 21:21

        попробовал xwiki через docker - при входе в WEB-интерфейс начинает отсчет процентов инициализации - на 50% падает.