Compare commits

...

3 Commits

  1. 11
      Makefile
  2. 161
      Makefile.common
  3. 1
      README.md
  4. 8
      generate-butane-spec.sh
  5. 80
      nextcloud/Makefile
  6. 5
      nextcloud/config/config.env
  7. 4
      nextcloud/config/custom-noinit.sh
  8. 14
      nextcloud/config/custom-post.sh
  9. 7
      nextcloud/config/custom-pre.sh
  10. 2
      nextcloud/config/nginx.conf
  11. 0
      nextcloud/config/redis-session.ini
  12. 9
      nextcloud/config/redis.conf
  13. 1
      nextcloud/fcos.bu
  14. 37
      nextcloud/nextcloud-app.container
  15. 60
      nextcloud/nextcloud-cron.container
  16. 12
      nextcloud/nextcloud-cron.timer
  17. 64
      nextcloud/nextcloud-init.container
  18. 26
      nextcloud/nextcloud-nginx.container
  19. 21
      nextcloud/nextcloud-redis.container
  20. 66
      nextcloud/nextcloud-upgrade.container
  21. 5
      nextcloud/nextcloud.target
  22. 3
      nextcloud/sysctl.d/nextcloud.conf
  23. 3
      nextcloud/tmpfiles.d/nextcloud.conf
  24. 2
      nginx/nginx-update.container
  25. 11
      nginx/nginx-update.timer
  26. 36
      postgresql/Makefile
  27. 2
      postgresql/config/config.env
  28. 1
      postgresql/fcos.bu
  29. 21
      postgresql/postgresql-backup.container
  30. 11
      postgresql/postgresql-backup.timer
  31. 13
      postgresql/postgresql-init.container
  32. 19
      postgresql/postgresql-server.container
  33. 12
      postgresql/postgresql-upgrade.container
  34. 3
      postgresql/postgresql.target
  35. 3
      postgresql/sysctl.d/postgresql.conf
  36. 3
      postgresql/tmpfiles.d/postgresql.conf
  37. 3
      virtiofs/Makefile
  38. 22
      virtiofs/backup-virtiofs.service
  39. 14
      virtiofs/fcos.bu
  40. 16
      virtiofs/restore-virtiofs.service
  41. 2
      virtiofs/tmpfiles.d/virtiofs.conf
  42. 19
      virtiofs/var-lib-virtiofs-data.mount

11
Makefile

@ -1,7 +1,7 @@
SUBDIRS := $(wildcard */Makefile)
SUBDIRS := $(dir $(SUBDIRS))
.PHONY: all help butane clean dryrun fcos-vm $(SUBDIRS)
.PHONY: all help butane clean dryrun fcos-vm clean-vm uninstall $(SUBDIRS)
all: help
help:
@ -11,17 +11,14 @@ help:
@echo " dryrun - Perform a dry run of the podman systemd generator"
@echo " fcos-vm - Launch a Fedora CoreOS VM with the generated Butane spec"
@echo " clean-vm - Clean up the Fedora CoreOS VM and its resources"
@echo " uninstall - Uninstall the generated resources"
dryrun: $(SUBDIRS)
butane: $(SUBDIRS)
clean: $(SUBDIRS)
fcos-vm: $(SUBDIRS)
clean-vm: $(SUBDIRS)
uninstall: $(SUBDIRS)
$(SUBDIRS):
@run() { echo $$*; "$$@"; }; \
if echo $(MAKECMDGOALS) | grep -Eq 'butane|fcos-vm'; then \
run $(MAKE) -C $@ $(MAKECMDGOALS); \
else \
run $(MAKE) -C $@ $(MAKECMDGOALS); \
fi
$(MAKE) -C $@ $(MAKECMDGOALS)

161
Makefile.common

