Compare commits

..

6 Commits

14 changed files with 233 additions and 188 deletions

View File

@@ -111,11 +111,11 @@ preapply: app/.dirstamp
apply: announce-start restic-init ignition.json $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars apply: announce-start restic-init ignition.json $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars
echo "NASSELLA_CONFIG: end" echo "NASSELLA_CONFIG: end"
terraform init terraform init --upgrade
bash -c "terraform apply -auto-approve -input=false -var-file=<(cat $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars)" bash -c "terraform apply -auto-approve -input=false -var-file=<(cat $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars)"
destroy: ignition.json $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars destroy: ignition.json $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars
terraform init terraform init --upgrade
bash -c "terraform destroy -auto-approve -input=false -var-file=<(cat $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars)" bash -c "terraform destroy -auto-approve -input=false -var-file=<(cat $(config_dir)$(TERRAFORM_ENV).tfvars generated.tfvars)"
.PHONY: restic-init .PHONY: restic-init

View File

@@ -14,8 +14,17 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with Nassella. If not, see <https://www.gnu.org/licenses/>. # along with Nassella. If not, see <https://www.gnu.org/licenses/>.
{$authelia_SUBDOMAIN}.{$ROOT_DOMAIN} { #{$authelia_SUBDOMAIN}.{$ROOT_DOMAIN} {
import maintenance_intercept # route {
# @authelia path /authelia /authelia/*
# handle @authelia {
# reverse_proxy authelia:9091
# }
# forward_auth authelia:9091 {
# uri /api/authz/forward-auth
# copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
# }
# reverse_proxy authelia:9091
# }
#}
reverse_proxy authelia:9091
}

View File

@@ -18,7 +18,8 @@ IMAGE_DEPS := $(IMAGE_DEPS) \
app/authelia/postgres_db \ app/authelia/postgres_db \
app/authelia/postgres_user \ app/authelia/postgres_user \
app/authelia/postgres_password \ app/authelia/postgres_password \
app/authelia/authelia-config/configuration.yml app/authelia/authelia-config/configuration.yml \
app/authelia/authelia-config/users.yml
app/authelia/postgres_db: $(apps_config) app/authelia/postgres_db: $(apps_config)
bash -c 'source ./$(apps_config); printf "%s\n" "$$AUTHELIA_POSTGRES_DB" > $@' bash -c 'source ./$(apps_config); printf "%s\n" "$$AUTHELIA_POSTGRES_DB" > $@'
@@ -29,3 +30,6 @@ app/authelia/postgres_password: $(apps_config)
app/authelia/authelia-config/configuration.yml: $(apps_config) app/authelia/authelia-config/configuration.yml.tmpl \ app/authelia/authelia-config/configuration.yml: $(apps_config) app/authelia/authelia-config/configuration.yml.tmpl \
app/authelia/make-authelia-config.sh app/authelia/make-authelia-config.sh
./app/authelia/make-authelia-config.sh $(apps_config) ./app/authelia/make-authelia-config.sh $(apps_config)
app/authelia/authelia-config/users.yml: $(apps_config) app/authelia/authelia-config/users.yml.tmpl \
app/authelia/make-authelia-users-config.sh
./app/authelia/make-authelia-users-config.sh $(apps_config)

View File

