Compare commits

..

1 Commits

Author SHA1 Message Date
joeac c07cdedeb3 gemini/Makefile: FEED_TARGET includes comitium.json, not
subscriptions.json
2026-06-22 22:46:32 +01:00
56 changed files with 215 additions and 1662 deletions
-3
View File
@@ -5,6 +5,3 @@ indent_size = 2
[*.{md,markdown,mdx}]
max_line_length = 80
[{Makefile,*.mk}]
indent_style = tab
-1
View File
@@ -26,4 +26,3 @@ pnpm-debug.log*
**/*.tfstate.backup
*.sqlite
DIGITALOCEAN_TOKEN
+40 -47
View File
@@ -1,13 +1,36 @@
include make/vars.mk
#############
# VARIABLES #
#############
submake_file = $(shell [ -f "$(module)/Makefile" ] && echo "$(module)/Makefile")
install_submake_file = $(shell [ -f "$(module)/install.mk" ] && echo "$(module)/install.mk")
CPU_ARCH := $(shell lscpu | grep ^Architecture: | sed "s/^Architecture:[[:space:]]*\([[:alnum:][:punct:]]\+\).*/\1/")
IMAGE_PREFIX := $(if $(filter armv7%,$(CPU_ARCH)),armv7/)
REGISTRY_DOMAIN := git.joeac.net
REGISTRY_USER := joeac
MODULES := http gemini smtp
#############
# FUNCTIONS #
#############
container_image_name = $(REGISTRY_DOMAIN)/$(REGISTRY_USER)/joeac.net-$(module)
define build_module_rule =
.PHONY: build_$(module)
build_$(module): make_$(module)
sudo podman-compose build $(module)
endef
define push_module_rule =
.PHONY: push_$(module)
push_$(module): login_registry build_$(module)
sudo podman push $(container_image_name)
endef
define make_module_rule =
.PHONY: make_$(module)
make_$(module): $(module)/.env
$(if $(submake_file),\
$(MAKE) --makefile=$(notdir $(submake_file)) --directory=$(module))
$(MAKE) --directory=$(module)
endef
define module_env_rule =
@@ -15,52 +38,22 @@ $(module)/.env: .env
cp .env $(module)/.env
endef
define install_module_rule =
.PHONY: install_$(module)
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 reinstall_module_rule =
.PHONY: reinstall_$(module)
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
#########
# RULES #
#########
define uninstall_module_rule =
.PHONY: uninstall_$(module)
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
)
endef
all: $(foreach module,$(MODULES),push_$(module))
.PHONY: all
all: $(ENV_RULES) $(MAKE_RULES) $(BUILD_RULES) $(PUSH_RULES)
$(foreach module,$(MODULES),$(eval $(call build_module_rule)))
$(foreach module,$(MODULES),$(eval $(call push_module_rule)))
$(foreach module,$(MODULES),$(eval $(call make_module_rule)))
$(foreach module,$(MODULES),$(eval $(call module_env_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(call make_module_rule)))
$(foreach module,$(ALL_MODULES),$(eval $(call module_env_rule)))
.PHONY: install
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_tls uninstall_joeac.net_service $(UNINSTALL_RULES) uninstall_crontab
$(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: login_registry
login_registry:
sudo podman login $(REGISTRY_DOMAIN)
.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
$(foreach module,$(MODULES),$(MAKE) --directory=$(module) clean;)
+28 -87
View File
@@ -2,103 +2,44 @@
Joe Carstairs' public Internet presence
To install:
```sh
wget -O- https://git.joeac.net/joeac/joeac.net/raw/branch/main/install.sh | sh
```
## DNS setup
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.
Deliver mail for mail.joeac.net to mail.joeac.net:
Structure:
```
mail.joeac.net. MX 10 mail.joeac.net.
/
├── gemini My Gemini capsule
├── smtp A local SMTP server
└── http My Website
```
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:
## Running with Podman
```
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
These instructions will probably work with Docker, too: just substitute `podman`
for `docker` in all the commands.
To run with Podman, first set up your environment variables. Copy `example.env`
to `.env` and edit the values accordingly.
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
```
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.
Now build and start the containers:
```
mail.joeac.net. TXT v=spf1 ip4:217.155.190.42 ip6:fdc9:6aec:7a18:0:2e0:4cff:fe61:9b17 mx ~all
```bash
sudo podman-compose build && sudo podman-compose up -d
```
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.
## Running on the host machine
```
_dmarc.mail.joeac.net. TXT v=DMARC1;p=reject;rua=mailto:dmarcreports@mail.joeac.net!10m
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
```
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"
```
Note that emails may not work locally without further setup. These instructions
are of course woefully incomplete.
-7
View File
@@ -1,7 +0,0 @@
I looked through some concordances while I was bored in the New College Library the other day.
I really love Robert Young's Analytical Concordance. Young's is a concordance to the Authorised Version, so the entries are the English words used in the AV. But each English-language entry groups together all the words in the Greek New Testament and Hebrew Old Testament which correspond to that English word, and lists all the occurrences of each of those words in their respective source texts. That makes it a perfect tool for understanding the relationships between the biblical source texts. The only disadvantage is that it doesn't seem at all interested in the Septuagint, which is really important for understanding the way New Testament authors use the Old Testament.
It astonished me that there are concordances available of the NIV and ESV texts. Why would you want that? The texts don't correspond very well to the Greek or Hebrew source texts, and they don't have nearly as much clout as self-standing faith texts as the Authorised Version does. I really don't get it. If I had one, I would probably never open it.
I couldn't get ahold of a Strong's, but I did happen to drop in Armchair Books on the way back from the library, and they have a beautiful 19th-century Cruden's Concordance. Cruden's was the first English-language concordance when its eponymous author painstakingly constructed it in 1737, and has never been out of print since then. By the looks of it, it remains very relevant for English-speaking readers of the Bible. And going for a bargain as well... if you get there before me, I'll eat your head.
-7
View File
@@ -1,7 +0,0 @@
So I've now got half a dozen services running on my homelab across two machines. I need to install the services on each machine, keep them running after crashes and reboots, update and restart them when the source code changes, and co-ordinate them with each other (since I can only have one public IP on my home network, but I want to serve stuff over the Internet from both machines).
I am currently managing this with Podman, some Dockerfiles and Docker-Compose files, Makefiles, and a lot of manual interference. This just about works, and is probably reasonably sensible for the small, low-consequence situation.
Still, it is a faff. I'm wondering: is this kind of what Kubernetes is for? I've never understood. Looking at the documentation, I find it really hard to understand what Kubernetes is for. Maybe it's for situations kind of like this (only typically much bigger)?
Answers on a postcard please.
-11
View File
@@ -1,11 +0,0 @@
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
@@ -1,3 +0,0 @@
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
+13 -71
View File
@@ -1,95 +1,37 @@
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/${IMAGE_PREFIX}joeac.net-gemini
container_name: joeac.net-gemini
image: git.joeac.net/joeac/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
- "1965:1965"
http:
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-http
container_name: joeac.net-http
image: git.joeac.net/joeac/joeac.net-http
build:
context: .
dockerfile: http.Dockerfile
environment:
MAX_DAILY_EMAILS: ${MAX_DAILY_EMAILS}
CONTACT_MAILBOX: ${CONTACT_MAILBOX}
CONTACT_MAILBOX_NAME: ${CONTACT_MAILBOX_NAME}
LOCAL_SMTP_FROM: ${LOCAL_SMTP_FROM}
LOCAL_SMTP_FROM_NAME: ${LOCAL_SMTP_FROM_NAME}
LOCAL_SMTP_HOST: ${LOCAL_SMTP_HOST}
LOCAL_SMTP_PORT: ${LOCAL_SMTP_PORT}
LOCAL_SMTP_USER: ${LOCAL_SMTP_USER}
LOCAL_SMTP_PASSWORD: ${LOCAL_SMTP_PASSWORD}
DB_SCHEME: ${DB_SCHEME}
DB_PATH: ${DB_PATH}
env_file:
- .env
ports:
- "${HTTP_PORT}:80"
etherpad:
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-etherpad
container_name: joeac.net-etherpad
build:
context: .
dockerfile: etherpad.Dockerfile
args:
ALPINE_VERSION: ${ALPINE_VERSION}
ETHERPAD_VERSION: ${ETHERPAD_VERSION}
volumes:
- ${ETHERPAD_DATA_DIR}:/var/app/var
environment:
NODE_ENV: ${ETHERPAD_NODE_ENV}
ADMIN_PASSWORD: ${ETHERPAD_ADMIN_PASSWORD}
restart: unless-stopped
ports:
- "${ETHERPAD_PORT}:9001"
- "8080:8080"
smtp:
image: git.joeac.net/joeac/${IMAGE_PREFIX}joeac.net-smtp
container_name: joeac.net-smtp
image: git.joeac.net/joeac/joeac.net-smtp
build:
context: .
dockerfile: smtp.Dockerfile
context: smtp
args:
SMTP_PORT: ${LOCAL_SMTP_PORT}
LOCAL_SMTP_PORT: 2500
env_file:
- .env
environment:
LOCAL_SMTP_ENVELOPE_FROM: ${LOCAL_SMTP_ENVELOPE_FROM}
REMOTE_SMTP_HOST: ${REMOTE_SMTP_HOST}
REMOTE_SMTP_PORT: ${REMOTE_SMTP_PORT}
REMOTE_SMTP_USER: ${REMOTE_SMTP_USER}
SMTP_PORT: ${LOCAL_SMTP_PORT}
SMTP_USER: ${LOCAL_SMTP_USER}
SMTP_PASSWORD: ${LOCAL_SMTP_PASSWORD}
LOCAL_SMTP_PORT: 2500
LOCAL_SMTP_PASSWORD: smtp
REMOTE_SMTP_PASSWORD_FILE: /run/secrets/remote_smtp_password
secrets:
- remote_smtp_password
vaultwarden:
image: docker.io/vaultwarden/server:latest
container_name: joeac.net-vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://pwd.joeac.net"
volumes:
- ${VAULTWARDEN_DATA_DIR}:/data/
ports:
- "${VAULTWARDEN_PORT}:80"
secrets:
remote_smtp_password:
external: true
-11
View File
@@ -1,11 +0,0 @@
ARG ALPINE_VERSION=3.23
FROM docker.io/alpine:${ALPINE_VERSION}
RUN mkdir -p /var/app
WORKDIR /var/app
ARG ETHERPAD_VERSION
RUN apk --no-cache add pnpm \
&& ENV=~/.profile SHELL=sh pnpm setup \
&& source ~/.profile \
&& pnpm install --global tsx
COPY etherpad/etherpad-${ETHERPAD_VERSION} .
CMD pnpx tsx src/node/server.ts
-2
View File
@@ -1,2 +0,0 @@
var
etherpad-*
-27
View File
@@ -1,27 +0,0 @@
ETHERPAD_VERSION := 3.3.2
SRC_DIR := etherpad-$(ETHERPAD_VERSION)
.PHONY: all
all: download_etherpad install_deps
.PHONY: download_etherpad
download_etherpad: $(SRC_DIR)
$(SRC_DIR):
wget -O- https://github.com/ether/etherpad/archive/refs/tags/v3.3.2.tar.gz | tar -xz
.PHONY: install_deps
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.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
@@ -1,13 +0,0 @@
.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)
-134
View File
@@ -1,134 +0,0 @@
{
"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}/}"]
}
]
}
}
+9 -11
View File
@@ -1,11 +1,15 @@
# The hostname to use for the local SMTP server
LOCAL_SMTP_HOST=smtp
LOCAL_SMTP_HOST=
# The port to use for the local SMTP server
LOCAL_SMTP_PORT=
# The username for authenticating to the local SMTP server
LOCAL_SMTP_USER=smtp
# Recommended to be the same as REMOTE_SMTP_USER, else the remote may reject it
LOCAL_SMTP_USER=
# The password for authenticating to the local SMTP server
LOCAL_SMTP_PASSWORD=smtp
LOCAL_SMTP_PASSWORD=
# The envelope-from used by the local SMTP server
# Theoretically should be "LOCAL_SMTP_FROM_NAME <LOCAL_SMTP_FROM>", but in
@@ -40,13 +44,7 @@ CONTACT_MAILBOX_NAME=
MAX_DAILY_EMAILS=
# The scheme part of the URI to the website database
DB_SCHEME=sqlite
DB_SCHEME=
# The path part of the URI to the website database
DB_PATH=/var/app/joeac.net-http.sqlite
# The Node.JS environment for the Etherpad instance (production, development)
ETHERPAD_NODE_ENV=production
# The admin password for the Etherpad instance
ETHERPAD_ADMIN_PASSWORD=
DB_PATH=
+5 -6
View File
@@ -1,19 +1,18 @@
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
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
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 \
&& rm crontab.tmp
COPY gemini/run.sh /var/app/
COPY gemini/content /var/app/content
COPY gemini/content gemini/comitium-data /var/app/
COPY common /var/common
CMD ./run.sh
+1 -1
View File
@@ -7,7 +7,7 @@ MICROLOG_ENTRIES := $(wildcard microlog/*)
LONGLOG_ENTRIES := $(wildcard content/logs/longlog/*)
LOGLOG_TARGET := content/logs/loglog.gmi
FEED_TARGET := $(COMITIUM_DATA)/comitium.json $(COMITIUM_DATA)/feed.gmi $(COMITIUM_DATA)/subscriptions.gmi
FEED_OUTPUTS := $(FEED_TARGET) $(COMITIUM_DATA)/comitium.json $(COMITIUM_DATA)/feed.gmi $(COMITIUM_DATA)/subscriptions.gmi
FEED_OUTPUTS := $(FEED_TARGET) $(COMITIUM_DATA)/subscriptions.json $(COMITIUM_DATA)/feed.gmi $(COMITIUM_DATA)/subscriptions.gmi
MICROLOG_TARGET := content/logs/microlog.gmi
MICROLOG_OUTPUTS := $(MICROLOG_TARGET) $(wildcard content/logs/microlog/*)
LONGLOG_TARGET := content/logs/longlog.gmi
-22
View File
@@ -1,22 +0,0 @@
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)
+2 -1
View File
@@ -1,4 +1,5 @@
#!/bin/sh
( ( sleep 5; comitium refresh --data comitium-data/ ) & ) \
make && \
( ( sleep 5; comitium refresh --data comitium-data/ ) & ) \
&& agate --content content/ --addr [::]:1965 --addr 0.0.0.0:1965 --lang en-GB
+3 -12
View File
@@ -1,13 +1,4 @@
FROM docker.io/arm32v7/nginx:1.30.3-alpine3.23
RUN apk --update add php85-fpm php85-pdo php85-pdo_sqlite
COPY http/vendor /usr/share/nginx/vendor
COPY http/php /usr/share/nginx/php
COPY http/out /usr/share/nginx/html
COPY http/php-fpm.conf /etc/php85/php-fpm.d/joeac.net-http_pool.conf
COPY http/nginx.conf /etc/nginx/nginx.conf
CMD mkdir -p "$(dirname "$DB_PATH")" \
&& touch $DB_PATH \
&& chown -R nginx:nginx "$(dirname "$DB_PATH")" \
&& chmod -R 700 "$(dirname "$DB_PATH")" \
&& php-fpm85 \
&& nginx -g "daemon off;"
RUN apk --update add php85-fpm
WORKDIR /var/app
COPY http/out http/php ./
+24 -51
View File
@@ -5,7 +5,8 @@ include config.mk
# VARIABLES #
#############
src_files := $(filter-out %.args,$(wildcard src/*.upp*)) $(filter-out %.args,$(wildcard src/**/*.upp*))
src_files := $(filter-out %.args,$(wildcard src/*.upp*))
src_files += $(filter-out %.args,$(wildcard src/**/*.upp*))
src_files_ext := $(suffix $(src_files))
out_deps_ext := $(src_files_ext:.upp%=%)
@@ -13,12 +14,10 @@ component_files := $(shell find -L share/components -type f)
layout_files := $(wildcard share/layouts/*.layout.upp*)
layouts := $(notdir $(basename $(basename $(layout_files))))
remove_all_layouts = $(call remove_layouts,$(layouts),$(1))
remove_layouts = $(let first rest,$(1),$(call remove_layout,$(first),$(if $(rest),$(call remove_layouts,$(rest),$(2)),$(2))))
remove_layout = $(subst .$(1).upp,.,$(2))
out_deps := $(subst .upp,.,$(call remove_all_layouts,$(addprefix out/,$(src_files:src/%=%))))
out_deps_without_src := $(filter-out $(out_deps),$(shell find -L out -type f))
out_deps += $(out_deps_without_src)
out_deps := $(addprefix out/,$(src_files:src/%=%))
remove_layout_from_out_deps = out_deps := $$(subst $(1).upp,.,$$(out_deps))
$(foreach layout,$(layouts),$(eval $(call remove_layout_from_out_deps,.$(layout))))
$(eval $(call remove_layout_from_out_deps,))
blogs := $(subst blogs/,,$(wildcard blogs/*))
@@ -33,25 +32,24 @@ blog_src = $(patsubst \
blogs/$(blog)/%.$(BLOG_EXT_$(blog)),\
src/$(blog)/%$(if $(BLOG_LAYOUT_$(blog)),.$(BLOG_LAYOUT_$(blog))).upphtml,\
$(shell find -L "blogs/$(blog)" -type f -and -name *.$(BLOG_EXT_$(blog))))
blog_src_without_post_src = $(filter-out $(blog_src),$(shell find -L "src/$(blog)" -type f -and -name *.$(BLOG_LAYOUT_$(blog)).upphtml))
default_layout_file = $(if \
$(shell [ -f "share/layouts/default.layout.$(ext)" ] && echo 1),\
share/layouts/default.layout.$(ext))
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) bin/pp
out/%.$(ext): src/%$(if $(layout),.$(layout)).upp$(ext) $(layout_file) $(component_files)
./bin/mkws https://joeac.net "$$$$(realpath $$<)"
endef
define blog_rss_feed_rule =
out/$(blog)/rss.xml: src/$(blog)/rss.uppxml $(blog_src) $(wildcard bin/*) bin/pp
out/$(blog)/rss.xml: src/$(blog)/rss.uppxml $(blog_src) $(wildcard bin/*)
./bin/mkws https://joeac.net "$$(realpath src/$(blog)/rss.uppxml)"
endef
define blog_rule =
.PHONY: $(blog)
$(blog): $(blog_src_without_post_src) $(blog_src) out/$(blog).html
$(blog): $(blog_src)
endef
define blog_post_rule =
@@ -63,35 +61,10 @@ $(blog_post_target): $(blog_post_src) $(let ext,html,$(layout_file) $(component_
-f $$*.$(BLOG_EXT_$(blog))
endef
define blog_index_rule =
out/$(blog).html: src/$(blog).upphtml $(blog_src) $(wildcard bin/*) bin/pp
./bin/mkws https://joeac.net "$$(realpath src/$(blog).upphtml)"
endef
define prune_blog_src_file_rule =
$(let rel_basename,$(patsubst src/$(blog)/%.$(BLOG_LAYOUT_$(blog)).upphtml,%,$(file)),
blogs/$(blog)/$(rel_basename).$(BLOG_EXT_$(blog)):
rm $(file)
)
endef
define prune_blog_out_file_rule =
$(let rel_basename,$(patsubst src/$(blog)/%.$(BLOG_LAYOUT_$(blog)).upphtml,%,$(file)),
$(file):
rm out/$(rel_basename).html
)
endef
define prune_out_file_rule =
$(let orig_ext,$(patsubst .%,%,$(suffix $(file))),
$(let ext,$(patsubst upp%,%,$(orig_ext)),
$(let rel_basename,$(patsubst out/%.$(orig_ext),%,$(file)),
$(let src_file,src/$(rel_basename).upp$(ext),
$(let src_files_with_layouts,$(foreach layout,$(layouts),src/$(rel_basename).$(layout).upp$(ext)),
$(let public_file,public/$(rel_basename).$(orig_ext),
$(src_file) $(src_files_with_layouts) $(public_file):
rm $(file)
))))))
define prune_blog_rule =
.PHONY: prune_$(blog)
prune_$(blog):
./bin/prune_blog $(blog) *$(if $(BLOG_LAYOUT_$(blog)),.$(BLOG_LAYOUT_$(blog))).upphtml
endef
@@ -99,8 +72,10 @@ endef
# RULES #
#########
all: $(blogs) public out vendor
$(MAKE) out # if blog posts have been pruned, outfiles must be re-calculated
all: bin/pp prune $(blogs) public out
bin/pp:
cd pp && $(MAKE) && cp pp ../bin/pp
share/man/man1/pp.1:
cp pp/pp.1 share/man/man1/pp.1
@@ -108,9 +83,6 @@ share/man/man1/pp.1:
.PHONY: out
out: $(out_deps)
bin/pp:
$(MAKE) --directory=pp && chmod +x pp/pp && cp pp/pp bin/pp
$(foreach ext,$(sort $(out_deps_ext)),\
$(eval $(out_rule)))
$(foreach layout,$(layouts),\
@@ -118,7 +90,6 @@ $(foreach layout,$(layouts),\
$(eval $(out_rule))))
$(foreach blog,$(blogs),$(eval $(blog_rule)))
$(foreach blog,$(blogs),$(eval $(blog_index_rule)))
$(foreach blog,$(blogs),\
$(foreach layout,$(layouts),\
@@ -134,16 +105,18 @@ public: $(subst public/,out/,$(shell find -L public/ -type f))
out/%: public/%
mkdir -p $(dir $@) && cp $< $@
$(foreach file,$(out_deps_without_src),$(eval $(prune_out_file_rule)))
$(foreach blog,$(blogs),$(foreach file,$(blog_src_without_post_src),$(eval $(prune_blog_src_file_rule))))
.PHONY: prune
prune: $(addprefix prune_,$(blogs)) prune_out
vendor: composer.json composer.lock
composer install
.PHONY: prune_out
prune_out:
./bin/prune_out
$(foreach blog,$(blogs),$(eval $(prune_blog_rule)))
.PHONY: clean
clean:
rm -rf \
vendor \
out \
$(foreach blog,$(blogs), \
$(foreach ext,upphtml upphtml.args,\
+2 -2
View File
@@ -102,8 +102,8 @@ do
export POST_TITLE="$name"
cat "$entry" | eval "${postprocess_cmd}" > "$dest"
else
export POST_TITLE="$(grep -e "^# " -m 1 "$entry" | cut -c3-)"
cat "$entry" | grep -e "^# " -v | eval "$postprocess_cmd" > "$dest"
export POST_TITLE="$(grep --regexp "^# " --max-count 1 "$entry" | cut -c3-)"
cat "$entry" | grep --regexp "^# " --invert-match | eval "$postprocess_cmd" > "$dest"
fi
cat "$entry" | eval "$extract_args_cmd" > "${dest}.args"
echo "Processed ${entry#$(dirname ${BLOGS})/} to ${dest#$(dirname ${SRC})/}"
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh -e
BLOG="$1"
BLOG_POST_PATTERN="${2:-*}"
SRC="$(realpath src)"
BLOGS="$(realpath blogs)"
LAYOUTS="$(realpath share/layouts)"
BLOG_DIR_IN_SRC="${SRC}/${BLOG}"
BLOG_EXT=gmi
for post in $(find "$BLOG_DIR_IN_SRC" -type f -and -name "$BLOG_POST_PATTERN")
do
last_segment="${post##*\.}"
if [ "$last_segment" = "args" ]
then
continue
fi
post_rel_path="$(echo "$post" | cut -c${#BLOG_DIR_IN_SRC}- | cut -c3-)"
post_basename_len=$(( ${#post_rel_path} - ( ${#last_segment} + 1 ) ))
post_basename="$(echo "${post_rel_path}" | cut -c1-${post_basename_len})"
post_src="${BLOGS}/${BLOG}/${post_basename}.${BLOG_EXT}"
if [ -f "$post_src" ]
then
continue
fi
penultimate_segment_len=$(( ${#post} - ( ${#last_segment} + 1 ) ))
penultimate_segment="$(echo "${post}" | cut -c1-${penultimate_segment_len})"
penultimate_segment="${penultimate_segment##*\.}"
post_basename_len=$(( ${#post_rel_path} - ( ${#last_segment} + ${#penultimate_segment} + 2 ) ))
post_basename="$(echo "${post_rel_path}" | cut -c1-${post_basename_len})"
if [ -f "${LAYOUTS}/${penultimate_segment}.layout.${last_segment}" ]
then
post_src="${BLOGS}/${BLOG}/${post_basename}.${BLOG_EXT}"
if [ -f "$post_src" ]
then
continue
fi
fi
echo "No source file found to match post ${post} in blog ${BLOG}: removing ${post}"
rm -f "$post" "${post}.args"
done
+32
View File
@@ -0,0 +1,32 @@
#!/bin/sh -e
for out in $(find out -type f)
do
out_basename="$(basename "$out")"
out_ext="${out_basename##*\.}"
rel_path="$(echo "${out}" | cut -c5-)"
if [ -f "public/${rel_path}" ]; then
continue
fi
base_rel_path="${rel_path%%.${out_ext}}"
src_path="src/${base_rel_path}.upp${out_ext}"
if [ -f "$src_path" ]; then
continue
fi
for layout in $(find share/layouts -name "*.layout.upp${out_ext}"); do
layout="$(basename "$layout")"
layout="$(echo ${layout} | cut -c1-$(( ${#layout} - 15 )) )"
rel_path="$(echo "${out}" | cut -c5-)"
base_rel_path="${rel_path%%.${out_ext}}"
src_path="src/${base_rel_path}.${layout}.upp${out_ext}"
if [ -f "$src_path" ]; then
continue 2
fi
done
echo "No source file found to match ${out}: removing it"
rm "$out"
done
+5 -5
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5145609ea1f80d6d838d0f9649acd36a",
"content-hash": "752c91265ea3414ab13b2ff0f8925706",
"packages": [
{
"name": "phpmailer/phpmailer",
@@ -92,10 +92,10 @@
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {},
"platform-dev": {},
"plugin-api-version": "2.9.0"
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.3.0"
}
-47
View File
@@ -1,47 +0,0 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
listen [::]:80;
server_name joeac.net;
access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
try_files $uri $uri.html $uri/index.html $uri.php $uri/index.php =404;
}
error_page 404 /error.html;
location ~ \.php$ {
root html;
fastcgi_pass unix:/run/php8.5-fpm-joeac.net-http.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /usr/share/nginx/html$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.sqlite {
deny all;
}
}
}
-36
View File
@@ -1,36 +0,0 @@
[joeac.net]
user = nginx
group = nginx
listen = /run/php8.5-fpm-joeac.net-http.sock
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
access.log = /var/log/php85/$pool.access.log
access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{milli}d %{kilo}M %C%%"
catch_workers_output = yes
php_admin_value[error_log] = /var/log/php85/$pool.error.log
php_admin_flag[log_errors] = on
php_admin_value[disable_functions] = exec,passthru,shell_exec,system
php_admin_flag[allow_url_fopen] = off
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 8
pm.max_requests = 500
clear_env = no
env[MAX_DAILY_EMAILS] = $MAX_DAILY_EMAILS
env[CONTACT_MAILBOX] = $CONTACT_MAILBOX
env[CONTACT_MAILBOX_NAME] = $CONTACT_MAILBOX_NAME
env[LOCAL_SMTP_FROM] = $LOCAL_SMTP_FROM
env[LOCAL_SMTP_FROM_NAME] = $LOCAL_SMTP_FROM_NAME
env[LOCAL_SMTP_HOST] = $LOCAL_SMTP_HOST
env[LOCAL_SMTP_PORT] = $LOCAL_SMTP_PORT
env[LOCAL_SMTP_USER] = $LOCAL_SMTP_USER
env[LOCAL_SMTP_PASSWORD] = $LOCAL_SMTP_PASSWORD
env[DB_SCHEME] = $DB_SCHEME
env[DB_PATH] = $DB_PATH
+1 -1
View File
@@ -1,4 +1,4 @@
#!
SITE_URL="${1}"
#!
<meta http-equiv=refresh content=0;url=${SITE_URL}/subscribe />
<meta http-equiv="refresh" content="0" ; url="${SITE_URL}/subscribe" />
+1 -1
View File
@@ -1,4 +1,4 @@
#!
SITE_URL="${1}"
#!
<meta http-equiv=refresh content=0;url=${SITE_URL}/microlog/rss.xml />
<meta http-equiv="refresh" content="0" ; url="${SITE_URL}/microlog/rss.xml" />
-113
View File
@@ -1,113 +0,0 @@
#!/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
-14
View File
@@ -1,14 +0,0 @@
.PHONY: install
install: $(PUBLIC_ROOT_DIR_ln)
.PHONY: reinstall
reinstall: install
$(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 $(PUBLIC_ROOT_DIR_ln)
-1
View File
@@ -1 +0,0 @@
<meta http-equiv="refresh" content="0;url=https://forms.zohopublic.eu/mejo1/form/MilehousehutJuly2026/formperma/Jzvv9p6KIJxon5_kc2l29tI0FiMjKy3bJ5ICS9K67NQ" />
-28
View File
@@ -1,28 +0,0 @@
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)
-13
View File
@@ -1,13 +0,0 @@
.PHONY: install_crontab
install_crontab: /etc/periodic/daily/joeac.net
.PHONY: reinstall_crontab
reinstall_crontab: /etc/periodic/daily/joeac.net
.PHONY: uninstall_crontab
uninstall_crontab:
sudo rm -f /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
@@ -1,55 +0,0 @@
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
@@ -1,75 +0,0 @@
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
@@ -1,74 +0,0 @@
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
@@ -1,89 +0,0 @@
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
@@ -1,91 +0,0 @@
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
@@ -1,2 +0,0 @@
config/dkim
config/adminpasswd
-6
View File
@@ -1,6 +0,0 @@
# 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
@@ -1,52 +0,0 @@
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
@@ -1,74 +0,0 @@
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
@@ -1,8 +0,0 @@
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
@@ -1,63 +0,0 @@
.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
@@ -1 +0,0 @@
nameserver: 127.0.0.1
-20
View File
@@ -1,20 +0,0 @@
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
@@ -1,22 +0,0 @@
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;
}
-99
View File
@@ -1,99 +0,0 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}
-33
View File
@@ -1,33 +0,0 @@
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
pcre_jit on;
error_log /var/log/nginx/error.log warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
client_max_body_size 1m;
sendfile on;
tcp_nopush on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:2m;
ssl_session_timeout 1h;
gzip on;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
include /etc/nginx/http.d/*.conf;
}
-15
View File
@@ -1,15 +0,0 @@
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
: $${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
}
-5
View File
@@ -1,5 +0,0 @@
FROM git.joeac.net/joeac/armv7/msmtp:1.8.32-alpine3.23
RUN apk --no-cache add gettext gnutls
ARG SMTP_PORT
EXPOSE $SMTP_PORT
COPY smtp/.msmtprc ./
+4
View File
@@ -0,0 +1,4 @@
FROM git.joeac.net/joeac/armv7/msmtp:1.8.32-alpine3.23
ARG LOCAL_SMTP_PORT
EXPOSE $LOCAL_SMTP_PORT
COPY .msmtprc ./
-11
View File
@@ -1,11 +0,0 @@
#!/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
@@ -1,13 +0,0 @@
.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)