Compare commits

...

15 Commits

Author SHA1 Message Date
Stéphan Sainléger
fda96565ad fix: [outline] align datastore ownership with the application user
Outline runs as an unprivileged user in the image (``nodejs`` since
1.10.0, ``root`` up to 1.6.1) while the datastore is provisioned by
``root``.  Without realignment the application cannot write its
``uploads``, ``public`` and ``avatars`` buckets, so every attachment
upload fails with "Permission denied writing to ... Check the host
machine file system permissions".  This was seen on elabore.coop
after the 1.6.1 to 1.10.0 upgrade.

The ``init`` hook now reads the image's ``Config.User`` and chowns the
datastore to that user, skipping root-based images.  It is idempotent
and version-agnostic: the realignment also covers buckets added by
future Outline versions.
2026-09-16 23:43:01 +02:00
Stéphan Sainléger
9947900389 fix: [outline] align database ownership before migrations
The ``pre_deploy`` hook reassigns every object of the outline database
(tables, sequences, views, materialized views, standalone types,
functions, procedures) to the application role before the container
runs its migrations.

Historical provisioning or restores running as the ``postgres``
superuser leave objects owned by ``postgres``, so any later ``ALTER``
on them fails with "must be owner of ..." and puts outline in a
crash-loop at migration time.  Seen on elabore.coop when upgrading
1.6.1 -> 1.10.0: migration
``20260714000000-add-mcp-to-search-queries-source.js`` failed on
``enum_search_queries_source``.

Extensions are excluded from the realignment (they are managed by the
``postgres`` charm).  The hook is idempotent, silent when there is no
drift, and blocks the deployment (``exit 1``) if any drift remains, so
the problem surfaces at deploy time instead of as a cryptic
crash-loop.
2026-09-16 23:41:42 +02:00
Stéphan Sainléger
31bcaab87f chg: [outline] add restart unless-stopped directive 2026-09-11 15:04:53 +02:00
Boris Gallet
e2f363439c chg: [carbone-ee] upd README with usage 2026-09-11 15:04:53 +02:00
default
dcebdd40a9 chg: [outline] upd to 1.6.1 2026-09-11 15:04:53 +02:00
Boris Gallet
ea874a54ad new: [carbone-ee] new charm for carbone.io 2026-09-11 15:04:53 +02:00
Stéphan Sainléger
a4c1c62952 fix: [outline] correct typo on postgres extention pg_trgm 2026-09-11 15:04:53 +02:00
Boris Gallet
3ef0857c56 fix: [plausible] smpt server config + init first install 2026-09-11 15:04:53 +02:00
Boris Gallet
58c5261756 fix: [outline] remove default OIDC_DISPLAY_NAME 2026-09-11 15:04:53 +02:00
default
56a78a93ee new: [plausible] add charm 2026-09-11 15:04:53 +02:00
Boris Gallet
415805341f chg: [outline] upd to 0.83.0 2026-09-11 15:04:53 +02:00
Boris Gallet
20e8ba665d fix: [outline] fix smtp server config 2026-09-11 15:04:53 +02:00
default
be31b80024 chg: [outline] upd to 0.81.1 2026-09-11 15:04:53 +02:00
Boris Gallet
710ffb15c6 fix: [n8n] upd 1.45.1 and fix email invitation 2026-09-11 15:04:53 +02:00
default
414895b673 new: [outline] add new charm 2026-09-11 15:04:53 +02:00
27 changed files with 964 additions and 1 deletions

17
carbone/README.org Normal file
View File

@@ -0,0 +1,17 @@
# -*- ispell-local-dictionary: "english" -*-
* Info
From: Carbone https://hub.docker.com/r/carbone/carbone-ee#running-carbone-community-edition-forever-free
## Usage :
ex :
```
carbone:
relations:
web-proxy:
frontend:
domain: carbone.dev1.elabore.coop
```

22
carbone/metadata.yml Normal file
View File

@@ -0,0 +1,22 @@
## From carbone/carbone-ee:full-5.4.2
docker-image: docker.0k.io/carbone-ee:5.4.2
data-resources:
- /app/template
uses:
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}:4000
backup:
constraint: recommended
auto: pair
solves:
backup: "Automatic regular backup"
default-options:

7
clickhouse/README.org Normal file
View File

@@ -0,0 +1,7 @@
# -*- ispell-local-dictionary: "english" -*-
* Info
This charm is provided to work with plausible charm
* Usage

