Working nassella deployment.

This commit is contained in:
2026-04-08 19:54:32 -07:00
parent 265a682b52
commit dcd1df754a
21 changed files with 835 additions and 88 deletions

View File

@@ -11,6 +11,8 @@ $(wildcard all-apps/nextcloud/*) \
$(wildcard all-apps/wg-easy/*) \
$(wildcard all-apps/ghost/*) \
$(wildcard all-apps/nassella/*) \
all-apps/nassella/authelia-config/configuration.yml \
all-apps/nassella/lldap-config/lldap_config.toml \
$(wildcard all-apps/dozzle/*)
rm -Rf app/
@@ -46,6 +48,32 @@ all-apps/nextcloud/redis_password: $(apps_config)
all-apps/nextcloud/nextcloud.env: $(apps_config) all-apps/nextcloud/nextcloud.env.tmpl make-nextcloud-env.sh
./make-nextcloud-env.sh $(apps_config)
# Nassella
all-apps/nassella/postgres_db: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_POSTGRES_DB" > $@'
all-apps/nassella/postgres_user: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_POSTGRES_USER" > $@'
all-apps/nassella/postgres_password: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_POSTGRES_PASSWORD" > $@'
all-apps/nassella/authelia_postgres_db: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_AUTHELIA_POSTGRES_DB" > $@'
all-apps/nassella/authelia_postgres_user: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_AUTHELIA_POSTGRES_USER" > $@'
all-apps/nassella/authelia_postgres_password: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_AUTHELIA_POSTGRES_PASSWORD" > $@'
all-apps/nassella/lldap_postgres_db: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_LLDAP_POSTGRES_DB" > $@'
all-apps/nassella/lldap_postgres_user: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_LLDAP_POSTGRES_USER" > $@'
all-apps/nassella/lldap_postgres_password: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_LLDAP_POSTGRES_PASSWORD" > $@'
all-apps/nassella/lldap_admin_password: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$NASSELLA_LLDAP_ADMIN_PASSWORD" > $@'
all-apps/nassella/authelia-config/configuration.yml: $(apps_config) all-apps/nassella/authelia-config/configuration.yml.tmpl make-nassella-authelia-config.sh
./make-nassella-authelia-config.sh $(apps_config)
all-apps/nassella/lldap-config/lldap_config.toml: $(apps_config) all-apps/nassella/lldap-config/lldap_config.toml.tmpl make-nassella-lldap-config.sh
./make-nassella-lldap-config.sh $(apps_config)
# Ghost
all-apps/ghost/.compose-env: $(apps_config) all-apps/ghost/.compose.env.tmpl make-ghost-env.sh
./make-ghost-env.sh $(apps_config)
@@ -68,7 +96,15 @@ all-apps/nextcloud/nextcloud.env \
all-apps/nassella/postgres_db \
all-apps/nassella/postgres_user \
all-apps/nassella/postgres_password \
all-apps/nassella/lldap_postgres_db \
all-apps/nassella/lldap_postgres_user \
all-apps/nassella/lldap_postgres_password \
all-apps/nassella/authelia_postgres_db \
all-apps/nassella/authelia_postgres_user \
all-apps/nassella/authelia_postgres_password \
all-apps/nassella/nassella.env \
all-apps/nassella/authelia-config/configuration.yml \
all-apps/nassella/lldap-config/lldap_config.toml \
all-apps/ghost/.compose-env \
restic-env \
restic-password \
@@ -109,7 +145,7 @@ restic-snapshots: $(apps_config) restic-password
archive:
tar -cf nassella-latest.tar all-apps cl.yaml init-restic.sh main.tf make-caddyfile.sh Makefile \
make-generated.sh make-nextcloud-env.sh make-ghost-env.sh make-restic-generated.sh make-restic-password.sh restic-snapshots.sh copy-apps.sh \
.terraform.lock.hcl
make-nassella-authelia-config.sh make-nassella-lldap-config.sh .terraform.lock.hcl
cp nassella-latest.tar src/
## to help me remember the command to run to test the config locally

View File

@@ -0,0 +1,119 @@
---
###############################################################
# Authelia configuration #
###############################################################
server:
address: 'tcp://:9091/authelia'
endpoints:
authz:
forward-auth:
implementation: 'ForwardAuth'
log:
level: 'debug'
totp:
issuer: 'authelia.com'
identity_validation:
reset_password:
jwt_secret: '$NASSELLA_AUTHELIA_JWT_SECRET'
# lldap service account user should instead
# use an account with lldap_password_manager group
# since that can't be used to change an admin password
authentication_backend:
ldap:
address: 'ldap://nassella_lldap:3890'
implementation: 'lldap'
timeout: '5s'
pooling:
enable: false
count: 5
retries: 2
timeout: '10 seconds'
base_dn: 'DC=nassella,DC=org'
# additional_users_dn: 'OU=users'
# additional_groups_dn: 'OU=groups'
# group_search_mode: 'filter'
# permit_referrals: false
permit_unauthenticated_bind: false
permit_feature_detection_failure: false
user: 'uid=admin,ou=people,dc=nassella,dc=org'
password: '$NASSELLA_LLDAP_ADMIN_PASSWORD'
# attributes:
# distinguished_name: 'distinguishedName'
# username: 'uid'
# display_name: 'displayName'
# family_name: 'sn'
# given_name: 'givenName'
# middle_name: 'middleName'
# nickname: ''
# gender: ''
# birthdate: ''
# website: 'wWWHomePage'
# profile: ''
# picture: ''
# zoneinfo: ''
# locale: ''
# phone_number: 'telephoneNumber'
# phone_extension: ''
# street_address: 'streetAddress'
# locality: 'l'
# region: 'st'
# postal_code: 'postalCode'
# country: 'c'
# mail: 'mail'
# member_of: 'memberOf'
# group_name: 'cn'
# extra:
# extra_example:
# name: ''
# multi_valued: false
# value_type: 'string'
access_control:
default_policy: 'deny'
rules:
# - domain: 'public.x.localhost'
# policy: 'bypass'
# - domain: 'app.nassella.org'
# policy: 'one_factor'
- domain: '$NASSELLA_FULL_DOMAIN'
policy: 'two_factor'
session:
secret: 'insecure_session_secret'
cookies:
- name: 'authelia_session'
domain: '$NASSELLA_FULL_DOMAIN' # Should match whatever your root protected domain is
authelia_url: 'https://$NASSELLA_FULL_DOMAIN/authelia'
expiration: '1 hour' # 1 hour
inactivity: '5 minutes' # 5 minutes
default_redirection_url: 'https://$NASSELLA_FULL_DOMAIN/dashboard'
regulation:
max_retries: 3
find_time: '2 minutes'
ban_time: '5 minutes'
storage:
encryption_key: '$NASSELLA_AUTHELIA_KEY_SEED'
postgres:
address: 'tcp://nassella_authelia_db:5432'
servers: []
database: 'authelia'
schema: 'public'
username: 'authelia'
password: '$NASSELLA_AUTHELIA_POSTGRES_PASSWORD'
timeout: '5s'
notifier:
smtp:
address: 'submission://$SMTP_HOST:$SMTP_PORT'
username: '$SMTP_AUTH_USER'
password: '$SMTP_AUTH_PASSWORD'
sender: '$SMTP_FROM'
...

View File

@@ -0,0 +1 @@
authelia

View File

@@ -0,0 +1 @@
authelia

View File

@@ -7,8 +7,100 @@ secrets:
file: ./nassella/postgres_password
nassella_postgres_user:
file: ./nassella/postgres_user
nassella_lldap_postgres_db:
file: ./nassella/lldap_postgres_db
nassella_lldap_postgres_password:
file: ./nassella/lldap_postgres_password
nassella_lldap_postgres_user:
file: ./nassella/lldap_postgres_user
nassella_authelia_postgres_db:
file: ./nassella/authelia_postgres_db
nassella_authelia_postgres_password:
file: ./nassella/authelia_postgres_password
nassella_authelia_postgres_user:
file: ./nassella/authelia_postgres_user
nassella_lldap_admin_password:
file: ./nassella/lldap_admin_password
services:
nassella_lldap_db:
image: postgres:17.6-trixie
environment:
- POSTGRES_DB_FILE=/run/secrets/nassella_lldap_postgres_db
- POSTGRES_USER_FILE=/run/secrets/nassella_lldap_postgres_user
- POSTGRES_PASSWORD_FILE=/run/secrets/nassella_lldap_postgres_password
shm_size: 128mb
restart: always
volumes:
- /nassella/nassella/lldap-var-lib-postgresql-data:/var/lib/postgresql/data
networks:
- nassella_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -d `cat $$POSTGRES_DB_FILE` -U `cat $$POSTGRES_USER_FILE`"]
start_period: 15s
interval: 30s
retries: 3
timeout: 5s
secrets:
- nassella_lldap_postgres_db
- nassella_lldap_postgres_password
- nassella_lldap_postgres_user
nassella_lldap:
image: lldap/lldap:stable
volumes:
- ./nassella/lldap-config/:/data
networks:
- lb
- nassella_internal
depends_on:
nassella_lldap_db:
condition: service_healthy
secrets:
- nassella_lldap_postgres_db
- nassella_lldap_postgres_password
- nassella_lldap_postgres_user
nassella_authelia_db:
image: postgres:17.6-trixie
environment:
- POSTGRES_DB_FILE=/run/secrets/nassella_authelia_postgres_db
- POSTGRES_USER_FILE=/run/secrets/nassella_authelia_postgres_user
- POSTGRES_PASSWORD_FILE=/run/secrets/nassella_authelia_postgres_password
shm_size: 128mb
restart: always
volumes:
- /nassella/nassella/authelia-var-lib-postgresql-data:/var/lib/postgresql/data
networks:
- nassella_internal
healthcheck:
test: ["CMD-SHELL", "pg_isready -d `cat $$POSTGRES_DB_FILE` -U `cat $$POSTGRES_USER_FILE`"]
start_period: 15s
interval: 30s
retries: 3
timeout: 5s
secrets:
- nassella_authelia_postgres_db
- nassella_authelia_postgres_password
- nassella_authelia_postgres_user
nassella_authelia:
image: 'authelia/authelia'
volumes:
- ./nassella/authelia-config/:/config
networks:
- lb
- nassella_internal
depends_on:
nassella_lldap:
condition: service_healthy
nassella_authelia_db:
condition: service_healthy
restart: 'unless-stopped'
healthcheck:
## In production the healthcheck section should be commented.
disable: true
nassella_db:
image: postgres:17.6-trixie
env_file:
@@ -40,6 +132,7 @@ services:
- nassella_postgres_db
- nassella_postgres_password
- nassella_postgres_user
- nassella_lldap_admin_password
networks:
- lb
- nassella_internal

View File

@@ -0,0 +1,161 @@
## Default configuration for Docker.
## All the values can be overridden through environment variables, prefixed
## with "LLDAP_". For instance, "ldap_port" can be overridden with the
## "LLDAP_LDAP_PORT" variable.
## Tune the logging to be more verbose by setting this to be true.
## You can set it with the LLDAP_VERBOSE environment variable.
# verbose=false
## The host address that the LDAP server will be bound to.
## To enable IPv6 support, simply switch "ldap_host" to "::":
## To only allow connections from localhost (if you want to restrict to local self-hosted services),
## change it to "127.0.0.1" ("::1" in case of IPv6).
## If LLDAP server is running in docker, set it to "0.0.0.0" ("::" for IPv6) to allow connections
## originating from outside the container.
ldap_host = "0.0.0.0"
## The port on which to have the LDAP server.
#ldap_port = 3890
## The host address that the HTTP server will be bound to.
## To enable IPv6 support, simply switch "http_host" to "::".
## To only allow connections from localhost (if you want to restrict to local self-hosted services),
## change it to "127.0.0.1" ("::1" in case of IPv6).
## If LLDAP server is running in docker, set it to "0.0.0.0" ("::" for IPv6) to allow connections
## originating from outside the container.
http_host = "0.0.0.0"
## The port on which to have the HTTP server, for user login and
## administration.
#http_port = 17170
## The public URL of the server, for password reset links.
http_url = "https://$NASSELLA_FULL_LLDAP_DOMAIN"
## The path to the front-end assets (relative to the working directory).
#assets_path = "./app"
## Random secret for JWT signature.
## This secret should be random, and should be shared with application
## servers that need to consume the JWTs.
## Changing this secret will invalidate all user sessions and require
## them to re-login.
## You should probably set it through the LLDAP_JWT_SECRET environment
## variable from a secret ".env" file.
## This can also be set from a file's contents by specifying the file path
## in the LLDAP_JWT_SECRET_FILE environment variable
## You can generate it with (on linux):
## LC_ALL=C tr -dc 'A-Za-z0-9!#%&'\''()*+,-./:;<=>?@[\]^_{|}~' </dev/urandom | head -c 32; echo ''
jwt_secret = "$NASSELLA_LLDAP_JWT_SECRET"
## Base DN for LDAP.
## This is usually your domain name, and is used as a
## namespace for your users. The choice is arbitrary, but will be needed
## to configure the LDAP integration with other services.
## The sample value is for "example.com", but you can extend it with as
## many "dc" as you want, and you don't actually need to own the domain
## name.
ldap_base_dn = "dc=nassella,dc=org"
## Admin username.
## For the LDAP interface, a value of "admin" here will create the LDAP
## user "cn=admin,ou=people,dc=example,dc=com" (with the base DN above).
## For the administration interface, this is the username.
#ldap_user_dn = "admin"
## Admin email.
## Email for the admin account. It is only used when initially creating
## the admin user, and can safely be omitted.
#ldap_user_email = "admin@example.com"
## Admin password.
## Password for the admin account, both for the LDAP bind and for the
## administration interface. It is only used when initially creating
## the admin user.
## It should be minimum 8 characters long.
## You can set it with the LLDAP_LDAP_USER_PASS environment variable.
## This can also be set from a file's contents by specifying the file path
## in the LLDAP_LDAP_USER_PASS_FILE environment variable
## Note: you can create another admin user for user administration, this
## is just the default one.
ldap_user_pass = "$NASSELLA_LLDAP_ADMIN_PASSWORD"
## Force reset of the admin password.
## Break glass in case of emergency: if you lost the admin password, you
## can set this to true to force a reset of the admin password to the value
## of ldap_user_pass above.
## Alternatively, you can set it to "always" to reset every time the server starts.
# force_ldap_user_pass_reset = false
## Database URL.
## This encodes the type of database (SQlite, MySQL, or PostgreSQL)
## , the path, the user, password, and sometimes the mode (when
## relevant).
## Note: SQlite should come with "?mode=rwc" to create the DB
## if not present.
## Example URLs:
## - "postgres://postgres-user:password@postgres-server/my-database"
## - "mysql://mysql-user:password@mysql-server/my-database"
##
## This can be overridden with the LLDAP_DATABASE_URL env variable.
database_url = "postgres://lldap:$NASSELLA_LLDAP_POSTGRES_PASSWORD@nassella_lldap_db/lldap"
## Private key file.
## Not recommended, use key_seed instead.
## Contains the secret private key used to store the passwords safely.
## Note that even with a database dump and the private key, an attacker
## would still have to perform an (expensive) brute force attack to find
## each password.
## Randomly generated on first run if it doesn't exist.
## Env variable: LLDAP_KEY_FILE
#key_file = "/data/private_key"
## Seed to generate the server private key, see key_file above.
## This can be any random string, the recommendation is that it's at least 12
## characters long.
## Env variable: LLDAP_KEY_SEED
key_seed = "$NASSELLA_LLDAP_KEY_SEED"
## Ignored attributes.
## Some services will request attributes that are not present in LLDAP. When it
## is the case, LLDAP will warn about the attribute being unknown. If you want
## to ignore the attribute and the service works without, you can add it to this
## list to silence the warning.
#ignored_user_attributes = [ "sAMAccountName" ]
#ignored_group_attributes = [ "mail", "userPrincipalName" ]
## Options to configure SMTP parameters, to send password reset emails.
## To set these options from environment variables, use the following format
## (example with "password"): LLDAP_SMTP_OPTIONS__PASSWORD
[smtp_options]
## Whether to enabled password reset via email, from LLDAP.
enable_password_reset=true
## The SMTP server.
server="$SMTP_HOST"
## The SMTP port.
port=$SMTP_PORT
## How the connection is encrypted, either "NONE" (no encryption), "TLS" or "STARTTLS".
smtp_encryption = "TLS"
## The SMTP user, usually your email address.
user="$SMTP_AUTH_USER"
## The SMTP password.
password="$SMTP_AUTH_PASSSWORD"
## The header field, optional: how the sender appears in the email. The first
## is a free-form name, followed by an email between <>.
from="$SMTP_FROM"
## Same for reply-to, optional.
#reply_to="Do not reply <noreply@localhost>"
## Options to configure LDAPS.
## To set these options from environment variables, use the following format
## (example with "port"): LLDAP_LDAPS_OPTIONS__PORT
[ldaps_options]
## Whether to enable LDAPS.
#enabled=true
## Port on which to listen.
#port=6360
## Certificate file.
#cert_file="/data/cert.pem"
## Certificate key file.
#key_file="/data/key.pem"

View File

View File

@@ -0,0 +1 @@
lldap

View File

@@ -0,0 +1 @@
lldap

View File

@@ -104,7 +104,7 @@ resource "digitalocean_reserved_ip" "machine" {
resource "cloudflare_dns_record" "root" {
zone_id = var.cloudflare_zone_id
name = "_nassella-instance"
name = "nassella-instance"
content = digitalocean_reserved_ip.machine.ip_address
type = "A"
proxied = false
@@ -115,7 +115,7 @@ resource "cloudflare_dns_record" "subdomains" {
for_each = toset(var.subdomains)
zone_id = var.cloudflare_zone_id
name = each.key
content = "_nassella-instance.${var.domain}"
content = "nassella-instance.${var.domain}"
type = "CNAME"
proxied = false
ttl = 300

View File

@@ -26,7 +26,26 @@ declare -A bodys
bodys["nextcloud"]=" reverse_proxy http://nextcloud:80"
bodys["wg-easy"]=" reverse_proxy http://wg-easy:80"
bodys["ghost"]=" reverse_proxy http://ghost:2368"
bodys["nassella"]=" reverse_proxy http://nassella:8080"
bodys["nassella"]=$(cat <<EOF
route {
@authelia path /authelia /authelia/*
handle @authelia {
reverse_proxy nassella_authelia:9091
}
handle /unsecured/* {
reverse_proxy http://nassella:8080
}
forward_auth nassella_authelia:9091 {
uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
}
reverse_proxy http://nassella:8080
}
EOF
)
bodys["dozzle"]=$(cat <<EOF
basic_auth {
$HOST_ADMIN_USER $host_admin_password_encoded
@@ -48,7 +67,16 @@ for config_string in ${APP_CONFIGS[@]}; do
echo "$fulldomain {"
echo $body
echo "}"
# this is a hack specifically for nassella
# because lldap should be on a separate domain
# for security but this was not designed for one app
# to map to multiple caddy blocks
# currently this is hardcoded to prefix the nassella
# domain with 'lldap'
if [ "$app" = "nassella" ]; then
echo "lldap.$subdomain.$ROOT_DOMAIN {"
echo " reverse_proxy nassella_lldap:17170"
echo "}"
fi
done

View File

@@ -27,12 +27,19 @@ for config_string in ${APP_CONFIGS[@]}; do
IFS=','
read -r -a config <<< "$config_string"
app=${config[0]}
subdomain=${config[1]}
echo -n "$separator"
echo -n "\"$subdomain\""
separator=', '
# see note about lldap in make-caddyfile.sh
if [ "$app" = "nassella" ]; then
echo -n "$separator"
echo -n "\"lldap.$subdomain\""
fi
done
echo "]"

View File

@@ -0,0 +1,39 @@
#!/bin/bash
set -e
set -a # export everything in the config for later use by envsubst
. $1 # source the apps.config file with then env vars
read -r -a APP_CONFIGS <<< "$APP_CONFIGS"
nassella_subdomain=
for config_string in ${APP_CONFIGS[@]}; do
IFS=','
read -r -a config <<< "$config_string"
app=${config[0]}
subdomain=${config[1]}
if [ "$app" = "nassella" ]; then
nassella_subdomain="$subdomain"
fi
done
export NASSELLA_FULL_DOMAIN="$nassella_subdomain.$ROOT_DOMAIN"
export NASSELLA_FULL_LLDAP_DOMAIN="$NASSELLA_LLDAP_SUBDOMAIN.$nassella_subdomain.$ROOT_DOMAIN"
envsubst < all-apps/nassella/authelia-config/configuration.yml.tmpl > all-apps/nassella/authelia-config/configuration.yml
# write secrets
echo "$NASSELLA_POSTGRES_DB" > all-apps/nassella/postgres_db
echo "$NASSELLA_POSTGRES_USER" > all-apps/nassella/postgres_user
echo "$NASSELLA_POSTGRES_PASSWORD" > all-apps/nassella/postgres_password
echo "$NASSELLA_AUTHELIA_POSTGRES_DB" > all-apps/nassella/authelia_postgres_db
echo "$NASSELLA_AUTHELIA_POSTGRES_USER" > all-apps/nassella/authelia_postgres_user
echo "$NASSELLA_AUTHELIA_POSTGRES_PASSWORD" > all-apps/nassella/authelia_postgres_password
echo "$NASSELLA_LLDAP_POSTGRES_DB" > all-apps/nassella/lldap_postgres_db
echo "$NASSELLA_LLDAP_POSTGRES_USER" > all-apps/nassella/lldap_postgres_user
echo "$NASSELLA_LLDAP_POSTGRES_PASSWORD" > all-apps/nassella/lldap_postgres_password

39
make-nassella-lldap-config.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/bin/bash
set -e
set -a # export everything in the config for later use by envsubst
. $1 # source the apps.config file with then env vars
read -r -a APP_CONFIGS <<< "$APP_CONFIGS"
nassella_subdomain=
for config_string in ${APP_CONFIGS[@]}; do
IFS=','
read -r -a config <<< "$config_string"
app=${config[0]}
subdomain=${config[1]}
if [ "$app" = "nassella" ]; then
nassella_subdomain="$subdomain"
fi
done
export NASSELLA_FULL_DOMAIN="$nassella_subdomain.$ROOT_DOMAIN"
export NASSELLA_FULL_LLDAP_DOMAIN="$NASSELLA_LLDAP_SUBDOMAIN.$nassella_subdomain.$ROOT_DOMAIN"
envsubst < all-apps/nassella/lldap-config/lldap_config.toml.tmpl > all-apps/nassella/lldap-config/lldap_config.toml
# write secrets
echo "$NASSELLA_POSTGRES_DB" > all-apps/nassella/postgres_db
echo "$NASSELLA_POSTGRES_USER" > all-apps/nassella/postgres_user
echo "$NASSELLA_POSTGRES_PASSWORD" > all-apps/nassella/postgres_password
echo "$NASSELLA_AUTHELIA_POSTGRES_DB" > all-apps/nassella/authelia_postgres_db
echo "$NASSELLA_AUTHELIA_POSTGRES_USER" > all-apps/nassella/authelia_postgres_user
echo "$NASSELLA_AUTHELIA_POSTGRES_PASSWORD" > all-apps/nassella/authelia_postgres_password
echo "$NASSELLA_LLDAP_POSTGRES_DB" > all-apps/nassella/lldap_postgres_db
echo "$NASSELLA_LLDAP_POSTGRES_USER" > all-apps/nassella/lldap_postgres_user
echo "$NASSELLA_LLDAP_POSTGRES_PASSWORD" > all-apps/nassella/lldap_postgres_password

View File

@@ -52,7 +52,7 @@ RUN chmod +x nassella-run
FROM debian:trixie-slim
RUN apt-get update && apt-get -y --no-install-recommends install \
libpq-dev \
libpq-dev ca-certificates gettext-base \
&& rm -rf /var/lib/apt/lists/*
COPY --from=buildeggs /usr/local/ /usr/local/

View File

@@ -18,5 +18,5 @@ drop table user_service_configs;
drop index instances_user_id_instance_id_idx;
drop table instances;
drop index users_auth_user_id_idx;
drop index users_username_idx;
drop table users;

View File

@@ -1,12 +1,11 @@
create table users(
user_id bigserial primary key,
auth_user_id int unique not null,
email varchar(255) not null,
username varchar(255) not null unique,
key_key varchar(255),
key_iv varchar(255)
);
create unique index users_auth_user_id_idx on users (auth_user_id);
create unique index users_username_idx on users (username);
create table instances(
instance_id bigserial primary key,

View File

@@ -7,7 +7,8 @@
db-init db-clean
create-user delete-user
create-instance get-user-instances
get-user-id-by-username
create-instance destroy-instance get-user-instances
get-instance-ssh-pub-key get-instance-ssh-priv-key
update-instance-ssh-pub-key
get-instance-restic-password
@@ -41,10 +42,17 @@
crypto-tools
spiffy)
(define connection-spec (make-parameter '((dbname . "nassella") (user . "nassella") (password . "password")
;; (host . "127.0.0.1")
(host . "nassella_db")
)))
(define connection-spec
(make-parameter
(cond-expand
(dev
'((dbname . "nassella") (user . "nassella") (password . "password")
(host . "127.0.0.1")))
(else
(let ((pw (string-trim-right (with-input-from-file "/run/secrets/nassella_postgres_password" read-string)))) ;; remove newline
`((dbname . "nassella") (user . "nassella") (password . ,pw)
(host . "nassella_db")))))))
(define db-connection (make-parameter #f))
(define (with-db proc)
@@ -101,7 +109,7 @@
(define *root-key-key* (ensure-root-key))
(define (get-user-key-and-iv conn user-id)
(row-alist (query conn "select auth_user_id, key_key, key_iv from users where user_id=$1;" user-id)))
(row-alist (query conn "select username, key_key, key_iv from users where user_id=$1;" user-id)))
(define (get-decrypted-user-key-and-iv conn user-id)
(let* ((auth-user-id-and-user-key-and-iv (get-user-key-and-iv conn user-id))
@@ -109,9 +117,9 @@
(raw-user-key (hexstring->blob (string-drop-right raw-user-key-and-tag (* tag-length 2))))
(raw-user-tag (hexstring->blob (string-take-right raw-user-key-and-tag (* tag-length 2))))
(user-key (decrypt (blob->string raw-user-key) (blob->string raw-user-tag) *root-key-key* *root-key-iv*
(string->blob (number->string (alist-ref 'auth_user_id auth-user-id-and-user-key-and-iv)))))
(string->blob (alist-ref 'username auth-user-id-and-user-key-and-iv))))
(user-iv (alist-ref 'key_iv auth-user-id-and-user-key-and-iv))
(auth-user-id (alist-ref 'auth_user_id auth-user-id-and-user-key-and-iv)))
(auth-user-id (alist-ref 'username auth-user-id-and-user-key-and-iv)))
(values (hexstring->blob user-key) (hexstring->blob user-iv) auth-user-id)))
(define (user-encrypt message user-key user-iv user-id)
@@ -131,17 +139,17 @@
(raw-tag (hexstring->blob (string-take-right message-and-tag (* tag-length 2)))))
(user-decrypt (blob->string raw-message) (blob->string raw-tag) user-key user-iv user-id)))
(define (create-user conn auth-user-id email username)
(define (create-user conn email username)
(let ((user-key (blob->hexstring/uppercase (generate-key)))
(user-iv (blob->hexstring/uppercase (generate-iv))))
(receive (enc-user-key tag)
(encrypt user-key *root-key-key* *root-key-iv* (string->blob (number->string auth-user-id)))
(encrypt user-key *root-key-key* *root-key-iv* (string->blob username))
(let ((user-id
(value-at
(query conn
"insert into users(auth_user_id, email, username, key_key, key_iv) values ($1, $2, $3, $4, $5)
"insert into users(email, username, key_key, key_iv) values ($1, $2, $3, $4)
returning users.user_id;"
auth-user-id email username
email username
(string-append (blob->hexstring/uppercase (string->blob enc-user-key))
(blob->hexstring/uppercase (string->blob tag)))
user-iv))))
@@ -150,6 +158,12 @@ returning users.user_id;"
(define (delete-user conn user-id)
(query conn "delete from users where user_id=$1;" user-id))
(define (get-user-id-by-username conn username)
(let ((res (query conn "select user_id from users where username=$1;" username)))
(if (> (row-count res) 0)
(value-at res)
#f)))
;; We also encrypt the ssh pub key not to hide it but to make it
;; more difficult for someone to tamper with it which could allow
;; an attacker to poison an instance with an ssh key that they have
@@ -171,6 +185,9 @@ returning users.user_id;"
(query conn "insert into user_terraform_state(user_id, instance_id) values ($1, $2);" user-id instance-id)
instance-id)))
(define (destroy-instance conn instance-id)
(query conn "delete from instances where instance_id=$1;" instance-id))
(define (get-instance-ssh-priv-key conn user-id instance-id)
(receive (user-key user-iv auth-user-id)
(get-decrypted-user-key-and-iv conn user-id)
@@ -386,7 +403,10 @@ returning users.user_id;"
(value-at (query conn "select status from deployments where id=$1;" deployment-id)))
(define (get-most-recent-deployment-status conn user-id instance-id)
(value-at (query conn "select status from deployments where user_id=$1 and instance_id=$2 order by id DESC limit 1;" user-id instance-id)))
(let ((res (query conn "select status from deployments where user_id=$1 and instance_id=$2 order by id DESC limit 1;" user-id instance-id)))
(if (> (row-count res) 0)
(value-at res)
#f)))
(define *deployments-column-map*
'((generate-configs . "generate_configs")
@@ -550,7 +570,7 @@ returning users.user_id;"
(string-split (with-input-from-file "db-init.sql" read-string) ";"))
(log-to (debug-log) "table creation finished")
(log-to (debug-log) "creating test user")
(create-user db 1 "me@example.com" "username")
(create-user db "me@example.com" "username")
(log-to (debug-log) "test user creation finished"))))))
(define (db-clean)

View File

@@ -298,7 +298,11 @@ h1, h2, h3, h4, h5, h6 {
(define test-user-id (make-parameter 1))
(define (session-user-id)
(or (session-get "user-id") (test-user-id)))
(cond-expand
(dev
(or (session-get "user-id") (test-user-id)))
(else
(session-get "user-id"))))
(define-syntax get/widgets
(syntax-rules ()
@@ -314,9 +318,14 @@ h1, h2, h3, h4, h5, h6 {
headers)
;; `((meta (@ (name "viewport") (content "width=device-width"))))
(begin
;; TODO remove once sessions are integrated
(session-set! "user-id" (test-user-id))
(session-set! "username" "me")
(cond-expand
(dev
(session-set! "user-id" (test-user-id))
(session-set! "username" "me"))
(else
(let ((user-id (with-db/transaction (lambda (db) (get-user-id-by-username db (header-value 'remote-user (request-headers (current-request))))))))
(when user-id (session-set! "user-id" user-id))
(session-set! "username" (header-value 'remote-user (request-headers (current-request)))))))
body ...))))))))
(define-widget (Container ((max-width ($ 'width.main.max)) (style '())) contents)
@@ -524,6 +533,57 @@ h1, h2, h3, h4, h5, h6 {
(json-parsers (cons array-as-list-parser (json-parsers)))
;; TODO change username to to a prod API key that has read access
;; to the checkout session
(define (send-stripe-request #!key (method 'GET) endpoint (body #f) (username ""))
(define api-endpoint "https://api.stripe.com/")
(define api-version "/v1")
(with-input-from-request
(make-request method: method
uri: (uri-reference (string-append api-endpoint api-version endpoint))
headers: (headers `((authorization . (#(basic ((username . ,username) (password . ""))))))))
body
read-json))
(define (stripe-session-email sid)
(alist-ref
'email
(alist-ref
'customer_details
(send-stripe-request endpoint: (string-append "/checkout/sessions/" sid)))))
(define (create-lldap-user username email)
;; query = mutation createUser($user:CreateUserInput!){createUser(user:$user){id email displayName firstName lastName avatar}}
;; variables = {\"user\":{\"id\":\"${id}\",\"email\":\"${email}\",\"displayName\":\"${name}\",\"firstName\":\"${firstName}\",\"lastName\":\"${lastName}\",\"avatar\":\"
;; data="{\"query\":\"${query}\",\"variables\":${variables}"
;; http://localhost:17170/api/graphql
;; -H 'Content-Type: application/json' \
;; -H "Authorization: Bearer $token" \
(let ((api-token
(alist-ref
'token
(with-input-from-request
(make-request method: 'POST
uri: (uri-reference "http://nassella_lldap:17170/auth/simple/login")
headers: (headers `((content-type application/json))))
(lambda ()
(write-json
`((username . "admin") (password . ,(string-trim-right (with-input-from-file "/run/secrets/nassella_lldap_admin_password" read-string)))))) ;; trim to remove newline
read-json))))
(with-input-from-request
(make-request method: 'POST
uri: (uri-reference "http://nassella_lldap:17170/api/graphql")
headers: (headers `((content-type application/json)
(authorization #(,(string-append "Bearer " api-token) raw)))))
(lambda ()
(write-json
`((query . "mutation createUser($user:CreateUserInput!){createUser(user:$user){id email displayName firstName lastName avatar}}")
(variables . ((user . ((id . ,username)
(email . ,email))))))))
read-json)))
(define (get-digital-ocean-regions api-token)
(filter
(lambda (r)
@@ -733,6 +793,21 @@ chmod -R 777 /opt/keys"))
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+={}[]|<>,.?")
30)))
(define (generate-jwt-secret)
(generator->string (gtake (make-random-char-generator
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+={}[]|<>,.?")
32)))
(define (generate-key-seed)
(generator->string (gtake (make-random-char-generator
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_-+={}[]|<>,.?")
32)))
(define (generate-authelia-key-seed)
(generator->string (gtake (make-random-char-generator
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
64)))
(define (generate-postgres-password)
(generator->string (gtake (make-random-char-generator
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
@@ -746,13 +821,34 @@ chmod -R 777 /opt/keys"))
(with-schematra-app app
(lambda ()
;;; UNSECURED PAGES
(get/widgets
("/unsecured/account/create")
`(App
(form
(@ (action "/unsecured/account/create-submit") (method POST))
(VStack
(Fieldset
(@ (title "Account Details"))
(Field (@ (name "username") (label ("Username"))))
(input (@ (type "hidden") (name "sid") (value ,(alist-ref 'sid (current-params) equal?))))
(Button (@ (type "submit")) "Create Account"))))))
(post "/unsecured/account/create-submit"
(let ((email (stripe-session-email (alist-ref 'sid (current-params))))
(username (alist-ref 'username (current-params))))
(create-lldap-user username email)
(with-db/transaction (lambda (db) (create-user db email username))))
(redirect "/authelia/reset-password"))
;;; REQUIRES AUTHED USER
(post "/config/wizard/create-instance"
(let* ((ssh-keys (generate-ssh-key (session-user-id)))
(instance-id (with-db/transaction
(lambda (db)
(create-instance db (session-user-id) (first ssh-keys) (second ssh-keys)
(generate-restic-password))))))
(redirect (conc "/config/wizard/services/" instance-id))))
(let* ((ssh-keys (generate-ssh-key (session-user-id)))
(instance-id (with-db/transaction
(lambda (db)
(create-instance db (session-user-id) (first ssh-keys) (second ssh-keys)
(generate-restic-password))))))
(redirect (conc "/config/wizard/services/" instance-id))))
;; TODO should all these key related form fields be of type password
;; so the browser doesn't save them???
@@ -957,7 +1053,11 @@ chmod -R 777 /opt/keys"))
,@(if (member 'nassella selected-apps)
`((Fieldset
(@ (title "Nassella"))
(Field (@ (name "nassella-subdomain") (label ("Subdomain")) (value ,(alist-ref 'subdomain (alist-ref 'nassella app-config eq? '()) eq? "nassella"))))))
(Field (@ (name "nassella-subdomain") (label ("Subdomain")) (value ,(alist-ref 'subdomain (alist-ref 'nassella app-config eq? '()) eq? "app"))))
(Field (@ (name "nassella-lldap-subdomain") (label ("LLDAP Subdomain"))
(value ,(alist-ref 'lldap-subdomain (alist-ref 'nassella app-config eq? '()) eq? "lldap"))))
(Field (@ (name "nassella-lldap-admin-password") (label ("Admin Password")) (type "password")
(value ,(alist-ref 'lldap-admin-password (alist-ref 'nassella app-config eq? '()) eq? ""))))))
'())
(Fieldset
(@ (title "Log Viewer"))
@@ -967,7 +1067,7 @@ chmod -R 777 /opt/keys"))
(value ,(alist-ref 'user (alist-ref 'log-viewer app-config eq? '()) eq? ""))))
(Field (@ (name "log-viewer-password") (label ("Password")) (type "password")
(value ,(alist-ref 'password (alist-ref 'log-viewer app-config eq? '()) eq? "")))))
,@(if (or (member 'nextcloud selected-apps) (member 'ghost selected-apps))
,@(if (or (member 'nextcloud selected-apps) (member 'ghost selected-apps) (member 'nassella selected-apps))
`((Fieldset
(@ (title "All Apps - Email - SMTP"))
(Field (@ (name "smtp-host") (label ("Host"))
@@ -1009,7 +1109,30 @@ chmod -R 777 /opt/keys"))
(redis-password . ,(or (alist-ref 'redis-password
(alist-ref 'nextcloud config eq? '()))
(generate-redis-password)))))
(nassella . ((subdomain . ,(alist-ref 'nassella-subdomain (current-params)))))
(nassella . ((subdomain . ,(alist-ref 'nassella-subdomain (current-params)))
(postgres-password . ,(or (alist-ref 'postgres-password
(alist-ref 'nassella config eq? '()))
(generate-postgres-password)))
(authelia-postgres-password . ,(or (alist-ref 'authelia-postgres-password
(alist-ref 'nassella config eq? '()))
(generate-postgres-password)))
(lldap-postgres-password . ,(or (alist-ref 'lldap-postgres-password
(alist-ref 'nassella config eq? '()))
(generate-postgres-password)))
(lldap-jwt-secret . ,(or (alist-ref 'lldap-jwt-secret
(alist-ref 'nassella config eq? '()))
(generate-jwt-secret)))
(lldap-key-seed . ,(or (alist-ref 'lldap-key-seed
(alist-ref 'nassella config eq? '()))
(generate-key-seed)))
(lldap-subdomain . ,(alist-ref 'nassella-lldap-subdomain (current-params)))
(lldap-admin-password . ,(alist-ref 'nassella-lldap-admin-password (current-params)))
(authelia-jwt-secret . ,(or (alist-ref 'authelia-jwt-secret
(alist-ref 'nassella config eq? '()))
(generate-jwt-secret)))
(authelia-key-seed . ,(or (alist-ref 'authelia-key-seed
(alist-ref 'nassella config eq? '()))
(generate-authelia-key-seed)))))
(log-viewer . ((subdomain . ,(alist-ref 'log-viewer-subdomain (current-params)))
(user . ,(alist-ref 'log-viewer-user (current-params)))
(password . ,(alist-ref 'log-viewer-password (current-params)))))
@@ -1117,7 +1240,8 @@ chmod -R 777 /opt/keys"))
(@ (step "Review"))
(h2 "Root Domain")
,root-domain
(h2 "Apps")
(h2 "Apps") ;; TODO if an app that was previously selected is now unselected we need to somehow delete its data
;; so that if the user then re-deploys the app later we don't have key conflicts
(ul ,@(map (lambda (app) `(li ,app " @ "
,(alist-ref 'subdomain (alist-ref app config))
"."
@@ -1182,6 +1306,21 @@ chmod -R 777 /opt/keys"))
("NEXTCLOUD_REDIS_PASSWORD" . ,(alist-ref 'redis-password (alist-ref 'nextcloud config)))
("GHOST_DATABASE_ROOT_PASSWORD" . ,(alist-ref 'postgres-root-password (alist-ref 'ghost config)))
("GHOST_DATABASE_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'ghost config)))
("NASSELLA_LLDAP_SUBDOMAIN" . ,(alist-ref 'lldap-subdomain (alist-ref 'nassella config)))
("NASSELLA_POSTGRES_DB" . "nassella")
("NASSELLA_POSTGRES_USER" . "nassella")
("NASSELLA_POSTGRES_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_POSTGRES_DB" . "authelia")
("NASSELLA_AUTHELIA_POSTGRES_USER" . "authelia")
("NASSELLA_AUTHELIA_POSTGRES_PASSWORD" . ,(alist-ref 'authelia-postgres-password (alist-ref 'nassella config)))
("NASSELLA_LLDAP_POSTGRES_DB" . "lldap")
("NASSELLA_LLDAP_POSTGRES_USER" . "lldap")
("NASSELLA_LLDAP_POSTGRES_PASSWORD" . ,(alist-ref 'lldap-postgres-password (alist-ref 'nassella config)))
("NASSELLA_LLDAP_JWT_SECRET" . ,(alist-ref 'lldap-jwt-secret (alist-ref 'nassella config)))
("NASSELLA_LLDAP_KEY_SEED" . ,(alist-ref 'lldap-key-seed (alist-ref 'nassella config)))
("NASSELLA_LLDAP_ADMIN_PASSWORD" . ,(alist-ref 'lldap-admin-password (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_JWT_SECRET" . ,(alist-ref 'authelia-jwt-secret (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_KEY_SEED" . ,(alist-ref 'authelia-key-seed (alist-ref 'nassella config)))
("SMTP_HOST" . ,(alist-ref 'smtp-host (alist-ref 'all-apps config)))
("SMTP_PORT" . ,(alist-ref 'smtp-port (alist-ref 'all-apps config)))
("SMTP_AUTH_USER" . ,(alist-ref 'smtp-auth-user (alist-ref 'all-apps config)))
@@ -1200,10 +1339,10 @@ chmod -R 777 /opt/keys"))
("cloudflare_api_token" . ,(alist-ref 'cloudflare-api-token service-config))
("cloudflare_zone_id" . ,(alist-ref 'cloudflare-zone-id service-config))
("cloudflare_account_id" . ,(alist-ref 'cloudflare-account-id service-config))
("cluster_name" . "mycluster")
("cluster_name" . "nassella")
("datacenter" . ,(alist-ref 'digitalocean-region service-config))
;; (source <(curl -sSfL https://stable.release.flatcar-linux.net/amd64-usr/current/version.txt); echo "${FLATCAR_VERSION_ID}")
("flatcar_stable_version" . "4459.2.3")))
("flatcar_stable_version" . "4459.2.4")))
;; remove the newline that generating the ssh key adds
(display "ssh_keys=[\"") (display (string-drop-right ssh-pub-key 1)) (print "\"]"))))
(let* ((instance-id (alist-ref "id" (current-params) equal?))
@@ -1346,7 +1485,9 @@ chmod -R 777 /opt/keys"))
(li "Upgrade Now (pending automatic upgrades scheduled for: )")
(li "Manage Backups")
(li (a (@ (href "/destroy/" ,(alist-ref 'instance-id instance)))
"Destroy (confirmation required)")))))))
"Destroy - deletes data and configuration (confirmation required)"))
(li (a (@ (href "/reset/" ,(alist-ref 'instance-id instance)))
"Reset - deletes data (confirmation required)")))))))
(with-db/transaction
(lambda (db)
(get-dashboard db (session-user-id))))))))))
@@ -1361,7 +1502,7 @@ chmod -R 777 /opt/keys"))
`(App
(h2 "Destroy Instance")
,root-domain
(h2 "This action is NOT reversible")
(h2 "This action is NOT reversible. All data will be lost!")
(form
(@ (action ,(conc "/destroy-submit/" instance-id)) (method POST))
(VStack
@@ -1399,50 +1540,66 @@ chmod -R 777 /opt/keys"))
(begin
(setup-deploy-files dir (alist-ref 'state terraform-state) (alist-ref 'backup terraform-state))
(with-output-to-file (string-append dir "/config/apps.config")
(lambda ()
(map (lambda (e)
(write-config-entry (car e) (cdr e)))
`(("ROOT_DOMAIN" . ,root-domain)
("APP_CONFIGS" . ,(string-intersperse
(map (lambda (app)
(conc (if (eq? app 'log-viewer) 'dozzle app)
","
(alist-ref 'subdomain (alist-ref app config))))
selected-apps)
" "))
("HOST_ADMIN_USER" . ,(alist-ref 'user (alist-ref 'log-viewer config)))
("HOST_ADMIN_PASSWORD" . ,(alist-ref 'password (alist-ref 'log-viewer config)))
("NEXTCLOUD_ADMIN_USER" . ,(alist-ref 'admin-user (alist-ref 'nextcloud config)))
("NEXTCLOUD_ADMIN_PASSWORD" . ,(alist-ref 'admin-password (alist-ref 'nextcloud config)))
("NEXTCLOUD_POSTGRES_DB" . "nextcloud")
("NEXTCLOUD_POSTGRES_USER" . "nextcloud")
("NEXTCLOUD_POSTGRES_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'nextcloud config)))
("NEXTCLOUD_REDIS_PASSWORD" . ,(alist-ref 'redis-password (alist-ref 'nextcloud config)))
("GHOST_DATABASE_ROOT_PASSWORD" . ,(alist-ref 'postgres-root-password (alist-ref 'ghost config)))
("GHOST_DATABASE_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'ghost config)))
("SMTP_HOST" . ,(alist-ref 'smtp-host (alist-ref 'all-apps config)))
("SMTP_PORT" . ,(alist-ref 'smtp-port (alist-ref 'all-apps config)))
("SMTP_AUTH_USER" . ,(alist-ref 'smtp-auth-user (alist-ref 'all-apps config)))
("SMTP_AUTH_PASSWORD" . ,(alist-ref 'smtp-auth-password (alist-ref 'all-apps config)))
("SMTP_FROM" . ,(alist-ref 'smtp-from (alist-ref 'all-apps config)))
("BACKBLAZE_KEY_ID" . ,(alist-ref 'backblaze-key-id service-config))
("BACKBLAZE_APPLICATION_KEY" . ,(alist-ref 'backblaze-application-key service-config))
("BACKBLAZE_BUCKET_URL" . ,(alist-ref 'backblaze-bucket-url service-config))
("RESTIC_PASSWORD" . ,restic-password)))))
(with-output-to-file (string-append dir "/config/production.tfvars")
(lambda ()
(map (lambda (e)
(write-config-entry (car e) (cdr e)))
`(("server_type" . ,(alist-ref 'digitalocean-size service-config))
("do_token" . ,(alist-ref 'digitalocean-api-token service-config))
("cloudflare_api_token" . ,(alist-ref 'cloudflare-api-token service-config))
("cloudflare_zone_id" . ,(alist-ref 'cloudflare-zone-id service-config))
("cloudflare_account_id" . ,(alist-ref 'cloudflare-account-id service-config))
("cluster_name" . "mycluster")
("datacenter" . ,(alist-ref 'digitalocean-region service-config))
("flatcar_stable_version" . "4459.2.1")))
;; remove the newline that generating the ssh key adds
(display "ssh_keys=[\"") (display (string-drop-right ssh-pub-key 1)) (print "\"]")))
(lambda ()
(map (lambda (e)
(write-config-entry (car e) (cdr e)))
`(("ROOT_DOMAIN" . ,root-domain)
("APP_CONFIGS" . ,(string-intersperse
(map (lambda (app)
(conc (if (eq? app 'log-viewer) 'dozzle app)
","
(alist-ref 'subdomain (alist-ref app config))))
selected-apps)
" "))
("HOST_ADMIN_USER" . ,(alist-ref 'user (alist-ref 'log-viewer config)))
("HOST_ADMIN_PASSWORD" . ,(alist-ref 'password (alist-ref 'log-viewer config)))
("NEXTCLOUD_ADMIN_USER" . ,(alist-ref 'admin-user (alist-ref 'nextcloud config)))
("NEXTCLOUD_ADMIN_PASSWORD" . ,(alist-ref 'admin-password (alist-ref 'nextcloud config)))
("NEXTCLOUD_POSTGRES_DB" . "nextcloud")
("NEXTCLOUD_POSTGRES_USER" . "nextcloud")
("NEXTCLOUD_POSTGRES_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'nextcloud config)))
("NEXTCLOUD_REDIS_PASSWORD" . ,(alist-ref 'redis-password (alist-ref 'nextcloud config)))
("GHOST_DATABASE_ROOT_PASSWORD" . ,(alist-ref 'postgres-root-password (alist-ref 'ghost config)))
("GHOST_DATABASE_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'ghost config)))
("NASSELLA_LLDAP_SUBDOMAIN" . ,(alist-ref 'lldap-subdomain (alist-ref 'nassella config)))
("NASSELLA_POSTGRES_DB" . "nassella")
("NASSELLA_POSTGRES_USER" . "nassella")
("NASSELLA_POSTGRES_PASSWORD" . ,(alist-ref 'postgres-password (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_POSTGRES_DB" . "authelia")
("NASSELLA_AUTHELIA_POSTGRES_USER" . "authelia")
("NASSELLA_AUTHELIA_POSTGRES_PASSWORD" . ,(alist-ref 'authelia-postgres-password (alist-ref 'nassella config)))
("NASSELLA_LLDAP_POSTGRES_DB" . "lldap")
("NASSELLA_LLDAP_POSTGRES_USER" . "lldap")
("NASSELLA_LLDAP_POSTGRES_PASSWORD" . ,(alist-ref 'lldap-postgres-password (alist-ref 'nassella config)))
("NASSELLA_LLDAP_JWT_SECRET" . ,(alist-ref 'lldap-jwt-secret (alist-ref 'nassella config)))
("NASSELLA_LLDAP_KEY_SEED" . ,(alist-ref 'lldap-key-seed (alist-ref 'nassella config)))
("NASSELLA_LLDAP_ADMIN_PASSWORD" . ,(alist-ref 'lldap-admin-password (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_JWT_SECRET" . ,(alist-ref 'authelia-jwt-secret (alist-ref 'nassella config)))
("NASSELLA_AUTHELIA_KEY_SEED" . ,(alist-ref 'authelia-key-seed (alist-ref 'nassella config)))
("SMTP_HOST" . ,(alist-ref 'smtp-host (alist-ref 'all-apps config)))
("SMTP_PORT" . ,(alist-ref 'smtp-port (alist-ref 'all-apps config)))
("SMTP_AUTH_USER" . ,(alist-ref 'smtp-auth-user (alist-ref 'all-apps config)))
("SMTP_AUTH_PASSWORD" . ,(alist-ref 'smtp-auth-password (alist-ref 'all-apps config)))
("SMTP_FROM" . ,(alist-ref 'smtp-from (alist-ref 'all-apps config)))
("BACKBLAZE_KEY_ID" . ,(alist-ref 'backblaze-key-id service-config))
("BACKBLAZE_APPLICATION_KEY" . ,(alist-ref 'backblaze-application-key service-config))
("BACKBLAZE_BUCKET_URL" . ,(alist-ref 'backblaze-bucket-url service-config))
("RESTIC_PASSWORD" . ,restic-password)))))
(with-output-to-file (string-append dir "/config/production.tfvars")
(lambda ()
(map (lambda (e)
(write-config-entry (car e) (cdr e)))
`(("server_type" . ,(alist-ref 'digitalocean-size service-config))
("do_token" . ,(alist-ref 'digitalocean-api-token service-config))
("cloudflare_api_token" . ,(alist-ref 'cloudflare-api-token service-config))
("cloudflare_zone_id" . ,(alist-ref 'cloudflare-zone-id service-config))
("cloudflare_account_id" . ,(alist-ref 'cloudflare-account-id service-config))
("cluster_name" . "nassella")
("datacenter" . ,(alist-ref 'digitalocean-region service-config))
;; (source <(curl -sSfL https://stable.release.flatcar-linux.net/amd64-usr/current/version.txt); echo "${FLATCAR_VERSION_ID}")
("flatcar_stable_version" . "4459.2.4")))
;; remove the newline that generating the ssh key adds
(display "ssh_keys=[\"") (display (string-drop-right ssh-pub-key 1)) (print "\"]")))
;; TODO need a new table to track destroying?
;; as this is creating a new "deployment"
;; to attach state to
@@ -1495,9 +1652,54 @@ chmod -R 777 /opt/keys"))
(with-input-from-file (string-append dir "/make-out") read-string))
(update-user-terraform-state db user-id instance-id
(if (eof-object? tf-state) "" tf-state)
(if (eof-object? tf-state-backup) "" tf-state-backup))))))))))))
(if (eof-object? tf-state-backup) "" tf-state-backup))
(when exit-normal
(destroy-instance db instance-id))))))))))))
(redirect (conc "/destroy-success/" (alist-ref "id" (current-params) equal?)))))))
(get/widgets
("/destroy-success/:id"
(let* ((instance-id (alist-ref "id" (current-params) equal?))
(res (with-db/transaction
(lambda (db)
`((status . ,(get-most-recent-deployment-status db (session-user-id) instance-id))))))
(status (or (and (alist-ref 'status res) (string->symbol (alist-ref 'status res))) 'destroyed)))
(if (or (eq? status 'complete) (eq? status 'failed) (eq? status 'destroyed))
'()
'((meta (@ (http-equiv "refresh") (content "5")))))))
(let* ((instance-id (alist-ref "id" (current-params) equal?))
(res (with-db/transaction
(lambda (db)
`((status . ,(get-most-recent-deployment-status db (session-user-id) instance-id))
(progress . ,(get-most-recent-deployment-progress db (session-user-id) instance-id))))))
(output (with-input-from-file (string-append (deployment-directory (session-user-id)) "/make-out") read-string))
(progress (alist-ref 'progress res))
(status (alist-ref 'status res)))
`(App
(Main-Container
(VStack
(h1
,(case (string->symbol status)
((queued) "Destroy queued")
((in-progress) "Destroy in progress")
((destroyed) "Destroy complete!")
((failed) "Destroy failed")))
,@(if (eq? status 'destroyed)
'((a (@ (href "/dashboard")) "Dashboard"))
`((ul (li "generate configs: " ,(progress-status->text (alist-ref 'generate-configs progress)))
(li "custom flatcar image: " ,(progress-status->text (alist-ref 'custom-image progress)))
(li "machine create: " ,(progress-status->text (alist-ref 'machine-create progress)))
(li "cleanup previous machine: " ,(progress-status->text (alist-ref 'machine-destroy progress))))
(div
(a (@ (href "/dashboard")) "Dashboard")
,@(if (or (eq? (string->symbol status) 'complete) (eq? (string->symbol status) 'failed))
'()
" (deployment will continue in the background if you leave this page)"))
(hr)
(pre (@ (style ((overflow-x "scroll"))))
,output)))
)))))
(schematra-install)
))