feat(consuldemocracy): add initial Cloudron package
Build from source on ruby:3.4.10-trixie (trimmed from the upstream dev Dockerfile: no Chromium, no sudo, fixed non-root user). Uses the postgresql and localstorage addons; start.sh regenerates database.yml/secrets.yml under /app/data every boot, seeds the DB once (marker file) with a local admin, runs memcached in-container, and backgrounds a delayed_job worker gated on migrations. Platform OIDC is wired into secrets.yml; local login stays for the seeded admin. Also drop --rm from the app container in grind-stack.sh so a crashed container keeps its logs for `logs`.
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ConsulDemocracy runtime setup for Cloudron:
|
||||
# 1. generate config/database.yml + config/secrets.yml under /app/data
|
||||
# (config/* are symlinks; addon creds rotate on restore/migration, so
|
||||
# both files are rewritten on EVERY start)
|
||||
# 2. start memcached (production cache_store is mem_cache_store/dalli)
|
||||
# 3. wait for the postgresql addon, then create/migrate/seed the DB once
|
||||
# (marker file) or just migrate on later boots
|
||||
# 4. background the delayed_job worker (gated on the web port, so the
|
||||
# schema exists first), then exec the Rails server on :3000
|
||||
#
|
||||
# Authentication: OIDC via the Cloudron platform provider, mapped into
|
||||
# secrets.yml (oidc_client_id/secret/issuer). The seeds create a local
|
||||
# admin (admin@consul.dev / 12345678) - CHANGE ITS PASSWORD on first
|
||||
# login (see README).
|
||||
|
||||
DATA_DIR="/app/data"
|
||||
SECRET_FILE="${DATA_DIR}/.secret_key_base"
|
||||
MARKER="${DATA_DIR}/.db_seeded"
|
||||
|
||||
cd /var/www/consul
|
||||
|
||||
# --- 1. runtime configs (regenerated every boot) -------------------------------
|
||||
wait_tcp() {
|
||||
local host="$1" port="$2" name="$3"
|
||||
echo "Waiting for ${name} at ${host}:${port} ..."
|
||||
until (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null; do
|
||||
echo "${name} is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
echo "${name} is up"
|
||||
}
|
||||
|
||||
yml_escape() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e "s/'/\\\\'/g"; }
|
||||
|
||||
if [[ ! -s "${SECRET_FILE}" ]]; then
|
||||
( umask 077; openssl rand -hex 32 > "${SECRET_FILE}" )
|
||||
echo "Generated new secret_key_base"
|
||||
fi
|
||||
SECRET_KEY_BASE="$(cat "${SECRET_FILE}")"
|
||||
|
||||
DB_HOST="${CLOUDRON_POSTGRESQL_HOST:-127.0.0.1}"
|
||||
DB_PORT="${CLOUDRON_POSTGRESQL_PORT:-5432}"
|
||||
DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE:-consul}"
|
||||
DB_USER="${CLOUDRON_POSTGRESQL_USERNAME:-consul}"
|
||||
DB_PASS="${CLOUDRON_POSTGRESQL_PASSWORD:-}"
|
||||
|
||||
cat > "${DATA_DIR}/database.yml" <<EOF
|
||||
default: &default
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
host: ${DB_HOST}
|
||||
port: ${DB_PORT}
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
schema_search_path: "public,shared_extensions"
|
||||
username: ${DB_USER}
|
||||
password: <%= ENV["PGPASSWORD"] %>
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
database: ${DB_NAME}
|
||||
EOF
|
||||
export PGPASSWORD="${DB_PASS}"
|
||||
|
||||
OIDC_CLIENT_ID="$(yml_escape "${CLOUDRON_OIDC_CLIENT_ID:-}")"
|
||||
OIDC_CLIENT_SECRET="$(yml_escape "${CLOUDRON_OIDC_CLIENT_SECRET:-}")"
|
||||
OIDC_ISSUER="$(yml_escape "${CLOUDRON_OIDC_ISSUER:-}")"
|
||||
SERVER_NAME="$(yml_escape "${CLOUDRON_APP_DOMAIN:-localhost}")"
|
||||
|
||||
cat > "${DATA_DIR}/secrets.yml" <<EOF
|
||||
# Generated by start.sh on every boot - manual edits will be lost.
|
||||
shared:
|
||||
map_tiles_provider: "//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
map_tiles_provider_attribution: "OpenStreetMap contributors"
|
||||
|
||||
production:
|
||||
secret_key_base: ${SECRET_KEY_BASE}
|
||||
server_name: ${SERVER_NAME}
|
||||
force_ssl: false
|
||||
delay_jobs: true
|
||||
multitenancy: false
|
||||
http_basic_username: ""
|
||||
http_basic_password: ""
|
||||
authentication_logs: false
|
||||
devise_lockable: false
|
||||
oidc_client_id: "${OIDC_CLIENT_ID}"
|
||||
oidc_client_secret: "${OIDC_CLIENT_SECRET}"
|
||||
oidc_issuer: "${OIDC_ISSUER}"
|
||||
EOF
|
||||
chmod 600 "${DATA_DIR}/secrets.yml"
|
||||
|
||||
# --- 2. memcached (localhost, as upstream's image intends) ----------------------
|
||||
memcached -d -p 11211 -u consul -m 64
|
||||
|
||||
# --- 3. database lifecycle -------------------------------------------------------
|
||||
wait_tcp "${DB_HOST}" "${DB_PORT}" "PostgreSQL"
|
||||
|
||||
if [[ ! -f "${MARKER}" ]]; then
|
||||
echo "First boot: creating + migrating + seeding the database ..."
|
||||
bundle exec rails db:create db:migrate db:seed
|
||||
touch "${MARKER}"
|
||||
echo "Database seeded (local admin: admin@consul.dev - change the password!)"
|
||||
else
|
||||
bundle exec rails db:migrate
|
||||
fi
|
||||
|
||||
# assets:precompile needs the booted environment (DB-dependent
|
||||
# initializers), so it runs here - after the PG wait, every boot
|
||||
# (idempotent: fast no-op when the manifests are current)
|
||||
bundle exec rails assets:precompile
|
||||
|
||||
# --- 4. delayed_job worker + web server ------------------------------------------
|
||||
# gate on the web port (migrations done) so the worker never races the schema
|
||||
(
|
||||
until (exec 3<>/dev/tcp/127.0.0.1/3000) 2>/dev/null; do sleep 2; done
|
||||
echo "web port is up - starting delayed_job worker"
|
||||
exec bundle exec rake jobs:work
|
||||
) &
|
||||
|
||||
echo "Starting ConsulDemocracy on :3000 ..."
|
||||
exec bundle exec rails server -b 0.0.0.0 -p 3000
|
||||
Reference in New Issue
Block a user