Очередная статья про настройку 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: true KC_HOSTNAME_STRICT: true 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 Мб ОЗУ - было бы вообще идеально )))
