aboutsummaryrefslogtreecommitdiff
path: root/scripts/hostclass
diff options
context:
space:
mode:
authorCullum Smith <cullum@sacredheartsc.com>2024-10-12 08:14:59 -0400
committerCullum Smith <cullum@sacredheartsc.com>2024-10-12 08:15:33 -0400
commit99b8524c16cc99ceeaf1ebf588f2fc0f2c0fbe0a (patch)
tree3ffa4113f23eca6cea8ff2c94ba7ce60188d943e /scripts/hostclass
parent1c882c769e5476b5cb3fa294257c76165a7a6f46 (diff)
downloadinfrastructure-99b8524c16cc99ceeaf1ebf588f2fc0f2c0fbe0a.tar.gz
add a bunch of hostclasses
Diffstat (limited to 'scripts/hostclass')
-rw-r--r--scripts/hostclass/asterisk_server74
-rw-r--r--scripts/hostclass/authoritative_nameserver27
-rw-r--r--scripts/hostclass/bitwarden_server58
-rw-r--r--scripts/hostclass/dav_server139
-rw-r--r--scripts/hostclass/imap_server/30-dovecot5
-rw-r--r--scripts/hostclass/postgresql_server8
-rw-r--r--scripts/hostclass/public_webserver42
-rw-r--r--scripts/hostclass/smtp_server/10-rspamd8
-rw-r--r--scripts/hostclass/smtp_server/20-postfix2
-rw-r--r--scripts/hostclass/ttrss_server137
-rw-r--r--scripts/hostclass/turn_server29
-rw-r--r--scripts/hostclass/xmpp_server132
-rw-r--r--scripts/hostclass/znc_server68
13 files changed, 719 insertions, 10 deletions
diff --git a/scripts/hostclass/asterisk_server b/scripts/hostclass/asterisk_server
new file mode 100644
index 0000000..d519730
--- /dev/null
+++ b/scripts/hostclass/asterisk_server
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+: ${asterisk_public_fqdn:='pbx.example.com'}
+: ${asterisk_public_ip:="$BOXCONF_DEFAULT_IPV4"}
+: ${asterisk_from_email:="asterisk-noreply@${email_domain}"}
+: ${asterisk_sip_domain:="${email_domain}"}
+: ${asterisk_locale:='en_US.UTF-8'}
+: ${asterisk_timezone:='America/New_York'}
+: ${asterisk_default_mailbox_password:='1234'}
+
+# asterisk_mailboxes='1001 1002'
+# asterisk_mailbox_1001_name='John Doe'
+# asterisk_mailbox_1001_email='johndoe@example.com'
+#
+# asterisk_trunks='upstream1 upstream2'
+# asterisk_trunk_upstream1_username=myusername
+# asterisk_trunk_upstream1_password=changeme
+# asterisk_trunk_upstream1_context=from-pstn
+# asterisk_trunk_upstream1_proto=tcp
+# asterisk_trunk_upstream1_remote='server.example.com:5060'
+#
+# asterisk_exts='1001 1002'
+# asterisk_ext_1001_password=changeme
+# asterisk_ext_1001_context=from-internal
+# asterisk_ext_1001_mailbox=1001
+# asterisk_ext_1001_cid_name='Bob Office'
+#
+# asterisk_queues=home
+# asterisk_queue_home_stragegy=ringall
+# asterisk_queue_home_timeout=30
+# asterisk_queue_home_retry=1
+# asterisk_queue_home_members='1001 1002'
+
+asterisk_public_tls_cert="${acme_cert_dir}/asterisk.crt"
+asterisk_public_tls_key="${acme_cert_dir}/asterisk.key"
+asterisk_conf_dir=/usr/local/etc/asterisk
+asterisk_db_dir=/var/db/asterisk
+asterisk_user=asterisk
+
+# Install packages.
+pkg install -y \
+ asterisk18 \
+ ca_root_nss
+
+# Create ZFS dataset for Asterisk DB.
+create_dataset -o "mountpoint=${asterisk_db_dir}" "${state_dataset}/asterisk"
+install_directory -o "$asterisk_user" -g "$asterisk_user" -m 0755 "$asterisk_db_dir"
+
+# Generate asterisk configuration.
+install_file -m 0644 \
+ "${asterisk_conf_dir}/extensions.conf" \
+ "${asterisk_conf_dir}/logger.conf" \
+ "${asterisk_conf_dir}/queues.conf" \
+ "${asterisk_conf_dir}/voicemail.conf"
+
+install_template -m 0644 \
+ "${asterisk_conf_dir}/voicemail.conf" \
+ "${asterisk_conf_dir}/pjsip.conf" \
+ "${asterisk_conf_dir}/rtp.conf"
+
+install_template -m 0640 \
+ "${asterisk_conf_dir}/pjsip_wizard.conf"
+
+# Acquire public TLS certificate.
+install_template -m 0600 /usr/local/etc/sudoers.d/acme
+acme_install_certificate \
+ -c "$asterisk_public_tls_cert" \
+ -k "$asterisk_public_tls_key" \
+ -r 'sudo service asterisk reload' \
+ "$asterisk_public_fqdn"
+
+# Enable and start asterisk.
+sysrc -v asterisk_enable=YES
+service asterisk restart
diff --git a/scripts/hostclass/authoritative_nameserver b/scripts/hostclass/authoritative_nameserver
new file mode 100644
index 0000000..69a7dde
--- /dev/null
+++ b/scripts/hostclass/authoritative_nameserver
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+: ${nsd_zones:=''}
+: ${nsd_threads:='2'}
+
+# nsd_zones='example1 example2'
+# nsd_example1_domain=example.com
+# nsd_example1_slaves='1.2.3.4 5.6.7.8'
+
+nsd_conf_dir=/usr/local/etc/nsd
+nsd_run_dir=/var/run/nsd
+
+# Install packages.
+pkg install -y nsd
+
+# Generate nsd configuration.
+install_template -m 0644 /usr/local/etc/nsd/nsd.conf
+
+# Copy zone files.
+for zone in $nsd_zones; do
+ eval "zone_name=\${nsd_${zone}_zone}"
+ install_file -m 0644 "${nsd_conf_dir}/${zone_name}.zone"
+done
+
+# Enable and start nsd.
+sysrc -v nsd_enable=YES
+service nsd restart
diff --git a/scripts/hostclass/bitwarden_server b/scripts/hostclass/bitwarden_server
new file mode 100644
index 0000000..5e19bdd
--- /dev/null
+++ b/scripts/hostclass/bitwarden_server
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+: ${vaultwarden_username:='s-vaultwarden'}
+: ${vaultwarden_dbname:='vaultwarden'}
+: ${vaultwarden_dbhost:="$postgres_host"}
+: ${vaultwarden_fqdn:="$fqdn"}
+
+vaultwarden_local_username=$nginx_user
+vaultwarden_uid=$(id -u "$vaultwarden_local_username")
+vaultwarden_https_cert="${nginx_conf_dir}/vaultwarden.crt"
+vaultwarden_https_key="${nginx_conf_dir}/vaultwarden.key"
+vaultwarden_home=/usr/local/www/vaultwarden
+vaultwarden_port=8080
+vaultwarden_client_keytab="${keytab_dir}/vaultwarden.client.keytab"
+
+pkg install -y \
+ vaultwarden \
+ nginx
+
+# Create vaultwarden principal and keytab.
+add_principal -nokey -x "containerdn=${robots_basedn}" "$vaultwarden_username"
+
+ktadd -k "$vaultwarden_client_keytab" "$vaultwarden_username"
+chgrp "$vaultwarden_local_username" "$vaultwarden_client_keytab"
+chmod 640 "$vaultwarden_client_keytab"
+
+install_directory -o "$vaultwarden_local_username" -m 0700 "/var/krb5/user/${vaultwarden_uid}"
+ln -snfv "$vaultwarden_client_keytab" "/var/krb5/user/${vaultwarden_uid}/client.keytab"
+
+# Create postgres user and database.
+postgres_create_role "$vaultwarden_dbhost" "$vaultwarden_username"
+postgres_create_database "$vaultwarden_dbhost" "$vaultwarden_dbname" "$vaultwarden_username"
+
+# Generate vaultwarden configuration.
+install_template -m 0644 /usr/local/etc/rc.conf.d/vaultwarden
+
+# Copy TLS certificate for nginx.
+install_certificate nginx "$vaultwarden_https_cert"
+install_certificate_key nginx "$vaultwarden_https_key"
+
+# Generate nginx configuration.
+install_template -m 0644 \
+ /usr/local/etc/nginx/nginx.conf \
+ /usr/local/etc/nginx/vhosts.conf
+
+# Enable and start daemons.
+sysrc -v \
+ vaultwarden_enable=YES \
+ vaultwarden_user="$vaultwarden_local_username" \
+ vaultwarden_group="$vaultwarden_local_username" \
+ nginx_enable=YES \
+
+service nginx restart
+
+# The vaultwarden rc script seems to hold onto open descriptors, which causes
+# the parent boxconf SSH process to never close.
+echo 'Restarting vaultwarden.'
+service vaultwarden restart > /dev/null 2>&1 < /dev/null
diff --git a/scripts/hostclass/dav_server b/scripts/hostclass/dav_server
new file mode 100644
index 0000000..b7391bd
--- /dev/null
+++ b/scripts/hostclass/dav_server
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+: ${davical_username:='s-davical'}
+: ${davical_dbname:='davical'}
+: ${davical_dbhost:="$postgres_host"}
+: ${davical_admin_email:="$root_mail_alias"}
+: ${davical_access_role:='dav-access'}
+: ${davical_repo:='https://gitlab.com/davical-project/davical.git'}
+: ${davical_branch:='master'}
+: ${davical_awl_repo:='https://gitlab.com/davical-project/awl.git'}
+: ${davical_awl_branch:='master'}
+
+davical_repo_dir=/usr/local/www/davical
+davical_awl_repo_dir=/usr/local/share/awl
+davical_webroot="${davical_repo_dir}/htdocs"
+
+davical_https_cert="${nginx_conf_dir}/davical.crt"
+davical_https_key="${nginx_conf_dir}/davical.key"
+davical_https_cacert="${nginx_conf_dir}/davical.ca.crt"
+davical_keytab="${keytab_dir}/davical.keytab"
+davical_client_keytab="${keytab_dir}/davical.client.keytab"
+davical_fpm_socket=/var/run/fpm-davical.sock
+
+davical_psql(){
+ postgres_run --host="$davical_dbhost" --dbname="$davical_dbname" "$@"
+}
+
+# Install required packages.
+pkg install -y \
+ git-lite \
+ nginx \
+ php${php_version} \
+ php${php_version}-calendar \
+ php${php_version}-curl \
+ php${php_version}-gettext \
+ php${php_version}-iconv \
+ php${php_version}-ldap \
+ php${php_version}-opcache \
+ php${php_version}-pdo_pgsql \
+ php${php_version}-pgsql \
+ php${php_version}-session \
+ php${php_version}-xml \
+ p5-DBD-Pg \
+ p5-DBI \
+ p5-YAML
+
+# Install davical from git.
+[ -d "$davical_repo_dir" ] || git clone "$davical_repo" "$davical_repo_dir"
+[ -d "$davical_awl_repo_dir" ] || git clone "$davical_awl_repo" "$davical_awl_repo_dir"
+
+# Update git repos.
+git -C "$davical_repo_dir" pull --ff-only
+git -C "$davical_repo_dir" switch "$davical_branch"
+git -C "$davical_awl_repo_dir" pull --ff-only
+git -C "$davical_awl_repo_dir" switch "$davical_awl_branch"
+
+# Create davical principal and keytab.
+add_principal -nokey -x "containerdn=${robots_basedn}" "$davical_username"
+
+ktadd -k "$davical_client_keytab" "$davical_username"
+chgrp "$nginx_user" "$davical_client_keytab"
+chmod 640 "$davical_client_keytab"
+
+nginx_uid=$(id -u "$nginx_user")
+install_directory -o "$nginx_user" -m 0700 "/var/krb5/user/${nginx_uid}"
+ln -snfv "$davical_client_keytab" "/var/krb5/user/${nginx_uid}/client.keytab"
+
+# Create HTTP principal and keytab.
+add_principal -nokey -x "containerdn=${services_basedn}" "HTTP/${fqdn}"
+
+ktadd -k "$davical_keytab" "HTTP/${fqdn}"
+chgrp "$nginx_user" "$davical_keytab"
+chmod 640 "$davical_keytab"
+
+ln -snfv "$davical_keytab" "/var/krb5/user/${nginx_uid}/keytab"
+
+# Generate davical configuration.
+install_template -m 0644 \
+ "${davical_repo_dir}/config/config.php" \
+ "${davical_repo_dir}/config/administration.yml"
+
+# Create postgres user and database.
+postgres_create_role "$davical_dbhost" "$davical_username"
+postgres_create_database "$davical_dbhost" "$davical_dbname"
+
+# Initialize davical database.
+if ! davical_psql -c 'SELECT 1 FROM awl_db_revision'; then
+ davical_psql \
+ -f "${davical_awl_repo_dir}/dba/awl-tables.sql" \
+ -f "${davical_awl_repo_dir}/dba/schema-management.sql" \
+ -f "${davical_repo_dir}/dba/davical.sql"
+
+ PGPASSWORD="$boxconf_password" PGSSLMODE=require \
+ "${davical_repo_dir}/dba/update-davical-database" --debug --nopatch
+
+ davical_psql -f "${davical_repo_dir}/dba/base-data.sql"
+
+ PGPASSWORD="$boxconf_password" PGSSLMODE=require \
+ "${davical_repo_dir}/dba/update-davical-database" --debug
+
+ davical_psql -c "delete from usr where username = 'admin'"
+fi
+
+# Copy TLS certificate for nginx.
+install_certificate nginx "$davical_https_cert"
+install_certificate_key nginx "$davical_https_key"
+
+# Generate nginx configuration.
+install_file -m 0644 "${nginx_conf_dir}/fastcgi_params"
+install_template -m 0644 \
+ "${nginx_conf_dir}/nginx.conf" \
+ "${nginx_conf_dir}/vhosts.conf"
+
+# Generate php-fpm configuration.
+install_file -m 0644 \
+ /usr/local/etc/php.ini \
+ /usr/local/etc/php-fpm.conf
+install_template -m 0644 \
+ /usr/local/etc/php-fpm.d/davical.conf
+> /usr/local/etc/php-fpm.d/www.conf
+
+# Enable and start daemons.
+sysrc -v \
+ nginx_enable=YES \
+ php_fpm_enable=YES
+service nginx restart
+service php_fpm restart
+
+# Sync groups from LDAP.
+su -m "$nginx_user" -c "${davical_repo_dir}/scripts/cron-sync-ldap.php ${fqdn}"
+
+# Create cron job for keeping LDAP groups up-to-date.
+install_template -m 0644 /etc/cron.d/davical
+
+# Create access role.
+ldap_add "cn=${davical_access_role},${roles_basedn}" <<EOF
+objectClass: groupOfMembers
+cn: ${davical_access_role}
+EOF
diff --git a/scripts/hostclass/imap_server/30-dovecot b/scripts/hostclass/imap_server/30-dovecot
index 07c089e..ff41da5 100644
--- a/scripts/hostclass/imap_server/30-dovecot
+++ b/scripts/hostclass/imap_server/30-dovecot
@@ -75,9 +75,10 @@ install_template -m 0644 \
"${dovecot_conf_dir}/conf.d/90-sieve-extprograms.conf" \
"${dovecot_conf_dir}/conf.d/auth-ldap.conf.ext"
-install_template -m 0550 -o root -g "$dovecot_user" \
+install_template -m 0640 -o root -g "$dovecot_user" "${dovecot_conf_dir}/rspamd.conf.sh"
+install_file -m 0555 \
"${dovecot_sieve_pipe_bin_dir}/report-spam.sh" \
- "${dovecot_sieve_pipe_bin_dir}/report-ham.sh" \
+ "${dovecot_sieve_pipe_bin_dir}/report-ham.sh"
install_file -m 0555 \
"${dovecot_script_dir}/quota-warning.sh"
diff --git a/scripts/hostclass/postgresql_server b/scripts/hostclass/postgresql_server
index a09c9f4..fb0ddcd 100644
--- a/scripts/hostclass/postgresql_server
+++ b/scripts/hostclass/postgresql_server
@@ -1,5 +1,7 @@
#!/bin/sh
+# PostgreSQL jails need allow.sysvipc=1.
+
: ${postgres_max_connections:='128'}
: ${postgres_shared_buffers:="$(( memsize / 2 ))"}
: ${postgres_work_mem:="$(( memsize / 4 / ${postgres_max_connections} ))"}
@@ -9,7 +11,7 @@
postgres_user=postgres
postgres_home=/var/db/postgres
-postgres_data_dir="${postgres_home}/data${postgres_version}"
+postgres_data_dir="${postgres_home}/data${postgresql_version}"
postgres_tls_cert="${postgres_home}/postgres.crt"
postgres_tls_key="${postgres_home}/postgres.key"
postgres_keytab="${keytab_dir}/postgres.keytab"
@@ -67,8 +69,8 @@ service postgresql restart > /dev/null 2>&1 < /dev/null
psql -c "DO
\$$
BEGIN
- IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${boxconf_user}') THEN
- CREATE ROLE \"${boxconf_user}\" WITH LOGIN SUPERUSER;
+ IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${boxconf_username}') THEN
+ CREATE ROLE \"${boxconf_username}\" WITH LOGIN SUPERUSER;
END IF;
END
\$$"
diff --git a/scripts/hostclass/public_webserver b/scripts/hostclass/public_webserver
new file mode 100644
index 0000000..ccf5991
--- /dev/null
+++ b/scripts/hostclass/public_webserver
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+# acme_certs='site1 site2'
+# acme_site1_domains='example.net example.com'
+
+: ${acme_certs:=''}
+
+vhosts_dir=/usr/local/www
+
+# Install packages.
+pkg install -y \
+ nginx \
+ rsync
+
+# Create ZFS dataset for webroots.
+create_dataset -o "mountpoint=${vhosts_dir}" "${state_dataset}/vhosts"
+
+# Configure nginx.
+install_template -m 0644 /usr/local/etc/nginx/nginx.conf
+install -Cv -m 0644 /dev/null /usr/local/etc/nginx/vhosts.conf
+sysrc -v nginx_enable=YES
+service nginx restart
+
+# Acquire public TLS certificates.
+install_template -m 0600 /usr/local/etc/sudoers.d/acme
+for cert in $acme_certs; do
+ eval "acme_domains=\${acme_${cert}_domains}"
+ acme_install_certificate \
+ -C "${acme_cert_dir}/${cert}.ca.crt" \
+ -c "${acme_cert_dir}/${cert}.crt" \
+ -k "${acme_cert_dir}/${cert}.key" \
+ -g "$nginx_user" \
+ -r 'sudo service nginx reload' \
+ $acme_domains
+done
+
+# Now that we have the ACME certs, add the vhosts.
+install_template -m 0644 /usr/local/etc/nginx/vhosts.conf
+service nginx restart
+
+# If any acmeproxy_domains were specified, setup the SFTP proxy.
+acme_setup_proxy
diff --git a/scripts/hostclass/smtp_server/10-rspamd b/scripts/hostclass/smtp_server/10-rspamd
index 094dc8a..1794e04 100644
--- a/scripts/hostclass/smtp_server/10-rspamd
+++ b/scripts/hostclass/smtp_server/10-rspamd
@@ -21,8 +21,8 @@ rspamd_redis_sock=/var/run/redis/rspamd.sock
rspamd_bayes_redis_sock=/var/run/redis/rspamd-bayes.sock
rspamd_redis_data_dir="${redis_data_dir}/rspamd"
rspamd_bayes_redis_data_dir="${redis_data_dir}/rspamd-bayes"
-rspamd_tls_cert=/usr/local/etc/nginx/rspamd.crt
-rspamd_tls_key=/usr/local/etc/nginx/rspamd.key
+rspamd_https_cert="${nginx_conf_dir}/rspamd.crt"
+rspamd_https_key="${nginx_conf_dir}/rspamd.key"
nginx_keytab="${keytab_dir}/nginx.keytab"
pkg install -y \
@@ -97,8 +97,8 @@ chgrp "$nginx_user" "$nginx_keytab"
chmod 640 "$nginx_keytab"
# Copy TLS certificate for nginx.
-install_certificate nginx "$rspamd_tls_cert"
-install_certificate_key nginx "$rspamd_tls_key"
+install_certificate nginx "$rspamd_https_cert"
+install_certificate_key nginx "$rspamd_https_key"
# Enable and start rspamd and nginx.
sysrc -v \
diff --git a/scripts/hostclass/smtp_server/20-postfix b/scripts/hostclass/smtp_server/20-postfix
index e224e9b..68ac474 100644
--- a/scripts/hostclass/smtp_server/20-postfix
+++ b/scripts/hostclass/smtp_server/20-postfix
@@ -52,7 +52,7 @@ install_certificate_key -m 0640 -o root -g "$postfix_user" postfix "$postfix_loc
if [ "$postfix_public_fqdn" != "$fqdn" ]; then
# Acquire public TLS certificate.
- install_file /usr/local/etc/sudoers.d/acme
+ install_template -m 0600 /usr/local/etc/sudoers.d/acme
acme_install_certificate \
-c "$postfix_public_tls_cert" \
-k "$postfix_public_tls_key" \
diff --git a/scripts/hostclass/ttrss_server b/scripts/hostclass/ttrss_server
new file mode 100644
index 0000000..1a2104a
--- /dev/null
+++ b/scripts/hostclass/ttrss_server
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+: ${ttrss_username:='s-ttrss'}
+: ${ttrss_dbname:='ttrss'}
+: ${ttrss_dbhost:="$postgres_host"}
+: ${ttrss_fqdn:="$fqdn"}
+: ${ttrss_access_role:='ttrss-access'}
+: ${ttrss_admin_role:='ttrss-admin'}
+: ${ttrss_mail_from:="ttrss-noreply@${email_domain}"}
+
+ttrss_https_cert="${nginx_conf_dir}/ttrss.crt"
+ttrss_https_key="${nginx_conf_dir}/ttrss.key"
+ttrss_repo='https://git.tt-rss.org/fox/tt-rss.git/'
+ttrss_branch=master
+ttrss_repo_dir=/usr/local/www/tt-rss
+ttrss_keytab="${keytab_dir}/ttrss.keytab"
+ttrss_client_keytab="${keytab_dir}/ttrss.client.keytab"
+ttrss_fpm_socket=/var/run/fpm-ttrss.sock
+
+# Install required packages.
+pkg install -y \
+ ca_root_nss \
+ nginx \
+ git-lite \
+ php${php_version}-ctype \
+ php${php_version}-curl \
+ php${php_version}-dom \
+ php${php_version}-exif \
+ php${php_version}-fileinfo \
+ php${php_version}-filter \
+ php${php_version}-gd \
+ php${php_version}-iconv \
+ php${php_version}-intl \
+ php${php_version}-ldap \
+ php${php_version}-mbstring \
+ php${php_version}-opcache \
+ php${php_version}-pcntl \
+ php${php_version}-pdo \
+ php${php_version}-pdo_pgsql \
+ php${php_version}-pgsql \
+ php${php_version}-phar \
+ php${php_version}-posix \
+ php${php_version}-session \
+ php${php_version}-simplexml \
+ php${php_version}-sockets \
+ php${php_version}-tokenizer \
+ php${php_version}-xml \
+ php${php_version}-xmlwriter \
+ php${php_version}-zip
+
+# Create ttrss principal and keytab.
+add_principal -nokey -x "containerdn=${robots_basedn}" "$ttrss_username"
+
+ktadd -k "$ttrss_client_keytab" "$ttrss_username"
+chgrp "$nginx_user" "$ttrss_client_keytab"
+chmod 640 "$ttrss_client_keytab"
+
+nginx_uid=$(id -u "$nginx_user")
+install_directory -o "$nginx_user" -m 0700 "/var/krb5/user/${nginx_uid}"
+ln -snfv "$ttrss_client_keytab" "/var/krb5/user/${nginx_uid}/client.keytab"
+
+# Create HTTP principal and keytab.
+add_principal -nokey -x "containerdn=${services_basedn}" "HTTP/${fqdn}"
+
+ktadd -k "$ttrss_keytab" "HTTP/${fqdn}"
+chgrp "$nginx_user" "$ttrss_keytab"
+chmod 640 "$ttrss_keytab"
+
+ln -snfv "$ttrss_keytab" "/var/krb5/user/${nginx_uid}/keytab"
+
+# Install ttrss from git.
+[ -d "$ttrss_repo_dir" ] || git clone "$ttrss_repo" "$ttrss_repo_dir"
+
+# Update git repos.
+git -C "$ttrss_repo_dir" pull --ff-only
+git -C "$ttrss_repo_dir" switch "$ttrss_branch"
+
+# Fix permissions on writable directories.
+for dir in lock cache feed-icons ; do
+ chmod 755 "${ttrss_repo_dir}/${dir}"
+ chown -R "${nginx_user}:${nginx_user}" "${ttrss_repo_dir}/${dir}"
+done
+
+# Generate config.php.
+install_template -m 0644 "${ttrss_repo_dir}/config.php"
+
+# Create postgres user and database.
+postgres_create_role "$ttrss_dbhost" "$ttrss_username"
+postgres_create_database "$ttrss_dbhost" "$ttrss_dbname" "$ttrss_username"
+
+# Initialize the database schema.
+su -m "$nginx_user" -c "${ttrss_repo_dir}/update.php --update-schema=force-yes"
+
+# Copy tt-rss LDAP auth plugin.
+install_directory -m 0755 "${ttrss_repo_dir}/plugins.local/auth_idm"
+install_file -m 0644 "${ttrss_repo_dir}/plugins.local/auth_idm/init.php"
+
+# Copy tt-rss rc script.
+install_file -m 0555 /usr/local/etc/rc.d/ttrssd
+
+# Allow ttrss user to perform git queries.
+git config --system --replace-all safe.directory "$ttrss_repo_dir"
+
+# Copy TLS certificate for nginx.
+install_certificate nginx "$ttrss_https_cert"
+install_certificate_key nginx "$ttrss_https_key"
+
+# Generate nginx configuration.
+install_file -m 0644 /usr/local/etc/nginx/fastcgi_params
+install_template -m 0644 \
+ /usr/local/etc/nginx/nginx.conf \
+ /usr/local/etc/nginx/vhosts.conf
+
+# Generate php-fpm configuration.
+install_file -m 0644 \
+ /usr/local/etc/php.ini \
+ /usr/local/etc/php-fpm.conf
+install_template -m 0644 \
+ /usr/local/etc/php-fpm.d/ttrss.conf
+> /usr/local/etc/php-fpm.d/www.conf
+
+# Enable and start daemons.
+sysrc -v \
+ nginx_enable=YES \
+ php_fpm_enable=YES \
+ ttrssd_enable=YES
+service nginx restart
+service php_fpm restart
+service ttrssd restart
+
+# Create roles.
+for role in "$ttrss_access_role" "$ttrss_admin_role"; do
+ ldap_add "cn=${role},${roles_basedn}" <<EOF
+objectClass: groupOfMembers
+cn: ${role}
+EOF
+done
diff --git a/scripts/hostclass/turn_server b/scripts/hostclass/turn_server
new file mode 100644
index 0000000..2c01e30
--- /dev/null
+++ b/scripts/hostclass/turn_server
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+: ${coturn_secret:="$turn_secret"}
+: ${coturn_external_ip:="$BOXCONF_DEFAULT_IPV4"}
+: ${coturn_realm:="$turn_domain"}
+: ${coturn_threads:="$nproc"}
+
+coturn_user=coturn
+coturn_uid=792
+
+add_user \
+ -u "$coturn_uid" \
+ -c 'Turnserver Psuedo-User' \
+ -d /nonexistent \
+ -s /usr/sbin/nologin \
+ "$coturn_user"
+
+# Install required packages.
+pkg install -y turnserver
+
+# Copy coturn config file.
+install_template -o root -g "$coturn_user" -m 0640 /usr/local/etc/turnserver.conf
+
+# Start turnserver.
+sysrc -v \
+ turnserver_enable=YES \
+ turnserver_user="$coturn_user" \
+ turnserver_group="$coturn_user"
+service turnserver restart
diff --git a/scripts/hostclass/xmpp_server b/scripts/hostclass/xmpp_server
new file mode 100644
index 0000000..1889447
--- /dev/null
+++ b/scripts/hostclass/xmpp_server
@@ -0,0 +1,132 @@
+#!/bin/sh
+
+# The LDAP library used by prosody (lualdap) does not support SASL binds.
+# Therefore, you must specify the prosody_ldap_password variable.
+
+# prosody_acme_host=
+: ${prosody_admins:=''}
+: ${prosody_public_fqdn:="$fqdn"}
+: ${prosody_domains:="$email_domain"}
+: ${prosody_ldap_passwd:='changeme'}
+: ${prosody_dbname:='prosody'}
+: ${prosody_dbhost:="$postgres_host"}
+: ${prosody_access_role:='xmpp-access'}
+: ${prosody_archive_expiration:='1w'}
+: ${prosody_upload_sizelimit:='104857600'} # 100 MB
+: ${prosody_upload_expiration:='604800'} # 1 week
+: ${prosody_upload_quota:='25769803776'} # 24 GB
+: ${prosody_turn_port:='3478'}
+: ${prosody_turn_host:="$turn_domain"}
+: ${prosody_turn_realm:="$turn_domain"}
+: ${prosody_turn_secret="$turn_secret"}
+
+prosody_dn="uid=${prosody_username},${robots_basedn}"
+prosody_local_user=prosody
+prosody_conf_dir=/usr/local/etc/prosody
+prosody_certs_dir="${prosody_conf_dir}/certs"
+prosody_keytab="${keytab_dir}/prosody.keytab"
+prosody_roster_path="${prosody_conf_dir}/roster.ini"
+prosody_http_port=8080
+prosody_upload_dir=/var/db/prosody/http_upload
+
+prosody_https_cacert="${acme_cert_dir}/nginx.ca.crt"
+prosody_https_cert="${acme_cert_dir}/nginx.crt"
+prosody_https_key="${acme_cert_dir}/nginx.key"
+
+# Install required packages.
+pkg install -y \
+ prosody \
+ prosody-modules \
+ lua54-luadbi \
+ lua54-lualdap \
+ nginx
+
+# Create ZFS dataset for HTTP upload files.
+create_dataset -o "mountpoint=${prosody_upload_dir}" "${state_dataset}/http_upload"
+
+# Set ownership on http_upload directory.
+install_directory -o "$prosody_local_user" -g "$prosody_local_user" -m 0750 "$prosody_upload_dir"
+
+# Create prosody user private group.
+ldap_add "cn=${prosody_username},${private_groups_basedn}" <<EOF
+objectClass: groupOfMembers
+objectClass: posixGroup
+cn: ${prosody_username}
+gidNumber: ${prosody_uid}
+member: uid=${prosody_username},${robots_basedn}
+EOF
+
+# Create prosody user account.
+ldap_add "$prosody_dn" <<EOF
+objectClass: account
+objectClass: posixAccount
+uid: ${prosody_username}
+cn: ${prosody_username}
+uidNumber: ${prosody_uid}
+gidNumber: ${prosody_uid}
+gecos: Prosody Pseudo-User
+loginShell: /sbin/nologin
+homeDirectory: /nonexistent
+EOF
+
+# Create principal and keytab.
+add_principal -nokey -x "dn=${prosody_dn}" "$prosody_username"
+
+ktadd -k "$prosody_keytab" "$prosody_username"
+chgrp "$prosody_local_user" "$prosody_keytab"
+chmod 640 "$prosody_keytab"
+
+prosody_local_uid=$(id -u "$prosody_local_user")
+install_directory -o "$prosody_local_user" -m 0700 "/var/krb5/user/${prosody_local_uid}"
+ln -snfv "$prosody_keytab" "/var/krb5/user/${prosody_local_uid}/client.keytab"
+
+# Set LDAP password for prosody user.
+ldap_passwd "$prosody_dn" "$prosody_ldap_password"
+
+# Create postgres user and database.
+postgres_create_role "$prosody_dbhost" "$prosody_username"
+postgres_create_database "$prosody_dbhost" "$prosody_dbname" "$prosody_username"
+
+# Retrieve prosody certificates via ACME proxy.
+install_directory -o root -g "$prosody_local_user" -m 0770 "$prosody_certs_dir"
+install_file -m 0555 /usr/local/libexec/prosody-acme-proxy
+su -m "$prosody_local_user" -c "/usr/local/libexec/prosody-acme-proxy ${prosody_username}@${prosody_acme_host} ${prosody_domains}"
+
+# Copy prosody configuration.
+install_template -o root -g "$prosody_local_user" -m 0640 /usr/local/etc/prosody/prosody.cfg.lua
+
+# Configure automatic roster.
+install_file -m 0555 /usr/local/libexec/prosody-update-roster
+install -Cv -m 0640 -o "$prosody_local_user" -g "$prosody_local_user" /dev/null "${prosody_conf_dir}/roster.ini"
+su -m "$prosody_local_user" -c "/usr/local/libexec/prosody-update-roster ${prosody_access_role} > ${prosody_roster_path}"
+
+# Copy prosody crontab.
+install_template -m 0644 /etc/cron.d/prosody
+
+# Configure nginx.
+install_template -m 0644 /usr/local/etc/nginx/nginx.conf
+sysrc -v nginx_enable=YES
+service nginx restart
+
+install_template -m 0600 /usr/local/etc/sudoers.d/acme
+acme_install_certificate \
+ -C "$prosody_https_cacert" \
+ -c "$prosody_https_cert" \
+ -k "$prosody_https_key" \
+ -g "$nginx_user" \
+ -r 'sudo service nginx reload' \
+ "$prosody_public_fqdn"
+
+# Now that we have the ACME certs, add the nginx vhost.
+install_template -m 0644 /usr/local/etc/nginx/vhosts.conf
+
+# Enable and start daemons.
+sysrc -v prosody_enable=YES
+service prosody restart
+service nginx restart
+
+# Create access role.
+ldap_add "cn=${prosody_access_role},${roles_basedn}" <<EOF
+objectClass: groupOfMembers
+cn: ${prosody_access_role}
+EOF
diff --git a/scripts/hostclass/znc_server b/scripts/hostclass/znc_server
new file mode 100644
index 0000000..c9f3780
--- /dev/null
+++ b/scripts/hostclass/znc_server
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+: ${znc_max_networks:='16'}
+: ${znc_access_role:='znc-access'}
+
+znc_http_port=8443
+znc_home=/usr/local/etc/znc
+znc_user=znc
+znc_tls_cert="${znc_home}/znc.crt"
+znc_tls_key="${znc_home}/znc.key"
+znc_clone_user='clone___'
+
+# Install required packages.
+pkg install -y \
+ cyrus-sasl-saslauthd \
+ nginx \
+ znc
+
+# Create ZFS dataset for ZNC configs.
+create_dataset -o "mountpoint=${znc_home}" "${state_dataset}/znc"
+
+# Set ownership on ZNC dir.
+install_directory -o "$znc_user" -g "$znc_user" -m 0755 "$znc_home"
+
+# Copy TLS certificate for ZNC.
+install_certificate -o "$znc_user" -g "$znc_user" znc "$znc_tls_cert"
+install_certificate_key -o "$znc_user" -g "$znc_user" znc "$znc_tls_key"
+
+# Generate ZNC configs.
+install_directory -o "$znc_user" -g "$znc_user" -m 0700 \
+ "${znc_home}/configs" \
+ "${znc_home}/moddata" \
+ "${znc_home}/moddata/cyrusauth"
+
+[ -f "${znc_home}/configs/znc.conf" ] \
+ || install_template -o "$znc_user" -g "$znc_user" -m 0600 "${znc_home}/configs/znc.conf"
+
+install_template -o "$znc_user" -g "$znc_user" -m 0600 "${znc_home}/moddata/cyrusauth/.registry"
+
+# Copy saslauthd configuration.
+# TODO: use ldap module for saslauthd.
+install_template -m 0644 \
+ /usr/local/lib/sasl2/znc.conf \
+ /etc/pam.d/znc
+
+# Allow znc to read the saslauthd socket.
+install_directory -m 0750 -o "$saslauthd_user" -g "$znc_user" "$saslauthd_runtime_dir"
+
+# Generate nginx configuration.
+install_template -m 0644 \
+ /usr/local/etc/nginx/nginx.conf \
+ /usr/local/etc/nginx/vhosts.conf
+
+sysrc -v \
+ saslauthd_enable=YES \
+ saslauthd_flags='-a pam' \
+ znc_enable=YES \
+ nginx_enable=YES
+
+service saslauthd restart
+service znc status || service znc start
+service nginx restart
+
+# Create access role.
+ldap_add "cn=${znc_access_role},${roles_basedn}" <<EOF
+objectClass: groupOfMembers
+cn: ${znc_access_role}
+EOF