@@ -35,58 +35,45 @@ identity_validation:
reset_password: reset_password:
jwt_secret: '$AUTHELIA_JWT_SECRET' jwt_secret: '$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: authentication_backend:
ldap: disable_reset_password: true
address: 'ldap://lldap:3890' file:
implementation: 'lldap' path: '/config/users.yml'
timeout: '5s' watch: false
pooling: search:
enable: false email: false
count: 5 case_insensitive: false
retries: 2 extra_attributes:
timeout: '10 seconds' extra_example:
base_dn: 'DC=nassella,DC=org' multi_valued: false
# additional_users_dn: 'OU=users' value_type: 'string'
# additional_groups_dn: 'OU=groups' password:
# group_search_mode: 'filter' algorithm: 'argon2'
# permit_referrals: false argon2:
permit_unauthenticated_bind: false variant: 'argon2id'
permit_feature_detection_failure: false iterations: 3
user: 'uid=admin,ou=people,dc=nassella,dc=org' memory: 65536
password: '$LLDAP_ADMIN_PASSWORD' parallelism: 4
# attributes: key_length: 32
# distinguished_name: 'distinguishedName' salt_length: 16
# username: 'uid' scrypt:
# display_name: 'displayName' variant: 'scrypt'
# family_name: 'sn' iterations: 16
# given_name: 'givenName' block_size: 8
# middle_name: 'middleName' parallelism: 1
# nickname: '' key_length: 32
# gender: '' salt_length: 16
# birthdate: '' pbkdf2:
# website: 'wWWHomePage' variant: 'sha512'
# profile: '' iterations: 310000
# picture: '' salt_length: 16
# zoneinfo: '' sha2crypt:
# locale: '' variant: 'sha512'
# phone_number: 'telephoneNumber' iterations: 50000
# phone_extension: '' salt_length: 16
# street_address: 'streetAddress' bcrypt:
# locality: 'l' variant: 'standard'
# region: 'st' cost: 12
# 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: access_control:
default_policy: 'deny' default_policy: 'deny'
@@ -102,11 +89,12 @@ session:
secret: '$AUTHELIA_SESSION_SECRET' secret: '$AUTHELIA_SESSION_SECRET'
cookies: cookies:
- name: 'authelia_session' - name: 'dozzle_session'
domain: '$ROOT_DOMAIN' # Should match whatever your root protected domain is domain: '$DOZZLE_FULL_DOMAIN' # Should match whatever your root protected domain is
authelia_url: 'https://$AUTHELIA_FULL_DOMAIN' authelia_url: 'https://$DOZZLE_FULL_DOMAIN/authelia'
expiration: '1 hour' # 1 hour expiration: '1 hour' # 1 hour
inactivity: '5 minutes' # 5 minutes inactivity: '5 minutes' # 5 minutes
default_redirection_url: 'https://$DOZZLE_FULL_DOMAIN/'
regulation: regulation:
max_retries: 3 max_retries: 3

View File

@@ -0,0 +1,7 @@
# yaml-language-server: $schema=https://www.authelia.com/schemas/latest/json-schema/user-database.json
users:
admin:
disabled: false
displayname: 'Admin'
password: '$HOST_ADMIN_PASSWORD'
email: '$HOST_ADMIN_USER'

View File

@@ -50,13 +50,11 @@ services:
image: 'authelia/authelia' image: 'authelia/authelia'
volumes: volumes:
- ./authelia/authelia-config/configuration.yml:/config/configuration.yml:ro - ./authelia/authelia-config/configuration.yml:/config/configuration.yml:ro
- ./authelia/authelia-config/users.yml:/config/users.yml:ro
networks: networks:
- lb - lb
- authelia_internal_db - authelia_internal_db
- lldap_internal
depends_on: depends_on:
lldap:
condition: service_healthy
authelia_db: authelia_db:
condition: service_healthy condition: service_healthy
restart: 'unless-stopped' restart: 'unless-stopped'
@@ -66,9 +64,6 @@ services:
networks: networks:
lb: lb:
lldap_internal:
driver: bridge
internal: true
authelia_internal_db: authelia_internal_db:
driver: bridge driver: bridge
internal: true internal: true

View File

@@ -0,0 +1,24 @@
#!/bin/bash
# Copyright 2025-2026 Thomas Hintz
# This file is part of Nassella.
# Nassella is free software: you can redistribute it and/or modify it under the
# terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
# Nassella is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with Nassella. If not, see <https://www.gnu.org/licenses/>.
set -e
set -a # export everything in the config for later use by envsubst
. $1 # source the apps.config file with then env vars
envsubst < app/authelia/authelia-config/users.yml.tmpl > app/authelia/authelia-config/users.yml

View File