21
clickhouse/hooks/init Executable file
View File

@@ -0,0 +1,21 @@
#!/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
init-config-add "
$SERVICE_NAME:
environment:
CLICKHOUSE_SKIP_USER_SETUP: 1
healthcheck:
test: [ \"CMD-SHELL\", \"wget --no-verbose --tries=1 -O - http://127.0.0.1:8123/ping || exit 1\" ]
"

21
clickhouse/metadata.yml Normal file
View File

@@ -0,0 +1,21 @@
docker-image: docker.0k.io/clickhouse:24.12-alpine
#docker-image: clickhouse/clickhouse-server:24.12-alpine
data-resources:
- /var/lib/clickhouse
- /var/log/clickhouse-server
charm-resources:
- /etc/clickhouse-server/config.d/logs.xml
- /etc/clickhouse-server/config.d/ipv4-only.xml
- /etc/clickhouse-server/config.d/low-resources.xml
provides:
event-db:
uses:
log-rotate:
constraint: recommended
auto: pair
solves:
disk-leak: "/var/log/clickhouse-server"

View File

@@ -0,0 +1,3 @@
<clickhouse>
<listen_host>0.0.0.0</listen_host>
</clickhouse>

View File

@@ -0,0 +1,28 @@
<clickhouse>
<logger>
<level>warning</level>
<console>true</console>
</logger>
<query_log replace="1">
<database>system</database>
<table>query_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
<engine>
ENGINE = MergeTree
PARTITION BY event_date
ORDER BY (event_time)
TTL event_date + interval 30 day
SETTINGS ttl_only_drop_parts=1
</engine>
</query_log>
<!-- Stops unnecessary logging -->
<metric_log remove="remove" />
<asynchronous_metric_log remove="remove" />
<query_thread_log remove="remove" />
<text_log remove="remove" />
<trace_log remove="remove" />
<session_log remove="remove" />
<part_log remove="remove" />
</clickhouse>

View File

@@ -0,0 +1,23 @@
<!-- https://clickhouse.com/docs/en/operations/tips#using-less-than-16gb-of-ram -->
<clickhouse>
<!--
https://clickhouse.com/docs/en/operations/server-configuration-parameters/settings#mark_cache_size -->
<mark_cache_size>524288000</mark_cache_size>
<profile>
<default>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_threads -->
<max_threads>1</max_threads>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_block_size -->
<max_block_size>8192</max_block_size>
<!-- https://clickhouse.com/docs/en/operations/settings/settings#max_download_threads -->
<max_download_threads>1</max_download_threads>
<!--
https://clickhouse.com/docs/en/operations/settings/settings#input_format_parallel_parsing -->
<input_format_parallel_parsing>0</input_format_parallel_parsing>
<!--
https://clickhouse.com/docs/en/operations/settings/settings#output_format_parallel_formatting -->
<output_format_parallel_formatting>0</output_format_parallel_formatting>
</default>
</profile>
</clickhouse>

View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -e
DOMAIN=$(relation-get domain) || {
echo "Failed to get domain"
exit 1
}
config-add "\
services:
$MASTER_BASE_SERVICE_NAME:
environment:
N8N_HOST: \"${DOMAIN}\"
WEBHOOK_URL: \"https:\/\/${DOMAIN}\"
"

View File

@@ -1,4 +1,4 @@
docker-image: docker.n8n.io/n8nio/n8n:1.23.0
docker-image: docker.0k.io/n8n:1.45.1
uses:
postgres-database:
@@ -22,6 +22,15 @@ uses:
proxy: "Public access"
default-options:
target: !var-expand ${MASTER_BASE_SERVICE_NAME}:5678
apache-custom-rules:
- !var-expand |
## Use RewriteEngine to handle WebSocket connection upgrades
RewriteEngine On
RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteRule /(.*)\$ ws://${MASTER_BASE_SERVICE_NAME}:5678/\$1 [P,L]
backup:
constraint: recommended
auto: pair

133
outline/README.org Normal file
View File

@@ -0,0 +1,133 @@
# -*- 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://<YOUR_OUTLINE>: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

112
outline/hooks/init Executable file
View File

@@ -0,0 +1,112 @@
#!/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:
restart: unless-stopped
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\"
NODE_ENV: \"production\"
LOG_LEVEL: \"debug\"
FORCE_HTTPS: \"false\"
FILE_STORAGE: \"local\"
#DEVELOPMENT_UNSAFE_INLINE_CSP: \"true\"
#DEBUG: \"http\"
"
## The datastore is bind-mounted into the container. Outline runs as
## an unprivileged user (image Config.User: root up to 1.6.1, "nodejs"
## since 1.10.0) and must write its uploads, public and avatars
## buckets. Provisioned by root, the datastore is not writable by
## that user and every upload fails with "Permission denied writing
## to ... Check the host machine file system permissions". Align the
## datastore ownership with the image user; skip images running as
## root. See README.org, "Datastore ownership alignment".
app_user=
if [ -n "$DOCKER_BASE_IMAGE" ]; then
app_user=$(docker image inspect "$DOCKER_BASE_IMAGE" \
--format '{{.Config.User}}') || exit 1
fi
case "$app_user" in
""|0|0:0|root)
## image runs as root: nothing to align
;;
*:*)
uid="${app_user%%:*}"
gid="${app_user#*:}"
;;
*)
uid_gid=($(docker_get_uid_gid "$SERVICE_NAME" "$app_user" "$app_user")) || exit 1
uid="${uid_gid[0]}"
gid="${uid_gid[1]}"
;;
esac
if [ -n "${uid:-}" ]; then
mkdir -p "$SERVICE_DATASTORE"
chown -R "$uid:$gid" "$SERVICE_DATASTORE" || {
err "Failed to align datastore ownership on '$uid:$gid'."
exit 1
}
info "Datastore ownership aligned on '$uid:$gid'."
fi