@ -1,4 +1,6 @@
.PHONY: all install install-etc install-var uninstall pre-requisites clean dryrun tail-logs butane help fcos-vm clean-vm console
.PHONY: all install install-etc install-var uninstall pre-requisites clean dryrun
.PHONY: tail-logs butane help fcos-vm clean-vm console units units-pre
.PHONY: clean-pre clean-post install-pre install-post uninstall-pre uninstall-post
all: help
help:
@ -14,12 +16,11 @@ help:
@echo " clean-vm - Clean up the Fedora CoreOS VM and its resources"
@echo " console - Connect to the Fedora CoreOS VM console"
TARGET_CHROOT ?=
PROJECT_NAME := $(shell basename "$${PWD}")
QUADLETS_FILES = $(wildcard *.container *.volume *.network *.pod *.build)
SYSTEMD_FILES = $(wildcard *.service *.target *.timer)
SYSTEMD_UNIT_NAMES := $(wildcard *.service *.target *.timer)
SYSTEMD_FILES = $(wildcard *.service *.target *.timer *.mount)
SYSTEMD_UNIT_NAMES := $(wildcard *.service *.target *.timer *.mount)
SYSTEMD_TIMER_NAMES := $(wildcard *.timer)
SYSTEMD_MAIN_UNIT_NAMES := $(wildcard *.target)
QUADLET_UNIT_NAMES := $(patsubst %.container, %.service, $(wildcard *.container)) \
@ -28,10 +29,19 @@ QUADLET_UNIT_NAMES := $(patsubst %.container, %.service, $(wildcard *.container)
$(patsubst %.pod, %-pod.service, $(wildcard *.pod)) \
$(patsubst %.build, %-build.service, $(wildcard *.build))
CONFIG_FILES = $(wildcard config/*)
TMPFILESD_FILES = $(wildcard tmpfiles.d/*)
SYSCTLD_FILES = $(wildcard sysctl.d/*)
TARGET_QUADLETS_FILES = $(addprefix $(TARGET_CHROOT)/etc/containers/systemd/, $(QUADLETS_FILES))
TARGET_SYSTEMD_FILES = $(addprefix $(TARGET_CHROOT)/etc/systemd/system/, $(SYSTEMD_FILES))
TARGET_CONFIG_FILES = $(patsubst config/%, $(TARGET_CHROOT)/etc/quadlets/$(PROJECT_NAME)/%, $(CONFIG_FILES))
TARGET_FILES = $(TARGET_QUADLETS_FILES) $(TARGET_SYSTEMD_FILES) $(TARGET_CONFIG_FILES)
TARGET_TMPFILESD_FILES = $(patsubst tmpfiles.d/%, $(TARGET_CHROOT)/etc/tmpfiles.d/%, $(TMPFILESD_FILES))
TARGET_SYSCTLD_FILES = $(patsubst sysctl.d/%, $(TARGET_CHROOT)/etc/sysctl.d/%, $(SYSCTLD_FILES))
DEPENDENCIES ?=
I_KNOW_WHAT_I_AM_DOING ?=
DEPENDENCIES_IGNITION_FILES = $(shell for dep in $(DEPENDENCIES); do echo $(TOP_LEVEL_DIR)/$$dep/$$dep.ign; done)
PROJECT_UID ?= 0
PROJECT_GID ?= 0
pre-requisites:
@if [ -z "$(TOP_LEVEL_DIR)" ]; then \
@ -53,62 +63,83 @@ $(TARGET_CHROOT)/etc/systemd/system:
install -D -d -m 0755 -o root -g root $@
$(TARGET_CHROOT)/etc/quadlets/$(PROJECT_NAME):
install -D -d -m 0755 -o root -g root $@
install -D -d -m 0755 -o $(PROJECT_UID) -g $(PROJECT_GID) $@
$(TARGET_CHROOT)/etc/containers/systemd/%.container: %.container $(TARGET_CHROOT)/etc/containers/systemd
install -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/containers/systemd/%.volume: %.volume $(TARGET_CHROOT)/etc/containers/systemd
install -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/tmpfiles.d:
install -D -d -m 0755 -o root -g root $@
$(TARGET_CHROOT)/etc/containers/systemd/%.network: %.network $(TARGET_CHROOT)/etc/containers/systemd
install -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/sysctl.d:
install -D -d -m 0755 -o root -g root $@
$(TARGET_CHROOT)/etc/containers/systemd/%.pod: %.pod $(TARGET_CHROOT)/etc/containers/systemd
$(TARGET_CHROOT)/etc/containers/systemd/%: % $(TARGET_CHROOT)/etc/containers/systemd
install -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/containers/systemd/%.build: %.build $(TARGET_CHROOT)/etc/containers/systemd
$(TARGET_CHROOT)/etc/systemd/system/%: % $(TARGET_CHROOT)/etc/systemd/system
install -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/systemd/system/%.service: %.service $(TARGET_CHROOT)/etc/systemd/system
install -D -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/systemd/system/%.target: %.target $(TARGET_CHROOT)/etc/systemd/system
install -D -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/systemd/system/%.timer: %.timer $(TARGET_CHROOT)/etc/systemd/system
install -D -m 0644 -o root -g root $< $@
$(TARGET_CHROOT)/etc/quadlets/$(PROJECT_NAME)/%: config/% $(TARGET_CHROOT)/etc/quadlets/$(PROJECT_NAME)
@run() { echo $$*; "$$@"; }; \
if [ -x $< ]; then \
run install -D -m 0755 -o root -g root $< $@; \
run install -D -m 0755 -o $(PROJECT_UID) -g $(PROJECT_GID) $< $@; \
else \
run install -D -m 0644 -o root -g root $< $@; \
run install -D -m 0644 -o $(PROJECT_UID) -g $(PROJECT_GID) $< $@; \
fi
$(TARGET_CHROOT)/var/lib/quadlets/$(PROJECT_NAME):
install -d -m 0755 -o root -g root $@
install -d -m 0755 -o $(PROJECT_UID) -g $(PROJECT_GID) $@
$(TARGET_CHROOT)/etc/tmpfiles.d/%: tmpfiles.d/% $(TARGET_CHROOT)/etc/tmpfiles.d
install -D -m 0644 -o root -g root $< $@
install-etc: $(TARGET_QUADLETS_FILES) $(TARGET_SYSTEMD_FILES) $(TARGET_CONFIG_FILES)
$(TARGET_CHROOT)/etc/sysctl.d/%: sysctl.d/% $(TARGET_CHROOT)/etc/sysctl.d
install -D -m 0644 -o root -g root $< $@
install-etc: $(TARGET_QUADLETS_FILES) $(TARGET_SYSTEMD_FILES) $(TARGET_CONFIG_FILES) $(TARGET_TMPFILESD_FILES) $(TARGET_SYSCTLD_FILES)
install-var: $(TARGET_CHROOT)/var/lib/quadlets/$(PROJECT_NAME)
install: pre-requisites dryrun install-etc install-var
install-pre::
@run() { echo $$*; "$$@"; }; \
for dep in $(DEPENDENCIES); do \
run $(MAKE) -C $(TOP_LEVEL_DIR)/$$dep install; \
done
install-post::
install: pre-requisites dryrun install-etc install-var install-pre
systemctl daemon-reload
systemd-analyze --generators=true verify $(QUADLET_UNIT_NAMES) $(SYSTEMD_UNIT_NAMES)
systemctl enable $(SYSTEMD_UNIT_NAMES)
@run() { echo $$*; "$$@"; }; \
if [ -f /etc/tmpfiles.d/$(PROJECT_NAME).conf ]; then \
run systemd-tmpfiles --create /etc/tmpfiles.d/$(PROJECT_NAME).conf; \
fi; \
if [ -f /etc/sysctl.d/$(PROJECT_NAME).conf ]; then \
run sysctl -q -p /etc/sysctl.d/$(PROJECT_NAME).conf; \
fi
systemctl enable $(SYSTEMD_MAIN_UNIT_NAMES) $(SYSTEMD_TIMER_NAMES)
systemctl start $(SYSTEMD_MAIN_UNIT_NAMES)
$(MAKE) install-post
uninstall-pre::
uninstall-post::
@run() { echo $$*; "$$@"; }; \
for dep in $(DEPENDENCIES); do \
run $(MAKE) -C $(TOP_LEVEL_DIR)/$$dep uninstall; \
done
uninstall: pre-requisites
systemctl --no-block disable $(SYSTEMD_UNIT_NAMES) || true
systemctl --no-block stop $(SYSTEMD_UNIT_NAMES) $(QUADLET_UNIT_NAMES) || true
uninstall: pre-requisites uninstall-pre
systemctl disable $(SYSTEMD_MAIN_UNIT_NAMES) $(SYSTEMD_TIMER_NAMES) || true
systemctl stop $(SYSTEMD_UNIT_NAMES) $(QUADLET_UNIT_NAMES) || true
@run() { echo $$*; "$$@"; }; \
if [ -f /etc/tmpfiles.d/$(PROJECT_NAME).conf ]; then \
run systemd-tmpfiles --purge /etc/tmpfiles.d/$(PROJECT_NAME).conf; \
fi
rm -f $(TARGET_QUADLETS_FILES) $(TARGET_SYSTEMD_FILES) $(TARGET_CONFIG_FILES)
systemctl daemon-reload
$(MAKE) uninstall-post
tail-logs: pre-requisites
@run() { echo $$*; "$$@"; }; \
declare -a journalctl_args=( -f ); \
for unit in $(SYSTEMD_MAIN_UNIT_NAMES) $(QUADLET_UNIT_NAMES); do \
for unit in $$($(MAKE) -s units 2>/dev/null | sort -u); do \
journalctl_args+=( -u "$$unit" ); \
done; \
run journalctl "$${journalctl_args[@]}"
@ -133,7 +164,12 @@ butane:
$(TOP_LEVEL_DIR)/local.ign: $(TOP_LEVEL_DIR)/local.bu
butane --strict -o $@ $<
fcos.ign: fcos.bu $(TOP_LEVEL_DIR)/local.ign $(PROJECT_NAME).ign
# Because we don't know how to build this file, it is safer to declare it as phony and let the Makefile of the dependency handle it.
.PHONY: $(DEPENDENCIES_IGNITION_FILES)
$(DEPENDENCIES_IGNITION_FILES):
$(MAKE) -C $(dir $@) $(notdir $@)
fcos.ign: fcos.bu $(TOP_LEVEL_DIR)/local.ign $(PROJECT_NAME).ign $(DEPENDENCIES_IGNITION_FILES)
@run() { echo $$*; "$$@"; }; \
tmp=$$(mktemp -d /tmp/butane-XXXXXX); \
run cp $(filter %.ign,$^) $$tmp; \
@ -150,34 +186,57 @@ fcos.ign: fcos.bu $(TOP_LEVEL_DIR)/local.ign $(PROJECT_NAME).ign
qcow2=$$(ls -1ctr /var/lib/libvirt/images/library/fedora-coreos-*.qcow2 | tail -n 1) ; \
run mv "$$qcow2" $@
/var/lib/libvirt/images/$(PROJECT_NAME)/fcos.ign: fcos.ign
/var/lib/libvirt/images/fcos-$(PROJECT_NAME)/fcos.ign: fcos.ign
install -D -o root -g root -m 0644 $< $@
/var/lib/libvirt/images/$(PROJECT_NAME)/root.qcow2: /var/lib/libvirt/images/library/fedora-coreos.qcow2
/var/lib/libvirt/images/fcos-$(PROJECT_NAME)/root.qcow2: /var/lib/libvirt/images/library/fedora-coreos.qcow2
install -D -o root -g root -m 0644 $< $@
fcos-vm: pre-requisites clean-vm /var/lib/libvirt/images/$(PROJECT_NAME)/fcos.ign /var/lib/libvirt/images/$(PROJECT_NAME)/root.qcow2
virt-install --name=$(PROJECT_NAME) --import --noautoconsole \
/srv/fcos-$(PROJECT_NAME):
install -d -o root -g root -m 0755 $@
fcos-vm: pre-requisites clean-vm /var/lib/libvirt/images/fcos-$(PROJECT_NAME)/fcos.ign /var/lib/libvirt/images/fcos-$(PROJECT_NAME)/root.qcow2 /srv/fcos-$(PROJECT_NAME)
virt-install --name=fcos-$(PROJECT_NAME) --import --noautoconsole \
--ram=4096 --vcpus=2 --os-variant=fedora-coreos-stable \
--disk path=/var/lib/libvirt/images/$(PROJECT_NAME)/root.qcow2,format=qcow2,size=50 \
--qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=/var/lib/libvirt/images/$(PROJECT_NAME)/fcos.ign" \
--disk path=/var/lib/libvirt/images/fcos-$(PROJECT_NAME)/root.qcow2,format=qcow2,size=50 \
--qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=/var/lib/libvirt/images/fcos-$(PROJECT_NAME)/fcos.ign" \
--network network=default,model=virtio \
--console=pty,target.type=virtio --serial=pty --graphics=none --boot=uefi
--console=pty,target.type=virtio --serial=pty --graphics=none --boot=uefi \
--memorybacking=access.mode=shared,source.type=memfd \
--filesystem=type=mount,accessmode=passthrough,driver.type=virtiofs,driver.queue=1024,source.dir=/srv/fcos-$(PROJECT_NAME),target.dir=data
clean-vm: pre-requisites
virsh destroy $(PROJECT_NAME) || true
virsh undefine $(PROJECT_NAME) --nvram || true
rm -rf /var/lib/libvirt/images/$(PROJECT_NAME)
virsh destroy fcos-$(PROJECT_NAME) || true
virsh undefine fcos-$(PROJECT_NAME) --nvram || true
rm -rf /var/lib/libvirt/images/fcos-$(PROJECT_NAME)
rm -rf /srv/fcos-$(PROJECT_NAME)
console: pre-requisites
@while sleep 2; do virsh console $(PROJECT_NAME); done
@while sleep 2; do virsh console fcos-$(PROJECT_NAME); echo -e "Disconnected. Reconnecting in 2 seconds...\nPress Ctrl-C to abort.\n"; done
units-pre::
@for dep in $(DEPENDENCIES); do \
$(MAKE) -s -C $(TOP_LEVEL_DIR)/$$dep units 2>/dev/null; \
done
units: units-pre
@for unit in $(SYSTEMD_UNIT_NAMES) $(QUADLET_UNIT_NAMES); do echo "$$unit"; done
clean: pre-requisites
clean-pre::
@run() { echo $$*; "$$@"; }; \
for dep in $(DEPENDENCIES); do \
run $(MAKE) -C $(TOP_LEVEL_DIR)/$$dep clean; \
done
clean-post::
clean: clean-pre pre-requisites
rm -f *.butane
@run() { echo $$*; "$$@"; }; \
read -p "This will remove all data of '$(PROJECT_NAME)'. Are you sure? (only 'yes' is accepted) " ans; \
if [ "$$ans" = "yes" ] || [ "$$ans" = "YES" ]; then \
run rm -rf /var/lib/quadlets/$(PROJECT_NAME)/ /var/run/quadlets/$(PROJECT_NAME)/ /etc/quadlets/$(PROJECT_NAME)/; \
else \
echo "Aborted."; exit 1; \
if [ "$(I_KNOW_WHAT_I_AM_DOING)" != "yes" ]; then \
read -p "This will remove all data of '$(PROJECT_NAME)'. Are you sure? (only 'yes' is accepted) " ans; \
if [ "$$ans" != "yes" ] && [ "$$ans" != "YES" ]; then \
echo "Aborted."; exit 1; \
fi; \
fi
rm -rf /var/lib/quadlets/$(PROJECT_NAME)/ /var/run/quadlets/$(PROJECT_NAME)/ /etc/quadlets/$(PROJECT_NAME)/
$(MAKE) clean-post

1
README.md

@ -7,6 +7,7 @@ This repository gathers all the recipes (hence the name "Cookbook") to deploy Op
- [nginx](nginx/): starts Nginx, content is initialized / updated from a GIT repository
- [postgresql](postgresql/): starts a PostgreSQL server, handles automated major upgrades, periodic backup and initialization of the database from the last backup.
- [nextcloud](nextcloud/): starts a Nextcloud server with all its dependencies, handles automated upgrades.
## License

8
generate-butane-spec.sh

@ -35,9 +35,9 @@ for file in $(find "$TARGET_CHROOT" \! -type d); do
group:
id: $(stat -c '%g' "$file")
contents:
compression: gzip
source: data:;base64,$(gzip -c "$file" | base64 -w0)
inline: |
EOF
sed 's/^/ /; $s/$/\n/' "$file"
done
cat <<"EOF"
directories:
@ -45,8 +45,10 @@ EOF
for dir in $(find "$TARGET_CHROOT" -type d); do
rel_path="${dir#$TARGET_CHROOT}"
if [[ "$rel_path" != "/var/lib/quadlets/"* ]] && [[ "$rel_path" != "/etc/quadlets/"* ]] \
&& [[ "$rel_path" != "/etc/systemd/system/"* ]] && [[ "$rel_path" != "/etc/containers/systemd/"* ]]; then
&& [[ "$rel_path" != "/etc/systemd/system/"* ]] && [[ "$rel_path" != "/etc/containers/systemd/"* ]] \
&& [[ "$rel_path" != "/etc/tmpfiles.d/"* ]] && [[ "$rel_path" != "/etc/sysctl.d/"* ]]; then
# Skip files & directories that are already part of the CoreOS default installation
continue
fi
cat <<EOF

80
nextcloud/Makefile

@ -1,35 +1,89 @@
##
## Makefile for PostgreSQL quadlet
##
# Project dependencies: Nextcloud also depends on virtiofs for data storage but is a transitive dependency via postgresql
DEPENDENCIES = postgresql
# Nextcloud quadlet is mapped to the 10008 user (nextcloud) and 10000 group (itix-svc)
PROJECT_UID = 10008
PROJECT_GID = 10000
TOP_LEVEL_DIR := ..
include $(TOP_LEVEL_DIR)/Makefile.common
.PHONY: test
.PHONY: test test-set-nextcloud-major
NEXTCLOUD_MAJOR_START ?= 25
NEXTCLOUD_MAJOR_LAST ?= 31
test-set-nextcloud-major:
sed -i 's/^NEXTCLOUD_MAJOR=.*/NEXTCLOUD_MAJOR=$(NEXTCLOUD_MAJOR_START)/' config/config.env
$(TARGET_CHROOT)/var/lib/quadlets/nextcloud/redis:
install -m 0700 -o 0 -g 0 -d $@
install -m 0700 -o 10008 -g 10000 -d $@
$(TARGET_CHROOT)/var/lib/quadlets/nextcloud/data $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/config:
install -m 0700 -o 82 -g 82 -d $@
install -m 0700 -o 10008 -g 10000 -d $@
install-var: $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/redis $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/data $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/config
$(TARGET_CHROOT)/etc/quadlets/nextcloud/www.conf: config/www.conf
install -m 0755 -o 10008 -g 10000 -D $< $@
# Nextcloud depends on the PostgreSQL quadlets
.PHONY: $(TOP_LEVEL_DIR)/postgresql/postgresql.ign
$(TOP_LEVEL_DIR)/postgresql/postgresql.ign:
make -C $(TOP_LEVEL_DIR)/postgresql postgresql.ign
fcos.ign: $(TOP_LEVEL_DIR)/postgresql/postgresql.ign
install-var: $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/redis $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/data $(TARGET_CHROOT)/var/lib/quadlets/nextcloud/config
test: uninstall clean install
test:
@run() { echo $$*; "$$@"; }; \
echo "Running Nextcloud tests..."; \
echo "Running Nextcloud upgrade test..."; \
set -Eeuo pipefail; \
source config/config.env; \
echo "Waiting for Nextcloud to be ready..."; \
until run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/status.php" &> /dev/null; do \
echo "Nextcloud is not ready yet. Retrying in 5 seconds..."; \
sleep 5; \
done; \
echo "Nextcloud is ready!"; \
echo "Uploading file..."; \
run curl -X PUT -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" --data-binary @tests/witness.txt "$${OVERWRITECLIURL}/remote.php/webdav/witness.txt"; \
echo "Verifying file upload..."; \
run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/remote.php/webdav/witness.txt" -o /tmp/witness.txt; \
if run cmp -s tests/witness.txt /tmp/witness.txt ; then \
echo "File upload verified successfully!"; \
rm -f /tmp/witness.txt; \
else \
echo "File upload verification failed!"; \
exit 1; \
fi
fi; \
exit 1; \
sleep 2; \
for (( ver=$(NEXTCLOUD_MAJOR_START); ver<$(NEXTCLOUD_MAJOR_LAST); ver++ )); do \
nextver=$$(($$ver + 1)); \
echo "Upgrading Nextcloud from $$ver to $$nextver..."; \
sed -i "s/^NEXTCLOUD_MAJOR=.*/NEXTCLOUD_MAJOR=$$nextver/" /etc/quadlets/nextcloud/config.env; \
systemctl stop nextcloud.target; \
sleep 1; \
systemctl start nextcloud.target; \
echo "Waiting for Nextcloud to be ready..."; \
until run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/status.php" &> /dev/null; do \
echo "Nextcloud is not ready yet. Retrying in 5 seconds..."; \
sleep 5; \
done; \
echo "Nextcloud is ready after upgrade!"; \
echo "Verifying file upload after upgrade..."; \
run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/remote.php/webdav/witness.txt" -o /tmp/witness.txt; \
if run cmp -s tests/witness.txt /tmp/witness.txt ; then \
echo "File upload verified successfully after upgrade!"; \
rm -f /tmp/witness.txt; \
else \
echo "File upload verification failed after upgrade!"; \
exit 1; \
fi; \
# Assert Nextcloud version after upgrade from status.php \
ACTUAL_VERSION=$$(run curl -X GET -sSf -u "$${NEXTCLOUD_ADMIN_USER}:$${NEXTCLOUD_ADMIN_PASSWORD}" "$${OVERWRITECLIURL}/status.php" | grep -oP '"version":"\K[^"]+'); \
EXPECTED_VERSION_PREFIX="$$nextver."; \
if [[ "$$ACTUAL_VERSION" == "$$EXPECTED_VERSION_PREFIX"* ]]; then \
echo "Nextcloud version $$ACTUAL_VERSION verified successfully after upgrade to $$nextver!"; \
else \
echo "Nextcloud version verification failed after upgrade to $$nextver! Expected prefix: $$EXPECTED_VERSION_PREFIX, Actual: $$ACTUAL_VERSION"; \
exit 1; \
fi; \
done; \
echo "Nextcloud upgrade tests completed successfully."

5
nextcloud/config/config.env

@ -2,6 +2,11 @@
## Nextcloud Configuration Environment Variables
##
# Major versions of all components
NEXTCLOUD_MAJOR=31
REDIS_MAJOR=8
NGINX_MAJOR=1.20
# Nextcloud domain configuration
NEXTCLOUD_TRUSTED_DOMAINS=localhost
OVERWRITEHOST=localhost

4
nextcloud/config/custom-noinit.sh

@ -0,0 +1,4 @@
#!/bin/sh
echo "This container is not intented to perform initialization or upgrade tasks."
exit 1

14
nextcloud/config/custom-post.sh

@ -0,0 +1,14 @@
#!/bin/sh
set -Eeuo pipefail
# Disable maintenance mode
echo "Disabling maintenance mode..."
php occ maintenance:mode --off
# Run database optimizations
echo "Adding missing database indices..."
php occ db:add-missing-indices || true
echo "Converting database columns to big int..."
php occ db:convert-filecache-bigint --no-interaction || true

7
nextcloud/config/custom-pre.sh

@ -0,0 +1,7 @@
#!/bin/sh
set -Eeuo pipefail
# Enable maintenance mode
echo "Enabling maintenance mode..."
php occ maintenance:mode --on || true

2
nextcloud/config/nginx.conf

@ -41,7 +41,7 @@ http {
}
server {
listen 80;
listen 8080;
# set max upload size
client_max_body_size 10G;

0
nextcloud/config/redis-session.ini

9
nextcloud/config/redis.conf

@ -1,3 +1,10 @@
requirepass nextcloud
# Network settings
port 6379
bind 127.0.0.1
# Set a password for Redis
requirepass nextcloud
# Hybrid mode (AOF + RDB)
appendonly yes
aof-use-rdb-preamble yes

1
nextcloud/fcos.bu

@ -3,6 +3,7 @@ version: 1.4.0
ignition:
config:
merge:
- local: virtiofs.ign
- local: nextcloud.ign
- local: postgresql.ign
- local: local.ign

37
nextcloud/nextcloud-app.container

@ -1,25 +1,32 @@
[Unit]
Description=Nextcloud PHP-FPM Application
Documentation=https://hub.docker.com/_/nextcloud/
After=network.target nextcloud-redis.service postgresql-server.service
Requires=nextcloud-redis.service postgresql-server.service
# Require initialization to complete first
Requires=nextcloud-redis.service
After=nextcloud-redis.service
After=network.target nextcloud-redis.service postgresql-server.service nextcloud-init.service nextcloud-upgrade.service var-lib-virtiofs-data.mount
Requires=nextcloud-redis.service postgresql-server.service nextcloud-init.service nextcloud-upgrade.service var-lib-virtiofs-data.mount
# Only start if Nextcloud has been configured
ConditionPathExists=/etc/quadlets/nextcloud/config.env
# and initialized (config.php exists)
ConditionPathExists=/var/lib/virtiofs/data/nextcloud/config/config.php
# Start/stop this unit when the target is started/stopped
PartOf=nextcloud.target
[Container]
ContainerName=nextcloud-app
Image=docker.io/library/nextcloud:31-fpm-alpine
Image=docker.io/library/nextcloud:${NEXTCLOUD_MAJOR}-fpm-alpine
# No need for root privileges
User=www-data
Group=www-data
# Fix the UID/GID of the PHP-FPM daemon
User=82:82
# UID/GID mapping to map the www-data (82) user inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:82
UIDMap=82:10008:1
UIDMap=83:1000083:65453
GIDMap=0:1000000:82
GIDMap=82:10000:1
GIDMap=83:1000083:65453
# Network configuration
Network=host
@ -29,9 +36,10 @@ AddCapability=CAP_NET_BIND_SERVICE
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# Volume mounts
Volume=/var/lib/quadlets/nextcloud/data:/var/www/html:z
Volume=/var/lib/quadlets/nextcloud/config/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z
Volume=/var/lib/quadlets/nextcloud/config/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z
Volume=/var/lib/virtiofs/data/nextcloud:/var/www/html:z
Volume=/etc/quadlets/nextcloud/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z
Volume=/run/quadlets/nextcloud/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z
Volume=/etc/quadlets/nextcloud/custom-noinit.sh:/docker-entrypoint-hooks.d/pre-installation/custom.sh:z,ro
# Health check (equivalent to readiness probe)
HealthCmd=nc -z localhost 9000
@ -46,9 +54,8 @@ RestartSec=10
TimeoutStartSec=600
TimeoutStopSec=30
# Skaffold filesystem + fix permissions
ExecStartPre=/bin/bash -Eeuo pipefail -c 'install -m 0700 -o 82 -g 82 /etc/quadlets/nextcloud/www.conf /var/lib/quadlets/nextcloud/config/www.conf ; \
install -m 0700 -o 82 -g 82 /etc/quadlets/nextcloud/redis-session.ini /var/lib/quadlets/nextcloud/config/redis-session.ini'
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# Wait for PostgreSQL to be ready on localhost
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1'

60
nextcloud/nextcloud-cron.container

@ -0,0 +1,60 @@
[Unit]
Description=Nextcloud Application - Cron Job
Documentation=https://hub.docker.com/_/nextcloud/
After=nextcloud-redis.service postgresql-server.service var-lib-virtiofs-data.mount
Requires=nextcloud-redis.service postgresql-server.service var-lib-virtiofs-data.mount
# Only start if Nextcloud has been configured
ConditionPathExists=/etc/quadlets/nextcloud/config.env
# and initialized (config.php exists)
ConditionPathExists=/var/lib/virtiofs/data/nextcloud/config/config.php
[Container]
ContainerName=nextcloud-cron-job
Image=docker.io/library/nextcloud:${NEXTCLOUD_MAJOR}-fpm-alpine
# No need for root privileges
User=www-data
Group=www-data
# UID/GID mapping to map the www-data (82) user inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:82
UIDMap=82:10008:1
UIDMap=83:1000083:65453
GIDMap=0:1000000:82
GIDMap=82:10000:1
GIDMap=83:1000083:65453
# Network configuration
Network=host
# Environment variables from config
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This is specific to the cron container
Entrypoint=php
Exec=-f /var/www/html/cron.php
# Volume mounts
Volume=/var/lib/virtiofs/data/nextcloud:/var/www/html:z
Volume=/etc/quadlets/nextcloud/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z
Volume=/run/quadlets/nextcloud/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z
[Service]
Restart=no
TimeoutStartSec=600
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This container is a job - run once to completion
Type=oneshot
# Wait for PostgreSQL to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1'
# Wait for Redis to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/6379; then echo "Waiting for Redis to be available..."; sleep 5; else exit 0; fi; done; exit 1'
[Install]
WantedBy=nextcloud-cron.timer

12
nextcloud/nextcloud-cron.timer

@ -0,0 +1,12 @@
[Unit]
Description=Nextcloud Application - Initialization
Documentation=https://hub.docker.com/_/nextcloud/
PartOf=nextcloud.target
[Timer]
OnActiveSec=15min
RandomizedDelaySec=15s
DeferReactivation=true
[Install]
WantedBy=nextcloud.target

64
nextcloud/nextcloud-init.container

@ -0,0 +1,64 @@
[Unit]
Description=Nextcloud Application - Initialization
Documentation=https://hub.docker.com/_/nextcloud/
After=network.target nextcloud-redis.service postgresql-server.service var-lib-virtiofs-data.mount
Before=nextcloud-app.service
Requires=nextcloud-redis.service postgresql-server.service var-lib-virtiofs-data.mount
# Only start if Nextcloud has been configured
ConditionPathExists=/etc/quadlets/nextcloud/config.env
# and NOT initialized (config.php does NOT exist)
ConditionPathExists=!/var/lib/virtiofs/data/nextcloud/config/config.php
# Start/stop this unit when the target is started/stopped
PartOf=nextcloud.target
[Container]
ContainerName=nextcloud-init-job
Image=docker.io/library/nextcloud:${NEXTCLOUD_MAJOR}-fpm-alpine
# No need for root privileges
User=www-data
Group=www-data
# UID/GID mapping to map the www-data (82) user inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:82
UIDMap=82:10008:1
UIDMap=83:1000083:65453
GIDMap=0:1000000:82
GIDMap=82:10000:1
GIDMap=83:1000083:65453
# Network configuration
Network=host
# Environment variables from config
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This is specific to the initialization container
Environment=NEXTCLOUD_UPDATE=1
Exec=/bin/true
# Volume mounts
Volume=/var/lib/virtiofs/data/nextcloud:/var/www/html:z
Volume=/etc/quadlets/nextcloud/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z
Volume=/run/quadlets/nextcloud/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z
[Service]
Restart=no
TimeoutStartSec=600
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This container is a job - run once to completion
Type=oneshot
# Wait for PostgreSQL to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1'
# Wait for Redis to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/6379; then echo "Waiting for Redis to be available..."; sleep 5; else exit 0; fi; done; exit 1'
[Install]
WantedBy=nextcloud.target

26
nextcloud/nextcloud-nginx.container

@ -1,8 +1,8 @@
[Unit]
Description=Nextcloud Nginx Reverse Proxy
Documentation=https://hub.docker.com/r/nginxinc/nginx-unprivileged/
After=network.target nextcloud-app.service
Requires=nextcloud-app.service
After=network.target nextcloud-app.service var-lib-virtiofs-data.mount
Requires=nextcloud-app.service var-lib-virtiofs-data.mount
# Only start if Nextcloud has been configured
ConditionPathExists=/etc/quadlets/nextcloud/config.env
@ -12,24 +12,33 @@ PartOf=nextcloud.target
[Container]
ContainerName=nextcloud-nginx
Image=docker.io/nginxinc/nginx-unprivileged:1.20-alpine
Image=docker.io/nginxinc/nginx-unprivileged:${NGINX_MAJOR}-alpine
# Network configuration
Network=host
AddCapability=CAP_NET_BIND_SERVICE
# Run with the same UID/GID as PHP-FPM
User=82:82
# No need for root privileges
User=nginx
Group=nginx
# UID/GID mapping to map the nginx (101) user inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:101
UIDMap=101:10008:1
UIDMap=102:1000102:65434
GIDMap=0:1000000:101
GIDMap=101:10000:1
GIDMap=102:1000102:65434
# Volume mounts
Volume=/var/lib/quadlets/nextcloud/data:/var/www/html:z
Volume=/var/lib/virtiofs/data/nextcloud:/var/www/html:z
Volume=/etc/quadlets/nextcloud/nginx.conf:/etc/nginx/nginx.conf:ro
# Health check (equivalent to readiness probe)
HealthCmd=curl -sSfL http://localhost/status.php
HealthInterval=30s
HealthTimeout=10s
HealthStartPeriod=30s
HealthStartPeriod=10s
HealthRetries=3
[Service]
@ -38,5 +47,8 @@ RestartSec=5
TimeoutStartSec=300
TimeoutStopSec=30
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
[Install]
WantedBy=nextcloud.target

21
nextcloud/nextcloud-redis.container

@ -1,6 +1,6 @@
[Unit]
Description=Redis Cache for Nextcloud
Documentation=https://redis.io/
Documentation=https://hub.docker.com/_/redis/
After=network.target
# Only start if Nextcloud has been configured
@ -11,7 +11,7 @@ PartOf=nextcloud.target
[Container]
ContainerName=nextcloud-redis
Image=docker.io/library/redis:8-alpine
Image=docker.io/library/redis:${REDIS_MAJOR}-alpine
# Network configuration
Network=host
@ -19,6 +19,18 @@ Network=host
# Redis configuration with authentication
Exec=redis-server /usr/local/etc/redis/redis.conf
# No need for root privileges
User=redis
Group=redis
# UID/GID mapping to map the redis (999) user / redis (1000) group inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:999
UIDMap=999:10008:1
UIDMap=1000:1001000:64536
GIDMap=0:1000000:1000
GIDMap=1000:10000:1
GIDMap=1001:1001001:64535
# Environment variables
Environment=REDISCLI_AUTH=${REDIS_HOST_PASSWORD}
@ -27,7 +39,7 @@ Volume=/var/lib/quadlets/nextcloud/redis:/data:Z
Volume=/etc/quadlets/nextcloud/redis.conf:/usr/local/etc/redis/redis.conf:ro
# Health check
HealthCmd=redis-cli ping -t 5 | grep -q PONG
HealthCmd=redis-cli -t 5 ping | grep -qFx PONG
HealthInterval=30s
HealthTimeout=5s
HealthStartPeriod=10s
@ -39,5 +51,8 @@ RestartSec=5
TimeoutStartSec=300
TimeoutStopSec=30
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
[Install]
WantedBy=nextcloud.target

66
nextcloud/nextcloud-upgrade.container

@ -0,0 +1,66 @@
[Unit]
Description=Nextcloud Application - Upgrade
Documentation=https://hub.docker.com/_/nextcloud/
After=network.target nextcloud-redis.service postgresql-server.service
Before=nextcloud-app.service
Requires=nextcloud-redis.service postgresql-server.service
# Only start if Nextcloud has been configured
ConditionPathExists=/etc/quadlets/nextcloud/config.env
# and initialized (config.php exists)
ConditionPathExists=/var/lib/virtiofs/data/nextcloud/config/config.php
# Start/stop this unit when the target is started/stopped
PartOf=nextcloud.target
[Container]
ContainerName=nextcloud-upgrade-to-${NEXTCLOUD_MAJOR}-job
Image=docker.io/library/nextcloud:${NEXTCLOUD_MAJOR}-fpm-alpine
# No need for root privileges
User=www-data
Group=www-data
# UID/GID mapping to map the www-data (82) user inside the container to arbitrary user 10008 / group 10000 on the host
UIDMap=0:1000000:82
UIDMap=82:10008:1
UIDMap=83:1000083:65453
GIDMap=0:1000000:82
GIDMap=82:10000:1
GIDMap=83:1000083:65453
# Network configuration
Network=host
# Environment variables from config
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This is specific to the upgrade container
Environment=NEXTCLOUD_UPDATE=1
Exec=/bin/true
Volume=/etc/quadlets/nextcloud/custom-pre.sh:/docker-entrypoint-hooks.d/pre-upgrade/custom.sh:z,ro
Volume=/etc/quadlets/nextcloud/custom-post.sh:/docker-entrypoint-hooks.d/post-upgrade/custom.sh:z,ro
# Volume mounts
Volume=/var/lib/virtiofs/data/nextcloud:/var/www/html:z
Volume=/etc/quadlets/nextcloud/www.conf:/usr/local/etc/php-fpm.d/www.conf:Z
Volume=/run/quadlets/nextcloud/redis-session.ini:/usr/local/etc/php/conf.d/redis-session.ini:Z
[Service]
Restart=no
TimeoutStartSec=600
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/nextcloud/config.env
# This container is a job - run once to completion
Type=oneshot
# Wait for PostgreSQL to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/5432; then echo "Waiting for PostgreSQL to be available..."; sleep 5; else exit 0; fi; done; exit 1'
# Wait for Redis to be ready
ExecStartPre=/bin/sh -c 'exec 2>/dev/null; for try in $(seq 0 12); do if ! /bin/true 5<> /dev/tcp/127.0.0.1/6379; then echo "Waiting for Redis to be available..."; sleep 5; else exit 0; fi; done; exit 1'
[Install]
WantedBy=nextcloud.target

5
nextcloud/nextcloud.target

@ -1,8 +1,9 @@
[Unit]
Description=Nextcloud Service Target
Documentation=man:systemd.target(5)
Requires=postgresql.target nextcloud-redis.service nextcloud-app.service nextcloud-nginx.service
After=postgresql.target nextcloud-redis.service nextcloud-app.service nextcloud-nginx.service
Requires=postgresql.target nextcloud-redis.service nextcloud-nginx.service nextcloud-app.service nextcloud-init.service nextcloud-upgrade.service nextcloud-cron.timer
After=postgresql.target nextcloud-redis.service nextcloud-nginx.service nextcloud-app.service nextcloud-init.service nextcloud-upgrade.service
Before=nextcloud-cron.timer
# Allow isolation - can stop/start this target independently
AllowIsolate=yes

3
nextcloud/sysctl.d/nextcloud.conf

@ -0,0 +1,3 @@
# Redis recommended settings for stability
# See https://redis.io/docs/latest/develop/get-started/faq/#background-saving-fails-with-a-fork-error-on-linux
#vm.overcommit_memory=1

3
nextcloud/tmpfiles.d/nextcloud.conf

@ -0,0 +1,3 @@
d$ /run/quadlets/nextcloud 0700 10008 10000 -
f+$ /run/quadlets/nextcloud/redis-session.ini 0600 10008 10000 -
d$ /var/lib/virtiofs/data/nextcloud 0700 10008 10000 -

2
nginx/nginx-update.container

@ -40,4 +40,4 @@ EnvironmentFile=/etc/quadlets/nginx/config.env
Type=oneshot
[Install]
WantedBy=nginx.target
WantedBy=nginx.target nginx-update.timer

11
nginx/nginx-update.timer

@ -0,0 +1,11 @@
[Unit]
Description=Update Nginx root directory from a Git repository - Timer
Documentation=https://hub.docker.com/r/alpine/git
PartOf=nginx.target
[Timer]
OnActiveSec=15min
RandomizedDelaySec=15s
[Install]
WantedBy=nginx.target

36
postgresql/Makefile

@ -1,26 +1,34 @@
##
## Makefile for PostgreSQL quadlet
##
# Project dependencies
DEPENDENCIES = virtiofs
# PostgreSQL quadlet is mapped to the 10004 user (postgres) and 10000 group (itix-svc)
PROJECT_UID = 10004
PROJECT_GID = 10000
# Include common Makefile
TOP_LEVEL_DIR := ..
include $(TOP_LEVEL_DIR)/Makefile.common
.PHONY: test test-set-pgmajor
.PHONY: test test-set-pgmajor install-var
PG_MAJOR_START ?= 14
PG_MAJOR_LAST ?= 18
test-set-pgmajor:
sed -i 's/^PG_MAJOR=.*/PG_MAJOR=$(PG_MAJOR_START)/' config/config.env
$(TARGET_CHROOT)/var/lib/quadlets/postgresql/backup $(TARGET_CHROOT)/var/lib/quadlets/postgresql $(TARGET_CHROOT)/var/run/quadlets/postgresql:
install -m 0700 -o 70 -g 70 -d $@
install-var: $(TARGET_CHROOT)/var/run/quadlets/postgresql $(TARGET_CHROOT)/var/lib/quadlets/postgresql $(TARGET_CHROOT)/var/lib/quadlets/postgresql/backup
# Integration tests for PostgreSQL quadlet: backup, restore + major version upgrade (14 to 18)
test: uninstall clean test-set-pgmajor install
@echo "Running PostgreSQL integration tests..."; \
set -Eeuo pipefail; \
sleep 2; \
echo "Creating a test database and a witness table..."; \
podman exec postgresql-server su postgres -c "createdb test"; \
podman exec postgresql-server su postgres -c "psql -U postgres -d test -c \"CREATE TABLE witness (id SERIAL PRIMARY KEY, version VARCHAR); INSERT INTO witness (version) SELECT version();\""; \
podman exec postgresql-server su postgres -c "psql -U postgres -d test -c \"SELECT * FROM witness;\""; \
podman exec postgresql-server createdb test; \
podman exec postgresql-server psql -U postgres -d test -c "CREATE TABLE witness (id SERIAL PRIMARY KEY, version VARCHAR); INSERT INTO witness (version) SELECT version();"; \
podman exec postgresql-server psql -U postgres -d test -c "SELECT * FROM witness;"; \
for (( ver=$(PG_MAJOR_START); ver<$(PG_MAJOR_LAST); ver++ )); do \
echo "Running a backup..."; \
nextver=$$(($$ver + 1)); \
@ -30,15 +38,15 @@ test: uninstall clean test-set-pgmajor install
rm -rf /var/lib/quadlets/postgresql/{$$ver,$$nextver,data,latest,.initialized}; \
echo "Restoring the backup to PostgreSQL $$ver..."; \
systemctl start postgresql.target; \
sleep 2; \
podman exec postgresql-server su postgres -c "psql -U postgres -d test -c \"SELECT * FROM witness;\""; \
sleep 5; \
podman exec postgresql-server psql -U postgres -d test -c "SELECT * FROM witness;"; \
echo "Testing upgrade from PostgreSQL $$ver to $$nextver..."; \
systemctl stop postgresql.target; \
sed -i "s/^PG_MAJOR=.*/PG_MAJOR=$$nextver/" /etc/quadlets/postgresql/config.env; \
systemctl start postgresql.target; \
sleep 2; \
sleep 5; \
echo "Inserting line into the witness table..."; \
podman exec postgresql-server su postgres -c "psql -U postgres -d test -c \"INSERT INTO witness (version) SELECT version();\""; \
podman exec postgresql-server psql -U postgres -d test -c "INSERT INTO witness (version) SELECT version();"; \
done; \
podman exec postgresql-server su postgres -c "psql -U postgres -d test -c \"SELECT * FROM witness;\""; \
podman exec postgresql-server psql -U postgres -d test -c "SELECT * FROM witness;"; \
echo "PostgreSQL upgrade tests completed."

2
postgresql/config/config.env

@ -5,5 +5,5 @@ POSTGRES_HOST_AUTH_METHOD=scram-sha-256
POSTGRES_INITDB_ARGS=--auth-host=scram-sha-256
POSTGRES_ARGS=-h 127.0.0.1
PGPORT=5432
PG_MAJOR=17
PG_MAJOR=14
POSTGRES_BACKUP_RETENTION=7

1
postgresql/fcos.bu

@ -3,5 +3,6 @@ version: 1.4.0
ignition:
config:
merge:
- local: virtiofs.ign
- local: postgresql.ign
- local: local.ign

21
postgresql/postgresql-backup.container

@ -1,8 +1,8 @@
[Unit]
Description=PostgreSQL Database Server - Backup
Documentation=https://hub.docker.com/_/postgres/
After=network.target postgresql-server.service
Requires=postgresql-server.service
After=network.target postgresql-server.service var-lib-virtiofs-data.mount
Requires=postgresql-server.service var-lib-virtiofs-data.mount
# Start/stop this unit when the target is started/stopped
PartOf=postgresql.target
@ -22,13 +22,26 @@ Entrypoint=/usr/local/bin/backup.sh
# No need for root privileges
User=postgres
Group=postgres
# UID/GID mapping to map the postgres (70) user inside the container to arbitrary user 10004 / group 10000 on the host
UIDMap=0:1000000:70
UIDMap=70:10004:1
UIDMap=71:1000071:65465
GIDMap=0:1000000:70
GIDMap=70:10000:1
GIDMap=71:1000071:65465
# Avoid issues with built-in volumes being created by root
PodmanArgs=--image-volume=ignore
# Volume mounts
Volume=/var/lib/quadlets/postgresql:/var/lib/postgresql:z
Volume=/var/lib/virtiofs/data/postgresql/backup:/var/lib/postgresql/backup:z
Volume=/etc/quadlets/postgresql/backup.sh:/usr/local/bin/backup.sh:z,ro
# Share /var/run/postgresql/ between containers in the pod for the Unix socket
Volume=/var/run/quadlets/postgresql:/var/run/postgresql:z
# Share /run/postgresql/ between containers in the pod for the Unix socket
Volume=/run/quadlets/postgresql:/var/run/postgresql:z
[Service]
Restart=no

11
postgresql/postgresql-backup.timer

@ -0,0 +1,11 @@
[Unit]
Description=PostgreSQL Database Backup Timer
Documentation=https://hub.docker.com/_/postgresql/
PartOf=postgresql.target
[Timer]
OnCalendar=daily
RandomizedDelaySec=15min
[Install]
WantedBy=postgresql.target

13
postgresql/postgresql-init.container

@ -34,9 +34,22 @@ Exec=${POSTGRES_ARGS}
# No need for root privileges
User=postgres
Group=postgres
# UID/GID mapping to map the postgres (70) user inside the container to arbitrary user 10004 / group 10000 on the host
UIDMap=0:1000000:70
UIDMap=70:10004:1
UIDMap=71:1000071:65465
GIDMap=0:1000000:70
GIDMap=70:10000:1
GIDMap=71:1000071:65465
# Avoid issues with built-in volumes being created by root
PodmanArgs=--image-volume=ignore
# Volume mounts
Volume=/var/lib/quadlets/postgresql:/var/lib/postgresql:z
Volume=/var/lib/virtiofs/data/postgresql/backup:/var/lib/postgresql/backup:z
Volume=/etc/quadlets/postgresql/init.sh:/usr/local/bin/init.sh:z,ro
Volume=/etc/quadlets/postgresql/init.sql:/docker-entrypoint-initdb.d/init.sql:z,ro

19
postgresql/postgresql-server.container

@ -39,6 +39,18 @@ Exec=${POSTGRES_ARGS}
# No need for root privileges
User=postgres
Group=postgres
# UID/GID mapping to map the postgres (70) user inside the container to arbitrary user 10004 / group 10000 on the host
UIDMap=0:1000000:70
UIDMap=70:10004:1
UIDMap=71:1000071:65465
GIDMap=0:1000000:70
GIDMap=70:10000:1
GIDMap=71:1000071:65465
# Avoid issues with built-in volumes being created by root
PodmanArgs=--image-volume=ignore
# Health check
HealthCmd=pg_isready -U $POSTGRES_USER -d $POSTGRES_DB -p $PGPORT
@ -47,8 +59,8 @@ HealthTimeout=10s
HealthStartPeriod=60s
HealthRetries=3
# Share /var/run/postgresql/ between containers in the pod for the Unix socket
Volume=/var/run/quadlets/postgresql:/var/run/postgresql:z
# Share /run/postgresql/ between containers in the pod for the Unix socket
Volume=/run/quadlets/postgresql:/var/run/postgresql:z
[Service]
Restart=always
@ -59,8 +71,5 @@ TimeoutStopSec=30
# These environment variables are sourced to be used by systemd in the Exec* commands
EnvironmentFile=/etc/quadlets/postgresql/config.env
# Skaffold filesystem + fix permissions
ExecStartPre=install -m 0700 -o 70 -g 70 -d /var/run/quadlets/postgresql
[Install]
WantedBy=postgresql.target

12
postgresql/postgresql-upgrade.container

@ -30,6 +30,18 @@ Entrypoint=/usr/local/bin/upgrade.sh
# No need for root privileges
User=postgres
Group=postgres
# UID/GID mapping to map the postgres (70) user inside the container to arbitrary user 10004 / group 10000 on the host
UIDMap=0:1000000:70
UIDMap=70:10004:1
UIDMap=71:1000071:65465
GIDMap=0:1000000:70
GIDMap=70:10000:1
GIDMap=71:1000071:65465
# Avoid issues with built-in volumes being created by root
PodmanArgs=--image-volume=ignore
# Set PostgreSQL command line arguments
Exec=${POSTGRES_ARGS}

3
postgresql/postgresql.target

@ -1,8 +1,9 @@
[Unit]
Description=PostgreSQL Service Target
Documentation=man:systemd.target(5)
Requires=postgresql-server.service postgresql-upgrade.service postgresql-init.service postgresql-set-major.service
Requires=postgresql-server.service postgresql-upgrade.service postgresql-init.service postgresql-set-major.service postgresql-backup.timer
After=postgresql-server.service postgresql-upgrade.service postgresql-init.service postgresql-set-major.service
Before=postgresql-backup.timer
# Allow isolation - can stop/start this target independently
AllowIsolate=yes

3
postgresql/sysctl.d/postgresql.conf

@ -0,0 +1,3 @@
# PostgreSQL recommended settings for stability
#vm.overcommit_memory=2
#vm.overcommit_ratio=80

3
postgresql/tmpfiles.d/postgresql.conf

@ -0,0 +1,3 @@
d$ /run/quadlets/postgresql 0700 10004 10000 -
d$ /var/lib/virtiofs/data/postgresql 0700 10004 10000 -
d$ /var/lib/virtiofs/data/postgresql/backup 0700 10004 10000 -

3
virtiofs/Makefile

@ -0,0 +1,3 @@
TOP_LEVEL_DIR := ..
include $(TOP_LEVEL_DIR)/Makefile.common
SYSTEMD_MAIN_UNIT_NAMES := var-lib-virtiofs-data.mount

22
virtiofs/backup-virtiofs.service

@ -0,0 +1,22 @@
[Unit]
Description=Backup the "data" virtio filesystems
Before=var-lib-virtiofs-data.mount
RequiresMountsFor=/var
ConditionPathIsMountPoint=!/var/lib/virtiofs/data
# Unless DefaultDependencies= is set to false, service units will implicitly
# have dependencies of type Requires= and After= on basic.target as well as
# dependencies of type Conflicts= and Before= on shutdown.target.
#
# So, we need to set DefaultDependencies to "no" to break the ordering cycle.
DefaultDependencies=no
# Only run on Fedora CoreOS
ConditionOSRelease=ID=fedora
ConditionOSRelease=VARIANT_ID=coreos
[Service]
Type=oneshot
UMask=077
ExecStart=/bin/bash -Eeuo pipefail -c 'if grep -q "data /var/lib/virtiofs/data" /proc/mounts; then echo "ERROR: /var/lib/virtiofs/data is mounted!"; exit 1; else tar -cf /var/lib/private/virtiofs.tar -C /var/lib/virtiofs/data .; fi'
RemainAfterExit=yes

14
virtiofs/fcos.bu

@ -0,0 +1,14 @@
variant: fcos
version: 1.4.0
ignition:
config:
merge:
- local: virtiofs.ign
- local: local.ign
storage:
files:
- path: "/var/lib/virtiofs/data/witness.txt"
mode: 0644
contents:
inline: |
Hello, World!

16
virtiofs/restore-virtiofs.service

@ -0,0 +1,16 @@
[Unit]
Description=Restore the "data" virtio filesystems
After=var-lib-virtiofs-data.mount
Requires=var-lib-virtiofs-data.mount
RequiresMountsFor=/var
ConditionPathIsMountPoint=/var/lib/virtiofs/data
ConditionPathExists=/var/lib/private/virtiofs.tar
# Only run on Fedora CoreOS
ConditionOSRelease=ID=fedora
ConditionOSRelease=VARIANT_ID=coreos
[Service]
Type=oneshot
ExecStart=/bin/bash -Eeuo pipefail -c 'if ! grep -q "data /var/lib/virtiofs/data" /proc/mounts; then echo "ERROR: /var/lib/virtiofs/data is NOT mounted!"; exit 1; else tar -xf /var/lib/private/virtiofs.tar -C /var/lib/virtiofs/data; rm -f /var/lib/private/virtiofs.tar; fi'
RemainAfterExit=yes

2
virtiofs/tmpfiles.d/virtiofs.conf

@ -0,0 +1,2 @@
d /var/lib/virtiofs 0755 0 0 -
d /var/lib/virtiofs/data 0755 0 0 -

19
virtiofs/var-lib-virtiofs-data.mount

@ -0,0 +1,19 @@
[Unit]
Description=Mount the "data" virtio filesystem
After=backup-virtiofs.service
Requires=backup-virtiofs.service
Before=restore-virtiofs.service
Wants=restore-virtiofs.service
# Only run on Fedora CoreOS
ConditionOSRelease=ID=fedora
ConditionOSRelease=VARIANT_ID=coreos
[Mount]
What=data
Where=/var/lib/virtiofs/data
Type=virtiofs
Options=context=system_u:object_r:container_file_t:s0
[Install]
WantedBy=local-fs.target
Loading…
Cancel
Save