@@ -15,9 +15,15 @@
# along with Nassella. If not, see <https://www.gnu.org/licenses/>. # along with Nassella. If not, see <https://www.gnu.org/licenses/>.
{$dozzle_SUBDOMAIN}.{$ROOT_DOMAIN} { {$dozzle_SUBDOMAIN}.{$ROOT_DOMAIN} {
route {
@authelia path /authelia /authelia/*
handle @authelia {
reverse_proxy authelia:9091
}
forward_auth authelia:9091 { forward_auth authelia:9091 {
uri /api/authz/forward-auth uri /api/authz/forward-auth
copy_headers Remote-User Remote-Groups Remote-Email Remote-Name copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
} }
reverse_proxy http://dozzle:8080 reverse_proxy http://dozzle:8080
} }
}

View File

@@ -84,7 +84,7 @@ access_control:
policy: 'two_factor' policy: 'two_factor'
session: session:
secret: 'insecure_session_secret' secret: 'insecure_session_secret' # TODO fix
cookies: cookies:
- name: 'authelia_session' - name: 'authelia_session'

28
main.tf
View File

@@ -2,23 +2,19 @@ terraform {
required_providers { required_providers {
digitalocean = { digitalocean = {
source = "digitalocean/digitalocean" source = "digitalocean/digitalocean"
version = "2.62.0" version = "2.96.0"
} }
ct = { ct = {
source = "poseidon/ct" source = "poseidon/ct"
version = "0.13.0" version = "0.14.0"
} }
null = { null = {
source = "hashicorp/null" source = "hashicorp/null"
version = "3.2.4" version = "3.3.0"
}
time = {
source = "hashicorp/time"
version = "0.13.1"
} }
cloudflare = { cloudflare = {
source = "cloudflare/cloudflare" source = "cloudflare/cloudflare"
version = "5.8.2" version = "5.22.0"
} }
} }
} }
@@ -86,6 +82,10 @@ variable "digitalocean_volume_size" {
provider "digitalocean" { provider "digitalocean" {
token = var.do_token token = var.do_token
http_retry_max = 10
http_retry_wait_min = 2
http_retry_wait_max = 60
} }
provider "cloudflare" { provider "cloudflare" {
@@ -142,21 +142,11 @@ resource "digitalocean_droplet" "machine" {
size = var.server_type size = var.server_type
ssh_keys = [digitalocean_ssh_key.first.fingerprint] ssh_keys = [digitalocean_ssh_key.first.fingerprint]
user_data = file("ignition.json") user_data = file("ignition.json")
lifecycle {
create_before_destroy = true
} }
}
resource "time_sleep" "wait_10_seconds" {
create_duration = "10s"
}
resource "digitalocean_reserved_ip_assignment" "machine" { resource "digitalocean_reserved_ip_assignment" "machine" {
depends_on = [digitalocean_volume_attachment.machine]
ip_address = digitalocean_reserved_ip.machine.ip_address ip_address = digitalocean_reserved_ip.machine.ip_address
droplet_id = digitalocean_droplet.machine.id droplet_id = digitalocean_droplet.machine.id
# sometimes digital ocean throws an error for this resource
# saying the droplet has a pending event so we delay a few seconds
depends_on = [time_sleep.wait_10_seconds]
} }
resource "digitalocean_volume_attachment" "machine" { resource "digitalocean_volume_attachment" "machine" {

View File

@@ -45,6 +45,7 @@ RUN chmod +x nassella-run
FROM quay.io/coreos/butane:release AS butane FROM quay.io/coreos/butane:release AS butane
FROM hashicorp/terraform:1.15 AS terraform FROM hashicorp/terraform:1.15 AS terraform
FROM authelia/authelia:latest AS authelia
FROM debian:trixie-slim FROM debian:trixie-slim
RUN apt-get update && apt-get -y --no-install-recommends install \ RUN apt-get update && apt-get -y --no-install-recommends install \
@@ -53,6 +54,7 @@ RUN apt-get update && apt-get -y --no-install-recommends install \
COPY --from=butane /usr/local/bin/butane /usr/local/bin/butane COPY --from=butane /usr/local/bin/butane /usr/local/bin/butane
COPY --from=terraform /bin/terraform /usr/local/bin/terraform COPY --from=terraform /bin/terraform /usr/local/bin/terraform
COPY --from=authelia /app/authelia /usr/local/bin/authelia
COPY --from=buildeggs /usr/local/ /usr/local/ COPY --from=buildeggs /usr/local/ /usr/local/

View File

@@ -417,6 +417,7 @@ where usa.user_id=$1 and usa.instance_id=$2;"
(volume-create . "terraform_volume_create") (volume-create . "terraform_volume_create")
(volume-destroy . "terraform_volume_destroy") (volume-destroy . "terraform_volume_destroy")
(instance-backup . "instance_backup") (instance-backup . "instance_backup")
(instance-up . "instance_up")
(status . "status") (status . "status")
(id . "id") (id . "id")
(instance-id . "instance_id"))) (instance-id . "instance_id")))
@@ -662,7 +663,8 @@ where user_id=$1;"
(6 . "adding-lldap-app") (6 . "adding-lldap-app")
(7 . "adding-authelia-app") (7 . "adding-authelia-app")
(8 . "adding-backblaze-bucket-name-and-id") (8 . "adding-backblaze-bucket-name-and-id")
(9 . "extend-deployment-status-add-ignored"))) (9 . "extend-deployment-status-add-ignored")
(10 . "adding-deployments-instance-up")))
(define (run-pending-migrations conn) (define (run-pending-migrations conn)
(let* ((migration-ids (sort (map car *migrations*) <)) (let* ((migration-ids (sort (map car *migrations*) <))

View File

@@ -0,0 +1 @@
alter table deployments add instance_up deployment_status not null default 'queued';

View File

@@ -869,67 +869,37 @@ h1, h2, h3, h4, h5, h6 {
`((success . #f) `((success . #f)
(result ,data)))))) (result ,data))))))
;; (define (test-backblaze-connection user-id instance-id key-id application-key bucket-url) (define *instance-up-timeout* 1) ;; seconds
;; (let* ((password-path (conc "restic-password-" user-id "-" instance-id)) (define *instance-up-retry-time* 120) ;; 120s, 2 minutes
;; (restic-password (define *instance-up-retries* (/ *instance-up-retry-time* *instance-up-timeout*))
;; (with-db/transaction (define (instance-up? path)
;; (lambda (db) (let loop ((i 0))
;; (get-instance-restic-password db user-id instance-id))))) (let ((thread
;; (dynamic-wind (thread-start!
;; (lambda () (lambda ()
;; (with-output-to-file password-path (lambda () (display restic-password)))) (condition-case
;; (lambda () (receive (data uri response) (with-input-from-request path #f read-string)
;; ;; restic retries (indefinitely?) if the connection can't be made so (if (eq? (response-code response) 401)
;; ;; we pass it through timeout to ensure it does not hang #t
;; (receive (in-port out-port pid err-port) (begin (thread-sleep! *instance-up-timeout*) ;; we depend on this taking at least as long as the timeout if the instance is still booting
;; (cond-expand #f)))
;; (dev (e (exn client-error)
;; (process* "timeout" `("--preserve-status" "8s" "docker" "run" "--rm" "--volume" (if (eq? (response-code (get-condition-property e 'client-error 'response)) 401)
;; ,(conc (current-directory) "/" password-path ":/restic-password") #t
;; "-e" ,(conc "AWS_ACCESS_KEY_ID=" key-id) (begin (thread-sleep! *instance-up-timeout*) ;; we depend on this taking at least as long as the timeout if the instance is still booting
;; "-e" ,(conc "AWS_SECRET_ACCESS_KEY=" application-key) #f)))
;; "-i" "restic/restic:0.18.0" "cat" "config" (e (exn net)
;; "--repo" ,(conc "s3:" bucket-url) (thread-sleep! *instance-up-timeout*)
;; "--password-file" "/restic-password" #f)
;; "--json"))) (e ()
;; (else (thread-sleep! *instance-up-timeout*)
;; (process* "/bin/timeout" #f))))))
;; `("--preserve-status" "8s" "/bin/restic" "cat" "config" (if (thread-join! thread *instance-up-timeout* #f)
;; "--repo" ,(conc "s3:" bucket-url) #t
;; "--password-file" ,(conc (current-directory) "/" password-path) (if (< i *instance-up-retries*)
;; "--json") (loop (+ i 1))
;; `(("AWS_ACCESS_KEY_ID" . ,key-id) #f)))))
;; ("AWS_SECRET_ACCESS_KEY" . ,application-key)))))
;; (let ((thread
;; (thread-start!
;; (lambda ()
;; (let loop ((i 0))
;; (thread-sleep! 1)
;; ;; We do a non-blocking wait here so that we don't
;; ;; block the entire web process.
;; (receive (wait-pid exit-normal status) (process-wait pid #t)
;; (if (= wait-pid 0) ;; wait-pid is 0 until the process has finished
;; (if (< i 12) ;; 12s timeout
;; (loop (+ i 1))
;; `((success . #f)
;; (errors ((message . "timeout trying to connect to backblaze")))))
;; (if exit-normal
;; (let ((res (with-input-from-port in-port read-json))
;; (err (with-input-from-port err-port read-json)))
;; ;; status 10 is what is returned if we can connect but the repo
;; ;; is not initialized yet
;; (if (or res (and err (= status 10)))
;; `((success . #t)
;; (result . ,res))
;; `((success . #f)
;; (errors ((message . "abnormal exit - check Key ID, Application ID, and Bucket URL"))))))
;; `((success . #f)
;; (errors ((message . "abnormal exit - check Key ID, Application ID, and Bucket URL"))))
;; ))))))))
;; (thread-join! thread))))
;; (lambda ()
;; (handle-exceptions exn 'ignore
;; (delete-file password-path))))))
(define (deployment-directory user-id instance-id) (define (deployment-directory user-id instance-id)
(string-append "deploy-" (number->string user-id) "-" (->string instance-id))) (string-append "deploy-" (number->string user-id) "-" (->string instance-id)))
@@ -1077,6 +1047,34 @@ chmod -R 777 /opt/keys")))
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
64))) 64)))
(define (create-authelia-password-hash password)
;; docker run --rm -it authelia/authelia:latest authelia crypto hash generate argon2
(receive (in-port out-port pid err-port)
(cond-expand
(dev
(process* "docker" `("run" "--rm" "authelia/authelia:latest" "authelia" "crypto" "hash" "generate" "argon2" "--password" ,password)))
(else
(process* "/usr/local/bin/authelia" `("crypto" "hash" "generate" "argon2" "--password" ,password))))
(let ((thread
(thread-start!
(lambda ()
(let loop ()
(thread-sleep! 1)
;; We do a non-blocking wait here so that we don't
;; block the entire web process.
(receive (wait-pid exit-normal status) (process-wait pid #t)
(if (= wait-pid 0) ;; wait-pid is 0 until the process has finished
(loop)
(if exit-normal
(begin
(with-input-from-port err-port read-string) ;; left here for debugging and to clear ports
;; the output is prefixed with "Digest: " and suffixed with a newline
;; so we clean it up
(string-drop (string-trim-right (with-input-from-port in-port read-string)) 8))
(begin (log-to (debug-log) "authelia: docker command error")
(error "Authelia for generating password hash had abnormal exit"))))))))))
(thread-join! thread))))
(define (generate-postgres-password) (define (generate-postgres-password)
(generator->string (gtake (make-random-char-generator (generator->string (gtake (make-random-char-generator
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
@@ -1401,8 +1399,9 @@ chmod -R 777 /opt/keys")))
'())) '()))
(Field (@ (name "wordpress") (type "checkbox") (label ("Wordpress")) (checked ,(member 'wordpress (alist-ref 'selected-apps results))))) (Field (@ (name "wordpress") (type "checkbox") (label ("Wordpress")) (checked ,(member 'wordpress (alist-ref 'selected-apps results)))))
(Field (@ (name "log-viewer") (type "checkbox") (label ("Log Viewer")) (checked #t) (disabled "disabled"))) (Field (@ (name "log-viewer") (type "checkbox") (label ("Log Viewer")) (checked #t) (disabled "disabled")))
(Field (@ (name "lldap") (type "checkbox") (label ("Admin LLDAP")) (checked #t) (disabled "disabled"))) ;; (Field (@ (name "lldap") (type "checkbox") (label ("Admin LLDAP")) (checked #t) (disabled "disabled")))
(Field (@ (name "authelia") (type "checkbox") (label ("Admin Authelia")) (checked #t) (disabled "disabled")))) ;; (Field (@ (name "authelia") (type "checkbox") (label ("Admin Authelia")) (checked #t) (disabled "disabled")))
)
;; TODO add config for when automatic upgrades are scheduled for? ;; TODO add config for when automatic upgrades are scheduled for?
;; TODO add config for server timezone? ;; TODO add config for server timezone?
(Form-Nav (@ (back-to ,(conc "/config/wizard/services-success/" instance-id)))))))))) (Form-Nav (@ (back-to ,(conc "/config/wizard/services-success/" instance-id))))))))))
@@ -1424,7 +1423,7 @@ chmod -R 777 /opt/keys")))
(ghost . ,(or (and (alist-ref 'ghost (current-params)) "6") #f)) (ghost . ,(or (and (alist-ref 'ghost (current-params)) "6") #f))
(nassella . ,(or (and (alist-ref 'nassella (current-params)) "b0.0.1") #f)) (nassella . ,(or (and (alist-ref 'nassella (current-params)) "b0.0.1") #f))
(wordpress . ,(or (and (alist-ref 'wordpress (current-params)) "8.4") #f)) (wordpress . ,(or (and (alist-ref 'wordpress (current-params)) "8.4") #f))
(lldap . "0.6.3") (lldap . #f) ;; "0.6.3"
(authelia . "4") (authelia . "4")
(instance-control . "b0.0.1") (instance-control . "b0.0.1")
(log-viewer . "20")))) (log-viewer . "20"))))
@@ -1500,31 +1499,30 @@ chmod -R 777 /opt/keys")))
(Field (@ (name "wordpress-subdomain") (label ("Subdomain")) (required #t) (Field (@ (name "wordpress-subdomain") (label ("Subdomain")) (required #t)
(value ,(alist-ref 'subdomain (alist-ref 'wordpress app-config eq? '()) eq? "wordpress")))))) (value ,(alist-ref 'subdomain (alist-ref 'wordpress app-config eq? '()) eq? "wordpress"))))))
'()) '())
,@(if (member 'lldap selected-apps) ;; ,@(if (member 'lldap selected-apps)
`((Fieldset ;; `((Fieldset
(@ (title "Admin LLDAP")) ;; (@ (title "Admin LLDAP"))
(Field (@ (name "lldap-subdomain") (label ("Subdomain")) (required #t) ;; (Field (@ (name "lldap-subdomain") (label ("Subdomain")) (required #t)
(value ,(alist-ref 'subdomain (alist-ref 'lldap app-config eq? '()) eq? "lldap")))) ;; (value ,(alist-ref 'subdomain (alist-ref 'lldap app-config eq? '()) eq? "lldap"))))
(Field (@ (name "lldap-user-email") (label ("Admin Email Address")) (type "email") (required #t) ;; (Field (@ (name "lldap-user-email") (label ("Admin Email Address")) (type "email") (required #t)
(value ,(alist-ref 'user-email (alist-ref 'lldap app-config eq? '()) eq? "")))) ;; (value ,(alist-ref 'user-email (alist-ref 'lldap app-config eq? '()) eq? ""))))
(Field (@ (name "lldap-admin-password") (label ("Admin Password")) (type "password") (required #t) ;; (Field (@ (name "lldap-admin-password") (label ("Admin Password")) (type "password") (required #t)
(value ,(alist-ref 'admin-password (alist-ref 'lldap app-config eq? '()) eq? "")))))) ;; (value ,(alist-ref 'admin-password (alist-ref 'lldap app-config eq? '()) eq? ""))))))
'()) ;; '())
,@(if (member 'authelia selected-apps) ;; ,@(if (member 'authelia selected-apps)
`((Fieldset ;; `((Fieldset
(@ (title "Admin Authelia")) ;; (@ (title "Admin Authelia"))
(Field (@ (name "authelia-subdomain") (label ("Subdomain")) (required #t) ;; (Field (@ (name "authelia-subdomain") (label ("Subdomain")) (required #t)
(value ,(alist-ref 'subdomain (alist-ref 'authelia app-config eq? '()) eq? "authelia")))))) ;; (value ,(alist-ref 'subdomain (alist-ref 'authelia app-config eq? '()) eq? "authelia"))))))
'()) ;; '())
(Fieldset (Fieldset
(@ (title "Log Viewer")) (@ (title "Log Viewer"))
(Field (@ (name "log-viewer-subdomain") (label ("Subdomain")) (required #t) (Field (@ (name "log-viewer-subdomain") (label ("Subdomain")) (required #t)
(value ,(alist-ref 'subdomain (alist-ref 'log-viewer app-config eq? '()) eq? "logs")))) (value ,(alist-ref 'subdomain (alist-ref 'log-viewer app-config eq? '()) eq? "logs"))))
;; (Field (@ (name "log-viewer-user") (label ("Username")) (required #t) (Field (@ (name "log-viewer-user") (label ("Admin Email")) (required #t)
;; (value ,(alist-ref 'user (alist-ref 'log-viewer app-config eq? '()) eq? "")))) (value ,(alist-ref 'user (alist-ref 'log-viewer app-config eq? '()) eq? ""))))
;; (Field (@ (name "log-viewer-password") (label ("Password")) (type "password") (required #t) (Field (@ (name "log-viewer-password") (label ("Admin Password")) (type "password") (required #t)
;; (value ,(alist-ref 'password (alist-ref 'log-viewer app-config eq? '()) eq? "")))) (value ,(alist-ref 'password (alist-ref 'log-viewer app-config eq? '()) eq? "")))))
)
,@(if (or (member 'nextcloud selected-apps) (member 'ghost selected-apps) (member 'nassella selected-apps) (member 'authelia selected-apps)) ,@(if (or (member 'nextcloud selected-apps) (member 'ghost selected-apps) (member 'nassella selected-apps) (member 'authelia selected-apps))
`((Fieldset `((Fieldset
(@ (title "All Apps - Email - SMTP")) (@ (title "All Apps - Email - SMTP"))
@@ -1662,8 +1660,8 @@ chmod -R 777 /opt/keys")))
(alist-ref 'authelia config eq? '())) (alist-ref 'authelia config eq? '()))
(generate-authelia-key-seed))))) (generate-authelia-key-seed)))))
(log-viewer . ((subdomain . ,(alist-ref 'log-viewer-subdomain (current-params))) (log-viewer . ((subdomain . ,(alist-ref 'log-viewer-subdomain (current-params)))
(user . "unused") ;; TODO can we remove? (user . ,(alist-ref 'log-viewer-user (current-params)))
(password . "unused"))) ;; TODO can we remove? (password . ,(alist-ref 'log-viewer-password (current-params)))))
(all-apps . ((smtp-host . ,(alist-ref 'smtp-host (current-params))) (all-apps . ((smtp-host . ,(alist-ref 'smtp-host (current-params)))
(smtp-port . ,(alist-ref 'smtp-port (current-params))) (smtp-port . ,(alist-ref 'smtp-port (current-params)))
(smtp-auth-user . ,(alist-ref 'smtp-auth-user (current-params))) (smtp-auth-user . ,(alist-ref 'smtp-auth-user (current-params)))
@@ -1836,13 +1834,14 @@ chmod -R 777 /opt/keys")))
(map (lambda (app-version) (map (lambda (app-version)
(conc (if (eq? (car app-version) 'log-viewer) 'dozzle (car app-version)) (conc (if (eq? (car app-version) 'log-viewer) 'dozzle (car app-version))
"," ","
(alist-ref 'subdomain (alist-ref (car app-version) config)) (or (alist-ref 'subdomain (alist-ref (car app-version) config))
"nassella-ignore") ;; a hack to get authelia to work for now
"," ","
(cdr app-version))) (cdr app-version)))
selected-apps) selected-apps)
" ")) " "))
("HOST_ADMIN_USER" . ,(alist-ref 'user (alist-ref 'log-viewer config))) ("HOST_ADMIN_USER" . ,(alist-ref 'user (alist-ref 'log-viewer config)))
("HOST_ADMIN_PASSWORD" . ,(alist-ref 'password (alist-ref 'log-viewer config))) ("HOST_ADMIN_PASSWORD" . ,(create-authelia-password-hash (alist-ref 'password (alist-ref 'log-viewer config))))
("NEXTCLOUD_ADMIN_USER" . ,(alist-ref 'admin-user (alist-ref 'nextcloud config))) ("NEXTCLOUD_ADMIN_USER" . ,(alist-ref 'admin-user (alist-ref 'nextcloud config)))
("NEXTCLOUD_ADMIN_PASSWORD" . ,(alist-ref 'admin-password (alist-ref 'nextcloud config))) ("NEXTCLOUD_ADMIN_PASSWORD" . ,(alist-ref 'admin-password (alist-ref 'nextcloud config)))
("NEXTCLOUD_POSTGRES_DB" . "nextcloud") ("NEXTCLOUD_POSTGRES_DB" . "nextcloud")
@@ -1999,13 +1998,29 @@ chmod -R 777 /opt/keys")))
(with-db/transaction (with-db/transaction
(lambda (db) (lambda (db)
(update-deployment-progress db deployment-id progress) (update-deployment-progress db deployment-id progress)
(update-deployment-status
db user-id deployment-id
(if (= status 0) 'complete 'failed)
(with-input-from-file (string-append dir "/make-out") read-string))
(update-user-terraform-state db user-id instance-id (update-user-terraform-state db user-id instance-id
(if (eof-object? tf-state) "" tf-state) (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))
(if (= status 0)
(update-deployment-progress db deployment-id `((instance-up . in-progress)))
(update-deployment-status
db user-id deployment-id
'failed
(with-input-from-file (string-append dir "/make-out") read-string)))))
(when (= status 0)
(let ((instance-up (instance-up? (string-append "https://"
(alist-ref 'subdomain (alist-ref 'log-viewer (alist-ref 'config app-config)))
"."
(alist-ref 'root-domain app-config)))))
(with-db/transaction
(lambda (db)
(update-deployment-progress db deployment-id `((instance-up . ,(if instance-up 'complete 'failed))))
(update-deployment-status
db user-id deployment-id
(if instance-up
'complete
'failed)
(with-input-from-file (string-append dir "/make-out") read-string))))))))))))))))
(redirect (conc "/config/wizard/success/" (alist-ref "id" (current-params) equal?)))) (redirect (conc "/config/wizard/success/" (alist-ref "id" (current-params) equal?))))
(get/widgets (get/widgets
@@ -2057,7 +2072,8 @@ chmod -R 777 /opt/keys")))
(ip-destroy . "Instance Mapped IP Disconnect") (ip-destroy . "Instance Mapped IP Disconnect")
(ip-create . "Instance Mapped IP Connect") (ip-create . "Instance Mapped IP Connect")
(volume-create . "App Volume Connect") (volume-create . "App Volume Connect")
(machine-destroy . "Cleanup Previous Instance")))) (machine-destroy . "Cleanup Previous Instance")
(instance-up . "Instance Starting Up"))))
(form (form
(@ (action "/dashboard") (method GET)) (@ (action "/dashboard") (method GET))
,@(if (or (eq? (string->symbol status) 'complete) (eq? (string->symbol status) 'failed)) ,@(if (or (eq? (string->symbol status) 'complete) (eq? (string->symbol status) 'failed))
@@ -2081,7 +2097,7 @@ chmod -R 777 /opt/keys")))
(define (instance-status->pill-type status) (define (instance-status->pill-type status)
(case (string->symbol status) (case (string->symbol status)
((queued) 'info) ((queued) 'info)
((in-progress 'warning)) ((in-progress) 'warning)
((complete) 'safe) ((complete) 'safe)
((failed) 'alert) ((failed) 'alert)
(else 'alert))) (else 'alert)))
@@ -2213,6 +2229,7 @@ chmod -R 777 /opt/keys")))
(@ (title "Delete Instance")) (@ (title "Delete Instance"))
(Field (@ (name "instance-domain") (label ("Type the domain name of the instance to confirm.")) (value ""))) (Field (@ (name "instance-domain") (label ("Type the domain name of the instance to confirm.")) (value "")))
(Field (@ (name "delete-backups") (label ("Delete backups for this instance")) (type "checkbox")))) (Field (@ (name "delete-backups") (label ("Delete backups for this instance")) (type "checkbox"))))
(input (@ (type "hidden") (value ,(alist-ref 'force (current-params))) (name "force")))
(Form-Nav (@ (back-to "/dashboard") (submit-button "Destroy")))))))) (Form-Nav (@ (back-to "/dashboard") (submit-button "Destroy"))))))))
(post "/destroy-submit/:id" (post "/destroy-submit/:id"