Compare commits

...

169 Commits

Author SHA1 Message Date
joeac 87527646fa certbot is --non-interactive 2026-07-07 13:29:26 +01:00
joeac 30af54b750 tls before nginx 2026-07-07 13:26:51 +01:00
joeac ac0ebb5302 add actualbudget 2026-07-07 13:25:30 +01:00
joeac 4bf24c2b18 mox_clientsettings: reverse proxy to mox 2026-07-07 13:06:55 +01:00
joeac 20b396effd adds mox nginx configs 2026-07-07 13:03:08 +01:00
joeac b3677dad4c zen, not sbl, .spamhaus.org 2026-07-07 11:13:32 +01:00
joeac b181f59f4c mkdir ~/mox/data if needed 2026-07-07 11:07:53 +01:00
joeac 5d849384bc chown tls_certs 2026-07-07 10:27:10 +01:00
joeac 5e640711ce fix cert-name in tls.mk 2026-07-07 10:22:32 +01:00
joeac 44e4605be8 adds install_tls rule 2026-07-07 10:11:01 +01:00
joeac 60ba3328fa improves certbot cmds 2026-07-07 10:07:17 +01:00
joeac 5a5301fefb fixes certonly command 2026-07-07 10:06:33 +01:00
joeac 4ee7c34161 mox depends on tls certs 2026-07-07 09:53:24 +01:00
joeac fe1d19a9c7 mox/reinstall 2026-07-07 09:49:24 +01:00
joeac 6d8541f052 install tls_domain, not tls_module 2026-07-07 09:48:09 +01:00
joeac ba2359f8b9 again 2026-07-07 09:41:35 +01:00
joeac 9ef09754be fix renew rule call 2026-07-07 09:37:48 +01:00
joeac cbe223166e certbot delete is non-interactive 2026-07-07 09:35:42 +01:00
joeac fb2785f694 again 2026-07-07 09:34:55 +01:00
joeac f9dcf157a0 fix delete_cert for real this time 2026-07-07 09:33:13 +01:00
joeac 83a482de03 fix syntax in delete_cert rule call 2026-07-07 09:30:11 +01:00
joeac 208f383925 if master node, install all subdomains, else none 2026-07-07 09:28:21 +01:00
joeac 05f9535a9b tls.mk: remove 'test' 2026-07-07 09:23:02 +01:00
joeac c18fd9c44e tls.mk: subdomains, not modules 2026-07-07 09:20:06 +01:00
joeac 07fa302d43 tls.mk 2026-07-07 09:10:58 +01:00
joeac 4a2b27f54a microlog post 2026-07-06 2026-07-06 12:58:25 +01:00
joeac ff507ea16a adds conf to mox deps 2026-07-05 23:23:01 +01:00
joeac 639196f0c9 install mox config 2026-07-05 23:18:11 +01:00
joeac 404cb2d98c also dirs != ordinary files 2026-07-05 23:08:02 +01:00
joeac 665d808660 fix case in last two commits 2026-07-05 23:06:29 +01:00
joeac 15027fef29 ditto, and fix dir 2026-07-05 23:05:18 +01:00
joeac 26ae12f638 calcs gemini_user in make, not shell 2026-07-05 23:04:09 +01:00
joeac 56393380b9 only make .env if dir exists 2026-07-05 23:01:56 +01:00
joeac d51d1a1266 adds mox_clientsettings to blade-canongate 2026-07-05 22:59:42 +01:00
joeac af5599ba54 missed a - 2026-07-05 22:58:57 +01:00
joeac ce8017c6d2 remove ref to removed install_dyndns rule 2026-07-05 22:47:45 +01:00
joeac b64f8ddffa replaces - with _ in vars 2026-07-05 22:30:21 +01:00
joeac e926d562c0 adds mox ports 2026-07-05 22:29:03 +01:00
joeac 064adbc0b7 adds mox sub-subdomains 2026-07-05 22:28:53 +01:00
joeac 4872b95c43 cp, not mv, resolv.conf 2026-07-05 16:30:24 +01:00
joeac 4547dd5681 unbound-anchor requires sudo 2026-07-05 16:27:06 +01:00
joeac 655ac1404a fix /etc/resolv.conf.joeac.net-backup rule 2026-07-05 16:24:12 +01:00
joeac 521d768635 adds mox 2026-07-05 16:09:20 +01:00
joeac f736c46b81 defines openrc rules for all modules (so they can be uninstalled) 2026-07-04 20:13:36 +01:00
joeac f537b99d02 uses IMAGE_PREFIX var in gemini.Dockerfile 2026-07-04 20:12:04 +01:00
joeac ad4b03871e moves container.mk vars to vars.mk so that IMAGE_PREFIX is defined
before it is used in COMPOSE_CMD
2026-07-04 20:11:07 +01:00
joeac 4ef4bbd4ad gives gemini pi-broughton -> blade-canongate 2026-07-04 20:02:02 +01:00
joeac 2e44bfa09a remove nginx site requires sudo 2026-07-04 14:25:49 +01:00
joeac 424b4c6971 uses ip addr instead of hostname to simplify dns config 2026-07-04 14:18:42 +01:00
joeac 4130448a55 swaps master node to blade-canongate 2026-07-04 08:43:00 +01:00
joeac ed6a6e5a34 update.sh exports XDG_RUNTIME_DIR 2026-07-03 16:29:17 +01:00
joeac 36ccd48cfc fix dollars in static.conf.template getting removed 2026-07-03 16:23:01 +01:00
joeac 61f2b376ee chown /var dirs 2026-07-03 16:02:05 +01:00
joeac b59888dc99 splits sudo to avoid env confusion 2026-07-03 15:50:14 +01:00
joeac d29e120c72 ...but for real this time 2026-07-03 15:40:15 +01:00
joeac 16cc43ad28 fix bin/pp again? 2026-07-03 15:37:36 +01:00
joeac 3773af632c fix bin/pp rule 2026-07-03 15:34:19 +01:00
joeac 2aeb58d9ed no quotes around COMPOSECMD 2026-07-03 15:23:13 +01:00
joeac affc7521d6 chmod +x for joeac.net openrc service 2026-07-03 15:21:41 +01:00
joeac f5a7d78d4f uses custom COMPOSECMD in joeac.net openrc services 2026-07-03 15:14:02 +01:00
joeac c42c636231 splits sudo to avoid env confusion 2026-07-03 15:08:46 +01:00
joeac 99b21dbe8e stores src in $HOME and more perm tweaks 2026-07-03 15:01:48 +01:00
joeac 3af4546839 adds XDG_RUNTIME_DIR explicitly in update.sh 2026-07-03 13:38:21 +01:00
joeac ace7801796 typo in openrc.mk: joeac.net.$(module) 2026-07-03 13:23:20 +01:00
joeac 7d0a920c8c provides ETHERPAD_VERSION in vars.mk 2026-07-03 12:37:02 +01:00
joeac f24f1ff4b7 provides ALPINE_VERSION in vars.mk 2026-07-03 12:31:49 +01:00
joeac 703359301b envsubst uses -io flags instead of positional params 2026-07-03 12:29:31 +01:00
joeac 66da50eb92 logins back in after updating groups 2026-07-03 12:27:50 +01:00
joeac 757a24894a chmod 770 on /home/joeac.net 2026-07-03 12:20:24 +01:00
joeac 4b19aabba6 chmod on .env 2026-07-03 12:14:02 +01:00
joeac 626642cd25 get_var_value 2026-07-03 12:12:25 +01:00
joeac a1c91da619 set +e install.sh 2026-07-03 12:07:05 +01:00
joeac fac4169389 removes CONTAINER_PREFIX from example.env (redundant) 2026-07-03 12:06:34 +01:00
joeac a5f1ea1358 copes with empty var values 2026-07-03 12:05:45 +01:00
joeac db8df130fe removes ^ from expr for portability 2026-07-03 12:02:38 +01:00
joeac 52f4ab3157 more portable conditon 2026-07-03 12:00:02 +01:00
joeac 8519d854fb expr has : 2026-07-03 11:51:16 +01:00
joeac edd01e4445 absolutely provides dir to make in install.sh 2026-07-03 11:47:05 +01:00
joeac 9ea2820411 absolutises example.env 2026-07-03 11:46:36 +01:00
joeac 967818f315 absolute chown /home/jeoac.net/joeac.net 2026-07-03 11:45:43 +01:00
joeac ca8e06f300 Merge branch 'main' of https://git.joeac.net/joeac/joeac.net 2026-07-03 11:44:50 +01:00
joeac a611b5010f another attempt to fix perms 2026-07-03 11:44:22 +01:00
joeac 2bc353c8fc install.sh gives rw permissions to user over repo using groups 2026-07-03 11:41:00 +01:00
joeac d4dd979311 install.sh gives rw permissions to user over repo using groups 2026-07-03 11:39:27 +01:00
joeac cd4bafbed3 install.sh: no password for joeac.net user 2026-07-03 11:22:56 +01:00
joeac 51d7222c84 install.sh: adds missing joeac/ in repo URL 2026-07-03 11:21:25 +01:00
joeac 888df5c664 install.sh: replaces doas with sudo 2026-07-03 11:20:36 +01:00
joeac 220394ed0d don't set -x in install.sh 2026-07-03 11:19:26 +01:00
joeac 89280d6087 install.sh: wget to /usr requires doas 2026-07-03 11:19:01 +01:00
joeac cb3ad780b1 install.sh: chmod +x in /usr needs doas 2026-07-03 11:17:46 +01:00
joeac 9615c940bb specifies explicit ENVSUBST_VERSION if wget from github 2026-07-03 11:17:17 +01:00
joeac 33e0fb21d6 set -x in install.sh 2026-07-03 11:15:10 +01:00
joeac 2a8b2b3a40 install.sh specifies latest version of envsubst when installing with go
install
2026-07-03 11:11:43 +01:00
joeac f765e1d38f go get -> go install 2026-07-03 11:06:21 +01:00
joeac e6c011d007 removes extraneous " in install.sh 2026-07-03 11:04:37 +01:00
joeac ba8aaa4ade removes redundant README stuff 2026-07-03 11:04:00 +01:00
joeac 9a93c6f983 install.sh installs dependencies 2026-07-03 11:03:36 +01:00
joeac 803e9e00e0 install.sh picks up where left off with env vars 2026-07-03 10:49:19 +01:00
joeac bb795a9555 install.sh picks up with cloning where left off 2026-07-03 10:46:58 +01:00
joeac ac81aeed31 install.sh doesn't attempt to re-add joeac.net user if already exists 2026-07-03 10:44:52 +01:00
joeac 6a4d6d88c5 install.sh creates remote_smtp_password 2026-07-03 10:41:08 +01:00
joeac 32ae7e2b38 checks for some dependencies in install.sh 2026-07-03 10:35:44 +01:00
joeac 4e62da3d52 installs etherpad data dir 2026-07-03 10:29:34 +01:00
joeac b19ae63ceb Merge branch 'main' of https://git.joeac.net/joeac/joeac.net 2026-07-03 10:28:22 +01:00
joeac 17c7e2446c pulls out GEMINI_CERTIFICATES_DIR and GEMINI_COMITIUM_DATA_DIR 2026-07-03 10:28:13 +01:00
joeac 1dab2c159a uninstalling module openrc file uses -f flag to avoid errors 2026-07-03 10:17:07 +01:00
joeac 9cabb1053d adds missing dollar in uninstall_module 2026-07-03 10:17:07 +01:00
joeac caa2f2d59c adds needed sudo to nginx module uninstall 2026-07-03 10:17:07 +01:00
joeac 6a23042b74 makes capitalise func POSIX-compatible 2026-07-03 10:17:07 +01:00
joeac 33d13bb455 appends /data to VAULTWARDEN_DATA_DIR 2026-07-03 10:13:09 +01:00
joeac 0074ad6643 pulls out ETHERPAD_DATA_DIR 2026-07-03 10:12:49 +01:00
joeac e2d3c05376 make install installs vaultwarden data dir 2026-07-03 10:09:47 +01:00
joeac c62f30ec32 adds missing space in openrc.mk 2026-07-03 10:06:10 +01:00
joeac 536a108040 install.sh sets up .env and DIGITALOCEAN_TOKEN 2026-07-03 09:57:48 +01:00
joeac c1525eaa60 automatically makes user.USER service for initing XDG_RUNTIME_DIR 2026-07-03 09:30:26 +01:00
joeac dcd2e1cd19 adds install.sh to install joeac.net under user joeac.net 2026-07-03 09:18:18 +01:00
joeac 19865adb6a pulls out USER variable 2026-07-03 09:02:22 +01:00
joeac b705e9e1e5 nginx installation uses templates and vars instead of explicit conf for
each module
2026-07-02 23:16:29 +01:00
joeac 87a6a3f1f8 adds PUBLIC_ROOT_DIR_ln to vars.mk 2026-07-02 22:33:50 +01:00
joeac 5914e2e578 removes capitalise from container.mk as now in vars.mk 2026-07-02 22:28:43 +01:00
joeac e39c7a47fc etherpad/settings is templated for port 2026-07-02 22:28:13 +01:00
joeac 24c55e142b PORT_module is aliased to MODULE_PORT 2026-07-02 22:27:50 +01:00
joeac de19519be0 makes module rules for all modules, not just ones to be installed here 2026-07-02 22:24:07 +01:00
joeac 3d87e928d4 make_module uses Makefile, not install.mk 2026-07-02 22:23:55 +01:00
joeac c89f338601 re-orders vars.mk 2026-07-02 17:55:00 +01:00
joeac 6feaddc542 adds some line breaks to vars.mk 2026-07-02 17:52:25 +01:00
joeac eaecbcf41f moves ports from .env to make 2026-07-02 17:51:33 +01:00
joeac 7b07b75747 Revert "sets MASTER_NODE to blade-canongate, not pi-broughton"
This reverts commit 0f397b0ee3.
2026-07-02 17:06:11 +01:00
joeac 0f397b0ee3 sets MASTER_NODE to blade-canongate, not pi-broughton 2026-07-02 17:03:44 +01:00
joeac 69918e681f microlog post: 2026-07-02 2026-07-02 16:21:33 +01:00
joeac cb90b72836 only pushes if is own image 2026-07-02 09:50:13 +01:00
joeac f5a6ffa8de COMPOSE_CMD specifies IMAGE_PREFIX 2026-07-02 09:48:13 +01:00
joeac d3845601e9 gets container_image_name from podman-compose config 2026-07-02 09:44:20 +01:00
joeac 1385472aaf push_module does not require build_module 2026-07-02 09:42:26 +01:00
joeac 5fd15c706b build_module only requires Dockerfile if one exists 2026-07-02 09:41:07 +01:00
joeac 5bd502c72f better crontab with update.sh 2026-07-02 09:39:10 +01:00
joeac f39c4a51dd adds empty build, push rules 2026-07-02 09:35:47 +01:00
joeac 8e857abf29 Merge branch 'main' of https://git.joeac.net/joeac/joeac.net 2026-07-02 09:34:38 +01:00
joeac 2abd95da6d has empty make_module rules 2026-07-02 09:34:38 +01:00
joeac 15a3697679 removes any existing joeac.net openrc service file before installing 2026-07-02 09:26:33 +01:00
joeac a2d7299e72 tidy Makefile 2026-07-02 09:23:33 +01:00
joeac dcdda64b9e distinguishes NGINX_SUBDOMAINS from SUBDOMAINS 2026-07-02 09:19:09 +01:00
joeac 5cc5b04b18 moves subdomain defs to top of vars.mk so can be used for SUBDOMAINS var 2026-07-02 09:17:14 +01:00
joeac 0c87d0cc65 Merge branch 'main' of https://git.joeac.net/joeac/joeac.net 2026-07-02 09:08:02 +01:00
joeac a7ea5780cc prunes dyndns crontabs on reinstall 2026-07-02 09:06:46 +01:00
joeac 6f06fc9503 prunes nginx sites on reinstall 2026-07-02 09:04:02 +01:00
joeac 6bc1f2d05f prunes nginx sites on reinstall 2026-07-02 08:53:24 +01:00
joeac c736041f3c adds uninstall_nginx_module rule content 2026-07-02 08:29:49 +01:00
joeac 8a5ad04776 adds reinstall rule 2026-07-02 08:10:25 +01:00
joeac 6a726a331d simplifies uninstall rule with var 2026-07-02 08:10:13 +01:00
joeac b0fe5b4a44 uses vars to simplify install rule 2026-07-02 08:05:51 +01:00
joeac 0e4aaabb54 all nginx modules are installed on master node 2026-07-01 18:45:42 +01:00
joeac 8ddbae5ee2 fix foreach in BUILD_RULES, PUSH_RULES 2026-07-01 18:40:13 +01:00
joeac cfa0dd5a9a adds reinstall/uninstall nginx module rules 2026-07-01 18:39:20 +01:00
joeac 5644789d64 all rule builds stuff 2026-07-01 18:37:44 +01:00
joeac c7c8a96a57 moves helper makefiles into make/ dir 2026-07-01 18:31:34 +01:00
joeac f73512107c renames some module rules 2026-07-01 18:26:22 +01:00
joeac f8401d00a4 fixes indent in dyndns.mk 2026-07-01 18:22:53 +01:00
joeac b79037c055 only install openrc module if it is an openrc module 2026-07-01 18:20:01 +01:00
joeac 8af9a55187 move uninstall_joeac.net_service to openrc.mk 2026-07-01 18:19:43 +01:00
joeac 1b200f217b move vars to vars.mk 2026-07-01 18:19:26 +01:00
joeac c0e6d13a1f moves stuff to nginx.mk 2026-07-01 18:11:08 +01:00
joeac 2b00e03999 pulls out uninstall to openrc.mk 2026-07-01 17:55:30 +01:00
joeac bf2dabe3f4 pulls out more dyndns stuff to dyndns.mk 2026-07-01 17:49:04 +01:00
joeac d0f549ce22 removes unneeded nginx vars in Makefiles 2026-07-01 17:33:53 +01:00
joeac 89165e9d88 pulls vars out to container.mk 2026-07-01 17:29:16 +01:00
joeac a601e93d00 pulls out nginx_module.mk 2026-07-01 17:22:50 +01:00
joeac fa4d275cc0 pulls out container.mk 2026-07-01 17:11:59 +01:00
joeac 9867d4f232 pulls out openrc.mk 2026-07-01 16:51:44 +01:00
46 changed files with 1216 additions and 1212 deletions
+3
View File
@@ -5,3 +5,6 @@ indent_size = 2
[*.{md,markdown,mdx}]
max_line_length = 80
[{Makefile,*.mk}]
indent_style = tab
+27 -152
View File
@@ -1,58 +1,13 @@
include config.mk
include make/vars.mk
#############
# VARIABLES #
#############
CPU_ARCH := $(if $(shell which arch 2>/dev/null),\
$(shell arch),\
$(shell lscpu | grep ^Architecture: | sed "s/^Architecture:[[:space:]]*\([[:alnum:][:punct:]]\+\).*/\1/"))
HOSTNAME := $(shell cat /etc/hostname)
IMAGE_PREFIX := $(if $(filter armv7%,$(CPU_ARCH)),armv7/)
REGISTRY_DOMAIN := git.joeac.net
REGISTRY_USER := joeac
MODULES_pi-broughton := http gemini smtp vaultwarden ln
MODULES_blade-canongate := etherpad
MASTER_NODE := pi-broughton
IS_MASTER_NODE := $(filter $(MASTER_NODE),$(HOSTNAME))
MODULES := $(MODULES_$(HOSTNAME))
SUBDOMAINS := $(foreach module,$(MODULES),$(SUBDOMAIN_$(module)))
COMPOSE_SERVICES := $(shell podman-compose config \
| yq ".services | keys" --output-format csv --csv-separator " ")
MAKE_MODULES := $(foreach module,$(MODULES),\
$(shell [ -f $(module)/Makefile ] && echo $(module)))
NGINX_CONFIG_SRC := nginx/nginx.conf
NGINX_CONFIG := /etc/nginx/nginx.conf
NGINX_CONFIG_BACKUP := /etc/nginx/nginx.joeac.net-backup
RESTART_NGINX := sudo rc-service nginx restart
#############
# FUNCTIONS #
#############
container_image_name = $(REGISTRY_DOMAIN)/$(REGISTRY_USER)/joeac.net-$(module)
nginx_module_target = $(if $(SUBDOMAIN_$(module)),/etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf)
dyndns_module_target = $(if $(SUBDOMAIN_$(module)),/etc/periodic/daily/dyndns-$(SUBDOMAIN_$(module)).joeac.net)
openrc_module_target = $(if $(filter $(COMPOSE_SERVICES),$(module)),~/.config/rc/init.d/joeac.net.$(module))
submake_file = $(shell [ -f "$(module)/Makefile" ] && echo "$(module)/Makefile")
install_submake_file = $(shell [ -f "$(module)/install.mk" ] && echo "$(module)/install.mk")
define build_module_rule =
.PHONY: build_$(module)
build_$(module): make_$(module) $(module).Dockerfile
podman-compose build $(module)
endef
define push_module_rule =
.PHONY: push_$(module)
push_$(module): login_registry build_$(module)
podman push $(container_image_name)
endef
define make_module_rule =
.PHONY: make_$(module)
make_$(module): $(module)/.env
$(MAKE) --directory=$(module)
$(if $(submake_file),\
$(MAKE) --makefile=$(notdir $(submake_file)) --directory=$(module))
endef
define module_env_rule =
@@ -62,130 +17,50 @@ endef
define install_module_rule =
.PHONY: install_$(module)
install_$(module): install_openrc_$(module) $(nginx_module_target) $(dyndns_module_target) $(install_submake_file)
install_$(module): install_openrc_$(module) install_dyndns_$(module) $(if $(SUBDOMAIN_$(module)),install_tls_$(SUBDOMAIN_$(module))) $(install_submake_file)
$(if $(install_submake_file),$(MAKE) --makefile=$(notdir $(install_submake_file)) --directory=$(dir $(install_submake_file)) install)
endef
define install_openrc_module_rule =
.PHONY: install_openrc_$(module)
install_openrc_$(module): $(openrc_module_target) openrc_add_$(module) openrc_start_$(module)
~/.config/rc/init.d/joeac.net.$(module): ~/.config/rc/init.d/joeac.net ~/.config/rc/runlevels/default
ln -s $(shell realpath ~)/.config/rc/init.d/joeac.net ~/.config/rc/init.d/joeac.net.$(module)
endef
define reinstall_module_rule =
.PHONY: reinstall_$(module)
reinstall_$(module): reinstall_openrc_$(module) $(nginx_module_target) $(dyndns_module_target) $(install_submake_file)
reinstall_$(module): reinstall_openrc_$(module) reinstall_dyndns_$(module) $(if $(SUBDOMAIN_$(module)),reinstall_tls_$(SUBDOMAIN_$(module))) $(install_submake_file)
$(if $(install_submake_file),$(MAKE) --makefile=$(notdir $(install_submake_file)) --directory=$(dir $(install_submake_file)) reinstall)
endef
define reinstall_openrc_module_rule =
.PHONY: reinstall_openrc_$(module)
reinstall_openrc_$(module): $(if $(openrc_module_target),$(openrc_module_target) openrc_restart_$(module))
endef
define uninstall_module_rule =
.PHONY: uninstall_$(module)
uninstall_$(module):
$(if $(openrc_module_target),\
rc-service -U joeac.net.$(module) stop \
rc-update -U del joeac.net.$(module) default \
rm ~/.config/rc/init.d/joeac.net.$(module) \
)
$(if $(SUBDOMAIN_$(module)),\
sudo rm -f /etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf; \
$(RESTART_NGINX); \
sudo rm -f /etc/periodic/daily/dyndns-$(SUBDOMAIN_$(module)).joeac.net; \
)
uninstall_$(module): uninstall_openrc_$(module) uninstall_dyndns_$(module) $(if $(SUBDOMAIN_$(module)),install_tls_$(SUBDOMAIN_$(module)))
$(if $(install_submake_file),\
$(MAKE) --makefile=$(notdir (install_submake_file)) --directory $(dir $(install_submake_file)) uninstall
$(MAKE) --makefile=$(notdir $(install_submake_file)) --directory $(dir $(install_submake_file)) uninstall
)
endef
define openrc_add_rule =
.PHONY: openrc_add_$(module)
openrc_add_$(module):
rc-update -U add joeac.net.$(module) default
endef
.PHONY: all
all: $(ENV_RULES) $(MAKE_RULES) $(BUILD_RULES) $(PUSH_RULES)
define openrc_start_rule =
.PHONY: openrc_start_$(module)
openrc_start_$(module):
rc-service -U joeac.net.$(module) start
endef
define openrc_restart_rule =
.PHONY: openrc_restart_$(module)
openrc_restart_$(module):
rc-service -U joeac.net.$(module) restart
endef
#########
# RULES #
#########
.SILENT: usage
.PHONY: usage
usage:
echo "usage:"
echo " make install"
$(foreach module,$(MODULES),echo " make install_$(module)")
echo " make reinstall"
$(foreach module,$(MODULES),echo " make reinstall_$(module)")
echo " make uninstall"
$(foreach module,$(MODULES),echo " make uninstall_$(module)")
$(foreach module,$(COMPOSE_SERVICES),$(eval $(call build_module_rule)))
$(foreach module,$(COMPOSE_SERVICES),$(eval $(call push_module_rule)))
$(foreach module,$(MAKE_MODULES),$(eval $(call make_module_rule)))
$(foreach module,$(MODULES),$(eval $(call module_env_rule)))
.PHONY: login_registry
login_registry:
podman login $(REGISTRY_DOMAIN)
$(foreach module,$(ALL_MODULES),$(eval $(call make_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(call module_env_rule)))
.PHONY: install
install: $(foreach module,$(MODULES),install_$(module)) install_crontab
install: install_tls install_nginx $(ENV_RULES) $(INSTALL_RULES) install_crontab
.PHONY: reinstall
reinstall: reinstall_tls reinstall_nginx reinstall_dyndns $(ENV_RULES) $(REINSTALL_RULES) reinstall_crontab
.PHONY: uninstall
uninstall: uninstall_nginx uninstall_dyndns uninstall_joeac.net_service $(foreach module,$(MODULES),uninstall_$(module)) uninstall_crontab
uninstall: uninstall_nginx uninstall_dyndns uninstall_tls uninstall_joeac.net_service $(UNINSTALL_RULES) uninstall_crontab
.PHONY: uninstall_joeac.net_service
rm ~/.config/rc/joeac.net
$(foreach module,$(MODULES),$(eval $(install_module_rule)))
$(foreach module,$(MODULES),$(eval $(reinstall_module_rule)))
$(foreach module,$(MODULES),$(eval $(uninstall_module_rule)))
$(foreach module,$(MODULES),$(eval $(install_openrc_module_rule)))
$(foreach module,$(MODULES),$(eval $(reinstall_openrc_module_rule)))
$(foreach module,$(MODULES),$(eval $(openrc_add_rule)))
$(foreach module,$(MODULES),$(eval $(openrc_start_rule)))
$(foreach module,$(MODULES),$(eval $(openrc_restart_rule)))
~/.config/rc/init.d/joeac.net: openrc/init.d/joeac.net ~/.config/rc/init.d ~/.config/rc/runlevels/default /etc/init.d/user.$(shell whoami) /etc/conf.d/user.$(shell whoami)
rm -f ~/.config/rc/init.d/joeac.net; \
mkdir -p ~/.config/rc/init.d; \
cp openrc/init.d/joeac.net ~/.config/rc/init.d/joeac.net
~/.config/rc/init.d:
mkdir -p ~/.config/rc/init.d
~/.config/rc/runlevels/default:
mkdir -p ~/.config/rc/runlevels/default
/etc/init.d/user.$(shell whoami):
sudo ln -s /etc/init.d/user /etc/init.d/user.$(shell whoami)
/etc/conf.d/user.$(shell whoami): openrc/conf.d/user.$(shell whoami)
sudo cp openrc/conf.d/user.$(shell whoami) /etc/conf.d/user.$(shell whoami)
sudo rc-update add user.$(shell whoami) default
include nginx.mk
include crontab.mk
include dyndns.mk
$(foreach module,$(ALL_MODULES),$(eval $(install_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(reinstall_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(uninstall_module_rule)))
.PHONY: clean
clean:
$(foreach module,$(MAKE_MODULES),$(MAKE) --directory=$(module) clean;)
include make/container.mk
include make/openrc.mk
include make/nginx.mk
include make/crontab.mk
include make/dyndns.mk
include make/tls.mk
+86 -27
View File
@@ -2,44 +2,103 @@
Joe Carstairs' public Internet presence
Structure:
To install:
```
/
├── gemini My Gemini capsule
├── smtp A local SMTP server
└── http My Website
```sh
wget -O- https://git.joeac.net/joeac/joeac.net/raw/branch/main/install.sh | sh
```
## Running with Podman
## DNS setup
These instructions will probably work with Docker, too: just substitute `podman`
for `docker` in all the commands.
A/AAAA DNS records are added automatically by `make install`, but in order to
get `mox` working as an email server, you'll have to manually add the following
DNS records.
To run with Podman, first set up your environment variables. Copy `example.env`
to `.env` and edit the values accordingly.
Deliver mail for mail.joeac.net to mail.joeac.net:
Then, create the `remote_smtp_password` secret, storing the password for the
remote SMTP server which will send the contact emails on behalf of the website.
```bash
sudo podman secret create remote_smtp_password /path/to/remote/smtp/password
```
mail.joeac.net. MX 10 mail.joeac.net.
```
Now build and start the containers:
All emails from the email server will be signed with two DKIM keys. So that
clients can verify the authenticity of the messages, provide these keys as TXT
records:
```bash
sudo podman-compose build && sudo podman-compose up -d
```
2026a._domainkey.mail.joeac.net. TXT v=DKIM1;h=sha256;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvYfjrzmYgBtYofzBwI80aiW98+M2g6z+gd1Iwz9g0y30rPFNTctFn9GwBNuYBgZiZxB+sqjEbPMbr3li/R7i0A9t/KbNNJhkNC+4IjKJjk+jw1CXm4vXOUa4YSYWwy7NVYTH/QwZGz6fwjVM7YvDnnE4gG2NwrVx+AXlONt2R1G+qgV6HAIIvVi8T0yCjjqEc5B5bKlqk0XU9vSyUFJhhKnR/KNRe79C+H9GWJzcU7HUCmIHX04Xi0JeB/wm3weF1xjtGTsWyy5BmHHsfWGqSr2Dbg5o6AI5W0h4VkQ4QzdEYGVQ9ZBDyqFQwQFXLn0oHZjCD/vFzPOPdM5pxF/OgwIDAQAB
2026b._domainkey.mail.joeac.net. TXT v=DKIM1;h=sha256;p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzcrk9FUt6AdrvnAP3KawuOTLw7uL5SJ+ZYmShuz41zwM6bPQteGSSwddFXIxcqVlJrdFahrK4KvHX/sw/hWVfZoPLDdwsGN5eI8cqQjNDE+JDu9BbPlTituva4Hkve0hbAKDqA8jmbcZg6aU7b44Kzq8UpWAlPO273Rq2tsbCBcITt8B3NFoeY9CSsZU1LqGl855GUtaNyhlPaAvfab3Q9/4wyusPhCHlBYaRK+ZzuSMs5KEOG6n4kbZfMVi2+4c/bPU5PdTuyvbSIEqjNH4TpfatE0I9ubGv0WbAzr5EZbv5+xtukZ/dIisPPMjn1AbjpSJYNYr2OYgey6+WvzRmQIDAQAB
```
## Running on the host machine
Specify the MX host is allowed to send for our domain and for itself (for DSNs).
~all means softfail for anything else, which is done instead of -all to prevent
older mail servers from rejecting the message because they never get to looking
for a dkim/dmarc pass.
To run on the host machine, first, as before, set up your environment variables
by copying `example.env` to `.env` and editing the values as appropriate.
```bash
npm run start
```
mail.joeac.net. TXT v=spf1 ip4:217.155.190.42 ip6:fdc9:6aec:7a18:0:2e0:4cff:fe61:9b17 mx ~all
```
Note that emails may not work locally without further setup. These instructions
are of course woefully incomplete.
Emails that fail the DMARC check (without aligned DKIM and without aligned SPF)
should be rejected, and request reports. If you email through mailing lists that
strip DKIM-Signature headers and don't rewrite the From header, you may want to
set the policy to p=none.
```
_dmarc.mail.joeac.net. TXT v=DMARC1;p=reject;rua=mailto:dmarcreports@mail.joeac.net!10m
```
Remote servers can use MTA-STS to verify our TLS certificate with the WebPKI
pool of CA's (certificate authorities) when delivering over SMTP with
STARTTLSTLS.
```
mta-sts.mail.joeac.net. CNAME mail.joeac.net.
_mta-sts.mail.joeac.net. TXT v=STSv1; id=20260705T153220
```
Request reporting about TLS failures.
```
_smtp._tls.mail.joeac.net. TXT v=TLSRPTv1; rua=mailto:tlsreports@mail.joeac.net
```
Client settings will reference a subdomain of the hosted domain, making it
easier to migrate to a different server in the future by not requiring settings
in all clients to be updated.
```
clientsettings.mail.joeac.net. CNAME mail.joeac.net.
```
Autoconfig is used by Thunderbird. Autodiscover is (in theory) used by
Microsoft.
```
autoconfig.mail.joeac.net. CNAME mail.joeac.net.
_autodiscover._tcp.mail.joeac.net. SRV 0 1 443 mail.joeac.net.
```
For secure IMAP and submission autoconfig, point to mail host.
```
_imaps._tcp.mail.joeac.net. SRV 0 1 993 mail.joeac.net.
_submissions._tcp.mail.joeac.net. SRV 0 1 465 mail.joeac.net.
```
Next records specify POP3 and non-TLS ports are not to be used. These are
optional and safe to leave out (e.g. if you have to click a lot in a DNS admin
web interface).
```
_imap._tcp.mail.joeac.net. SRV 0 0 0 .
_submission._tcp.mail.joeac.net. SRV 0 0 0 .
_pop3._tcp.mail.joeac.net. SRV 0 0 0 .
_pop3s._tcp.mail.joeac.net. SRV 0 0 0 .
```
Optional: You could mark Let's Encrypt as the only Certificate Authority allowed
to sign TLS certificates for your domain.
```
mail.joeac.net. CAA 0 issue "letsencrypt.org"
```
+11
View File
@@ -0,0 +1,11 @@
Writing is hard! I've just spent 2 days fixing 1 paragraph in my dissertation. The problem was to give the reader good reason to think that the fossil fuel industry exerts a strong influence on the majority epistemology. The end result is a single paragraph with four citations. You'd think that would take half a day, not 2 full days.
I think what made it hard was that I needed to gain understanding. If I just needed a fact or two, it would have been easier: skim a few abstracts, extract a few figures, call it a day. But I didn't know in advance what fossil-think looks like. I couldn't write a paragraph that was almost done but for a few missing statistics. I needed to know the whens, the hows, the whos.
What I ended up doing was collecting a score of browser tabs with relevant articles and ebooks (which was easy enough), then going through each one and reading it: properly reading it, for understanding, not just skimming abstracts.
This worked: after about half a dozen articles, I reviewed my notes, realised I had enough to be getting on with, and wrote up my paragraph. Along the way I gained a (very shallow) understanding of how the fossil fuel industry influences public discourse.
But: it took a heck of a long time for one paragraph.
I really wish I had an experienced researcher looking over my shoulder, popping by a couple of times a day, to offer advice. Maybe they would tell me: sorry, that's just how it is, you need to gain understanding and that takes time. Or maybe they would say: you should really be cutting the corner here, let me show you how. It's one of the many things you cannot get from a monthly check-in with a supervisor.
+3
View File
@@ -0,0 +1,3 @@
I've been following Ink and Switch's blog for a while. They're working creatively with computers. This is a great discussion about LLMs which covers what they are, how they are being abused to de-humanise the world, and how things could be different, concluding in a call for 'punk computing.' Let's have it!
=> https://www.inkandswitch.com/tangents/artificial/ Artificial | Ink and Switch
+18 -6
View File
@@ -1,15 +1,27 @@
services:
actualbudget:
image: docker.io/actualbudget/actual-server:latest-alpine
container_name: joeac.net-actualbudget
restart: unless-stopped
volumes:
- ${ACTUALBUDGET_DATA_DIR}:/data/
ports:
- "${ACTUALBUDGET_PORT}:5006"
gemini:
image: git.joeac.net/joeac/${CONTAINER_PREFIX}joeac.net-gemini
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-gemini
container_name: joeac.net-gemini
build:
context: .
dockerfile: gemini.Dockerfile
ports:
- "${GEMINI_PORT}:1965"
volumes:
- ${GEMINI_CERTIFICATES_DIR}:/var/app/.certificates
- ${GEMINI_COMITIUM_DATA_DIR}:/var/app/comitium-data
http:
image: git.joeac.net/joeac/${CONTAINER_PREFIX}joeac.net-http
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-http
container_name: joeac.net-http
build:
context: .
@@ -30,7 +42,7 @@ services:
- "${HTTP_PORT}:80"
etherpad:
image: git.joeac.net/joeac/${CONTAINER_PREFIX}joeac.net-etherpad
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-etherpad
container_name: joeac.net-etherpad
build:
context: .
@@ -39,7 +51,7 @@ services:
ALPINE_VERSION: ${ALPINE_VERSION}
ETHERPAD_VERSION: ${ETHERPAD_VERSION}
volumes:
- ./etherpad/var:/var/app/var
- ${ETHERPAD_DATA_DIR}:/var/app/var
environment:
NODE_ENV: ${ETHERPAD_NODE_ENV}
ADMIN_PASSWORD: ${ETHERPAD_ADMIN_PASSWORD}
@@ -48,7 +60,7 @@ services:
- "${ETHERPAD_PORT}:9001"
smtp:
image: git.joeac.net/joeac/${CONTAINER_PREFIX}joeac.net-smtp
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-smtp
container_name: joeac.net-smtp
build:
context: .
@@ -74,7 +86,7 @@ services:
environment:
DOMAIN: "https://pwd.joeac.net"
volumes:
- ./vaultwarden/vw-data/:/data/
- ${VAULTWARDEN_DATA_DIR}:/data/
ports:
- "${VAULTWARDEN_PORT}:80"
-4
View File
@@ -1,4 +0,0 @@
SUBDOMAIN_http := @
SUBDOMAIN_vaultwarden := pwd
SUBDOMAIN_etherpad := docs
SUBDOMAIN_ln := ln
-38
View File
@@ -1,38 +0,0 @@
.PHONY: install_dyndns
install_dyndns: /usr/local/bin/dyndns.sh ~/.config/dyndns/DIGITALOCEAN_TOKEN
.PHONY: reinstall_dyndns
reinstall_dyndns: /usr/local/bin/dyndns.sh ~/.config/dyndns/DIGITALOCEAN_TOKEN
.PHONY: uninstall_dyndns
uninstall_dyndns:
sudo rm -rf \
/usr/local/bin/dyndns.sh \
/usr/local/bin/get_ip_addr.sh \
/usr/local/lib/digitalocean_dyndns/ \
~/.config/dyndns \
~/.cache/dyndns
/usr/local/bin/dyndns.sh: /usr/local/lib/digitalocean_dyndns/dyndns.sh /usr/local/bin/get_ip_addr.sh
sudo rm -f /usr/local/bin/dyndns.sh
sudo cp /usr/local/lib/digitalocean_dyndns/dyndns.sh /usr/local/bin/dyndns.sh
/usr/local/bin/get_ip_addr.sh: /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh
sudo rm -f /usr/local/bin/get_ip_addr.sh
sudo cp /usr/local/lib/digitalocean_dyndns/get_ip_addr.sh /usr/local/bin/get_ip_addr.sh
/usr/local/lib/digitalocean_dyndns/%:
cd /usr/local/lib \
&& sudo git clone https://git.joeac.net/joeac/digitalocean_dyndns.git
~/.config/dyndns/DIGITALOCEAN_TOKEN: DIGITALOCEAN_TOKEN
mkdir -p ~/.config/dyndns/
rm -f ~/.config/dyndns/DIGITALOCEAN_TOKEN
cp DIGITALOCEAN_TOKEN ~/.config/dyndns/DIGITALOCEAN_TOKEN
/etc/periodic/daily/dyndns-%joeac.net: /usr/local/bin/dyndns.sh
echo "#!/bin/sh" > crontab.tmp
echo " /usr/local/bin/dyndns.sh 4 $(*F)joeac.net" >> crontab.tmp
echo "CONN_DEVICE_NAME=eth0 /usr/local/bin/dyndns.sh 6 $(*F)joeac.net" >> crontab.tmp
sudo mv crontab.tmp $@
sudo chmod +x $@
+6 -2
View File
@@ -16,8 +16,12 @@ install_deps: $(SRC_DIR)/node_modules
$(SRC_DIR)/node_modules: $(SRC_DIR) $(SRC_DIR)/settings.json $(SRC_DIR)/package.json $(SRC_DIR)/pnpm-lock.yaml
cd $(SRC_DIR) && pnpm install
$(SRC_DIR)/settings.json: $(SRC_DIR) settings.json
cp settings.json $(SRC_DIR)/settings.json
$(SRC_DIR)/settings.json: $(SRC_DIR) settings.json.template
ifdef ETHERPAD_PORT
ETHERPAD_PORT=$(ETHERPAD_PORT) envsubst -no-unset -i settings.json.template -o $(SRC_DIR)/settings.json
else
$(error ETHERPAD_PORT is not defined)
endif
clean:
rm -rf etherpad-*/
+13
View File
@@ -0,0 +1,13 @@
.PHONY: install
install: $(ETHERPAD_DATA_DIR)
.PHONY: reinstall
reinstall: $(ETHERPAD_DATA_DIR)
$(ETHERPAD_DATA_DIR):
sudo mkdir -p $(ETHERPAD_DATA_DIR)
ETHERPAD_USER=$(whoami) && sudo chown $$ETHERPAD_USER:$$ETHERPAD_USER $(ETHERPAD_DATA_DIR)
.PHONY: uninstall
uninstall:
$(info Nothing to do)
-747
View File
@@ -1,747 +0,0 @@
/*
* This file must be valid JSON. But comments are allowed
*
* Please edit settings.json, not settings.json.template
*
* Please note that starting from Etherpad 1.6.0 you can store DB credentials in
* a separate file (credentials.json).
*
*
* ENVIRONMENT VARIABLE SUBSTITUTION
* =================================
*
* All the configuration values can be read from environment variables using the
* syntax "${ENV_VAR}" or "${ENV_VAR:default_value}".
*
* This is useful, for example, when running in a Docker container.
*
* DETAILED RULES:
* - If the environment variable is set to the string "true" or "false", the
* value becomes Boolean true or false.
* - If the environment variable is set to the string "null", the value
* becomes null.
* - If the environment variable is set to the string "undefined", the setting
* is removed entirely, except when used as the member of an array in which
* case it becomes null.
* - If the environment variable is set to a string representation of a finite
* number, the string is converted to that number.
* - If the environment variable is set to any other string, including the
* empty string, the value is that string.
* - If the environment variable is unset and a default value is provided, the
* value is as if the environment variable was set to the provided default:
* - "${UNSET_VAR:}" becomes the empty string.
* - "${UNSET_VAR:foo}" becomes the string "foo".
* - "${UNSET_VAR:true}" and "${UNSET_VAR:false}" become true and false.
* - "${UNSET_VAR:null}" becomes null.
* - "${UNSET_VAR:undefined}" causes the setting to be removed (or be set
* to null, if used as a member of an array).
* - If the environment variable is unset and no default value is provided,
* the value becomes null. THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF
* ETHERPAD; if you want the default value to be null, you should explicitly
* specify "null" as the default value.
*
* EXAMPLE:
* "port": "${PORT:9001}"
* "minify": "${MINIFY}"
* "skinName": "${SKIN_NAME:colibris}"
*
* Would read the configuration values for those items from the environment
* variables PORT, MINIFY and SKIN_NAME.
*
* If PORT and SKIN_NAME variables were not defined, the default values 9001 and
* "colibris" would be used.
* The configuration value "minify", on the other hand, does not have a
* designated default value. Thus, if the environment variable MINIFY were
* undefined, "minify" would be null.
*
* REMARKS:
* 1) please note that variable substitution always needs to be quoted.
*
* "port": 9001, <-- Literal values. When not using
* "minify": false substitution, only strings must be
* "skinName": "colibris" quoted. Booleans and numbers must not.
*
* "port": "${PORT:9001}" <-- CORRECT: if you want to use a variable
* "minify": "${MINIFY:true}" substitution, put quotes around its name,
* "skinName": "${SKIN_NAME}" even if the required value is a number or
* a boolean.
* Etherpad will take care of rewriting it
* to the proper type if necessary.
*
* "port": ${PORT:9001} <-- ERROR: this is not valid json. Quotes
* "minify": ${MINIFY} around variable names are missing.
* "skinName": ${SKIN_NAME}
*
* 2) Beware of undefined variables and default values: nulls and empty strings
* are different!
*
* This is particularly important for user's passwords (see the relevant
* section):
*
* "password": "${PASSW}" // if PASSW is not defined would result in password === null
* "password": "${PASSW:}" // if PASSW is not defined would result in password === ''
*
* If you want to use an empty value (null) as default value for a variable,
* simply do not set it, without putting any colons: "${SOFFICE}".
*
* 3) if you want to use newlines in the default value of a string parameter,
* use "\n" as usual.
*
* "defaultPadText" : "${DEFAULT_PAD_TEXT:Line 1\nLine 2}"
*/
{
/*
* Name your instance!
*/
"title": "joeac's docs",
/*
* Whether to show recent pads on the homepage or not.
*/
"showRecentPads": true,
/*
* Pathname of the favicon you want to use. If null, the skin's favicon is
* used if one is provided by the skin, otherwise the default Etherpad favicon
* is used. If this is a relative path it is interpreted as relative to the
* Etherpad root directory.
*/
"favicon": null,
/*
* Skin name.
*
* Its value has to be an existing directory under src/static/skins.
* You can write your own, or use one of the included ones:
*
* - "no-skin": an empty skin (default). This yields the unmodified,
* traditional Etherpad theme.
* - "colibris": the new experimental skin (since Etherpad 1.8), candidate to
* become the default in Etherpad 2.0
*/
"skinName": "colibris",
/*
* Skin Variants
*
* Use the UI skin variants builder at /p/test#skinvariantsbuilder
*
* For the colibris skin only, you can choose how to render the three main
* containers:
* - toolbar (top menu with icons)
* - editor (containing the text of the pad)
* - background (area outside of editor, mostly visible when using page style)
*
* For each of the 3 containers you can choose 4 color combinations:
* super-light, light, dark, super-dark.
*
* For example, to make the toolbar dark, you will include "dark-toolbar" into
* skinVariants.
*
* You can provide multiple skin variants separated by spaces. Default
* skinVariant is "super-light-toolbar super-light-editor light-background".
*
* For the editor container, you can also make it full width by adding
* "full-width-editor" variant (by default editor is rendered as a page, with
* a max-width of 900px).
*/
"skinVariants": "super-light-toolbar super-light-editor light-background",
/*
* IP and port which Etherpad should bind at.
*
* Binding to a Unix socket is also supported: just use an empty string for
* the ip, and put the full path to the socket in the port parameter.
*
* EXAMPLE USING UNIX SOCKET:
* "ip": "", // <-- has to be an empty string
* "port" : "/somepath/etherpad.socket", // <-- path to a Unix socket
*/
"ip": "0.0.0.0",
"port": 9001,
/*
* Option to hide/show the settings.json in admin page.
*
* Default option is set to true
*/
"showSettingsInAdminPage": true,
/*
* Enable/disable the metrics endpoint.
*
* This is used by the monitoring plugins to collect metrics about Etherpad.
* If you do not use any monitoring plugins, you can disable this.
*/
"enableMetrics": "${ENABLE_METRICS:true}",
/*
* Settings for cleanup of pads
*/
"cleanup": {
"enabled": false,
"keepRevisions": 5
},
/*
* Node native SSL support
*
* This is disabled by default.
* Make sure to have the minimum and correct file access permissions set so
* that the Etherpad server can access them
*/
/*
"ssl" : {
"key" : "/path-to-your/epl-server.key",
"cert" : "/path-to-your/epl-server.crt",
"ca": ["/path-to-your/epl-intermediate-cert1.crt", "/path-to-your/epl-intermediate-cert2.crt"]
},
*/
/*
* The type of the database.
*
* You can choose between many DB drivers, for example: dirty, postgres,
* sqlite, mysql.
*
* You shouldn't use "dirty" for for anything else than testing or
* development.
*
*
* Database specific settings are dependent on dbType, and go in dbSettings.
* Remember that since Etherpad 1.6.0 you can also store this information in
* credentials.json.
*
* For a complete list of the supported drivers, please refer to:
* https://www.npmjs.com/package/ueberdb2
*/
"dbType": "sqlite",
"dbSettings": {
"filename": "var/etherpad.sq3"
},
/*
* An Example of MySQL Configuration (commented out).
*
* See: https://github.com/ether/etherpad/wiki/How-to-use-Etherpad-Lite-with-MySQL
*/
/*
"dbType" : "mysql",
"dbSettings" : {
"user": "etherpaduser",
"host": "localhost",
"port": 3306,
"password": "PASSWORD",
"database": "etherpad_lite_db",
"charset": "utf8mb4"
},
*/
/*
* The default text of a pad
*/
"defaultPadText" : "",
/*
* Default Pad behavior.
*
* Change them if you want to override.
*/
"padOptions": {
"noColors": false,
"showControls": true,
"showChat": true,
"showLineNumbers": true,
"useMonospaceFont": false,
"userName": null,
"userColor": null,
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": null
},
/*
* Pad Shortcut Keys
*/
"padShortcutEnabled" : {
"altF9": true, /* focus on the File Menu and/or editbar */
"altC": true, /* focus on the Chat window */
"cmdShift2": true, /* shows a gritter popup showing a line author */
"delete": true,
"return": true,
"esc": true, /* in mozilla versions 14-19 avoid reconnecting pad */
"cmdS": true, /* save a revision */
"tab": true, /* indent */
"cmdZ": true, /* undo/redo */
"cmdY": true, /* redo */
"cmdI": true, /* italic */
"cmdB": true, /* bold */
"cmdU": true, /* underline */
"cmd5": true, /* strike through */
"cmdShiftL": true, /* unordered list */
"cmdShiftN": true, /* ordered list */
"cmdShift1": true, /* ordered list */
"cmdShiftC": true, /* clear authorship */
"cmdH": true, /* backspace */
"ctrlHome": true, /* scroll to top of pad */
"pageUp": true,
"pageDown": true
},
/*
* Enables the use of a different server. We have a different one that syncs changes from the original server.
* It is hosted on GitHub and should not be blocked by many firewalls.
* https://etherpad.org/ep_infos
*/
"updateServer": "https://etherpad.org/ep_infos",
/*
* Should we suppress errors from being visible in the default Pad Text?
*/
"suppressErrorsInPadText": false,
/*
* If this option is enabled, a user must have a session to access pads.
* This effectively allows only group pads to be accessed.
*/
"requireSession": false,
/*
* Users may edit pads but not create new ones.
*
* Pad creation is only via the API.
* This applies both to group pads and regular pads.
*/
"editOnly": false,
/*
* If true, all css & js will be minified before sending to the client.
*
* This will improve the loading performance massively, but makes it difficult
* to debug the javascript/css
*/
"minify": true,
/*
* How long may clients use served javascript code (in seconds)?
*
* Not setting this may cause problems during deployment.
* Set to 0 to disable caching.
*/
"maxAge": 21600, // 60 * 60 * 6 = 6 hours
/*
* This is the absolute path to the soffice executable.
*
* LibreOffice is used for advanced import/export of pads (docx, pdf, odt).
* Setting it to null disables LibreOffice and will only allow plain text
* and HTML import/exports.
*/
"soffice": null,
/*
* When true (the default), the "Microsoft Word" export button downloads a .docx file via
* LibreOffice (requires "soffice" to be set). Set to false to revert to legacy .doc output
* (which also requires "soffice").
*/
"docxExport": true,
/*
* txt, doc, docx, rtf, odt, html & htm
*/
"allowUnknownFileEnds": true,
/*
* This setting is used if you require authentication of all users.
*
* Note: "/admin" always requires authentication.
*/
"requireAuthentication": false,
/*
* Require authorization by a module, or a user with is_admin set, see below.
*/
"requireAuthorization": false,
/*
* When you use NGINX or another proxy/load-balancer set this to true.
*
* This is especially necessary when the reverse proxy performs SSL
* termination, otherwise the cookies will not have the "secure" flag.
*
* The other effect will be that the logs will contain the real client's IP,
* instead of the reverse proxy's IP.
*/
"trustProxy": false,
/*
* Settings controlling the session cookie issued by Etherpad.
*/
"cookie": {
/*
* Prefix for all cookie names set by Etherpad. Set this to "ep_" or similar
* if Etherpad's cookie names (token, sessionID, etc.) conflict with those
* of another application on the same domain. Default: "" (no prefix).
*/
// "prefix": "ep_",
/*
* How often (in milliseconds) the key used to sign the express_sid cookie
* should be rotated. Long rotation intervals reduce signature verification
* overhead (because there are fewer historical keys to check) and database
* load (fewer historical keys to store, and less frequent queries to
* get/update the keys). Short rotation intervals are slightly more secure.
*
* Multiple Etherpad processes sharing the same database (table) is
* supported as long as the clock sync error is significantly less than this
* value.
*
* Key rotation can be disabled (not recommended) by setting this to 0 or
* null, or by disabling session expiration (see sessionLifetime).
*/
"keyRotationInterval": 86400000, // = 1d * 24h/d * 60m/h * 60s/m * 1000ms/s
/*
* Value of the SameSite cookie property. "Lax" is recommended unless
* Etherpad will be embedded in an iframe from another site, in which case
* this must be set to "None". Note: "None" will not work (the browser will
* not send the cookie to Etherpad) unless https is used to access Etherpad
* (either directly or via a reverse proxy with "trustProxy" set to true).
*
* "Strict" is not recommended because it has few security benefits but
* significant usability drawbacks vs. "Lax". See
* https://stackoverflow.com/q/41841880 for discussion.
*/
"sameSite": "Lax",
/*
* How long (in milliseconds) after navigating away from Etherpad before the
* user is required to log in again. (The express_sid cookie is set to
* expire at time now + sessionLifetime when first created, and its
* expiration time is periodically refreshed to a new now + sessionLifetime
* value.) If requireAuthentication is false then this value does not really
* matter.
*
* The "best" value depends on your users' usage patterns and the amount of
* convenience you desire. A long lifetime is more convenient (users won't
* have to log back in as often) but has some drawbacks:
* - It increases the amount of state kept in the database.
* - It might weaken security somewhat: The cookie expiration is refreshed
* indefinitely without consulting authentication or authorization
* hooks, so once a user has accessed a pad, the user can continue to
* use the pad until the user leaves for longer than sessionLifetime.
* - More historical keys (sessionLifetime / keyRotationInterval) must be
* checked when verifying signatures.
*
* Session lifetime can be set to infinity (not recommended) by setting this
* to null or 0. Note that if the session does not expire, most browsers
* will delete the cookie when the browser exits, but a session record is
* kept in the database forever.
*/
"sessionLifetime": 864000000, // = 10d * 24h/d * 60m/h * 60s/m * 1000ms/s
/*
* How long (in milliseconds) before the expiration time of an active user's
* session is refreshed (to now + sessionLifetime). This setting affects the
* following:
* - How often a new session expiration time will be written to the
* database.
* - How often each user's browser will ping the Etherpad server to
* refresh the expiration time of the session cookie.
*
* High values reduce the load on the database and the load from browsers,
* but can shorten the effective session lifetime if Etherpad is restarted
* or the user navigates away.
*
* Automatic session refreshes can be disabled (not recommended) by setting
* this to null.
*/
"sessionRefreshInterval": 86400000, // = 1d * 24h/d * 60m/h * 60s/m * 1000ms/s
/*
* Whether to periodically clean up expired and stale sessions from the
* database. Set to false to disable. Default: true.
*/
"sessionCleanup": true
},
/*
* Privacy: disable IP logging
*/
"disableIPlogging": false,
/*
* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
* message is shown to user.
*
* Set to 0 to disable automatic reconnection.
*/
"automaticReconnectionTimeout": 0,
/*
* By default, when caret is moved out of viewport, it scrolls the minimum
* height needed to make this line visible.
*/
"scrollWhenFocusLineIsOutOfViewport": {
/*
* Percentage of viewport height to be additionally scrolled.
*
* E.g.: use "percentage.editionAboveViewport": 0.5, to place caret line in
* the middle of viewport, when user edits a line above of the
* viewport
*
* Set to 0 to disable extra scrolling
*/
"percentage": {
"editionAboveViewport": 0,
"editionBelowViewport": 0
},
/*
* Time (in milliseconds) used to animate the scroll transition.
* Set to 0 to disable animation
*/
"duration": 0,
/*
* Flag to control if it should scroll when user places the caret in the
* last line of the viewport
*/
"scrollWhenCaretIsInTheLastLineOfViewport": false,
/*
* Percentage of viewport height to be additionally scrolled when user
* presses arrow up in the line of the top of the viewport.
*
* Set to 0 to let the scroll to be handled as default by Etherpad
*/
"percentageToScrollWhenUserPressesArrowUp": 0
},
/*
* User accounts. These accounts are used by:
* - default HTTP basic authentication if no plugin handles authentication
* - some but not all authentication plugins
* - some but not all authorization plugins
*
* User properties:
* - password: The user's password. Some authentication plugins will ignore
* this.
* - is_admin: true gives access to /admin. Defaults to false. If you do not
* uncomment this, /admin will not be available!
* - readOnly: If true, this user will not be able to create new pads or
* modify existing pads. Defaults to false.
* - canCreate: If this is true and readOnly is false, this user can create
* new pads. Defaults to true.
*
* Authentication and authorization plugins may define additional properties.
*
* WARNING: passwords should not be stored in plaintext in this file.
* If you want to mitigate this, please install ep_hash_auth and
* follow the section "secure your installation" in README.md
*/
"users": {
"admin": {
// 1) "password" can be replaced with "hash" if you install ep_hash_auth
// 2) please note that if password is null, the user will not be created
"password": "admin",
"is_admin": true
}
},
/*
* Restrict socket.io transport methods
*/
"socketTransportProtocols" : ["websocket", "polling"],
"socketIo": {
/*
* Maximum permitted client message size (in bytes). This controls the
* maximum single-message size for socket.io and directly affects large
* paste operations. All messages from clients that are larger than this
* will be rejected. Large values make it possible to paste large amounts
* of text, and plugins may require a larger value to work properly, but
* increasing the value increases susceptibility to denial of service
* attacks (malicious clients can exhaust memory).
*
* 1MB accommodates large pastes while still preventing abuse.
*/
"maxHttpBufferSize": 1000000
},
/*
* Allow Load Testing tools to hit the Etherpad Instance.
*
* WARNING: this will disable security on the instance.
*/
"loadTest": false,
/**
* Disable dump of objects preventing a clean exit
*/
"dumpOnUncleanExit": false,
/*
* Disable indentation on new line when previous line ends with some special
* chars (':', '[', '(', '{')
*/
/*
"indentationOnNewLine": false,
*/
/*
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
* limited.
*
* The default is to allow at most 10 requests per IP in a 90 seconds window.
* After that the import/export request is rejected.
*
* See https://github.com/nfriedly/express-rate-limit for more options
*/
"importExportRateLimiting": {
// duration of the rate limit window (milliseconds)
"windowMs": 90000,
// maximum number of requests per IP to allow during the rate limit window
"max": 10
},
/*
* From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported
* file is always bounded.
*
* File size is specified in bytes. Default is 50 MB.
*/
"importMaxFileSize": 52428800, // 50 * 1024 * 1024
/*
The authentication method used by the server.
The default value is sso
If you want to use the old authentication system, change this to apikey
*/
"authenticationMethod": "${AUTHENTICATION_METHOD:sso}",
/**
* Allow setting dark mode for the enduser. This is so if the user has preferred dark mode in their browser, Etherpad will respect that.
* Of course this overrides all the skin variants and the skinName set by the administrator.
**/
"enableDarkMode": "${ENABLE_DARK_MODE:true}",
/**
* Enable creator-owned Pad-wide Settings and new-pad default seeding from My View.
* Disabled by default to preserve the legacy single-settings behavior.
**/
"enablePadWideSettings": "${ENABLE_PAD_WIDE_SETTINGS:false}",
/*
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
*
* The default is to allow at most 10 changes per IP in a 1 second window.
* After that the change is rejected.
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
"commitRateLimiting": {
// duration of the rate limit window (seconds)
"duration": 1,
// maximum number of changes per IP to allow during the rate limit window
"points": 10
},
/*
* Toolbar buttons configuration.
*
* Uncomment to customize.
*/
/*
"toolbar": {
"left": [
["bold", "italic", "underline", "strikethrough"],
["orderedlist", "unorderedlist", "indent", "outdent"],
["undo", "redo"],
["clearauthorship"]
],
"right": [
["importexport", "timeslider", "savedrevision"],
["settings", "embed", "home"],
["showusers"]
],
"timeslider": [
["timeslider_export", "timeslider_returnToPad"]
]
},
*/
/*
* Expose Etherpad version in the web interface and in the Server http header.
*
* Do not enable on production machines.
*/
"exposeVersion": false,
/*
* The log level we are using.
*
* Valid values: DEBUG, INFO, WARN, ERROR
*/
"loglevel": "INFO",
/*
* The log layout type to use.
*
* Valid values: basic, colored
*/
"logLayoutType": "colored",
/* Override any strings found in locale directories */
"customLocaleStrings": {},
/* Disable Admin UI tests */
"enableAdminUITests": false,
/*
* Enable/Disable case-insensitive pad names.
*/
"lowerCasePadIds": false,
"sso": {
"issuer": "${SSO_ISSUER:http://localhost:9001}",
"clients": [
{
"client_id": "${ADMIN_CLIENT:admin_client}",
"client_secret": "${ADMIN_SECRET:admin}",
"grant_types": ["authorization_code"],
"response_types": ["code"],
"redirect_uris": ["${ADMIN_REDIRECT:http://localhost:9001/admin/}"]
},
{
"client_id": "${USER_CLIENT:user_client}",
"client_secret": "${USER_SECRET:user}",
"grant_types": ["authorization_code"],
"response_types": ["code"],
"redirect_uris": ["${USER_REDIRECT:http://localhost:9001/}"]
}
]
}
/* Set the time to live for the tokens
This is the time of seconds a user is logged into Etherpad
"ttl": {
"AccessToken": 3600,
"AuthorizationCode": 600,
"ClientCredentials": 3600,
"IdToken": 3600,
"RefreshToken": 86400
}
*/
}
+134
View File
@@ -0,0 +1,134 @@
{
"title": "joeac's docs",
"showRecentPads": true,
"favicon": null,
"skinName": "colibris",
"skinVariants": "super-light-toolbar super-light-editor light-background",
"ip": "0.0.0.0",
"port": ${ETHERPAD_PORT},
"showSettingsInAdminPage": true,
"enableMetrics": "$${ENABLE_METRICS:true}",
"cleanup": {
"enabled": false,
"keepRevisions": 5
},
"dbType": "sqlite",
"dbSettings": {
"filename": "var/etherpad.sq3"
},
"defaultPadText" : "",
"padOptions": {
"noColors": false,
"showControls": true,
"showChat": true,
"showLineNumbers": true,
"useMonospaceFont": false,
"userName": null,
"userColor": null,
"rtl": false,
"alwaysShowChat": false,
"chatAndUsers": false,
"lang": null
},
"padShortcutEnabled" : {
"altF9": true,
"altC": true,
"cmdShift2": true,
"delete": true,
"return": true,
"esc": true,
"cmdS": true,
"tab": true,
"cmdZ": true,
"cmdY": true,
"cmdI": true,
"cmdB": true,
"cmdU": true,
"cmd5": true,
"cmdShiftL": true,
"cmdShiftN": true,
"cmdShift1": true,
"cmdShiftC": true,
"cmdH": true,
"ctrlHome": true,
"pageUp": true,
"pageDown": true
},
"updateServer": "https://etherpad.org/ep_infos",
"suppressErrorsInPadText": false,
"requireSession": false,
"editOnly": false,
"minify": true,
"maxAge": 21600,
"soffice": null,
"docxExport": true,
"allowUnknownFileEnds": true,
"requireAuthentication": false,
"requireAuthorization": false,
"trustProxy": false,
"cookie": {
"keyRotationInterval": 86400000,
"sameSite": "Lax",
"sessionLifetime": 864000000,
"sessionRefreshInterval": 86400000,
"sessionCleanup": true
},
"disableIPlogging": false,
"automaticReconnectionTimeout": 0,
"scrollWhenFocusLineIsOutOfViewport": {
"percentage": {
"editionAboveViewport": 0,
"editionBelowViewport": 0
},
"duration": 0,
"scrollWhenCaretIsInTheLastLineOfViewport": false,
"percentageToScrollWhenUserPressesArrowUp": 0
},
"users": {
"admin": {
"password": "admin",
"is_admin": true
}
},
"socketTransportProtocols" : ["websocket", "polling"],
"socketIo": {
"maxHttpBufferSize": 1000000
},
"loadTest": false,
"dumpOnUncleanExit": false,
"importExportRateLimiting": {
"windowMs": 90000,
"max": 10
},
"importMaxFileSize": 52428800, // 50 * 1024 * 1024
"authenticationMethod": "$${AUTHENTICATION_METHOD:sso}",
"enableDarkMode": "$${ENABLE_DARK_MODE:true}",
"enablePadWideSettings": "$${ENABLE_PAD_WIDE_SETTINGS:false}",
"commitRateLimiting": {
"duration": 1,
"points": 10
},
"logLayoutType": "colored",
"customLocaleStrings": {},
"enableAdminUITests": false,
"lowerCasePadIds": false,
"sso": {
"issuer": "$${SSO_ISSUER:http://localhost:${ETHERPAD_PORT}}",
"clients": [
{
"client_id": "$${ADMIN_CLIENT:admin_client}",
"client_secret": "$${ADMIN_SECRET:admin}",
"grant_types": ["authorization_code"],
"response_types": ["code"],
"redirect_uris": ["$${ADMIN_REDIRECT:http://localhost:${ETHERPAD_PORT}/admin/}"]
},
{
"client_id": "$${USER_CLIENT:user_client}",
"client_secret": "$${USER_SECRET:user}",
"grant_types": ["authorization_code"],
"response_types": ["code"],
"redirect_uris": ["$${USER_REDIRECT:http://localhost:${ETHERPAD_PORT}/}"]
}
]
}
}
-14
View File
@@ -1,13 +1,6 @@
# A prefix for remote container names
# If you're running on ARM 32-bit CPU architecture, use 'armv7/', otherwise keep blank
CONTAINER_PREFIX=
# The hostname to use for the local SMTP server
LOCAL_SMTP_HOST=smtp
# The port to use for the local SMTP server
LOCAL_SMTP_PORT=2500
# The username for authenticating to the local SMTP server
LOCAL_SMTP_USER=smtp
@@ -57,10 +50,3 @@ ETHERPAD_NODE_ENV=production
# The admin password for the Etherpad instance
ETHERPAD_ADMIN_PASSWORD=
# The ports on which to run apps
# See also: LOCAL_SMTP_PORT
ETHERPAD_PORT=9001
HTTP_PORT=8080
GEMINI_PORT=1965
VAULTWARDEN_PORT=9000
+3 -5
View File
@@ -1,13 +1,12 @@
FROM git.joeac.net/joeac/armv7/agate:3.3.20-alpine3.23 AS agate
FROM git.joeac.net/joeac/armv7/comitium:1.8.2-alpine3.23 AS comitium
FROM git.joeac.net/joeac/armv7/crond:1.37.0-r30-alpine3.23 AS final
FROM git.joeac.net/joeac/${IMAGE_PREFIX}agate:3.3.20-alpine3.23 AS agate
FROM git.joeac.net/joeac/${IMAGE_PREFIX}comitium:1.8.2-alpine3.23 AS comitium
FROM git.joeac.net/joeac/${IMAGE_PREFIX}crond:1.37.0-r30-alpine3.23 AS final
RUN apk --no-cache add gcc # dependency for agate
COPY --from=agate /root/.cargo/bin/agate /usr/local/bin/agate
COPY --from=comitium /usr/local/bin/comitium /usr/local/bin/comitium
RUN mkdir -p /var/app/content
WORKDIR /var/app
COPY gemini/.certificates .certificates
RUN crontab -l > crontab.tmp \
&& echo "0 */6 * * * /usr/local/bin/comitium refresh --data /var/app/comitium-data" >> crontab.tmp \
&& crontab crontab.tmp \
@@ -15,7 +14,6 @@ RUN crontab -l > crontab.tmp \
COPY gemini/run.sh /var/app/
COPY gemini/content /var/app/content
COPY gemini/comitium-data /var/app/comitium-data
COPY common /var/common
CMD ./run.sh
+22
View File
@@ -0,0 +1,22 @@
GEMINI_COMITIUM_DATA_FILES := $(addprefix $(GEMINI_COMITIUM_DATA_DIR)/,$(notdir $(wildcard comitium-data/*)))
.PHONY: install
install: $(GEMINI_CERTIFICATES_DIR) $(GEMINI_COMITIUM_DATA_FILES)
.PHONY: reinstall
reinstall: $(GEMINI_CERTIFICATES_DIR) $(GEMINI_COMITIUM_DATA_FILES)
$(GEMINI_CERTIFICATES_DIR):
sudo mkdir -p $(GEMINI_CERTIFICATES_DIR)
$(let gemini_user,$(shell whoami),sudo chown -R $(gemini_user):$(gemini_user) $(GEMINI_CERTIFICATES_DIR))
$(GEMINI_COMITIUM_DATA_DIR)/%: comitium-data/% $(GEMINI_COMITIUM_DATA_DIR)
sudo cp $< $@
$(let gemini_user,$(shell whoami),sudo chown $(gemini_user):$(gemini_user) $@)
$(GEMINI_COMITIUM_DATA_DIR):
sudo mkdir -p $(GEMINI_COMITIUM_DATA_DIR)
.PHONY: uninstall
uninstall:
sudo rm -rf $(GEMINI_COMITIUM_DATA_DIR)
+5 -5
View File
@@ -40,12 +40,12 @@ default_layout_file = $(if \
layout_file = $(if $(layout),share/layouts/$(layout).layout.upp$(ext),$(default_layout_file))
define out_rule =
out/%.$(ext): src/%$(if $(layout),.$(layout)).upp$(ext) $(layout_file) $(component_files)
out/%.$(ext): src/%$(if $(layout),.$(layout)).upp$(ext) $(layout_file) $(component_files) bin/pp
./bin/mkws https://joeac.net "$$$$(realpath $$<)"
endef
define blog_rss_feed_rule =
out/$(blog)/rss.xml: src/$(blog)/rss.uppxml $(blog_src) $(wildcard bin/*)
out/$(blog)/rss.xml: src/$(blog)/rss.uppxml $(blog_src) $(wildcard bin/*) bin/pp
./bin/mkws https://joeac.net "$$(realpath src/$(blog)/rss.uppxml)"
endef
@@ -64,7 +64,7 @@ $(blog_post_target): $(blog_post_src) $(let ext,html,$(layout_file) $(component_
endef
define blog_index_rule =
out/$(blog).html: src/$(blog).upphtml $(blog_src) $(wildcard bin/*)
out/$(blog).html: src/$(blog).upphtml $(blog_src) $(wildcard bin/*) bin/pp
./bin/mkws https://joeac.net "$$(realpath src/$(blog).upphtml)"
endef
@@ -106,10 +106,10 @@ share/man/man1/pp.1:
cp pp/pp.1 share/man/man1/pp.1
.PHONY: out
out: bin/pp $(out_deps)
out: $(out_deps)
bin/pp:
cd pp && $(MAKE) && chmod +x pp && cp pp ../bin/pp
$(MAKE) --directory=pp && chmod +x pp/pp && cp pp/pp bin/pp
$(foreach ext,$(sort $(out_deps_ext)),\
$(eval $(out_rule)))
+113
View File
@@ -0,0 +1,113 @@
#!/bin/sh
get_var_value()
{
expr "$(env | grep "^${1}=")" : "${1}=\(.*\)\$"
}
if [ -z "$(which podman 2>/dev/null)" ]
then
sudo apk add podman
fi
if [ -z "$(which podman-compose 2>/dev/null)" ]
then
sudo apk add podman-compose
fi
if [ -z "$(which yq 2>/dev/null)" ]
then
YQ_PLATFORM="linux_$(expr "$(arch)" : "armv7" && echo arm || expr "$(arch)" : "x86_64" && echo amd64 || arch)"
sudo wget -O /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_${YQ_PLATFORM}
sudo chmod +x /usr/local/bin/yq
fi
if [ -z "$(which envsubst 2>/dev/null)" ]
then
if [ "$(arch)" = "x86_64" ] || [ "$(arch)" = "arm64" ]
then
ENVSUBST_VERSION=1.4.3
sudo wget -O /usr/local/bin/envsubst https://github.com/a8m/envsubst/releases/download/v${ENVSUBST_VERSION}/envsubst-Linux-$(arch)
sudo chmod +x /usr/local/bin/envsubst
else
sudo apk add go
go install github.com/a8m/envsubst/cmd/envsubst@latest
fi
fi
if [ -z "$(which bash 2>/dev/null)" ]
then
sudo apk add bash
fi
if [ -z "$(grep "\bjoeac.net\b" /etc/group)" ]
then
sudo adduser -D -h /home/joeac.net joeac.net
sudo adduser joeac.net joeac.net
fi
if ! ( expr "$(groups joeac.net)" wheel )
then
sudo adduser joeac.net wheel
fi
su -l joeac.net
if ! [ $(whoami) = "joeac.net" ]
then
echo "Could not log in to user: joeac.net. Log in manually and re-run this script as joeac.net."
exit 1
fi
if ! [ -d /home/joeac.net/joeac.net/.git ]
then
git clone https://git.joeac.net/joeac/joeac.net.git /home/joeac.net/joeac.net
fi
sudo chown joeac.net:joeac.net /home/joeac.net/joeac.net
sudo chmod 770 /home/joeac.netjoeac.net
if ! [ -f DIGITALOCEAN_TOKEN ]
then
if [ -z "${DIGITALOCEAN_TOKEN}" ]
then
read -sp "DIGITALOCEAN_TOKEN: " DIGITALOCEAN_TOKEN
fi
echo ${DIGITALOCEAN_TOKEN} > /home/joeac.net/joeac.net/DIGITALOCEAN_TOKEN
fi
if ! ( podman secret exists remote_smtp_password )
then
if [ -z "${REMOTE_SMTP_PASSWORD}" ]
then
read -sp "REMOTE_SMTP_PASSWORD: " REMOTE_SMTP_PASSWORD
fi
echo "${REMOTE_SMTP_PASSWORD}" | podman secret create remote_smtp_password -
unset REMOTE_SMTP_PASSWORD
fi
while read line
do
if expr "${line}" : "[[:alnum:]_]\+=" 1>/dev/null
then
var_name="$(expr "${line}" : "\([[:alnum:]_]\+\)=")"
if [ -n "$(grep "^${var_name}=" /home/joeac.net/joeac.net/.env)" ]
then
continue
fi
if [ -z "$(get_var_value ${var_name})" ]
then
default_value="$(expr "${line}" : "[[:alnum:]_]\+=\(.*\)\$")"
read -sp "${var_name}: " ${var_name}
if [ -z "$(get_var_value ${var_name})" ]
then
eval ${var_name}="${default_value}"
fi
fi
echo "${var_name}=$(get_var_value ${var_name})" >> /home/joeac.net/joeac.net/.env
fi
done </home/joeac.net/joeac.net/example.env
make --directory=/home/joeac.net/joeac.net
make --directory=/home/joeac.net/joeac.net install
+6 -6
View File
@@ -1,14 +1,14 @@
.PHONY: install
install: /var/ln.joeac.net/public
install: $(PUBLIC_ROOT_DIR_ln)
.PHONY: reinstall
reinstall: install
/var/ln.joeac.net/public: public
sudo rm -rf /var/ln.joeac.net/public
sudo mkdir -p /var/ln.joeac.net/
sudo cp -r public/ /var/ln.joeac.net/public/
$(PUBLIC_ROOT_DIR_ln): public
sudo rm -rf $(PUBLIC_ROOT_DIR_ln)
sudo mkdir -p $(PUBLIC_ROOT_DIR_ln)
sudo cp -r public/ $(PUBLIC_ROOT_DIR_ln)/
.PHONY: uninstall
uninstall:
sudo rm -rf /var/ln.joeac.net
sudo rm -rf $(PUBLIC_ROOT_DIR_ln)
+28
View File
@@ -0,0 +1,28 @@
REGISTRY_DOMAIN := git.joeac.net
REGISTRY_USER := joeac
container_image_name = $(shell $(COMPOSE_CMD) config | yq ".services.$(module).image")
is_containerised = $(filter $(COMPOSE_SERVICES),$(module))
is_own_image = $(filter git.joeac.net/%,$(container_image_name))
has_dockerfile = $(shell test -f $(module).Dockerfile)
define build_module_rule =
.PHONY: build_$(module)
build_$(module): $(if $(is_containerised),make_$(module) $(if $(has_dockerfile),$(module).Dockerfile))
$(if $(is_containerised),\
$(COMPOSE_CMD) build $(module))
endef
define push_module_rule =
.PHONY: push_$(module)
push_$(module): $(if $(is_containerised),login_registry)
$(if $(is_containerised),$(if $(is_own_image),\
podman push $(container_image_name)))
endef
$(foreach module,$(MODULES),$(eval $(call build_module_rule)))
$(foreach module,$(MODULES),$(eval $(call push_module_rule)))
.PHONY: login_registry
login_registry:
podman login $(REGISTRY_DOMAIN)
+2 -6
View File
@@ -8,10 +8,6 @@ reinstall_crontab: /etc/periodic/daily/joeac.net
uninstall_crontab:
sudo rm -f /etc/periodic/daily/joeac.net
/etc/periodic/daily/joeac.net:
echo "#!/bin/sh" > crontab.tmp
echo "git -C /usr/local/lib/joeac.net pull && $(MAKE) --directory /usr/local/lib/joeac.net && rc-service joeac.net restart" \
>> crontab.tmp
sudo mv crontab.tmp /etc/periodic/daily/joeac.net
/etc/periodic/daily/joeac.net: update.sh
sudo cp update.sh /etc/periodic/daily/joeac.net
sudo chmod +x /etc/periodic/daily/joeac.net
+55
View File
@@ -0,0 +1,55 @@
installed_dyndns_crontabs = $(wildcard /etc/periodic/daily/dyndns-*.joeac.net)
installed_dyndns_subdomains = $(installed_dyndns_crontabs:/etc/periodic/daily/dyndns-%.joeac.net=%)
dyndns_subdomains_to_remove = $(filter-out $(SUBDOMAINS),$(installed_dyndns_subdomains))
dyndns_crontabs_to_remove = $(dyndns_subdomains_to_remove:%=/etc/periodic/daily/dyndns-%.joeac.net)
define install_dyndns_module_rule =
.PHONY: install_dyndns_$(module)
install_dyndns_module_$(module): $(if $(SUBDOMAIN_$(module)),/etc/periodic/daily/dyndns-$(SUBDOMAIN_$(module)).joeac.net)
endef
define reinstall_dyndns_module_rule =
.PHONY: reinstall_dyndns_$(module)
reinstall_dyndns_$(module): $(if $(SUBDOMAIN_$(module)),/etc/periodic/daily/dyndns-$(SUBDOMAIN_$(module)).joeac.net reinstall_dyndns)
endef
define uninstall_dyndns_module_rule =
.PHONY: uninstall_dyndns_$(module)
uninstall_dyndns_$(module):
$(if $(SUBDOMAIN_$(module)), \
sudo rm -f /etc/periodic/daily/dyndns-$(SUBDOMAIN_$(module)).joeac.net
)
endef
$(foreach module,$(ALL_MODULES), $(eval $(install_dyndns_module_rule)))
$(foreach module,$(ALL_MODULES), $(eval $(reinstall_dyndns_module_rule)))
$(foreach module,$(ALL_MODULES), $(eval $(uninstall_dyndns_module_rule)))
.PHONY: remove_/etc/periodic/daily/dyndns-%.joeac.net
remove_/etc/periodic/daily/dyndns-%.joeac.net:
rm -f $(@:remove_%=%)
/etc/periodic/daily/dyndns-%joeac.net: ~/digitalocean_dyndns/dyndns.sh ~/.config/dyndns/DIGITALOCEAN_TOKEN
echo "#!/bin/sh" > crontab.tmp
echo " $(shell realpath ~)/digitalocean_dyndns/dyndns.sh 4 $(*F)joeac.net" >> crontab.tmp
echo "CONN_DEVICE_NAME=eth0 $(shell realpath ~)/digitalocean_dyndns/dyndns.sh 6 $(*F)joeac.net" >> crontab.tmp
sudo mv crontab.tmp $@
sudo chmod +x $@
.PHONY: reinstall_dyndns
reinstall_dyndns: $(addprefix remove_,$(dyndns_crontabs_to_remove))
.PHONY: uninstall_dyndns
uninstall_dyndns: $(foreach module,$(ALL_MODULES),$(uninstall_dyndns_$(module)))
sudo rm -rf \
~/digitalocean_dyndns/ \
~/.config/dyndns \
~/.cache/dyndns
~/digitalocean_dyndns/%:
git clone https://git.joeac.net/joeac/digitalocean_dyndns.git ~/digitalocean_dyndns
~/.config/dyndns/DIGITALOCEAN_TOKEN: DIGITALOCEAN_TOKEN
mkdir -p ~/.config/dyndns/
rm -f ~/.config/dyndns/DIGITALOCEAN_TOKEN
cp DIGITALOCEAN_TOKEN ~/.config/dyndns/DIGITALOCEAN_TOKEN
+75
View File
@@ -0,0 +1,75 @@
installed_nginx_sites = $(wildcard /etc/nginx/http.d/*.joeac.net.conf)
installed_nginx_subdomains = $(installed_nginx_sites:/etc/nginx/http.d/%.joeac.net.conf=%)
nginx_subdomains_to_remove = $(filter-out $(NGINX_SUBDOMAINS),$(installed_nginx_subdomains))
nginx_sites_to_remove = $(nginx_subdomains_to_remove:%=/etc/nginx/http.d/%.joeac.net.conf)
nginx_module_config_template = \
$(if $(PORT_$(module)),nginx/http.d/reverse_proxy.conf.template) \
$(if $(PUBLIC_ROOT_DIR_$(module)),nginx/http.d/static.conf.template)
nginx_module_config_template_args = \
CERTNAME=$(SUBDOMAIN_$(module)).joeac.net \
DOMAIN=$(if $(filter-out @,$(SUBDOMAIN_$(module))),$(SUBDOMAIN_$(module)).)joeac.net \
$(if $(PORT_$(module)),PORT=$(PORT_$(module)) HOST=$(HOST_$(module))) \
$(if $(PUBLIC_ROOT_DIR_$(module)),ROOT="$(PUBLIC_ROOT_DIR_$(module))")
define install_nginx_module_rule =
.PHONY: install_nginx_$(module)
install_nginx_$(module): $(if $(SUBDOMAIN_$(module)),/etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf)
endef
define reinstall_nginx_module_rule =
.PHONY: reinstall_nginx_$(module)
reinstall_nginx_$(module): $(if $(SUBDOMAIN_$(module)),/etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf)
endef
define uninstall_nginx_module_rule =
.PHONY: uninstall_nginx_$(module)
uninstall_nginx_$(module):
$(if $(SUBDOMAIN_$(module)),sudo rm -f /etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf)
endef
define remove_nginx_site_rule =
remove_$(site):
sudo rm -f $(site)
endef
define nginx_module_config_rule =
/etc/nginx/http.d/$(SUBDOMAIN_$(module)).joeac.net.conf: $(nginx_module_config_template) /etc/nginx/http.d /etc/nginx/nginx.conf
$(nginx_module_config_template_args) envsubst -i $$< -o $$(notdir $$@).tmp
sudo cp $$(notdir $$@).tmp $$@
sudo rc-service nginx restart
endef
$(foreach module,$(ALL_MODULES),$(eval $(install_nginx_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(reinstall_nginx_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(uninstall_nginx_module_rule)))
$(foreach site,$(nginx_sites_to_remove),$(eval $(remove_nginx_site_rule)))
$(foreach module,$(ALL_NGINX_MODULES),$(eval $(nginx_module_config_rule)))
/etc/nginx/http.d:
sudo mkdir -p /etc/nginx/http.d
.PHONY: remove_/etc/nginx/http.d/%.joeac.net.conf
remove_/etc/nginx/http.d/%.joeac.net.conf:
rm -f $(@:remove_%=%)
.PHONY: install_nginx
install_nginx: /etc/nginx/nginx.conf $(addprefix install_nginx_,$(NGINX_MODULES))
.PHONY: reinstall_nginx
reinstall_nginx: /etc/nginx/nginx.conf $(add_prefix reinstall_nginx_,$(NGINX_MODULES)) $(addprefix remove_,$(nginx_sites_to_remove))
.PHONY: uninstall_nginx
uninstall_nginx: $(foreach module,$(NGINX_MODULES),uninstall_nginx_$(module))
ifeq ($(shell test -d /etc/nginx/nginx.joeac.net-backup && echo 1 || echo 0),0)
$(warn No nginx backup config detected: doing nothing)
else
sudo mv /etc/nginx/nginx.joeac.net-backup /etc/nginx/nginx.conf
sudo rc-service nginx restart
endif
/etc/nginx/nginx.conf: nginx/nginx.conf /etc/nginx/nginx.joeac.net-backup
sudo cp $< $@
sudo rc-service nginx restart
/etc/nginx/nginx.joeac.net-backup:
sudo mv /etc/nginx/nginx.conf /etc/nginx/nginx.joeac.net-backup
+74
View File
@@ -0,0 +1,74 @@
is_openrc_module = $(filter $(COMPOSE_SERVICES),$(module))
openrc_module_target = $(if $(is_openrc_module),~/.config/rc/init.d/joeac.net.$(module))
define install_openrc_module_rule =
.PHONY: install_openrc_$(module)
install_openrc_$(module): $(if $(is_openrc_module),$(openrc_module_target) openrc_add_$(module) openrc_start_$(module))
~/.config/rc/init.d/joeac.net.$(module): ~/.config/rc/init.d/joeac.net ~/.config/rc/runlevels/default
rm -f ~/.config/rc/init.d/joeac.net.$(module)
ln -s $(shell realpath ~)/.config/rc/init.d/joeac.net ~/.config/rc/init.d/joeac.net.$(module)
endef
define reinstall_openrc_module_rule =
.PHONY: reinstall_openrc_$(module)
reinstall_openrc_$(module): $(if $(is_openrc_module),$(openrc_module_target) openrc_restart_$(module))
endef
define uninstall_openrc_module_rule =
.PHONY: uninstall_openrc_$(module)
uninstall_openrc_$(module):
$(if $(is_openrc_module),\
rc-service -U joeac.net.$(module) stop; \
rc-update -U del joeac.net.$(module) default; \
rm -f ~/.config/rc/init.d/joeac.net.$(module); \
)
endef
define openrc_add_rule =
.PHONY: openrc_add_$(module)
openrc_add_$(module):
rc-update -U add joeac.net.$(module) default
endef
define openrc_start_rule =
.PHONY: openrc_start_$(module)
openrc_start_$(module):
rc-service -U joeac.net.$(module) start
endef
define openrc_restart_rule =
.PHONY: openrc_restart_$(module)
openrc_restart_$(module):
rc-service -U joeac.net.$(module) restart
endef
$(foreach module,$(ALL_MODULES),$(eval $(install_openrc_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(reinstall_openrc_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(uninstall_openrc_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(openrc_add_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(openrc_start_rule)))
$(foreach module,$(MODULES),$(eval $(openrc_restart_rule)))
~/.config/rc/init.d/joeac.net: openrc/init.d/joeac.net.template ~/.config/rc/init.d ~/.config/rc/runlevels/default /etc/init.d/user.$(USER) /etc/conf.d/user.$(USER)
rm -f ~/.config/rc/init.d/joeac.net; \
mkdir -p ~/.config/rc/init.d; \
COMPOSECMD="$(COMPOSE_CMD)" envsubst -i openrc/init.d/joeac.net.template -o ~/.config/rc/init.d/joeac.net
chmod +x ~/.config/rc/init.d/joeac.net
~/.config/rc/init.d:
mkdir -p ~/.config/rc/init.d
~/.config/rc/runlevels/default:
mkdir -p ~/.config/rc/runlevels/default
/etc/init.d/user.$(USER):
sudo ln -s /etc/init.d/user /etc/init.d/user.$(USER)
/etc/conf.d/user.$(USER): openrc/conf.d/user.template
USER=$(USER) envsubst -i openrc/conf.d/user.template -o openrc/conf.d/user.$(USER)
sudo mv openrc/conf.d/user.$(USER) /etc/conf.d/user.$(USER)
sudo rc-update add user.$(USER) default
.PHONY: uninstall_joeac.net_service
rm ~/.config/rc/joeac.net
+89
View File
@@ -0,0 +1,89 @@
installed_tls_crontabs = $(wildcard /etc/periodic/daily/tls-*.joeac.net)
installed_tls_crontab_subdomains = $(installed_tls_crontabs:/etc/periodic/daily/tls-%.joeac.net=%)
tls_crontab_subdomains_to_remove = $(filter-out $(SUBDOMAINS),$(installed_tls_crontab_subdomains))
tls_crontabs_to_remove = $(tls_crontab_subdomains_to_remove:%=/etc/periodic/daily/tls-%.joeac.net)
installed_tls_certs = $(wildcard /etc/letsencrypt/live/*.joeac.net)
installed_tls_cert_subdomains = $(installed_tls_certs:/etc/letsencrypt/live/%.joeac.net=%)
tls_cert_subdomains_to_delete = $(filter-out $(SUBDOMAINS),$(installed_tls_cert_subdomains))
tls_crontab = /etc/periodic/daily/tls-$(subdomain).joeac.net
tls_cert = /etc/letsencrypt/live/$(subdomain).joeac.net/fullchain.pem
define install_tls_subdomain_rule =
.PHONY: install_tls_$(subdomain)
install_tls_$(subdomain): $(tls_crontab) $(tls_cert)
endef
define reinstall_tls_subdomain_rule =
.PHONY: reinstall_tls_$(subdomain)
reinstall_tls_$(subdomain): $(tls_crontab) renew_$(tls_cert)
endef
define uninstall_tls_subdomain_rule =
.PHONY: uninstall_tls_$(subdomain)
uninstall_tls_$(subdomain): delete_cert_$(subdomain).joeac.net
$(if $(shell [ -f $(tls_crontab) ] && echo 1),\
sudo rm -f $(tls_crontab)
)
endef
define obtain_or_renew_cert_cmd =
sudo certbot certonly \
--nginx \
--cert-name $(subdomain).joeac.net \
--domain $(subst @.,,$(subdomain).)joeac.net \
--non-interactive \
&& sudo chown -R joeac.net:joeac.net $(dir $(tls_cert))
endef
is_cert_expired = $(shell sudo certbot certificates --cert-name $(subdomain).joeac.net \
| grep "Expiry Date" | grep -E "(EXPIRED|REVOKED)")
define cert_rule =
$(tls_cert):
$(obtain_or_renew_cert_cmd)
.PHONY: renew_$(tls_cert)
renew_$(tls_cert):
$$(if $$(is_cert_expired),$(obtain_or_renew_cert_cmd))
endef
$(foreach subdomain,$(ALL_SUBDOMAINS), $(eval $(install_tls_subdomain_rule)))
$(foreach subdomain,$(ALL_SUBDOMAINS), $(eval $(reinstall_tls_subdomain_rule)))
$(foreach subdomain,$(ALL_SUBDOMAINS), $(eval $(uninstall_tls_subdomain_rule)))
$(foreach subdomain,$(ALL_SUBDOMAINS), $(eval $(cert_rule)))
.PHONY: remove_/etc/periodic/daily/tls-%.joeac.net
remove_/etc/periodic/daily/tls-%.joeac.net:
rm -f $(@:remove_%=%)
.PHONY: delete_cert_%
delete_cert_%:
sudo certbot delete --cert-name $(@:delete_cert_%=%).joeac.net --non-interactive
/etc/periodic/daily/tls-%joeac.net:
echo "#!/bin/sh" > crontab.tmp
echo "$(obtain_or_renew_cert_cmd)" >> crontab.tmp
sudo mv crontab.tmp $@
sudo chmod +x $@
certs_to_renew = $(foreach subdomain,$(filter $(SUBDOMAINS),$(installed_tls_cert_subdomains)),$(tls_cert))
tls_crontabs_to_install = $(foreach subdomain,$(SUBDOMAINS),$(tls_crontab))
tls_certs_to_install = $(foreach subdomain,$(SUBDOMAINS),$(tls_cert))
.PHONY: install_tls
install_tls: $(tls_crontabs_to_install) $(tls_certs_to_install) $(addprefix remove_,$(tls_crontabs_to_remove)) $(addprefix delete_cert_,$(tls_cert_subdomains_to_delete)) $(addprefix renew_,$(certs_to_renew))
.PHONY: reinstall_tls
reinstall_tls: install_tls
.PHONY: uninstall_tls
uninstall_tls: $(foreach subdomain,$(ALL_SUBDOMAINS),$(uninstall_tls_$(subdomain)))
.PHONY: install_certbot
install_certbot: /usr/bin/certbot /usr/bin/python /usr/lib/$(shell ls /usr/bin | grep python[0-9]\\.)/certbot_nginx
/usr/bin/certbot /usr/bin/python:
sudo apk add certbot
/usr/lib/python%/certbot_nginx:
sudo apk add certbot-nginx
+91
View File
@@ -0,0 +1,91 @@
capitalise = $(shell echo "$(1)" | tr '[:lower:]' '[:upper:]')
USER := $(shell whoami)
HOSTNAME := $(shell cat /etc/hostname)
HOSTNAMES := pi-broughton blade-canongate
IP_ADDR_pi-broughton := 192.168.178.54
IP_ADDR_blade-canongate := 192.168.178.75
MASTER_NODE := blade-canongate
IS_MASTER_NODE := $(filter $(MASTER_NODE),$(HOSTNAME))
MODULES_pi-broughton := actualbudget http smtp vaultwarden ln
MODULES_blade-canongate := etherpad gemini mox mox_clientsettings mox_autoconfig mox_mta_sts
ALL_NGINX_MODULES := actualbudget http vaultwarden ln etherpad mox mox_clientsettings mox_autoconfig mox_mta_sts
NGINX_MODULES := $(if $(IS_MASTER_NODE),$(ALL_NGINX_MODULES))
MODULES := $(MODULES_$(HOSTNAME))
ALL_MODULES := $(sort $(foreach hostname,$(HOSTNAMES),$(MODULES_$(hostname))))
COMPOSE_SERVICES := $(shell podman-compose config \
| yq ".services | keys" --output-format csv --csv-separator " ")
MAKE_MODULES := $(foreach module,$(MODULES),\
$(shell [ -f $(module)/Makefile ] && echo $(module)))
SUBDOMAIN_actualbudget := budget
SUBDOMAIN_http := @
SUBDOMAIN_vaultwarden := pwd
SUBDOMAIN_etherpad := docs
SUBDOMAIN_ln := ln
SUBDOMAIN_mox := mail
SUBDOMAIN_mox_autoconfig := autoconfig.mail
SUBDOMAIN_mox_clientsettings := clientsettings.mail
SUBDOMAIN_mox_mta_sts := mta-sts.mail
PORT_actualbudget := 5006
PORT_etherpad := 9001
PORT_http := 8080
PORT_gemini := 1965
PORT_mox := 81
PORT_mox_autoconfig := $(PORT_mox)
PORT_mox_clientsettings := $(PORT_mox)
PORT_mox_mta_sts := $(PORT_mox)
PORT_smtp := 2500
PORT_vaultwarden := 9000
PUBLIC_ROOT_DIR_ln := /var/ln.joeac.net/public
export ACTUALBUDGET_DATA_DIR := /var/joeac.net-actualbudget/var
export ETHERPAD_DATA_DIR := /var/etherpad/var
export GEMINI_CERTIFICATES_DIR := /var/joeac.net-gemini/certificates
export GEMINI_COMITIUM_DATA_DIR := /var/joeac.net-gemini/comitium-data
export VAULTWARDEN_DATA_DIR := /var/vaultwarden/data
ALPINE_VERSION := 3.23
ETHERPAD_VERSION := 3.3.2
$(foreach module,$(ALL_MODULES),$(if $(PORT_$(module)),$(eval \
export $(call capitalise,$(module))_PORT := $(PORT_$(module)))))
$(foreach hostname,$(HOSTNAMES),$(foreach module,$(MODULES_$(hostname)),$(eval \
export HOST_$(module) := $(if $(filter $(HOSTNAME),$(hostname)),127.0.0.1,$(IP_ADDR_$(hostname))))))
ALL_SUBDOMAINS := $(foreach module,$(ALL_MODULES),$(SUBDOMAIN_$(module)))
SUBDOMAINS := $(if $(IS_MASTER_NODE),$(ALL_SUBDOMAINS))
NGINX_SUBDOMAINS := $(foreach module,$(NGINX_MODULES),$(SUBDOMAIN_$(module)))
ENV_RULES := $(foreach module,$(MODULES),$(if $(shell test -d $(module) && echo 1),$(module)/.env))
MAKE_RULES := $(foreach module,$(MAKE_MODULES),make_$(module))
BUILD_RULES := $(foreach module,$(filter $(COMPOSE_SERVICES),$(MODULES)),build_$(module))
PUSH_RULES := $(foreach module,$(filter $(COMPOSE_SERVICES),$(MODULES)),push_$(module))
INSTALL_RULES := $(foreach module,$(MODULES),install_$(module))
REINSTALL_RULES := $(foreach module,$(MODULES),reinstall_$(module))
UNINSTALL_RULES := $(foreach module,$(MODULES),uninstall_$(module))
CPU_ARCH := $(if $(shell which arch 2>/dev/null),\
$(shell arch),\
$(shell lscpu | grep ^Architecture: | sed "s/^Architecture:[[:space:]]*\([[:alnum:][:punct:]]\+\).*/\1/"))
IMAGE_PREFIX := $(if $(filter armv7%,$(CPU_ARCH)),armv7/)
COMPOSE_CMD := \
ALPINE_VERSION="$(ALPINE_VERSION)" \
IMAGE_PREFIX="$(IMAGE_PREFIX)" \
GEMINI_CERTIFICATES_DIR="$(GEMINI_CERTIFICATES_DIR)" \
GEMINI_COMITIUM_DATA_DIR="$(GEMINI_COMITIUM_DATA_DIR)" \
ETHERPAD_DATA_DIR="$(ETHERPAD_DATA_DIR)" \
ETHERPAD_VERSION="$(ETHERPAD_VERSION)" \
VAULTWARDEN_DATA_DIR="$(VAULTWARDEN_DATA_DIR)" \
LOCAL_SMTP_PORT=$(PORT_smtp) \
$(foreach module,$(ALL_MODULES),$(call capitalise,$(module))_PORT=$(PORT_$(module))) \
podman-compose
$(foreach module,$(ALL_NGINX_MODULES), \
$(if $(SUBDOMAIN_$(module)),, \
$(error $(module) is declared as an nginx module, but SUBDOMAIN_$(module) is not set)))
$(foreach module,$(ALL_NGINX_MODULES), \
$(if $(PORT_$(module)) $(HOST_$(module)),,$(if $(PUBLIC_ROOT_DIR_$(module)),, \
$(error $(module) is declared as an nginx module, but neither PUBLIC_ROOT_DIR_$(module), nor both PORT_$(module) and HOST_$(module), are set))))
+2
View File
@@ -0,0 +1,2 @@
config/dkim
config/adminpasswd
+6
View File
@@ -0,0 +1,6 @@
# mox
There are three secrets required for mox. One is the admin password, which
should be stored in config/adminpasswd. The other two are DKIM private keys,
which should be stored in the config/dkim directory. The public parts of these
keys should be exposed in DNS TXT records.
+52
View File
@@ -0,0 +1,52 @@
Domains:
mail.joeac.net:
ClientSettingsDomain: clientsettings.mail.joeac.net
LocalpartCatchallSeparator: +
DKIM:
Selectors:
2026a:
Expiration: 72h
PrivateKeyFile: dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem
2026b:
Expiration: 72h
PrivateKeyFile: dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem
Sign:
- 2026a
DMARC:
Localpart: dmarcreports
Account: me
Mailbox: DMARC
MTASTS:
PolicyID: 20260705T153220
Mode: enforce
MaxAge: 24h0m0s
MX:
- mail.joeac.net
TLSRPT:
Localpart: tlsreports
Account: me
Mailbox: TLSRPT
Accounts:
me:
Domain: mail.joeac.net
Destinations:
me@mail.joeac.net: nil
SubjectPass:
Period: 12h0m0s
RejectsMailbox: Rejects
AutomaticJunkFlags:
Enabled: true
JunkMailboxRegexp: ^(junk|spam)
NeutralMailboxRegexp: ^(inbox|neutral|postmaster|dmarc|tlsrpt|rejects)
JunkFilter:
Threshold: 0.950000
Params:
Onegrams: true
MaxPower: 0.010000
TopWords: 10
IgnoreWords: 0.100000
RareWords: 2
NoCustomPassword: true
MonitorDNSBLs:
- zen.spamhaus.org
- bl.spamcop.net
+74
View File
@@ -0,0 +1,74 @@
DataDir: ../data
LogLevel: debug
User: mox
Hostname: mail.joeac.net
AdminPasswordFile: adminpasswd
Listeners:
internal:
IPs:
- 127.0.0.1
- ::1
Hostname: localhost
AccountHTTP:
Enabled: true
Port: 1080
Forwarded: true
AdminHTTP:
Enabled: true
Port: 1080
Forwarded: true
WebmailHTTP:
Enabled: true
Port: 1080
Forwarded: true
WebAPIHTTP:
Enabled: true
Port: 1080
Forwarded: true
MetricsHTTP:
Enabled: true
AutoconfigHTTPS:
Enabled: true
Port: 81
NonTLS: true
MTASTSHTTPS:
Enabled: true
Port: 81
NonTLS: true
WebserverHTTP:
Enabled: true
Port: 81
public:
IPs:
- 192.168.178.75
- fdc9:6aec:7a18:0:2e0:4cff:fe61:9b17
NATIPs:
- 217.155.190.42
- fdc9:6aec:7a18:0:2e0:4cff:fe61:9b17
TLS:
KeyCerts:
-
CertFile: /etc/letsencrypt/live/mail.joeac.net/fullchain.pem
KeyFile: /etc/letsencrypt/live/mail.joeac.net/privkey.pem
-
CertFile: /etc/letsencrypt/live/mta-sts.mail.joeac.net/fullchain.pem
KeyFile: /etc/letsencrypt/live/mta-sts.mail.joeac.net/privkey.pem
-
CertFile: /etc/letsencrypt/live/autoconfig.mail.joeac.net/fullchain.pem
KeyFile: /etc/letsencrypt/live/autoconfig.mail.joeac.net/privkey.pem
-
CertFile: /etc/letsencrypt/live/clientsettings.mail.joeac.net/fullchain.pem
KeyFile: /etc/letsencrypt/live/clientsettings.mail.joeac.net/privkey.pem
SMTP:
Enabled: true
Submissions:
Enabled: true
IMAPS:
Enabled: true
Postmaster:
Account: me
Mailbox: Postmaster
HostTLSRPT:
Account: me
Mailbox: TLSRPT
Localpart: tlsreports
+8
View File
@@ -0,0 +1,8 @@
server:
auto-trust-anchor-file: /var/lib/unbound/root.key
qname-minimisation: yes
interface: 0.0.0.0
access-control: 192.168.0.0/16 allow
remote-control:
control-enable: yes
+63
View File
@@ -0,0 +1,63 @@
.PHONY: install
install: install_unbound install_mox
.PHONY: reinstall
reinstall: install_unbound install_mox
.PHONY: install_unbound
install_unbound: /usr/sbin/unbound /etc/resolv.conf /var/lib/unbound/root.key install_unbound_anchor_crontab /etc/unbound/unbound.conf.d/dnssec.conf
/usr/sbin/unbound:
sudo apk add unbound
sudo rc-update add unbound default
sudo rc-service unbound start
/etc/resolv.conf: resolv.conf /etc/resolv.conf.joeac.net-backup
sudo cp resolv.conf /etc/resolv.conf
/etc/resolv.conf.joeac.net-backup:
sudo mv /etc/resolv.conf /etc/resolv.conf.joeac.net-backup
/var/lib/unbound/root.key:
sudo mkdir -p /var/lib/unbound && sudo unbound-anchor -a /var/lib/unbound/root.key
UNBOUND_ANCHOR_CRONTAB_ENTRY := @reboot unbound-anchor -a /var/lib/unbound/root.key # managed by joeac.net
IS_CRONTAB_UP_TO_DATE := $(sudo grep "$(UNBOUND_ANCHOR_CRONTAB_ENTRY)" /etc/crontabs/root)
.PHONY: install_unbound_anchor_crontab
install_unbound_anchor_crontab:
$(if $(IS_CRONTAB_UP_TO_DATE),,\
sudo crontab -l > crontab.tmp; \
sed -i "s/.*unbound-anchor.*# managed by joeac.net//" crontab.tmp; \
echo "$(UNBOUND_ANCHOR_CRONTAB_ENTRY)" >> crontab.tmp; \
sudo crontab crontab.tmp; \
rm crontab.tmp; \
)
/etc/unbound/unbound.conf.d/dnssec.conf: dnssec.conf
sudo mkdir -p $(dir $@) && sudo cp $< $@
DKIM_PRIVATE_KEY_A := ~/mox/config/dkim/2026a._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem
DKIM_PRIVATE_KEY_B := ~/mox/config/dkim/2026b._domainkey.mail.joeac.net.20260705T163220.rsa2048.privatekey.pkcs8.pem
DKIM_PRIVATE_KEYS := $(DKIM_PRIVATE_KEY_A) $(DKIM_PRIVATE_KEY_B)
MOX_TLS_CERTS := /etc/letsencrypt/live/mail.joeac.net/fullchain.pem /etc/letsencrypt/live/autoconfig.mail.joeac.net/fullchain.pem /etc/letsencrypt/live/clientsettings.mail.joeac.net/fullchain.pem /etc/letsencrypt/live/mta-sts.mail.joeac.net/fullchain.pem
.PHONY: install_mox
install_mox: /usr/local/bin/mox ~/mox/config/mox.conf ~/mox/config/domains.conf ~/mox/config/adminpasswd ~/mox/data $(DKIM_PRIVATE_KEYS) $(MOX_TLS_CERTS)
MOX_PLATFORM := $(if $(filter armv7% arm32%,$(CPU_ARCH)),arm,amd64)
MOX_VERSION := 0.0.15
MOX_GO_VERSION := 1.26.4
MOX_CHECKSUM_amd64_v0.0.15_go1.26.4 := 09OE-1QkNVgmpoRj53mtX9gxoEmY
MOX_CHECKSUM_arm_v0.0.15_go1.26.4 := 038X9nQx6hhai60Fk1BhcP3r6Mew
MOX_CHECKSUM := $(MOX_CHECKSUM_$(MOX_PLATFORM)_v$(MOX_VERSION)_go$(MOX_GO_VERSION))
MOX_URL_BASE := https://beta.gobuilds.org/github.com/mjl-/mox
MOX_URL := $(MOX_URL_BASE)@v$(MOX_VERSION)/linux-$(MOX_PLATFORM)-go$(MOX_GO_VERSION)/$(MOX_CHECKSUM)
/usr/local/bin/mox:
wget -O- $(MOX_URL) | gzip -d > mox
chmod +x mox
sudo mv mox /usr/local/bin/mox
~/mox/config/%: config/%
mkdir -p $(dir $@) && cp $< $@
~/mox/data:
mkdir -p $@
+1
View File
@@ -0,0 +1 @@
nameserver: 127.0.0.1
-28
View File
@@ -1,28 +0,0 @@
.PHONY: install_nginx
install_nginx: $(NGINX_CONFIG_BACKUP) $(NGINX_CONFIG)
.PHONY: reinstall_nginx
reinstall_nginx: $(NGINX_CONFIG_BACKUP) $(NGINX_CONFIG)
.PHONY: uninstall_nginx
uninstall_nginx: uninstall_dyndns
ifeq ($(shell test -d $(NGINX_CONFIG_BACKUP) && echo 1 || echo 0),0)
$(warn No nginx backup config detected: doing nothing)
else
sudo mv $(NGINX_CONFIG_BACKUP) $(NGINX_CONFIG)
$(RESTART_NGINX)
endif
$(NGINX_CONFIG_BACKUP):
sudo mv $(NGINX_CONFIG) $(NGINX_CONFIG_BACKUP)
$(NGINX_CONFIG): $(NGINX_CONFIG_SRC) $(NGINX_CONFIG_BACKUP)
sudo cp $< $@
$(RESTART_NGINX)
/etc/nginx/http.d/%joeac.net.conf: nginx/http.d/%joeac.net.conf /etc/nginx/http.d $(NGINX_CONFIG)
sudo cp $< $@
$(RESTART_NGINX)
/etc/nginx/http.d:
sudo mkdir -p /etc/nginx/http.d
-27
View File
@@ -1,27 +0,0 @@
server {
server_name joeac.net;
location / {
proxy_pass http://127.0.0.1:8080;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/joeac.net-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/joeac.net-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = joeac.net) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server;
listen [::]:80 default_server;
server_name joeac.net;
return 404; # managed by Certbot
}
-26
View File
@@ -1,26 +0,0 @@
server {
server_name docs.joeac.net;
location / {
proxy_pass http://localhost:9001;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/docs.joeac.net/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/docs.joeac.net/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = docs.joeac.net) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name docs.joeac.net;
listen 80;
return 404; # managed by Certbot
}
-25
View File
@@ -1,25 +0,0 @@
server {
server_name ln.joeac.net;
location / {
root /var/ln.joeac.net/public;
try_files $uri $uri.html $uri/index.html =404;
error_page 404 /index.html;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ln.joeac.net/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/ln.joeac.net/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = ln.joeac.net) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name ln.joeac.net;
listen 80;
return 404; # managed by Certbot
}
-23
View File
@@ -1,23 +0,0 @@
server {
server_name pwd.joeac.net;
location / {
proxy_pass http://localhost:9000;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/pwd.joeac.net/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/pwd.joeac.net/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = pwd.joeac.net) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name pwd.joeac.net;
listen 80;
return 404; # managed by Certbot
}
+20
View File
@@ -0,0 +1,20 @@
server {
server_name ${DOMAIN};
location / {
proxy_pass http://${HOST}:${PORT};
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($$host = ${DOMAIN}) {
return 301 https://$$host$$request_uri;
}
server_name ${DOMAIN};
listen 80;
return 404;
}
+22
View File
@@ -0,0 +1,22 @@
server {
server_name ${DOMAIN};
location / {
root ${ROOT};
try_files $$uri $$uri.html $$uri/index.html =404;
error_page 404 /index.html;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/${CERTNAME}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${CERTNAME}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($$host = ${DOMAIN}) {
return 301 https://$$host$$request_uri;
} # managed by Certbot
server_name ln.joeac.net;
listen 80;
return 404;
}
-3
View File
@@ -1,3 +0,0 @@
*
!.gitignore
!user.example
-3
View File
@@ -1,3 +0,0 @@
# copy this file to user.<USER> for the user you want to be running joeac.net via OpenRC
# add in here any user setup, bearing in mind that OpenRC doesn't use your usual login flow
# this may include setting up $HOME and/or $XDG_RUNTIME_DIR
+15
View File
@@ -0,0 +1,15 @@
UID="$$(id -u ${USER})"
if test -z "$${XDG_RUNTIME_DIR}"; then
export XDG_RUNTIME_DIR=/tmp/xdg/"$${UID}"-xdg-runtime-dir
if ! test -d "$${XDG_RUNTIME_DIR}"; then
mkdir -p "$${XDG_RUNTIME_DIR}"
chown -R ${USER}:${USER} "$${XDG_RUNTIME_DIR}"
chmod -R 0700 "$${XDG_RUNTIME_DIR}"
fi
fi
if ! test -f "$${XDG_RUNTIME_DIR}/openrc/softlevel"; then
mkdir -p "$${XDG_RUNTIME_DIR}/openrc"
touch "$${XDG_RUNTIME_DIR}/openrc/softlevel"
chown -R ${USER}:${USER} "$${XDG_RUNTIME_DIR}"
chmod -R 0700 "$${XDG_RUNTIME_DIR}"
fi
-65
View File
@@ -1,65 +0,0 @@
#!/sbin/openrc-run
# credits:
# https://gist.github.com/itzwam/2069e935385193207f7e5bea7156e21d
# https://github.com/0x17de/dockerservice-openrc
: ${MODULENAME:=${RC_SVCNAME##*.}}
: ${SRCDIR:=/usr/local/lib/joeac.net}
DOCKER_COMPOSE_UP_ARGS=${DOCKER_COMPOSE_UP_ARGS-"--no-build --no-recreate --no-deps"}
[ -z "${MODULENAME}" ] && exit 1
: ${COMPOSEFILE:="${SRCDIR}/compose.yml"}
: ${MAKEFILE:="${SRCDIR}/Makefile"}
COMPOSECMD="/usr/bin/podman-compose"
export COMPOSE_HTTP_TIMEOUT=300
description="Manage dockerservices defined in ${COMPOSEFILE}"
extra_commands="configtest"
description_configtest="Check configuration via \"podman-compose -f ${COMPOSEFILE} config\""
configtest() {
if ! [ -f "${COMPOSEFILE}" ]; then
eerror "The docker-compose file ${COMPOSEFILE} does not exist!"
return 1
fi
if "${COMPOSECMD}" -f "${COMPOSEFILE}" config >&/dev/null; then
einfo "config: ok"
else
eerror "config: error"
return 1
fi
}
start() {
configtest || return 1
ebegin "Starting ${MODULENAME}"
"${COMPOSECMD}" -f "${COMPOSEFILE}" up -d ${DOCKER_COMPOSE_UP_ARGS} ${MODULENAME}
eend $?
}
stop() {
ebegin "Stopping ${MODULENAME}"
"${COMPOSECMD}" -f "${COMPOSEFILE}" down --timeout=5 ${MODULENAME}
eend $?
}
status() {
pods=0
started=0
while read pod; do
pod_service="$(podman inspect $pod | jq .[0].Config.Labels.[\"com.docker.compose.service\"])"
if ! [ "$pod_service" = "\"${MODULENAME}\"" ]; then
continue
fi
pods=$(($pods+1))
if podman top $pod &>/dev/null; then
einfo "status: [$pod] started"
exit 0
else
einfo "status: [$pod] stopped"
exit 3
fi
done < <("${COMPOSECMD}" -f "${COMPOSEFILE}" ps --quiet)
einfo "status: no container found for ${MODULENAME}"
exit 3
}
+65
View File
@@ -0,0 +1,65 @@
#!/sbin/openrc-run
# credits:
# https://gist.github.com/itzwam/2069e935385193207f7e5bea7156e21d
# https://github.com/0x17de/dockerservice-openrc
: $${HOME:=/home/joeac.net}
: $${MODULENAME:=$${RC_SVCNAME##*.}}
: $${SRCDIR:=$$HOME/joeac.net}
DOCKER_COMPOSE_UP_ARGS=$${DOCKER_COMPOSE_UP_ARGS-"--no-build --no-recreate --no-deps"}
[ -z "$${MODULENAME}" ] && exit 1
: $${COMPOSEFILE:="$${SRCDIR}/compose.yml"}
: $${MAKEFILE:="$${SRCDIR}/Makefile"}
export COMPOSE_HTTP_TIMEOUT=300
description="Manage dockerservices defined in $${COMPOSEFILE}"
extra_commands="configtest"
description_configtest="Check configuration via \"podman-compose -f $${COMPOSEFILE} config\""
configtest() {
if ! [ -f "$${COMPOSEFILE}" ]; then
eerror "The docker-compose file $${COMPOSEFILE} does not exist!"
return 1
fi
if ${COMPOSECMD} -f "$${COMPOSEFILE}" config >&/dev/null; then
einfo "config: ok"
else
eerror "config: error"
return 1
fi
}
start() {
configtest || return 1
ebegin "Starting $${MODULENAME}"
${COMPOSECMD} -f "$${COMPOSEFILE}" up -d $${DOCKER_COMPOSE_UP_ARGS} $${MODULENAME}
eend $$?
}
stop() {
ebegin "Stopping $${MODULENAME}"
${COMPOSECMD} -f "$${COMPOSEFILE}" down --timeout=5 $${MODULENAME}
eend $$?
}
status() {
pods=0
started=0
while read pod; do
pod_service="$$(podman inspect $$pod | jq .[0].Config.Labels.[\"com.docker.compose.service\"])"
if ! [ "$$pod_service" = "\"$${MODULENAME}\"" ]; then
continue
fi
pods=$$(($$pods+1))
if podman top $$pod &>/dev/null; then
einfo "status: [$$pod] started"
exit 0
else
einfo "status: [$$pod] stopped"
exit 3
fi
done < <(${COMPOSECMD} -f "$${COMPOSEFILE}" ps --quiet)
einfo "status: no container found for $${MODULENAME}"
exit 3
}
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
if [ -z "${XDG_RUNTIME_DIR}" ]
then
export XDG_RUNTIME_DIR=/tmp/xdg/$(id -u joeac.net)-xdg-runtime-dir
fi
cd /home/joeac.net/joeac.net \
&& sudo -u joeac.net git pull \
&& sudo -u joeac.net make \
&& sudo -u joeac.net make reinstall
+13
View File
@@ -0,0 +1,13 @@
.PHONY: install
install: $(VAULTWARDEN_DATA_DIR)
.PHONY: reinstall
reinstall: $(VAULTWARDEN_DATA_DIR)
$(VAULTWARDEN_DATA_DIR):
sudo mkdir -p $(VAULTWARDEN_DATA_DIR)
VAULTWARDEN_USER=$(whoami) && sudo chown $$VAULTWARDEN_USER:$$VAULTWARDEN_USER $(VAULTWARDEN_DATA_DIR)
.PHONY: uninstall
uninstall:
$(info Nothing to do)