Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
863538311d | ||
|
|
5dc3d86de0 | ||
|
|
85201a8fa7 | ||
|
|
fd6280026a | ||
|
|
09383b8f89 | ||
|
|
90f5ff3fbb | ||
|
|
29bf8a4dfa | ||
|
|
d9b24e61a7 | ||
|
|
c30b8cd57a | ||
|
|
f771ba08f3 | ||
|
|
398f3c2bbb | ||
|
|
48e7e26bb0 | ||
|
|
919455212a | ||
|
|
a49cdecfd8 | ||
|
|
576f2e2dda | ||
|
|
ead44b7103 | ||
|
|
c00d5b4a16 | ||
|
|
4aacfaa0ee |
@@ -63,6 +63,12 @@ DB_MIGRATE_INDEXES_CONCURRENTLY=false
|
||||
# docker secret cannot be used due to distroless image, threfore add secret here
|
||||
#SONIC_PASSWORD=
|
||||
|
||||
# ====================================
|
||||
# STATIC FILES & MAINTENANCE PAGE
|
||||
|
||||
## serve uploads with nginx (bypassing Phoenix) and show a maintenance page while the app restarts. NOTE: don't combine with BASIC_AUTH, as uploads would bypass it
|
||||
#COMPOSE_FILE="$COMPOSE_FILE:compose.static.yml"
|
||||
|
||||
# ====================================
|
||||
# MAIL
|
||||
|
||||
@@ -122,6 +128,7 @@ UPLOAD_LIMIT=20000000
|
||||
# OpenID Connect: connect as a client to the OpenID Connect provider with callback url https://yourinstance.tld/openid/client/openid_1
|
||||
#COMPOSE_FILE="$COMPOSE_FILE:compose.auth.openid.yml"
|
||||
#SECRET_OPENID_CLIENT_SECRET_VERSION=v1
|
||||
# OPENID_1_ISSUER=
|
||||
# OPENID_1_DISCOVERY=
|
||||
# OPENID_1_DISPLAY_NAME=
|
||||
# OPENID_1_CLIENT_ID=
|
||||
|
||||
@@ -15,24 +15,42 @@ A [coop-cloud](https://coopcloud.tech) recipe for deploying [Bonfire](https://bo
|
||||
|
||||
## Basic usage
|
||||
|
||||
1. Set up Docker Swarm and [`abra`]
|
||||
2. Deploy [`coop-cloud/traefik`]
|
||||
3. `abra app new bonfire --secrets` (optionally with `--pass` if you'd like to save secrets in `pass`) and select your server from the list and enter the domain name you want Bonfire to be served from
|
||||
4. `abra app config YOUR_APP_DOMAIN_NAME` and check/edit the config keys
|
||||
5. `abra app deploy YOUR_APP_DOMAIN_NAME`
|
||||
6. Open the configured domain in your browser and sign up!
|
||||
1. `abra app new bonfire --secrets`
|
||||
2. `abra app config <app-name>`
|
||||
3. `abra app deploy <app-name>`
|
||||
4. Open the Elixir console: `abra app run <app-name> app bin/bonfire remote`
|
||||
5. Create admin account in Elixir console (the first account created this way will get admin rights): `Bonfire.Me.make_account_only("my@email.net", "my pw")`
|
||||
|
||||
## Upgrades
|
||||
`abra app deploy --force your-server.domain.name`
|
||||
## Use a custom Bonfire flavour
|
||||
|
||||
NOTE: we recommend switching to the new `sonic` search backend (instead of the deprecated `meilisearch`):
|
||||
1. comment the `COMPOSE_FILE` line that contains `compose.meilisearch.yml` in the `.env` file for your bonfire instancem and replace with a line like `COMPOSE_FILE="compose.yml:compose.sonic.yml"`
|
||||
By default, this recipe deploys the Bonfire `social` flavour. If you want a different one uncomment and define `APP_FLAVOUR` in you env file.
|
||||
|
||||
## Use a custom Bonfire version
|
||||
|
||||
Attention: This is not recommended for production deployment!
|
||||
|
||||
You can deploy another version of Bonfire by using `APP_VERSION` and `APP_PLATFORM`. Be aware that not all versions might be compatible with this recipe, especially the quite old ones.
|
||||
If you want to use bleeding edge releases try setting `APP_VERSION=latest-alpha`. When using this, every time you deploy with `abra app deploy --force <app-name>` the latest alpha replease will be used.
|
||||
|
||||
## Using a search backend
|
||||
|
||||
1. uncomment the `COMPOSE_FILE` line that contains `compose.sonic.yml` in the `.env` file for your bonfire instance.
|
||||
2. add `SONIC_PASSWORD=a-super-secret-password` to the same file (make sure to change the password after pasting!)
|
||||
3. redeploy with `abra app deploy --force your-server.domain.name`
|
||||
3. redeploy with `abra app deploy --force <app-name>`
|
||||
|
||||
## Serve uploads with nginx and show a maintenance page
|
||||
|
||||
[`abra`]: https://docs.coopcloud.tech/abra/
|
||||
[`coop-cloud/traefik`]: https://git.coopcloud.tech/coop-cloud/traefik
|
||||
An optional nginx service serves uploaded files straight from the uploads volume, so they don't go through the app, and keep working while it restarts. It also shows a maintenance page whenever the app is down, such as during upgrades. Uncomment the `COMPOSE_FILE` line that contains `compose.static.yml` in the `.env` file for your bonfire instance to enable.
|
||||
|
||||
## Protect the instance with basic auth
|
||||
|
||||
For e.g. a testing phase you can protect your Bonfire instance with basic auth:
|
||||
|
||||
1. uncomment the section `## BASIC_AUTH`
|
||||
2. create a username/password combination with `echo $(htpasswd -nB <username>)`
|
||||
3. add the combination as secret with `abra app secret insert <app-name> usersfile v1 '<the-user-password-combination>'`
|
||||
|
||||
##
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -44,3 +62,5 @@ Go into your app's Elixir console and enter something like `Bonfire.Me.make_acco
|
||||
|
||||
### How can I get to the app's Elixir console?
|
||||
`abra app run your-server.domain.name app bin/bonfire remote`
|
||||
|
||||
For more, see [docs.coopcloud.tech](https://docs.coopcloud.tech).
|
||||
@@ -1,5 +1,11 @@
|
||||
export APP_ENTRYPOINT_VERSION=v4
|
||||
export PG_BACKUP_VERSION=v6
|
||||
export MEILI_BACKUP_VERSION=v4
|
||||
export SONIC_CFG_VERSION=v1
|
||||
export SONIC_CFG_VERSION=v2
|
||||
export SONIC_ENTRYPOINT_VERSION=v1
|
||||
export POSTGRES_ENTRYPOINT_VERSION=v7
|
||||
export POSTGRES_TUNE_VERSION=v2
|
||||
export POSTGRES_CONF_TMPL_VERSION=v3
|
||||
export BASH_ENVSUBST_VERSION=v1
|
||||
export STATIC_NGINX_CONF_VERSION=v1
|
||||
export MAINTENANCE_HTML_VERSION=v1
|
||||
@@ -4,6 +4,7 @@ services:
|
||||
app:
|
||||
environment:
|
||||
- OPENID_1_DISPLAY_NAME
|
||||
- OPENID_1_ISSUER
|
||||
- OPENID_1_DISCOVERY
|
||||
- OPENID_1_CLIENT_ID
|
||||
- OPENID_1_CLIENT_SECRET_FILE=/run/secrets/openid_client_secret
|
||||
|
||||
@@ -7,6 +7,8 @@ services:
|
||||
labels:
|
||||
- "traefik.http.middlewares.${STACK_NAME}_basicauth.basicauth.usersFile=/run/secrets/usersfile"
|
||||
- "traefik.http.routers.${STACK_NAME}.middlewares=${STACK_NAME}_basicauth"
|
||||
# the uploads router (see compose.yml) needs the basic auth too, or uploads would bypass it
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.middlewares=${STACK_NAME}_basicauth,${STACK_NAME}_uploads_csp"
|
||||
|
||||
secrets:
|
||||
usersfile:
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ services:
|
||||
# - sonic_password
|
||||
|
||||
search:
|
||||
image: valeriansaliou/sonic:v1.7.4
|
||||
image: valeriansaliou/sonic:v1.8.1
|
||||
# secrets:
|
||||
# - sonic_password
|
||||
volumes:
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
version: "3.8"
|
||||
|
||||
# Opt-in nginx sidecar that serves uploads directly from the volume (bypassing Phoenix) and shows a maintenance page whenever the app has no running task (e.g. during redeploys and upgrades). NOTE: don't combine with `compose.basicauth.yml`, as uploads served from here would bypass the basic auth.
|
||||
|
||||
services:
|
||||
static:
|
||||
image: nginx:1.29.0-alpine
|
||||
networks:
|
||||
- proxy
|
||||
volumes:
|
||||
# NOTE: the app and this service share the local volume, so they must run on the same node
|
||||
- upload-data:/srv/data/uploads:ro
|
||||
configs:
|
||||
- source: static_nginx_conf
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
mode: 0444
|
||||
- source: maintenance_html
|
||||
target: /srv/maintenance/maintenance.html
|
||||
mode: 0444
|
||||
healthcheck:
|
||||
# 127.0.0.1 rather than localhost, as nginx only listens on IPv4
|
||||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/maintenance.html"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.services.${STACK_NAME}_static.loadbalancer.server.port=80"
|
||||
# serve uploads from here rather than through the app's `_uploads` router (which has the same rule, so this one needs an explicit higher priority)
|
||||
- "traefik.http.routers.${STACK_NAME}_static_uploads.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS}) && PathPrefix(`/data/uploads/`)"
|
||||
- "traefik.http.routers.${STACK_NAME}_static_uploads.priority=10000"
|
||||
- "traefik.http.routers.${STACK_NAME}_static_uploads.entrypoints=web-secure"
|
||||
- "traefik.http.routers.${STACK_NAME}_static_uploads.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||
- "traefik.http.routers.${STACK_NAME}_static_uploads.service=${STACK_NAME}_static"
|
||||
# lowest priority fallback: Traefik drops the app's routers while it has no running task, so requests land here and get the maintenance page
|
||||
- "traefik.http.routers.${STACK_NAME}_maintenance.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
|
||||
- "traefik.http.routers.${STACK_NAME}_maintenance.priority=1"
|
||||
- "traefik.http.routers.${STACK_NAME}_maintenance.entrypoints=web-secure"
|
||||
- "traefik.http.routers.${STACK_NAME}_maintenance.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||
- "traefik.http.routers.${STACK_NAME}_maintenance.service=${STACK_NAME}_static"
|
||||
|
||||
configs:
|
||||
static_nginx_conf:
|
||||
name: ${STACK_NAME}_static_nginx_conf_${STATIC_NGINX_CONF_VERSION:-v1}
|
||||
file: static.nginx.conf
|
||||
maintenance_html:
|
||||
name: ${STACK_NAME}_maintenance_html_${MAINTENANCE_HTML_VERSION:-v1}
|
||||
file: maintenance.html
|
||||
+13
-2
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${CONTAINER_REGISTRY:-bonfirenetworks/bonfire}:${APP_VERSION:-1.0.5}-${APP_FLAVOUR:-social}-${APP_PLATFORM:-amd64}
|
||||
image: ${CONTAINER_REGISTRY:-bonfirenetworks/bonfire}:${APP_VERSION:-1.0.7}-${APP_FLAVOUR:-social}-${APP_PLATFORM:-amd64}
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
@@ -49,6 +49,8 @@ services:
|
||||
- ENABLE_SSO_PROVIDER
|
||||
- OAUTH_ISSUER
|
||||
|
||||
- BONFIRE_CONSOLE_TOKEN
|
||||
|
||||
- SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
|
||||
- SIGNING_SALT_FILE=/run/secrets/signing_salt
|
||||
- ENCRYPTION_SALT_FILE=/run/secrets/encryption_salt
|
||||
@@ -69,6 +71,8 @@ services:
|
||||
- proxy
|
||||
- internal
|
||||
entrypoint: ["/docker-entrypoint.sh", "./bin/bonfire", "start"]
|
||||
# give the app time to shut down cleanly: on SIGTERM Oban stops fetching jobs and waits up to its `shutdown_grace_period` (default 15s) for running ones to finish, then the endpoint drains LiveView sockets and waits up to Thousand Island's `shutdown_timeout` (default 15s) for HTTP requests. These run one after the other, but the Swarm default (10s) force-kills the app first on every redeploy. Keep this above their sum.
|
||||
stop_grace_period: 42s
|
||||
configs:
|
||||
- source: app_entrypoint
|
||||
target: /docker-entrypoint.sh
|
||||
@@ -86,7 +90,14 @@ services:
|
||||
- "traefik.http.routers.${STACK_NAME}.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS})"
|
||||
- "traefik.http.routers.${STACK_NAME}.entrypoints=web-secure"
|
||||
- "traefik.http.routers.${STACK_NAME}.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||
- "coop-cloud.${STACK_NAME}.version=1.0.0+1.0.5-social-amd64"
|
||||
# uploads (e.g. SVG) are served from the instance's origin, so stop any script in them from running if opened directly
|
||||
- "traefik.http.middlewares.${STACK_NAME}_uploads_csp.headers.customresponseheaders.Content-Security-Policy=sandbox; default-src 'none'; style-src 'unsafe-inline'"
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.rule=Host(`${DOMAIN}`${EXTRA_DOMAINS}) && PathPrefix(`/data/uploads/`)"
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.entrypoints=web-secure"
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.tls.certresolver=${LETS_ENCRYPT_ENV}"
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.service=${STACK_NAME}"
|
||||
- "traefik.http.routers.${STACK_NAME}_uploads.middlewares=${STACK_NAME}_uploads_csp"
|
||||
- "coop-cloud.${STACK_NAME}.version=2.0.3+1.0.7-social-amd64"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:4000"]
|
||||
interval: 30s
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="30">
|
||||
<title>Back in a moment</title>
|
||||
<style>
|
||||
body { margin: 0; min-height: 100vh; display: grid; place-items: center; font-family: system-ui, sans-serif; background: #1d1f21; color: #e5e5e5; text-align: center; }
|
||||
main { max-width: 32rem; padding: 2rem; }
|
||||
h1 { font-size: 1.5rem; }
|
||||
p { line-height: 1.5; opacity: 0.8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Back in a moment</h1>
|
||||
<p>This site is offline, possibly restarting for an upgrade.</p>
|
||||
<p>Will try again in a few seconds...</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
|
||||
configs:
|
||||
postgres_entrypoint:
|
||||
name: ${STACK_NAME}_postgres_entrypoint_${POSTGRES_ENTRYPOINT_VERSION:-v6}
|
||||
name: ${STACK_NAME}_postgres_entrypoint_${POSTGRES_ENTRYPOINT_VERSION:-v7}
|
||||
file: postgres/postgres-entrypoint.sh
|
||||
postgres_tune:
|
||||
name: ${STACK_NAME}_postgres_tune_${POSTGRES_TUNE_VERSION:-v2}
|
||||
|
||||
@@ -8,8 +8,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
[[ "$SCRIPT_DIR" == "/" ]] && SCRIPT_DIR=""
|
||||
|
||||
# Configuration via environment variables
|
||||
POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/var/lib/postgresql/data/postgresql.auto.conf}"
|
||||
POSTGRES_START_CMD="${POSTGRES_START_CMD:-docker-entrypoint.sh postgres}"
|
||||
# The tuned config is a standalone config_file OUTSIDE the data dir, so it never shares postgresql.auto.conf with `ALTER SYSTEM`. Bonfire's InstanceTuning clears its own ALTER SYSTEM writes on every app boot (`ALTER SYSTEM RESET`), which wiped the tuner's values when both lived in auto.conf. Now RESET falls back to this file, and admin ALTER SYSTEM values survive restarts. Outside the data dir also keeps a fresh volume empty for initdb.
|
||||
# POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/var/lib/postgresql/data/postgresql.auto.conf}"
|
||||
POSTGRES_CONF_PATH="${POSTGRES_CONF_PATH:-/etc/postgresql/bonfire.conf}"
|
||||
# NOT `-c name=value` per setting: command-line values outrank postgresql.auto.conf and would silently disable InstanceTuning
|
||||
POSTGRES_START_CMD="${POSTGRES_START_CMD:-docker-entrypoint.sh postgres -c config_file=${POSTGRES_CONF_PATH}}"
|
||||
PGDATA="${PGDATA:-/var/lib/postgresql/data}"
|
||||
|
||||
# Check if envsubst is available, if not, load our bash implementation
|
||||
if ! command -v envsubst &> /dev/null; then
|
||||
@@ -45,10 +49,32 @@ if mkdir -p "${CONF_DIR}" 2>/dev/null; then
|
||||
# Write the processed config
|
||||
echo "$config" > "${POSTGRES_CONF_PATH}"
|
||||
echo "PostgreSQL: Configuration generated at ${POSTGRES_CONF_PATH}"
|
||||
|
||||
|
||||
# One-time cleanup for volumes created when this script wrote the tuned config INTO postgresql.auto.conf: those stale lines override the config_file above (and shared_buffers, max_connections etc. are not InstanceTuning knobs, so nothing else would ever clear them). Runs once per volume (marker file), so later admin ALTER SYSTEM values persist.
|
||||
AUTO_CONF="${PGDATA}/postgresql.auto.conf"
|
||||
TUNE_MARKER="${PGDATA}/.bonfire_tune_v2"
|
||||
if [[ -f "${PGDATA}/PG_VERSION" && -f "${AUTO_CONF}" && ! -f "${TUNE_MARKER}" ]]; then
|
||||
echo "PostgreSQL: removing keys the tuner now sets from ${AUTO_CONF} (one-time migration)"
|
||||
tmp_conf="$(mktemp)"
|
||||
# first input = the generated config (collect its keys), second = auto.conf (drop those keys, keep the rest)
|
||||
awk 'NR==FNR { k=$0; sub(/[[:space:]]*=.*/, "", k); keys[k]=1; next }
|
||||
{ k=$0; sub(/[[:space:]]*=.*/, "", k); if (!(k in keys)) print }' \
|
||||
<(echo "$config") "${AUTO_CONF}" > "${tmp_conf}"
|
||||
# write in place (not mv) so the file keeps its postgres ownership
|
||||
cat "${tmp_conf}" > "${AUTO_CONF}"
|
||||
rm -f "${tmp_conf}"
|
||||
touch "${TUNE_MARKER}"
|
||||
elif [[ ! -f "${PGDATA}/PG_VERSION" && ! -f "${TUNE_MARKER}" ]]; then
|
||||
# Fresh volume: nothing to clean, but the marker can't be written yet (initdb needs an empty data dir). Write it once initdb is done, or the next boot would wrongly run the cleanup and drop admin ALTER SYSTEM values. The loop survives the exec below as an orphan and gives up after 10 minutes.
|
||||
( for _ in $(seq 1 600); do
|
||||
if [[ -f "${PGDATA}/PG_VERSION" ]]; then touch "${TUNE_MARKER}"; break; fi
|
||||
sleep 1
|
||||
done ) >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# Check if we should start postgres
|
||||
if [[ -n "${POSTGRES_START_CMD}" && "${POSTGRES_START_CMD}" != "none" ]]; then
|
||||
# Start PostgreSQL - postgresql.auto.conf is automatically loaded from data dir
|
||||
# Start PostgreSQL with the tuned config_file. postgresql.auto.conf (ALTER SYSTEM) is still loaded from the data dir and layered on top
|
||||
exec ${POSTGRES_START_CMD}
|
||||
else
|
||||
echo "PostgreSQL: Skipping startup (POSTGRES_START_CMD is '${POSTGRES_START_CMD}')"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Mailisearch search backend was removed. If you want to use a search backend switch to sonic. See readme on how to use it.
|
||||
@@ -6,13 +6,15 @@
|
||||
|
||||
[channel]
|
||||
inet = "0.0.0.0:1491"
|
||||
# keep in sync with :keepalive_interval in Bonfire.Search.Sonic, which must stay under this or Sonic drops our idle ingest/search connections
|
||||
tcp_timeout = 300
|
||||
|
||||
[channel.search]
|
||||
query_limit_default = 10
|
||||
query_limit_maximum = 200
|
||||
suggest_limit_default = 5
|
||||
suggest_limit_maximum = 20
|
||||
# moved out of [channel.search], deprecated since Sonic 1.8
|
||||
[search]
|
||||
query_limit_default = 10
|
||||
query_limit_maximum = 200
|
||||
suggest_limit_default = 5
|
||||
suggest_limit_maximum = 20
|
||||
|
||||
[store.kv]
|
||||
path = "/var/lib/sonic/store/kv/"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# used by compose.static.yml
|
||||
server {
|
||||
listen 80;
|
||||
server_tokens off;
|
||||
root /srv;
|
||||
|
||||
# mirrors the uploads `Plug.Static` in Bonfire's endpoint
|
||||
location /data/uploads/ {
|
||||
# uploads (e.g. SVG) are served from the instance's origin, so stop any script in them from running if opened directly
|
||||
add_header Content-Security-Policy "sandbox; default-src 'none'; style-src 'unsafe-inline'" always;
|
||||
gzip_static on;
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# anything else only reaches this service while the app is down (see the maintenance router in compose.static.yml)
|
||||
location / {
|
||||
return 503;
|
||||
}
|
||||
|
||||
error_page 503 /maintenance.html;
|
||||
|
||||
location = /maintenance.html {
|
||||
root /srv/maintenance;
|
||||
add_header Retry-After 30 always;
|
||||
add_header Cache-Control "no-store" always;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user