From 02755e516c29f9e55a1e9251b6678b79966f1a1b Mon Sep 17 00:00:00 2001 From: default Date: Tue, 21 May 2024 11:04:48 +0200 Subject: [PATCH] new: [outline] add new charm --- outline/README.org | 117 ++++++++++++++++++ outline/hooks/init | 74 +++++++++++ .../hooks/postgres_database-relation-joined | 18 +++ outline/hooks/redis_database-relation-joined | 25 ++++ outline/hooks/smtp_server-relation-joined | 21 ++++ outline/hooks/web_proxy-relation-joined | 48 +++++++ outline/metadata.yml | 47 +++++++ 7 files changed, 350 insertions(+) create mode 100644 outline/README.org create mode 100755 outline/hooks/init create mode 100755 outline/hooks/postgres_database-relation-joined create mode 100755 outline/hooks/redis_database-relation-joined create mode 100755 outline/hooks/smtp_server-relation-joined create mode 100755 outline/hooks/web_proxy-relation-joined create mode 100644 outline/metadata.yml diff --git a/outline/README.org b/outline/README.org new file mode 100644 index 0000000..e9b73dd --- /dev/null +++ b/outline/README.org @@ -0,0 +1,117 @@ +# -*- 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 + +* 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. + +These fix are on 0.76.0 + +** First fix + +We need to add "url.port = '';" in ~build/server/middlewares/passport.js~ to remove the port. Note that this is a bad fix but works for our setup. + +#+begin_src bash +IMAGE=docker.0k.io/outline:0.76.0-elabore + +echo 'apk add patch bash' | dupd -u "$IMAGE" -- -u 1 +cat <<'EOF1' | dupd -u "$IMAGE" -- -u 0 +patch -p 1 <<'EOF2' +--- a/build/server/middlewares/passport.js ++++ b/build/server/middlewares/passport.js +@@ -40,6 +40,7 @@ + const requestHost = ctx.get("host"); + const url = new URL("".concat(reqProtocol, "://").concat(requestHost).concat(redirectUrl)); + url.host = host; ++ url.port = ''; + return ctx.redirect("".concat(url.toString()).concat(hasQueryString ? "&" : "?", "notice=").concat(notice)); + } + if (_env.default.isDevelopment) { +EOF2 +EOF1 +#+end_src + +** Second 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:0.76.0-elabore + +cat <<'EOF1' | dupd -u "$IMAGE" -- -u 0 +patch -p 1 <<'EOF2' +--- a/build/server/utils/passport.js.orig ++++ b/build/server/utils/passport.js +@@ -37,7 +37,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); + }); +@@ -53,7 +53,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 diff --git a/outline/hooks/init b/outline/hooks/init new file mode 100755 index 0000000..2b1066a --- /dev/null +++ b/outline/hooks/init @@ -0,0 +1,74 @@ +#!/bin/bash + +## Init is run on host +## For now it is run every time the script is launched, but +## it should be launched only once after build. + +## Accessible variables are: +## - SERVICE_NAME Name of current service +## - DOCKER_BASE_IMAGE Base image from which this service might be built if any +## - SERVICE_DATASTORE Location on host of the DATASTORE of this service +## - SERVICE_CONFIGSTORE Location on host of the CONFIGSTORE of this service + + +set -e + +PASSWORD_FILE="$SERVICE_DATASTORE"/.compose/password/secret-key +UTILS_SECRET="$SERVICE_DATASTORE"/.compose/password/utils-secret + +if ! [ -f "$UTILS_SECRET" ]; then + info "Generating secret password" + mkdir -p "${UTILS_SECRET%/*}" + umask 077 + openssl rand -hex 32 > "$UTILS_SECRET" +else + info "Using existing utils-secret" +fi + +if ! [ -f "$PASSWORD_FILE" ]; then + info "Generating secret password" + mkdir -p "${PASSWORD_FILE%/*}" + umask 077 + openssl rand -hex 32 > "$PASSWORD_FILE" +else + info "Using existing secret password" +fi + +secret_password=$(cat "$PASSWORD_FILE") +utils_secret=$(cat "$UTILS_SECRET") + +sender=$(options-get sender-email) || exit 1 +oidc_client_id=$(options-get oidc-client-id) || exit 1 +oidc_client_secret=$(options-get oidc-client-secret) || exit 1 +oidc_auth_uri=$(options-get oidc-auth-uri) || exit 1 +oidc_token_uri=$(options-get oidc-token-uri) || exit 1 +oidc_user_info_uri=$(options-get oidc-user-info-uri) || exit 1 +oidc_logout_uri=$(options-get oidc-logout-uri) || exit 1 + +init-config-add " +$SERVICE_NAME: + volumes: + - $SERVICE_DATASTORE:/var/lib/outline/data + environment: + SMTP_FROM_EMAIL: \"$sender\" + DEFAULT_LANGUAGE: \"fr_FR\" + SECRET_KEY: \"$secret_password\" + UTILS_SECRET: \"$utils_secret\" + OIDC_CLIENT_ID: \"$oidc_client_id\" + OIDC_CLIENT_SECRET: \"$oidc_client_secret\" + OIDC_AUTH_URI: \"$oidc_auth_uri\" + OIDC_TOKEN_URI: \"$oidc_token_uri\" + OIDC_USERINFO_URI: \"$oidc_user_info_uri\" + OIDC_LOGOUT_URI: \"$oidc_logout_uri\" + OIDC_SCOPES: \"openid\" + OIDC_USERNAME_CLAIM: \"preferred_username\" + OIDC_DISPLAY_NAME: \"OpenID Connect\" + NODE_ENV: \"production\" + LOG_LEVEL: \"debug\" + FORCE_HTTPS: \"false\" + FILE_STORAGE: \"local\" + #DEVELOPMENT_UNSAFE_INLINE_CSP: \"true\" + #DEBUG: \"http\" +" + + diff --git a/outline/hooks/postgres_database-relation-joined b/outline/hooks/postgres_database-relation-joined new file mode 100755 index 0000000..3793953 --- /dev/null +++ b/outline/hooks/postgres_database-relation-joined @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +PASSWORD="$(relation-get password)" +USER="$(relation-get user)" +DBNAME="$(relation-get dbname)" + + +config-add "\ +services: + $MASTER_BASE_SERVICE_NAME: + environment: + DATABASE_URL: postgres://$USER:$PASSWORD@$TARGET_SERVICE_NAME:5432/$DBNAME + PGSSLMODE: disable +" + +info "Configured $SERVICE_NAME code for $TARGET_SERVICE_NAME access." diff --git a/outline/hooks/redis_database-relation-joined b/outline/hooks/redis_database-relation-joined new file mode 100755 index 0000000..83676fe --- /dev/null +++ b/outline/hooks/redis_database-relation-joined @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + + +# USER="$(relation-get user)" +# DBNAME="$(relation-get dbname)" +# PASSWORD=$(relation-get password) || { +# err "Can't get password for '$SERVICE_NAME' from '$TARGET_SERVICE_NAME'." +# exit 1 +# } + +PASSWORD=$(relation-get password) || { + err "Can't get password for '$SERVICE_NAME' from '$TARGET_SERVICE_NAME'." + exit 1 +} + +config-add "\ +services: + $MASTER_BASE_SERVICE_NAME: + environment: + REDIS_URL: redis://:$PASSWORD@$TARGET_SERVICE_NAME:6379 +" + +info "Configured $SERVICE_NAME code for $TARGET_SERVICE_NAME access." \ No newline at end of file diff --git a/outline/hooks/smtp_server-relation-joined b/outline/hooks/smtp_server-relation-joined new file mode 100755 index 0000000..ae91c6a --- /dev/null +++ b/outline/hooks/smtp_server-relation-joined @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +host=$(relation-get host) || exit 1 +port=$(relation-get port) || exit 1 +user=$(relation-get login) || exit 1 +password="$(relation-get password)" || exit 1 + + +config-add "\ +services: + $MASTER_BASE_SERVICE_NAME: + environment: + SMTP_USERNAME: \"$user\" + SMTP_PASS: \"${password//\$/\$\$}\" + SMTP_HOST: \"$host\" + SMTP_PORT: \"$port\" + #SMTP_SECURE: \"false\" +" + diff --git a/outline/hooks/web_proxy-relation-joined b/outline/hooks/web_proxy-relation-joined new file mode 100755 index 0000000..b70e574 --- /dev/null +++ b/outline/hooks/web_proxy-relation-joined @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +URL=$(relation-get url) || { + echo "Failed to query for 'url' value" + exit 1 +} +DOMAIN_PATH="${URL#*://}" + +if [[ "$DOMAIN_PATH" == *"/"* ]]; then + DOMAIN="${DOMAIN_PATH%%/*}" + UPATH="/${DOMAIN_PATH#*/}" +else + DOMAIN="${DOMAIN_PATH}" + UPATH="" +fi + +PROTO="${URL%:*}" +if [[ "$DOMAIN" == *":"* ]]; then + PORT="${DOMAIN#*:}" + DOMAIN="${DOMAIN%%:*}" +else + + case "$PROTO" in + http) + PORT=80 + ;; + https) + PORT=443 + ;; + *) + echo "Unknown portocol '$PROTO' in url '$URL'." + exit 1 + ;; + esac + +fi + + +config-add "\ +services: + $MASTER_BASE_SERVICE_NAME: + environment: + URL: \"${PROTO}://${DOMAIN}:${PORT}${UPATH}\" + +" + diff --git a/outline/metadata.yml b/outline/metadata.yml new file mode 100644 index 0000000..70e2b18 --- /dev/null +++ b/outline/metadata.yml @@ -0,0 +1,47 @@ +docker-image: docker.0k.io/outline:0.76.0-elabore + +uses: + postgres-database: + #constraint: required | recommended | optional + #auto: pair | summon | none ## default: pair + constraint: required + auto: summon + solves: + database: "main storage" + default-options: + extensions: + - uuid-ossp + redis-database: + constraint: required + auto: summon + solves: + database: "short time storage" + smtp-server: + constraint: required + auto: summon + solves: + proxy: "Public access" + web-proxy: + #constraint: required | recommended | optional + #auto: pair | summon | none ## default: pair + constraint: recommended + auto: pair + solves: + proxy: "Public access" + default-options: + target: !var-expand ${MASTER_BASE_SERVICE_NAME}:3000 + apache-custom-rules: + - !var-expand | + + ## Use RewriteEngine to handle WebSocket connection upgrades + RewriteEngine On + RewriteCond %{HTTP:Connection} Upgrade [NC] + RewriteCond %{HTTP:Upgrade} websocket [NC] + RewriteRule /(.*)\$ ws://${MASTER_BASE_SERVICE_NAME}:3000/\$1 [P,L] + + backup: + constraint: recommended + auto: pair + solves: + backup: "Automatic regular backup" + default-options: