Outline – selfhostovaná znalostní báze

Takovej selfhosting Notionu bez fancy tabulek.
Outline je open-source nástroj pro tvorbu a správu interní dokumentace a znalostních bází.

Použité technologie

Nginx

Nginx je výkonný webový server a reverzní proxy, který se používá pro obsluhu statického obsahu, směrování požadavků na backend služby a vyvažování zátěže. Je známý svou rychlostí, nízkou spotřebou paměti a spolehlivostí při vysoké zátěži.

Outline

Outline je open-source nástroj pro tvorbu a správu interní dokumentace a znalostních bází. Poskytuje jednoduché a přehledné uživatelské rozhraní pro týmovou spolupráci, verzování a rychlé vyhledávání obsahu.

Dex

Dex je open-source identitní služba, která funguje jako „OpenID Connect“ provider. Slouží k centralizovanému ověřování uživatelů a umožňuje propojit různé aplikace s externími identity providery (např. Google, GitHub nebo LDAP).

Postgres

PostgreSQL (Postgres) je pokročilý open-source relační databázový systém. Nabízí podporu pro komplexní dotazy, transakce, indexy, JSON data a rozšiřitelnost pomocí vlastních funkcí, čímž se hodí pro širokou škálu aplikací od menších po enterprise řešení.

Redis

Redis je in-memory databáze a cache systém, který umožňuje velmi rychlý přístup k datům. Často se používá pro ukládání relací, front, výsledků výpočtů nebo jako prostředník pro komunikaci mezi službami díky podpoře publikace a odběru zpráv (pub/sub).

Docker / Docker Compose

Docker je platforma pro kontejnerizaci aplikací, která umožňuje spouštět software izolovaně s veškerými závislostmi. Docker Compose pak usnadňuje definování a správu vícekontejnerových aplikací pomocí jednoduchého konfiguračního souboru.

Diagram

Postřehy

Outline

Outline neumožňuje přihlašování pomocí uživatelského jména a hesla, což je poněkud nepříjemné. Musí se nakonfigurovat jedna z podporovaných služeb. Aplikace umí pracovat se Slack identitami, Google identitami a dalšími poskytovateli. Pokud nemůžete použít žádnou z těchto služeb, je tam naštěstí možnost Magic Link via Email. Je to sice nepříjemné, ale funkční řešení. Pokaždé, když se chcete přihlásit, pošle vám aplikace email s přihlašovacím odkazem. V mém setupu jsem se rozhodl použít Dex jako OIDC službu, přes kterou se mohu přihlašovat pomocí emailu a hesla.

Dex

Dex je velmi minimalistický, takže nemá webové rozhraní. Navíc jeho dokumentace je hodně nekvalitní. Nejjednodušším způsobem, jak vše rozchodit, je přidat statického klienta a uživatele přímo do konfiguračního souboru. Musíte si ale vytvořit bcrypt hashovaná hesla. Vycházel jsem z tohoto návodu.

SMTP

Aby fungovalo odesílání emailů, je potřeba nakonfigurovat SMTP server. Pokud žádný po ruce nemáte, můžete použít váš Gmail účet. V nastavení Gmailu se musí vytvořit aplikační klíč, který se pak vloží do .env souboru do SMTP sekce.

Setup

.env

URL=https://outline.<domain.com>
PORT=3050
WEB_CONCURRENCY=1
SECRET_KEY=<secret key>
UTILS_SECRET=<utils secret>
DATABASE_URL=postgres://outline:<db password>@outline-postgres:5432/outline
PGSSLMODE=disable

POSTGRES_USER=outline
POSTGRES_PASSWORD=<db password>
POSTGRES_DB=outline

REDIS_URL=redis://outline-redis:6379

FILE_STORAGE=local

FORCE_HTTPS=true

OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=<oidc client secret>
OIDC_AUTH_URI=https://auth.<domain.com>/dex/auth
OIDC_TOKEN_URI=http://dex:5556/dex/token
OIDC_USERINFO_URI=http://dex:5556/dex/userinfo
OIDC_USERNAME_CLAIM=email
OIDC_DISPLAY_NAME=OIDC Provider
OIDC_SCOPES=openid profile email

SMTP_SERVICE=gmail
SMTP_USERNAME=<you>@gmail.com
SMTP_PASSWORD="<app code>"
SMTP_FROM_EMAIL=<you>@gmail.com

RATE_LIMITER_ENABLED=true
RATE_LIMITER_REQUESTS=1000
RATE_LIMITER_DURATION_WINDOW=60

ENABLE_UPDATES=true
DEBUG=http
LOG_LEVEL=info

DEX Config (config.yaml)

issuer: https://auth.<domain.com>/dex

storage:
  type: sqlite3
  config:
    file: /var/dex/dex.db

web:
  http: 0.0.0.0:5556

staticClients:
  - id: outline
    redirectURIs:
      - "https://outline.<domain.com>/auth/oidc.callback"
    name: "Knowledge Base"
    secret: <oidc client secret>

oauth2:
  skipApprovalScreen: true

enablePasswordDB: true

staticPasswords:
  # Admin
  - email: "<admin>@gmail.com"
    hash: "<bcrypt password hash>"
    username: "admin"
    userID: "admin-001"

  - email: "<user>@gmail.com"
    hash: "<bcript password hash>"
    username: "user"
    userID: "user-001"

# Pro debug
logger:
  level: "info"
  format: "text"

Docker Compose

services:
  outline:
    image: docker.getoutline.com/outlinewiki/outline:latest
    env_file: ./.env
    ports:
      - "3050:3050"
    expose:
      - "3050"
    volumes:
      - storage-data:/var/lib/outline/data
    depends_on:
      - outline-postgres
      - outline-redis

  outline-redis:
    image: redis
    env_file: ./.env
    expose:
      - "6379"
    volumes:
      - ./redis.conf:/redis.conf
    command: ["redis-server", "/redis.conf"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  outline-postgres:
    image: postgres
    env_file: ./.env
    expose:
      - "5432"
    volumes:
      - database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "outline", "-U", "user"]
      interval: 30s
      timeout: 20s
      retries: 3

  dex:
    image: dexidp/dex:v2.37.0
    ports:
      - "5556:5556"  # Vystaveno pro nginx proxy
    expose:
      - "5556"
    volumes:
      - ./dex-config:/etc/dex:ro  # Read-only mount konfigurace
      - dex-data:/var/dex         # Persistentni SQLite databáze
    command: ["dex", "serve", "/etc/dex/config.yaml"]
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5556/dex/healthz"]
      interval: 30s
      timeout: 10s
      retries: 3
    restart: unless-stopped

volumes:
  storage-data:
  database-data:
  dex-data:

Nginx

auth.

server {
    listen 80;
    server_name auth.<domain.com>;

    # P┼Öesm─Ťrov├ín├ş HTTP na HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name auth.<domain.com>;

    # SSL certifik├íty (upravte cestu podle va┼í├ş konfigurace)
    ssl_certificate /etc/nginx/ssl/<domain.com>/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/<domain.com>/privkey.pem;

    # SSL konfigurace
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    # Proxy nastaven├ş pro Home Assistant
    location / {
        proxy_pass http://<service ip>:5556;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeout nastaven├ş
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Buffering nastaven├ş
        proxy_buffering off;
        proxy_request_buffering off;
    }

    # Logov├ín├ş
    access_log /var/log/nginx/auth.<domain.com>.access.log;
    error_log /var/log/nginx/auth.<domain.com>.error.log;
}

outline.

server {
    listen 80;
    server_name outline.<domain.com>;

    # P┼Öesm─Ťrov├ín├ş HTTP na HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name outline.<domain.com>;

    # SSL certifik├íty (upravte cestu podle va┼í├ş konfigurace)
    ssl_certificate /etc/nginx/ssl/<domain.com>/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/<domain.com>/privkey.pem;

    # SSL konfigurace
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;

    # Proxy nastaven├ş pro Home Assistant
    location / {
        proxy_pass http://<service ip>:3050;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # Timeout nastaven├ş
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # Buffering nastaven├ş
        proxy_buffering off;
        proxy_request_buffering off;
    }

    # Logov├ín├ş
    access_log /var/log/nginx/outline.<domain.com>.access.log;
    error_log /var/log/nginx/outline.<domain.com>.error.log;
}

Loading