Compare commits
15 Commits
keycloak
...
fda96565ad
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda96565ad | ||
|
|
9947900389 | ||
|
|
31bcaab87f | ||
|
|
e2f363439c | ||
|
|
dcebdd40a9 | ||
|
|
ea874a54ad | ||
|
|
a4c1c62952 | ||
|
|
3ef0857c56 | ||
|
|
58c5261756 | ||
|
|
56a78a93ee | ||
|
|
415805341f | ||
|
|
20e8ba665d | ||
|
|
be31b80024 | ||
|
|
710ffb15c6 | ||
|
|
414895b673 |
17
carbone/README.org
Normal file
17
carbone/README.org
Normal 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
22
carbone/metadata.yml
Normal 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
7
clickhouse/README.org
Normal 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
21
clickhouse/hooks/init
Executable 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
21
clickhouse/metadata.yml
Normal 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"
|
||||
@@ -0,0 +1,3 @@
|
||||
<clickhouse>
|
||||
<listen_host>0.0.0.0</listen_host>
|
||||
</clickhouse>
|
||||
28
clickhouse/resources/etc/clickhouse-server/config.d/logs.xml
Normal file
28
clickhouse/resources/etc/clickhouse-server/config.d/logs.xml
Normal 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>
|
||||
@@ -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>
|
||||
17
n8n/hooks/web_proxy-relation-joined
Executable file
17
n8n/hooks/web_proxy-relation-joined
Executable 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}\"
|
||||
"
|
||||
|
||||
@@ -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
133
outline/README.org
Normal 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
112
outline/hooks/init
Executable 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
|
||||
|
||||
|
||||
18
outline/hooks/postgres_database-relation-joined
Executable file
18
outline/hooks/postgres_database-relation-joined
Executable 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
150
outline/hooks/pre_deploy
Executable 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}'."
|
||||
25
outline/hooks/redis_database-relation-joined
Executable file
25
outline/hooks/redis_database-relation-joined
Executable 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."
|
||||
21
outline/hooks/smtp_server-relation-joined
Executable file
21
outline/hooks/smtp_server-relation-joined
Executable 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\"
|
||||
"
|
||||
|
||||
48
outline/hooks/web_proxy-relation-joined
Executable file
48
outline/hooks/web_proxy-relation-joined
Executable 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
65
outline/lib/common
Normal 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
49
outline/metadata.yml
Normal 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:
|
||||
13
outline/tests/compose/basic-deploy/compose.yml
Normal file
13
outline/tests/compose/basic-deploy/compose.yml
Normal 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
7
plausible/README.org
Normal file
@@ -0,0 +1,7 @@
|
||||
# -*- ispell-local-dictionary: "english" -*-
|
||||
|
||||
* Info
|
||||
|
||||
From: https://github.com/plausible/community-edition/
|
||||
|
||||
* Usage
|
||||
14
plausible/hooks/event_db-relation-joined
Executable file
14
plausible/hooks/event_db-relation-joined
Executable 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
27
plausible/hooks/init
Executable 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"
|
||||
17
plausible/hooks/postgres_database-relation-joined
Executable file
17
plausible/hooks/postgres_database-relation-joined
Executable 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."
|
||||
22
plausible/hooks/smtp_server-relation-joined
Executable file
22
plausible/hooks/smtp_server-relation-joined
Executable 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\"
|
||||
"
|
||||
|
||||
16
plausible/hooks/web_proxy-relation-joined
Executable file
16
plausible/hooks/web_proxy-relation-joined
Executable 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
58
plausible/metadata.yml
Normal 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:
|
||||
Reference in New Issue
Block a user