View File

@@ -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."

150
outline/hooks/pre_deploy Executable file
View File

@@ -0,0 +1,150 @@
#!/bin/bash
## Should be executable N time in a row with same result.
##
## Ensure the outline application role owns every object of its
## database before the container boots 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. See
## README.org, section "Database ownership alignment".
. lib/common
set -e
relation="postgres-database"
db_role=$(outline:named-relation-get "$relation" user) || {
err "Couldn't get ${WHITE}user${NORMAL} value" \
"in ${DARKCYAN}$relation${NORMAL} relation's data."
exit 1
}
## List every object of schema "public" not owned by the application
## role: tables, sequences, views, materialized views, standalone
## types and functions. Extensions are excluded (managed by the
## postgres charm).
audit_query="SET app.role = '$db_role';
SELECT obj FROM (
SELECT CASE c.relkind
WHEN 'S' THEN 'sequence '
WHEN 'v' THEN 'view '
WHEN 'm' THEN 'matview '
ELSE 'table '
END || c.relname AS obj
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind IN ('r','p','S','v','m')
AND pg_get_userbyid(c.relowner) <> current_setting('app.role')
UNION ALL
SELECT 'type ' || t.typname AS obj
FROM pg_type t
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'public'
AND t.typtype IN ('e','d','c')
AND NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.reltype = t.oid)
AND pg_get_userbyid(t.typowner) <> current_setting('app.role')
UNION ALL
SELECT CASE p.prokind
WHEN 'p' THEN 'procedure '
ELSE 'function '
END || p.proname AS obj
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public'
AND p.prokind IN ('f','p')
AND pg_get_userbyid(p.proowner) <> current_setting('app.role')
) drift
ORDER BY obj;"
drift=$(sql < <(e "$audit_query")) || {
err "Failed to audit database ownership for ${WHITE}$db_role${NORMAL}."
exit 1
}
if [ -z "$drift" ]; then
## fast-path: nothing to do
exit 0
fi
info "Found database objects not owned by '${db_role}', reassigning ownership:"
e "$drift" | prefix " ${GRAY}|${NORMAL} " >&2
dbname=$(outline:named-relation-get "$relation" dbname) || {
err "Couldn't get ${WHITE}dbname${NORMAL} value" \
"in ${DARKCYAN}$relation${NORMAL} relation's data."
exit 1
}
## Reassign ownership of every drifted object, then the database
## itself. Role name is passed through a session GUC and quoted
## with format('%I') in every generated statement.
repair_query="SET app.role = '$db_role';
DO \$\$
DECLARE
r record;
app_role text := current_setting('app.role');
BEGIN
FOR r IN
SELECT CASE c.relkind
WHEN 'S' THEN format('ALTER SEQUENCE public.%I OWNER TO %I', c.relname, app_role)
WHEN 'v' THEN format('ALTER VIEW public.%I OWNER TO %I', c.relname, app_role)
WHEN 'm' THEN format('ALTER MATERIALIZED VIEW public.%I OWNER TO %I', c.relname, app_role)
ELSE format('ALTER TABLE public.%I OWNER TO %I', c.relname, app_role)
END AS stmt
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'public'
AND c.relkind IN ('r','p','S','v','m')
AND pg_get_userbyid(c.relowner) <> app_role
UNION ALL
SELECT format('ALTER TYPE public.%I OWNER TO %I', t.typname, app_role)
FROM pg_type t
JOIN pg_namespace n ON n.oid = t.typnamespace
WHERE n.nspname = 'public'
AND t.typtype IN ('e','d','c')
AND NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.reltype = t.oid)
AND pg_get_userbyid(t.typowner) <> app_role
UNION ALL
SELECT CASE p.prokind
WHEN 'p' THEN format('ALTER PROCEDURE public.%I(%s) OWNER TO %I',
p.proname, pg_get_function_identity_arguments(p.oid), app_role)
ELSE format('ALTER FUNCTION public.%I(%s) OWNER TO %I',
p.proname, pg_get_function_identity_arguments(p.oid), app_role)
END AS stmt
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = 'public'
AND p.prokind IN ('f','p')
AND pg_get_userbyid(p.proowner) <> app_role
LOOP
EXECUTE r.stmt;
END LOOP;
END \$\$;
ALTER DATABASE \"$dbname\" OWNER TO \"$db_role\";"
sql < <(e "$repair_query") || {
err "Failed to reassign ownership to '${db_role}'."
exit 1
}
## Fail-hard: verify the realignment actually worked.
remaining_drift=$(sql < <(e "$audit_query")) || {
err "Failed to re-audit database ownership for ${WHITE}$db_role${NORMAL}."
exit 1
}
if [ -n "$remaining_drift" ]; then
err "Some database objects are still not owned by '${db_role}':"
e "$remaining_drift" | prefix " ${GRAY}|${NORMAL} " >&2
err "Deployment halted. Fix the ownership manually and retry, e.g.:"
err " docker exec <postgres-container> psql -U postgres -d \"$dbname\" \\" >&2
err " -c 'ALTER TYPE public.<name> OWNER TO \"$db_role\"'" >&2
exit 1
fi
info "Database ownership aligned on '${db_role}'."

View File

@@ -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."

View File

@@ -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_PASSWORD: \"${password//\$/\$\$}\"
SMTP_HOST: \"$host\"
SMTP_PORT: \"$port\"
SMTP_FROM_EMAIL: \"$user\"
"

View File

@@ -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}\"
"

65
outline/lib/common Normal file
View File

@@ -0,0 +1,65 @@
# -*- mode: shell-script -*-
##
## Database access helpers (from cyclos/immich pattern in 0k-charms)
##
## Get target service name for a named relation
outline:relation-get-target-service() {
local relation="$1" ts
if ! read-0 ts _ _ < <(get_service_relation "$SERVICE_NAME" "$relation"); then
err "Couldn't find relation ${DARKCYAN}$relation${NORMAL}."
return 1
fi
e "$ts"
}
## Get the raw data of a named relation
outline:relation-get-config() {
local relation="$1" ts relation_dir
ts=$(outline:relation-get-target-service "$relation") || return 1
relation_dir=$(get_relation_data_dir "$SERVICE_NAME" "$ts" "$relation") || return 1
cat "${relation_dir}/data"
}
## Get a key from the relation data
outline:named-relation-get() {
local relation="$1" key="$2" config
config=$(outline:relation-get-config "$relation") || return 1
e "$config" | shyaml get-value "$key" || {
err "Couldn't get ${WHITE}$key${NORMAL} value" \
"in ${DARKCYAN}$relation${NORMAL} relation's data."
return 1
}
}
## Run SQL as the postgres superuser on the database related to this
## service through the "postgres-database" relation.
## Usage: sql < <(echo "SELECT ...")
## echo "SELECT ..." | sql
sql() {
(
local dbname ts target_charm target_charm_path
dbname="$(outline:named-relation-get "postgres-database" dbname)" || exit 1
ts=$(outline:relation-get-target-service "postgres-database") || exit 1
export SERVICE_NAME="$ts"
export SERVICE_DATASTORE="$DATASTORE/$SERVICE_NAME"
DOCKER_BASE_IMAGE=$(service_ensure_image_ready "$SERVICE_NAME") || exit 1
export DOCKER_BASE_IMAGE
target_charm=$(get_service_charm "$ts") || exit 1
target_charm_path=$(charm.get_dir "$target_charm") || exit 1
set +e
. "$target_charm_path/lib/common"
set -e
ensure_db_docker_running
ddb -d "$dbname" -v ON_ERROR_STOP=1
)
}

