diff --git a/cookbooks/quay/Makefile b/cookbooks/quay/Makefile index 8ac602c..97dcff7 100644 --- a/cookbooks/quay/Makefile +++ b/cookbooks/quay/Makefile @@ -6,7 +6,7 @@ PROJECT_UID = 10026 PROJECT_GID = 10000 -DEPENDENCIES = postgresql lego nftables +DEPENDENCIES = postgresql redis lego nftables # Include common Makefile include ../../scripts/common.mk diff --git a/cookbooks/quay/config/examples/app/config.yaml b/cookbooks/quay/config/examples/app/config.yaml index 4b9c458..42ff106 100644 --- a/cookbooks/quay/config/examples/app/config.yaml +++ b/cookbooks/quay/config/examples/app/config.yaml @@ -2,8 +2,8 @@ # Copy this file to /etc/quadlets/quay/app/config.yaml and customize it. # # For more information on configuration options, see: -# - the json schema of the config tool: https://github.com/quay/app/blob/master/config-tool/utils/generate/schema.json -# - the json schema of the Python core: https://github.com/quay/app/blob/master/util/config/schema.py +# - the json schema of the config tool: https://github.com/quay/quay/blob/master/config-tool/utils/generate/schema.json +# - the json schema of the Python core: https://github.com/quay/quay/blob/master/util/config/schema.py # The URL at which Quay is accessible, without the scheme. SERVER_HOSTNAME: localhost diff --git a/cookbooks/quay/other/redis/quay.acl b/cookbooks/quay/other/redis/quay.acl new file mode 100644 index 0000000..b3fa101 --- /dev/null +++ b/cookbooks/quay/other/redis/quay.acl @@ -0,0 +1 @@ +user default on >quay ~quay:* +@all -@dangerous diff --git a/cookbooks/quay/quay-app.container b/cookbooks/quay/quay-app.container index 932d3f4..69483e6 100644 --- a/cookbooks/quay/quay-app.container +++ b/cookbooks/quay/quay-app.container @@ -1,8 +1,8 @@ [Unit] Description=Quay Container Registry Application Documentation=https://docs.projectquay.io/ -After=network.target quay-redis.service quay-init-certificate.service var-lib-virtiofs-data.mount -Requires=quay-redis.service quay-init-certificate.service var-lib-virtiofs-data.mount +After=network.target redis-server.service quay-init-certificate.service var-lib-virtiofs-data.mount +Requires=redis-server.service quay-init-certificate.service var-lib-virtiofs-data.mount # Only start if Quay has been configured ConditionPathExists=/etc/quadlets/quay/app/config.yaml diff --git a/cookbooks/quay/quay-redis.container b/cookbooks/quay/quay-redis.container deleted file mode 100644 index ba23f85..0000000 --- a/cookbooks/quay/quay-redis.container +++ /dev/null @@ -1,49 +0,0 @@ -[Unit] -Description=Redis cache for Quay -Documentation=https://hub.docker.com/_/redis -After=network.target var-lib-virtiofs-data.mount -Requires=var-lib-virtiofs-data.mount - -# Only start if Redis has been configured -ConditionPathExists=/etc/quadlets/quay/redis/redis.env -ConditionPathExists=/etc/quadlets/quay/redis/redis.conf - -# Start/stop this unit when the target is started/stopped -PartOf=quay.target - -[Container] -ContainerName=quay-redis -Image=quay-redis.image - -# Network configuration -Network=host - -# Redis configuration with authentication -Exec=redis-server /usr/local/etc/redis/redis.conf - -# No need for root privileges -User=10026 -Group=10000 - -# Storage -Volume=/var/lib/virtiofs/data/quay/redis:/data:Z -Volume=/etc/quadlets/quay/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro,Z - -# Health check -HealthCmd=redis-cli -t 5 ping | grep -qFx PONG -HealthInterval=30s -HealthTimeout=5s -HealthStartPeriod=10s -HealthRetries=3 - -[Service] -Restart=always -RestartSec=10 -TimeoutStartSec=300 -TimeoutStopSec=30 - -# These environment variables are sourced to be used by systemd in the Exec* commands -EnvironmentFile=/etc/quadlets/quay/redis/redis.env - -[Install] -WantedBy=quay.target diff --git a/cookbooks/quay/quay-redis.image b/cookbooks/quay/quay-redis.image deleted file mode 100644 index b483a9b..0000000 --- a/cookbooks/quay/quay-redis.image +++ /dev/null @@ -1,5 +0,0 @@ -[Unit] -Description=podman pull docker.io/library/redis:7 - -[Image] -Image=docker.io/library/redis:7 diff --git a/cookbooks/quay/quay.target b/cookbooks/quay/quay.target index 2f3d51d..08f4255 100644 --- a/cookbooks/quay/quay.target +++ b/cookbooks/quay/quay.target @@ -1,8 +1,8 @@ [Unit] Description=Quay Container Registry Target Documentation=https://docs.projectquay.io/ -Requires=quay-redis.service quay-clair.service quay-app.service quay-init-certificate.service -After=quay-redis.service quay-clair.service quay-app.service quay-init-certificate.service +Requires=postgresql.target redis.target quay-clair.service quay-app.service quay-init-certificate.service +After=postgresql.target redis.target quay-clair.service quay-app.service quay-init-certificate.service # Allow isolation - can stop/start this target independently AllowIsolate=yes diff --git a/cookbooks/redis/Makefile b/cookbooks/redis/Makefile new file mode 100644 index 0000000..d05127f --- /dev/null +++ b/cookbooks/redis/Makefile @@ -0,0 +1,18 @@ +## +## Makefile for Redis quadlet +## + +# Redis runs as UID 10021 / GID 10000 on the host +PROJECT_UID = 10021 +PROJECT_GID = 10000 + +# Include common Makefile +include ../../scripts/common.mk + +TARGET_FILES += $(TARGET_CHROOT)/etc/quadlets/redis/acl.d +$(TARGET_CHROOT)/etc/quadlets/redis/acl.d: + install -m 0700 -o root -g root -D -d $@ + +TARGET_REDIS_ACL_FILES = $(patsubst config/examples/acl.d/%, $(TARGET_CHROOT)/etc/quadlets/redis/acl.d/%, $(wildcard config/examples/acl.d/*)) +$(TARGET_REDIS_ACL_FILES): $(TARGET_CHROOT)/etc/quadlets/redis/acl.d/%.acl: config/examples/acl.d/%.acl + install -m 0600 -o root -g root $< $@ diff --git a/cookbooks/redis/config/examples/acl.d/probe.acl b/cookbooks/redis/config/examples/acl.d/probe.acl new file mode 100644 index 0000000..c0aaa41 --- /dev/null +++ b/cookbooks/redis/config/examples/acl.d/probe.acl @@ -0,0 +1 @@ +user probe on >probe +ping diff --git a/cookbooks/redis/config/examples/redis.conf b/cookbooks/redis/config/examples/redis.conf new file mode 100644 index 0000000..fdd5b7e --- /dev/null +++ b/cookbooks/redis/config/examples/redis.conf @@ -0,0 +1,11 @@ +# Network settings +port 6379 +bind 127.0.0.1 + +# ACL file for multi-tenant access control (generated from acl.d/*.acl fragments) +aclfile /usr/local/etc/redis/users.acl + +# AOF persistence mode +save "" +appendonly yes +appendfsync everysec diff --git a/cookbooks/redis/config/generate-acl.sh b/cookbooks/redis/config/generate-acl.sh new file mode 100755 index 0000000..d02fc6a --- /dev/null +++ b/cookbooks/redis/config/generate-acl.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -Eeuo pipefail + +if [[ $# -eq 0 ]]; then + set -- /etc/quadlets/redis/users.acl /etc/quadlets/redis/acl.d/*.acl +fi + +target_file="$1" +shift +for file in "$@"; do + cat "$file" + echo +done > "$target_file" + +if ! grep -qE '^user +default' "$target_file"; then + echo "Warning: 'user default' entry not found in ACL files. Disabling it in $target_file." >&2 + echo "user default off" +fi >> "$target_file" + +# Remove empty lines from the generated ACL file +sed -i '/^$/d' "$target_file" + +if [[ -n "${REDIS_UID:-}" && -n "${REDIS_GID:-}" ]]; then + chown "$REDIS_UID:$REDIS_GID" "$target_file" +fi diff --git a/cookbooks/redis/hooks.mk b/cookbooks/redis/hooks.mk new file mode 100644 index 0000000..b06ac9e --- /dev/null +++ b/cookbooks/redis/hooks.mk @@ -0,0 +1,5 @@ +# Redis ACL fragments +TARGET_REDIS_FILES = $(patsubst other/redis/%.acl, $(TARGET_CHROOT)/etc/quadlets/redis/acl.d/%.acl, $(wildcard other/redis/*.acl)) +TARGET_EXAMPLE_FILES += $(TARGET_REDIS_FILES) +$(TARGET_CHROOT)/etc/quadlets/redis/acl.d/%.acl: other/redis/%.acl + install -D -m 0644 -o root -g root $< $@ diff --git a/cookbooks/redis/redis-server.container b/cookbooks/redis/redis-server.container new file mode 100644 index 0000000..5b83292 --- /dev/null +++ b/cookbooks/redis/redis-server.container @@ -0,0 +1,59 @@ +[Unit] +Description=Redis +Documentation=https://hub.docker.com/_/redis/ +After=network.target +RequiresMountsFor=/var/lib/virtiofs/data + +# Start/stop this unit when the target is started/stopped +PartOf=redis.target + +# Only start if Redis has been configured +ConditionPathExists=/etc/quadlets/redis/redis.conf + +[Container] +ContainerName=redis-server +Image=redis-server.image + +# Network configuration +Network=host + +# Redis configuration +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 user (999) & group (1000) inside the container to host UID 10021 / GID 10000 +UIDMap=0:1000000:65535 +UIDMap=+999:10021:1 +GIDMap=0:1000000:65535 +GIDMap=+1000:10000:1 + +# Volume mounts for data persistence and configuration +Volume=/var/lib/virtiofs/data/redis:/data:Z +Volume=/etc/quadlets/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro,Z +Volume=/etc/quadlets/redis/users.acl:/usr/local/etc/redis/users.acl:ro,Z + +# Password for the "probe" user for health checks +Environment=REDISCLI_AUTH=probe + +# Health check +HealthCmd=redis-cli --user probe ping | grep -qFx PONG +HealthInterval=30s +HealthTimeout=5s +HealthStartPeriod=10s +HealthRetries=3 + +[Service] +Restart=always +RestartSec=5 +TimeoutStartSec=300 +TimeoutStopSec=30 + +# Concatenate all ACL fragments into a single users.acl before starting +Environment=REDIS_UID=10021 REDIS_GID=10000 +ExecStartPre=/etc/quadlets/redis/generate-acl.sh + +[Install] +WantedBy=redis.target diff --git a/cookbooks/redis/redis-server.image b/cookbooks/redis/redis-server.image new file mode 100644 index 0000000..70eafb1 --- /dev/null +++ b/cookbooks/redis/redis-server.image @@ -0,0 +1,9 @@ +[Unit] +Description=podman pull docker.io/library/redis +Documentation=https://hub.docker.com/_/redis/ + +# Only start if Redis has been configured +ConditionPathExists=/etc/quadlets/redis/redis.conf + +[Image] +Image=docker.io/library/redis:8-alpine diff --git a/cookbooks/redis/redis.target b/cookbooks/redis/redis.target new file mode 100644 index 0000000..26941d8 --- /dev/null +++ b/cookbooks/redis/redis.target @@ -0,0 +1,13 @@ +[Unit] +Description=Redis Service Target +Documentation=man:systemd.target(5) +Requires=redis-server.service +After=redis-server.service + +AllowIsolate=yes + +# Only start if Redis has been configured +ConditionPathExists=/etc/quadlets/redis/redis.conf + +[Install] +WantedBy=multi-user.target diff --git a/cookbooks/redis/tmpfiles.d/redis.conf b/cookbooks/redis/tmpfiles.d/redis.conf new file mode 100644 index 0000000..185e44c --- /dev/null +++ b/cookbooks/redis/tmpfiles.d/redis.conf @@ -0,0 +1 @@ +d$ /var/lib/virtiofs/data/redis 0700 10021 10000 - diff --git a/scripts/common.mk b/scripts/common.mk index 4f17499..4cbf0ad 100644 --- a/scripts/common.mk +++ b/scripts/common.mk @@ -111,10 +111,10 @@ TARGET_EXAMPLES_SYSCTLD_FILES = $(patsubst sysctl.d/examples/%, $(TARGET_CHROOT) TARGET_EXAMPLES_PROFILED_FILES = $(patsubst profile.d/examples/%, $(TARGET_CHROOT)/etc/profile.d/%, $(EXAMPLES_PROFILED_FILES)) # Example quadlet and systemd drop-ins files -EXAMPLES_QUADLET_DROPINS_FILES := $(shell if [ -d examples ]; then find examples -mindepth 1 -type f | grep -E '\.(container|volume|network|pod|build|image)\.d/' 2>/dev/null; fi) -EXAMPLES_SYSTEMD_DROPINS_FILES := $(shell if [ -d examples ]; then find examples -mindepth 1 -type f | grep -E '\.(service|target|timer|mount)\.d/' 2>/dev/null; fi) -TARGET_EXAMPLES_QUADLET_DROPINS_FILES = $(patsubst examples/%, $(TARGET_CHROOT)/etc/containers/systemd/%, $(EXAMPLES_QUADLET_DROPINS_FILES)) -TARGET_EXAMPLES_SYSTEMD_DROPINS_FILES = $(patsubst examples/%, $(TARGET_CHROOT)/etc/systemd/system/%, $(EXAMPLES_SYSTEMD_DROPINS_FILES)) +EXAMPLES_QUADLET_DROPINS_FILES := $(shell if [ -d dropins ]; then find dropins -mindepth 1 -type f | grep -E '\.(container|volume|network|pod|build|image)\.d/' 2>/dev/null; fi) +EXAMPLES_SYSTEMD_DROPINS_FILES := $(shell if [ -d dropins ]; then find dropins -mindepth 1 -type f | grep -E '\.(service|target|timer|mount)\.d/' 2>/dev/null; fi) +TARGET_EXAMPLES_QUADLET_DROPINS_FILES = $(patsubst dropins/%, $(TARGET_CHROOT)/etc/containers/systemd/%, $(EXAMPLES_QUADLET_DROPINS_FILES)) +TARGET_EXAMPLES_SYSTEMD_DROPINS_FILES = $(patsubst dropins/%, $(TARGET_CHROOT)/etc/systemd/system/%, $(EXAMPLES_SYSTEMD_DROPINS_FILES)) # All configuration files to be installed TARGET_FILES += $(addprefix $(TARGET_CHROOT)/etc/containers/systemd/, $(QUADLETS_FILES)) \ @@ -198,7 +198,11 @@ $(filter-out %.env, $(TARGET_CONFIG_FILES) $(TARGET_EXAMPLES_CONFIG_FILES)): if [ -d $< ]; then \ run install -d -m 0755 -o $(PROJECT_UID) -g $(PROJECT_GID) $@; \ else \ - if [ -x $< ]; then \ + path="$<"; \ + extension="$${path##*.}"; \ + if [ "$$extension" == "sh" ] && [ -x "$<" ]; then \ + run install -m 0755 -o root -g root $< $@; \ + elif [ -x $< ]; then \ run install -m 0755 -o $(PROJECT_UID) -g $(PROJECT_GID) $< $@; \ else \ run install -m 0644 -o $(PROJECT_UID) -g $(PROJECT_GID) $< $@; \ @@ -210,8 +214,8 @@ $(filter %.env, $(TARGET_CONFIG_FILES) $(TARGET_EXAMPLES_CONFIG_FILES)): install -m 0600 -o root -g root -D $< $@ # Copy systemd and quadlet drop-ins files -$(TARGET_EXAMPLES_QUADLET_DROPINS_FILES): $(TARGET_CHROOT)/etc/containers/systemd/%: examples/% $(TARGET_CHROOT)/etc/containers/systemd -$(TARGET_EXAMPLES_SYSTEMD_DROPINS_FILES): $(TARGET_CHROOT)/etc/systemd/system/%: examples/% $(TARGET_CHROOT)/etc/systemd/system +$(TARGET_EXAMPLES_QUADLET_DROPINS_FILES): $(TARGET_CHROOT)/etc/containers/systemd/%: dropins/% $(TARGET_CHROOT)/etc/containers/systemd +$(TARGET_EXAMPLES_SYSTEMD_DROPINS_FILES): $(TARGET_CHROOT)/etc/systemd/system/%: dropins/% $(TARGET_CHROOT)/etc/systemd/system $(TARGET_EXAMPLES_QUADLET_DROPINS_FILES) $(TARGET_EXAMPLES_SYSTEMD_DROPINS_FILES): install -D -m 0644 -o root -g root $< $@