# -*- ispell-local-dictionary: "english" -*- * Info From: https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t * Usage Config info: https://github.com/outline/outline/blob/main/.env.sample Odoo config: if you configure odoo OIDC connector, the callback url should be like this : https://:443/auth/oidc.callback #Requires a =smtp-server= provider to be functional, you can use #=smtp-stub= charm to provide information to externally managed =SMTP=. #+begin_src yaml outline: options: sender-email: #the sender email (beware the conf of your SMTP server) oidc-client-id: #the client id of your OIDC provider oidc-client-secret: #the client oidc-auth-uri: #the host of your OIDC provider oidc-token-uri: #the token uri of your OIDC provider oidc-user-info-uri: #the user info uri of your OIDC provider oidc-logout-uri: #the login uri of your OIDC provider #smtp-stub: # options: # host: smtp.myhost.com # port: 465 # connection-security: "ssl/tls" # auth-method: password #IMPORTANT: if not present login password doesn’t work # login: myuser # password: myp4ssw0rd #+end_src ** Odoo 14 We monkey-patch odoo in order to make it work, be sure to use latest version in 14.0 of galicea openIDConnection module * Database ownership alignment The =pre_deploy= hook ensures that every object of the database (tables, sequences, views, materialized views, standalone types, functions, procedures) is owned by the application role before the container starts and runs its migrations. Historical provisioning or restores executed as the =postgres= superuser leave objects owned by =postgres=, which makes any later =ALTER= on these objects fail with "must be owner of ..." and puts outline in a crash-loop at migration time. This was seen on 2026-09-11 when upgrading elabore.coop from 1.6.1 to 1.10.0: migration =20260714000000-add-mcp-to-search-queries-source.js= failed on =enum_search_queries_source=. The same drift was found on every managed server (lokavaluto.fr, lagemme.org, moneko.org). Extensions are excluded from the realignment (they are managed by the =postgres= charm). The hook is idempotent and silent when there is no drift, and blocks the deployment (=exit 1=) if the realignment fails, so the problem is visible at deploy time instead of as a cryptic crash-loop. * Datastore ownership alignment The =init= hook aligns the ownership of the service datastore with the user the Outline container runs as. Since 1.10.0 the image runs as the unprivileged =nodejs= user (older images, up to 1.6.1, ran as =root=), while the datastore is provisioned by =root=. Without realignment the application cannot write its =uploads=, =public= and =avatars= buckets and every attachment upload fails with "Permission denied writing to ... Check the host machine file system permissions". This was seen on 2026-09-11 on elabore.coop after the 1.6.1 to 1.10.0 upgrade, on every existing datastore. The hook reads the user from the image's =Config.User=, so it stays version-agnostic: images running as =root= are left untouched, and re-running the hook on an already aligned datastore is a no-op. * Building a new image We use the official image with an added patch due to 2 bugs: - https://github.com/outline/outline/issues/6859 - second was not reported yet Note that a PR was pushed with a fix on the first bug. But this was not yet tested. The fix are on 1.6.1 ** Fix Upon calling "/oidc" url, outline will return "Set-Cookie" header with a "domain:" value that is incorrect (still the inner docker domain: "outline" instead of the outer proxy domain from the frontend.) Fortunately we can simply remove the value "domain" from the cookie by commenting only 2 lines in ~build/server/utils/passport.js~. The patches will change the "build/" files, so this is a very temporary and brittle fix. #+begin_src bash IMAGE=docker.0k.io/outline:1.6.1-elabore echo 'apt update && apt install patch' | dupd -u "$IMAGE" -- -u 0 cat <<'EOF1' | dupd -u "$IMAGE" -- -u 0 patch -p 1 <<'EOF2' --- a/build/server/utils/passport.js.orig +++ b/build/server/utils/passport.js @@ -56,7 +56,7 @@ const state = buildState(host, token, client); ctx.cookies.set(this.key, state, { expires: (0, _dateFns.addMinutes)(new Date(), 10), - domain: (0, _domains.getCookieDomain)(ctx.hostname, _env.default.isCloudHosted) + //domain: (0, _domains.getCookieDomain)(ctx.hostname, _env.default.isCloudHosted) }); callback(null, token); }); @@ -73,7 +73,7 @@ // Destroy the one-time pad token and ensure it matches ctx.cookies.set(this.key, "", { expires: (0, _dateFns.subMinutes)(new Date(), 1), - domain: (0, _domains.getCookieDomain)(ctx.hostname, _env.default.isCloudHosted) + //domain: (0, _domains.getCookieDomain)(ctx.hostname, _env.default.isCloudHosted) }); if (!token || token !== providedToken) { return callback((0, _errors.OAuthStateMismatchError)(), false, token); EOF2 EOF1 #+end_src