49
outline/metadata.yml Normal file
View File

@@ -0,0 +1,49 @@
docker-image: docker.0k.io/outline:0.83.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
- unaccent
- pg_trgm
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:

View File

@@ -0,0 +1,13 @@
outline:
options:
sender-email: outline@example.com
oidc-client-id: test-client
oidc-client-secret: test-secret
oidc-auth-uri: https://example.com/auth
oidc-token-uri: https://example.com/token
oidc-user-info-uri: https://example.com/userinfo
oidc-logout-uri: https://example.com/logout
smtp-stub:
options:
host: smtp.example.com

7
plausible/README.org Normal file
View File

@@ -0,0 +1,7 @@
# -*- ispell-local-dictionary: "english" -*-
* Info
From: https://github.com/plausible/community-edition/
* Usage

View File

@@ -0,0 +1,14 @@
#!/bin/bash
set -e
config-add "\
services:
$MASTER_BASE_SERVICE_NAME:
environment:
CLICKHOUSE_DATABASE_URL: http://$TARGET_SERVICE_NAME:8123/$TARGET_SERVICE_NAME
"
info "Configured $SERVICE_NAME code for $TARGET_SERVICE_NAME access."

27
plausible/hooks/init Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
SECRET_KEY_BASE="$SERVICE_DATASTORE"/secret-key
SHARE_DIR="$SERVICE_DATASTORE"/var/lib/plausible
mkdir -p $SHARE_DIR
uid=$(docker_get_uid "$SERVICE_NAME" "plausible")
if ! [ -f "$SECRET_KEY_BASE" ]; then
info "Generating secret key"
mkdir -p "${SECRET_KEY_BASE%/*}"
umask 077
openssl rand -base64 64 > "$SECRET_KEY_BASE"
else
info "Using existing secret key"
fi
secret_key_base=$(cat "$SECRET_KEY_BASE")
init-config-add "
$SERVICE_NAME:
environment:
SECRET_KEY_BASE: \"$secret_key_base\"
"
chown -v "$uid" "$SHARE_DIR"

View File

@@ -0,0 +1,17 @@
#!/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
"
info "Configured $SERVICE_NAME code for $TARGET_SERVICE_NAME access."

View File

@@ -0,0 +1,22 @@
#!/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_USER_NAME: \"$user\"
SMTP_USER_PWD: \"${password//\$/\$\$}\"
SMTP_HOST_ADDR: \"$host\"
SMTP_HOST_PORT: \"$port\"
SMTP_HOST_SSL_ENABLE: \"true\"
MAILER_EMAIL: \"$user\"
"

View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
DOMAIN=$(relation-get domain) || {
echo "Failed to get domain"
exit 1
}
config-add "\
services:
$MASTER_BASE_SERVICE_NAME:
environment:
BASE_URL: \"https:\/\/${DOMAIN}\"
"

58
plausible/metadata.yml Normal file
View File

@@ -0,0 +1,58 @@
docker-image: docker.0k.io/plausible:3.0.1
#docker-image: ghcr.io/plausible/community-edition:v3.0.1
data-resources:
- /var/lib/plausible
docker-compose:
entrypoint: sh -c "/entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
#entrypoint: sh -c "/entrypoint.sh run"
uses:
event-db:
#constraint: required | recommended | optional
#auto: pair | summon | none ## default: pair
constraint: required
auto: summon
solves:
database: "event db"
postgres-database:
#constraint: required | recommended | optional
#auto: pair | summon | none ## default: pair
constraint: required
auto: summon
solves:
database: "main storage"
default-options:
extensions:
- citext
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}:8000
apache-custom-rules:
- !var-expand |
ProxyPreserveHost On
#Set web sockets
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/(live/websocket)$ ws://${MASTER_BASE_SERVICE_NAME}:8000/\$1 [P,L]
backup:
constraint: recommended
auto: pair
solves:
backup: "Automatic regular backup"
default-options: