happyDomain/cloud-init.yaml

383 lines
19 KiB
YAML

#cloud-config
users:
- default
package_update: true
packages:
- ca-certificates
- cron
- docker.io
- jq
- restic
- syslog-ng
write_files:
- content: |
{
#acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
app.happydomain.org {
reverse_proxy app-happydomain:8081 {
flush_interval -1
}
}
try.happydomain.org {
reverse_proxy demo-happydomain:8081 {
flush_interval -1
}
}
lists.happydomain.org {
tls {
issuer acme {
disable_http_challenge
}
}
@adminroute path /admin /admin/* /api/* /webhooks/*
basic_auth @adminroute {
nemunaire "$2a$14$F937dQbs6iizd/.p8UZdKuY0O5BjKfkmshKFDH0uA4HJEIrSq2hZa"
happyuser "$2a$14$QvpmUl8MBxuqB14mP393yuuQ24/lxibj3zBRzvCdovJQOPeKTzAQC"
}
reverse_proxy listmonk:9000 {
flush_interval -1
}
}
feedback.happydomain.org {
reverse_proxy feedback:3000 {
flush_interval -1
}
}
path: /etc/caddy/Caddyfile
- content: |
@version:3.30
@include "scl.conf"
# syslog-ng configuration file.
#
# See syslog-ng(8) and syslog-ng.conf(5) for more information.
#
# Note: It also sources additional configuration files (*.conf)
# located in /etc/syslog-ng/conf.d/.
#
# Options
#
options {
# Create destination directories if missing.
create_dirs(yes);
# The default action of syslog-ng is to log a MARK line to the file every
# 20 minutes. That's seems high for most people so turn it down to once an
# hour. Set it to zero if you don't want the functionality at all.
mark_freq(3600);
# The default action of syslog-ng is to log a STATS line to the file every
# 10 minutes. That's pretty ugly after a while. Change it to every 12 hours
# so you get a nice daily update of how many messages syslog-ng missed (0).
stats_freq(43200);
# Time to wait before a died connection is re-established (default is 60).
time_reopen(5);
# Disable DNS usage.
# syslog-ng blocks on DNS queries, so enabling DNS may lead to a DoS attack.
use_dns(no);
dns-cache(no);
# Default owner, group, and permissions for log files.
owner(root);
group(adm);
perm(0640);
# Default permissions for created directories.
dir_perm(0755);
};
source src { system(); internal(); };
filter f_auth { facility(auth, authpriv); };
filter f_syslog { not facility(authpriv, mail) and not message("^grsec:( From [^:]+:)? exec of.*") and not (program("named") and message("^client ")); };
filter f_cron { facility(cron); };
filter f_daemon { facility(daemon); };
filter f_kern { facility(kern); };
filter f_mail { facility(mail, news); };
filter f_user { facility(user); };
filter f_debug { not facility(auth, authpriv, news, mail); };
filter f_messages { level(info..warn) and not facility(auth, authpriv, mail, news) and not message("^grsec:( From [^:]+:)? exec of.*"); };
filter f_emergency { level(emerg); };
filter f_info { level(info); };
filter f_notice { level(notice); };
filter f_warn { level(warn); };
filter f_crit { level(crit); };
filter f_err { level(err); };
filter f_audit { message("^audit.*"); };
filter f_history { message(".*HISTORY*"); };
destination authlog { file("/var/log/auth.log"); };
destination syslog { file("/var/log/syslog"); };
destination kern { file("/var/log/kern.log"); };
destination user { file("/var/log/user.log"); };
destination mailinfo { file("/var/log/mail/mail.info"); };
destination mailwarn { file("/var/log/mail/mail.warn"); };
destination mailerr { file("/var/log/mail/mail.err"); };
destination audit { file("/var/log/audit.log"); };
destination messages { file("/var/log/messages"); };
destination emergency { file("/var/log/emergency"); };
log { source(src); filter(f_auth); destination(authlog); };
log { source(src); filter(f_mail); filter(f_err); destination(mailerr); };
#log { source(src); filter(f_messages); destination(messages); };
log { source(src); filter(f_emergency); destination(emergency); };
# Remote loghost
destination loghost1 { tcp6("geb.ra.nemunai.re"); };
log { source(src); destination(loghost1); };
destination loghost2 { tcp6("jizah.masr.nemunai.re"); };
log { source(src); destination(loghost2); };
# Source additional configuration files (.conf extension only)
@include "/etc/syslog-ng/conf.d/*.conf"
path: /etc/syslog-ng/syslog-ng.conf
- content: |
# /etc/crontab: configuration file for cron
# See cron(8) and crontab(5) for details.
# m h dom mon dow user command
23 12 * * * root /root/launch_container_demo.sh && sleep 1 && /root/demo_initialize_data.sh
23 0 * * * root /root/launch_container_demo.sh && sleep 1 && /root/demo_initialize_data.sh
path: /etc/crontab
- content: |
#!/bin/sh
export AWS_ACCESS_KEY_ID=$(cloud-init query ds.metadata.RESTIC_AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(cloud-init query ds.metadata.RESTIC_AWS_SECRET_ACCESS_KEY)
export RESTIC_REPOSITORY=$(cloud-init query ds.metadata.RESTIC_REPOSITORY)
export RESTIC_PASSWORD=$(cloud-init query ds.metadata.RESTIC_PASSWORD)
export RESTIC_COMPRESSION=max
mkdir -p /var/backups/happydomain
docker exec -i app-happydomain hadmin /api/backup.json -X POST > /var/backups/happydomain/db.json
restic backup /var/backups/happydomain
path: /etc/cron.daily/backup_happydomain
permissions: 0o755
- content: |
#!/bin/sh
export AWS_ACCESS_KEY_ID=$(cloud-init query ds.metadata.RESTIC_AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(cloud-init query ds.metadata.RESTIC_AWS_SECRET_ACCESS_KEY)
export RESTIC_REPOSITORY=$(cloud-init query ds.metadata.RESTIC_REPOSITORY_POSTGRES)
export RESTIC_PASSWORD=$(cloud-init query ds.metadata.RESTIC_PASSWORD)
export RESTIC_COMPRESSION=max
mkdir -p /var/backups/postgres
DBLIST=$(docker exec -i postgres psql -U postgres -d postgres -q -t -c "SELECT datname FROM pg_database WHERE datname NOT IN ('postgres', 'template0', 'template1')")
for dbname in $DBLIST; do
echo "Dumping database '$dbname'"
docker exec -i postgres pg_dump -U postgres --no-owner --no-privileges --dbname="$dbname" > /var/backups/postgres/$dbname.sql || true # Ignore failures
done
restic backup /var/backups/postgres
path: /etc/cron.daily/backup_postgres
permissions: 0o755
- content: |
#!/bin/bash
set -e
set -u
function create_user_and_database() {
local database=$1
echo " Creating user and database '$database'"
PWD_VAR="POSTGRES_PASSWORD_$database"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE USER $database WITH PASSWORD '${!PWD_VAR}';
CREATE DATABASE $database;
ALTER DATABASE $database OWNER TO $database;
GRANT ALL PRIVILEGES ON DATABASE $database TO $database;
EOSQL
[ -f "/var/backups/postgres/${database}.sql" ] && \
psql -v ON_ERROR_STOP=1 -U "${database}" -d "${database}" < "/var/backups/postgres/${database}.sql"
}
if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
create_user_and_database $db
done
echo "Multiple databases created"
fi
path: /etc/pgsql-init/00-create-databases.sh
permissions: 0o755
- content: |
#!/bin/sh
export HAPPYDOMAIN_BIND="0.0.0.0:8081"
export HAPPYDOMAIN_CUSTOM_HEAD_HTML="<script async defer data-website-id=\"$(cloud-init query ds.metadata.UMAMI_ID)\" src=\"https://pythagore.p0m.fr/pythagore.js\"></script>"
export HAPPYDOMAIN_DEFAULT_NS="9.9.9.9:53"
export HAPPYDOMAIN_EXTERNALURL="https://$(cloud-init query ds.metadata.MY_DOMAIN)"
export HAPPYDOMAIN_JWT_SECRET_KEY="$(cloud-init query ds.metadata.HAPPYDOMAIN_JWT_SECRET_KEY)"
export HAPPYDOMAIN_NEWSLETTER_SERVER_URL="https://$(cloud-init query ds.metadata.LISTMONK_API_USERNAME):$(cloud-init query ds.metadata.LISTMONK_API_PASSWORD)@lists.happydomain.org/"
export HAPPYDOMAIN_NEWSLETTER_ID="$(cloud-init query ds.metadata.LISTMONK_NEWSLETTER_ID)"
export HAPPYDOMAIN_MAIL_FROM="Fred from happyDomain <contact@happydomain.org>"
export HAPPYDOMAIN_MAIL_SMTP_HOST="$(cloud-init query ds.metadata.EMAIL_SMTP_HOST)"
export HAPPYDOMAIN_MAIL_SMTP_PORT=$(cloud-init query ds.metadata.EMAIL_SMTP_PORT)
export HAPPYDOMAIN_MAIL_SMTP_USERNAME="$(cloud-init query ds.metadata.EMAIL_SMTP_USERNAME)"
export HAPPYDOMAIN_MAIL_SMTP_PASSWORD="$(cloud-init query ds.metadata.EMAIL_SMTP_PASSWORD)"
export HAPPYDOMAIN_OVH_APPLICATION_KEY="$(cloud-init query ds.metadata.HAPPYDOMAIN_OVH_APPLICATION_KEY)"
export HAPPYDOMAIN_OVH_APPLICATION_SECRET="$(cloud-init query ds.metadata.HAPPYDOMAIN_OVH_APPLICATION_SECRET)"
export HAPPYDOMAIN_STORAGE_ENGINE="leveldb"
[ -z "${HAPPYDOMAIN_VERSION}" ] && export HAPPYDOMAIN_VERSION=$(cloud-init query ds.metadata.HAPPYDOMAIN_VERSION)
docker inspect app-happydomain > /dev/null && {
docker pull happydomain/happydomain:${HAPPYDOMAIN_VERSION}
docker stop app-happydomain
docker rm app-happydomain
}
docker run -d --restart unless-stopped --network local -e HAPPYDOMAIN_BIND -e HAPPYDOMAIN_CUSTOM_HEAD_HTML -e HAPPYDOMAIN_DEFAULT_NS -e HAPPYDOMAIN_EXTERNALURL -e HAPPYDOMAIN_JWT_SECRET_KEY -e HAPPYDOMAIN_NEWSLETTER_SERVER_URL -e HAPPYDOMAIN_NEWSLETTER_ID -e HAPPYDOMAIN_MAIL_FROM -e HAPPYDOMAIN_MAIL_SMTP_HOST -e HAPPYDOMAIN_MAIL_SMTP_PORT -e HAPPYDOMAIN_MAIL_SMTP_USERNAME -e HAPPYDOMAIN_MAIL_SMTP_PASSWORD -e HAPPYDOMAIN_OVH_APPLICATION_KEY -e HAPPYDOMAIN_OVH_APPLICATION_SECRET -e HAPPYDOMAIN_STORAGE_ENGINE -p "8081:8081" --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=app-happydomain --name app-happydomain --pull always happydomain/happydomain:${HAPPYDOMAIN_VERSION}
path: /root/launch_container_app.sh
permissions: 0o755
- content: |
#!/bin/sh
# pdns
docker inspect pdns-demo-happydomain > /dev/null && {
docker pull nemunaire/pdns
docker stop pdns-demo-happydomain
docker rm pdns-demo-happydomain
}
docker run -d --restart unless-stopped --network local -e PDNS_AUTH_API_KEY=changeme --entrypoint /bin/sh --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=pdns-demo-happydomain --name pdns-demo-happydomain --pull always nemunaire/pdns -c "rm /var/lib/powerdns/pdns.sqlite3; sqlite3 /var/lib/powerdns/pdns.sqlite3 < /usr/share/doc/pdns/schema.sqlite3.sql && exec tini -- /usr/sbin/pdns_server-startup"
# happyDomain demo
export HAPPYDOMAIN_BIND="0.0.0.0:8081"
export HAPPYDOMAIN_CUSTOM_HEAD_HTML="<script async defer data-website-id=\"$(cloud-init query ds.metadata.TRY_UMAMI_ID)\" src=\"https://pythagore.p0m.fr/pythagore.js\"></script>"
export HAPPYDOMAIN_DEFAULT_NS="9.9.9.9:53"
export HAPPYDOMAIN_EXTERNALURL="https://$(cloud-init query ds.metadata.TRY_DOMAIN)"
export HAPPYDOMAIN_STORAGE_ENGINE="leveldb"
[ -z "${HAPPYDOMAIN_VERSION}" ] && export HAPPYDOMAIN_VERSION=$(cloud-init query ds.metadata.HAPPYDOMAIN_VERSION)
docker inspect demo-happydomain > /dev/null && {
docker pull happydomain/happydomain:${HAPPYDOMAIN_VERSION}
docker stop demo-happydomain
docker rm demo-happydomain
}
docker run -d --restart unless-stopped --network local -e HAPPYDOMAIN_BIND -e HAPPYDOMAIN_CUSTOM_HEAD_HTML -e HAPPYDOMAIN_DEFAULT_NS -e HAPPYDOMAIN_EXTERNALURL -e HAPPYDOMAIN_DISABLE_PROVIDERS_EDIT=true -e HAPPYDOMAIN_NO_AUTH=1 -e HAPPYDOMAIN_MSG_HEADER_TEXT="Shared demo instance; data reset at 00:34 and 12:34 UTC" -e HAPPYDOMAIN_MSG_HEADER_COLOR="warning" -e HAPPYDOMAIN_STORAGE_ENGINE --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=demo-happydomain --name demo-happydomain --pull always happydomain/happydomain:${HAPPYDOMAIN_VERSION}
path: /root/launch_container_demo.sh
permissions: 0o755
- content: |
#!/bin/sh
[ -z "$POSTGRES_PASSWORD_fider" ] && export $(docker inspect postgres -f 'json' | jq -r '.[0].Config.Env[]' | grep ^POSTGRES_PASSWORD_fider)
docker run -d --restart unless-stopped --network local -e BASE_URL="https://$(cloud-init query ds.metadata.FIDER_DOMAIN)" -e DATABASE_URL="postgres://fider:${POSTGRES_PASSWORD_fider}@postgres:5432/fider?sslmode=disable" -e JWT_SECRET="$(cloud-init query ds.metadata.FIDER_JWT_SECRET)" -e EMAIL_NOREPLY="feedback@happydomain.org" -e EMAIL_SMTP_HOST="$(cloud-init query ds.metadata.EMAIL_SMTP_HOST)" -e EMAIL_SMTP_PORT=$(cloud-init query ds.metadata.EMAIL_SMTP_PORT) -e EMAIL_SMTP_USERNAME="$(cloud-init query ds.metadata.EMAIL_SMTP_USERNAME)" -e EMAIL_SMTP_PASSWORD="$(cloud-init query ds.metadata.EMAIL_SMTP_PASSWORD)" -e EMAIL_SMTP_ENABLE_STARTTLS=true -e OAUTH_GITHUB_CLIENTID="$(cloud-init query ds.metadata.FIDER_GITHUB_CLIENTID)" -e OAUTH_GITHUB_SECRET="$(cloud-init query ds.metadata.FIDER_GITHUB_SECRET)" -p 3000:3000 --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=feedback --name feedback --pull always getfider/fider:stable
docker run -d --restart unless-stopped --network local -v /etc/listmonk.toml:/listmonk/config.toml:ro -e GENERIC_TIMEZONE="Europe/Paris" -e TZ="Europe/Paris" -p "9000:9000" --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=listmonk --name listmonk --pull always ghcr.io/knadh/listmonk:latest
path: /root/launch_container_other.sh
permissions: 0o755
runcmd:
# Allow traffic in IPv4
- sed -i '/-A INPUT -j REJECT/i-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT\n-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT' /etc/iptables/rules.v4
- iptables -I INPUT 5 -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
- iptables -I INPUT 5 -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
# Retrieve last backups
- export AWS_ACCESS_KEY_ID=$(cloud-init query ds.metadata.RESTIC_AWS_ACCESS_KEY_ID)
- export AWS_SECRET_ACCESS_KEY=$(cloud-init query ds.metadata.RESTIC_AWS_SECRET_ACCESS_KEY)
- export RESTIC_PASSWORD=$(cloud-init query ds.metadata.RESTIC_PASSWORD)
- export RESTIC_REPOSITORY=$(cloud-init query ds.metadata.RESTIC_REPOSITORY)
- mkdir -p /var/backups/happydomain
- restic restore latest --target / --include /var/backups/happydomain
- export RESTIC_REPOSITORY=$(cloud-init query ds.metadata.RESTIC_REPOSITORY_POSTGRES)
- mkdir -p /var/backups/postgres
- restic restore latest --target / --include /var/backups/postgres
- touch /var/backups/postgres/fider.sql
- touch /var/backups/postgres/listmonk.sql
# Create docker network
- docker network create local
# Generate database password
- export POSTGRES_PASSWORD_fider=$(openssl rand -base64 30 | sed 's@/@.@g')
- export POSTGRES_PASSWORD_listmonk=$(openssl rand -base64 30)
# Launch database
- |
cat > /etc/pgsql-init/70-update-listmonk-settings.sh <<EOF
#!/bin/sh
set -e
set -u
psql -v ON_ERROR_STOP=1 --username "\$POSTGRES_USER" -d listmonk <<-EOSQL
UPDATE settings SET value = '"https://$(cloud-init query ds.metadata.LISTMONK_DOMAIN)"' WHERE key = 'app.root_url';
UPDATE settings SET value = '"$(cloud-init query ds.metadata.LISTMONK_S3_CLIENT_ID)"' WHERE key = 'upload.s3.aws_access_key_id';
UPDATE settings SET value = '"$(cloud-init query ds.metadata.LISTMONK_S3_CLIENT_SECRET)"' WHERE key = 'upload.s3.aws_secret_access_key';
UPDATE settings SET value = '"https://$(cloud-init query ds.metadata.LISTMONK_S3_HOST)/"' WHERE key = 'upload.s3.url';
UPDATE settings SET value = '"$(cloud-init query ds.metadata.LISTMONK_S3_REGION)"' WHERE key = 'upload.s3.aws_default_region';
UPDATE settings SET value = '"$(cloud-init query ds.metadata.LISTMONK_S3_BUCKET)"' WHERE key = 'upload.s3.bucket';
UPDATE settings SET value = '[{"host": "$(cloud-init query ds.metadata.EMAIL_SMTP_HOST)", "port": $(cloud-init query ds.metadata.EMAIL_SMTP_PORT), "uuid": "16aee4cf-4e54-401d-a02d-70097e44315e", "enabled": true, "password": $(cloud-init query ds.metadata.EMAIL_SMTP_PASSWORD | jq -R . | sed 's/\$/\\$/'), "tls_type": "STARTTLS", "username": "$(cloud-init query ds.metadata.EMAIL_SMTP_USERNAME)", "max_conns": 10, "idle_timeout": "1m", "wait_timeout": "5s", "auth_protocol": "plain", "email_headers": [], "hello_hostname": "", "max_msg_retries": 4, "tls_skip_verify": false}]' WHERE key = 'smtp';
EOSQL
EOF
- chmod +x /etc/pgsql-init/70-update-listmonk-settings.sh
- docker run -d --restart unless-stopped --network local --shm-size=512MB -v /var/backups/postgres/:/var/backups/postgres/ -v /etc/pgsql-init/:/docker-entrypoint-initdb.d/ -v /var/lib/postgres/data:/var/lib/postgresql/data -e POSTGRES_PASSWORD=$(cloud-init query ds.metadata.POSTGRES_PASSWORD) -e POSTGRES_MULTIPLE_DATABASES="fider,listmonk" -e POSTGRES_PASSWORD_fider -e POSTGRES_PASSWORD_listmonk --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=postgres --name postgres --pull always --name postgres postgres:alpine
# Launch web server
- docker run -d --restart unless-stopped --network local -v /etc/caddy:/etc/caddy -v /srv/:/srv/ -p 80:80 -p 443:443 --log-driver syslog --log-opt "syslog-address=unixgram:///dev/log" --log-opt syslog-facility=daemon --log-opt tag=caddy --name caddy caddy:latest
# Launch container
- /root/launch_container_app.sh
# Generate listmonk config
- |
cat <<EOF > /etc/listmonk.toml
[app]
address = "0.0.0.0:9000"
[db]
host = "postgres"
port = 5432
user = "listmonk"
password = "${POSTGRES_PASSWORD_listmonk}"
database = "listmonk"
ssl_mode = "disable"
max_open = 25
max_idle = 25
max_lifetime = "300s"
EOF
# Launch others containers
- /root/launch_container_other.sh
# Launch demo containers
- /root/launch_container_demo.sh
# Restore happydomain backups
- |
[ -f /var/backups/happydomain/db.json ] && docker exec -i app-happydomain hadmin /api/backup.json -X PUT -d @- < /var/backups/happydomain/db.json
# Apply demo data
- |
[ -x /root/demo_initialize_data.sh ] && /root/demo_initialize_data.sh