chaotic-v4 (stormwing)
This container is the main Chaotic-AUR builder and repository sync node, responsible for building and distributing packages.
General
This is the nspawn container used to run Chaotic-AUR's new build system, infra 4.0
.
Restarting the Docker stack, in case it is needed, can happen via sudo chaotic-restart
.
For information on how to use the new build system, please refer to the documentation.
In general, manual intervention should not be needed, as the system is designed to be fully automated via GitLab CI or GitHub actions.
Nix expression
{
config,
garuda-lib,
sources,
pkgs,
...
}:
let
wrapperScript = pkgs.writeScriptBin "chaotic-restart" ''
echo "Restarting Chaotic-AUR containers..."
systemctl restart compose-runner-chaotic-v4.service
echo "Done."
'';
in
{
imports = sources.defaultModules ++ [
../../modules
"${sources.chaotic-portable-builder}/nix/nixos.nix"
../../modules/special/ssh-allow-chaotic.nix
];
# This container is just for compose stuff
garuda.services.compose-runner.chaotic-v4 = {
envfile = config.sops.secrets."compose/chaotic-v4".path;
source = ../../../compose/chaotic-v4;
extraEnv = {
"REDIS_SSH_HOST" = garuda-lib.dns.aerialis;
"REDIS_SSH_PORT" = "270";
};
};
# Allow controlling infra 4.0's containers without root
environment.systemPackages = [ wrapperScript ];
security.sudo.extraRules = [
{
users = [ "xiota" ];
commands = [
{
command = "${wrapperScript}/bin/chaotic-restart";
options = [ "NOPASSWD" ];
}
];
}
];
# Expose raw /proc for podman
systemd.services.expose-raw-proc = {
description = "Expose clean /proc for podman";
wantedBy = [ "multi-user.target" ];
serviceConfig.Type = "oneshot";
script = ''
mkdir /tmp/raw_proc
${pkgs.mount}/bin/mount --bind /proc /tmp/raw_proc
'';
};
networking.firewall.allowedTCPPorts = [
config.services.rsyncd.port # Rsync
8384 # Syncthing web interface
];
# Enable the user accounts of chaotic maintainers
garuda-lib.chaoticUsers = true;
# Syncthing setup
services.syncthing = {
enable = true;
openDefaultPorts = true;
configDir = config.services.syncthing.dataDir;
cert = config.sops.secrets."keypairs/syncthing/cert".path;
key = config.sops.secrets."keypairs/syncthing/private".path;
overrideFolders = false;
overrideDevices = false;
user = "root";
group = "chaotic-op";
settings = {
gui = {
apikey = "garudalinux";
insecureSkipHostcheck = true;
inherit (garuda-lib.secrets.syncthing.esxi-build.credentials) user password;
};
};
guiAddress = "10.0.5.10:8384";
};
# Auto reset syncthing stuff
systemd.services.syncthing-reset = {
serviceConfig.Type = "oneshot";
script = ''
"${pkgs.curl}/bin/curl" -X POST -H "X-API-Key: garudalinux" http://10.0.5.10:8384/rest/db/override?folder=${garuda-lib.secrets.syncthing.folders.chaotic-aur}
'';
};
systemd.timers.syncthing-reset = {
wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = [ "hourly" ];
};
# This disables HTTPS certificates and forced redirects
garuda-lib.behind_proxy = true;
# Nginx
services.nginx = {
enable = true;
virtualHosts = {
"builds.garudalinux.org" = {
extraConfig = ''
# Disable index.html
index fully_disabled.html;
# Our beautiful autoindex theme
autoindex on;
autoindex_exact_size off;
autoindex_format xml;
xslt_string_param path $uri;
xslt_string_param hostname "Chaotic-AUR main node - Temeraire";
# Security
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'; frame-ancestors 'self' https://aur.chaotic.cx;" always;
add_header Permissions-Policy "interest-cohort=()" always;
# Locations
location ~* ^.+\.log {
default_type text/plain;
}
location ~* /repos/(chaotic-aur|garuda)/x86_64/(?!.*(chaotic-aur|garuda)\.(db|files)).+\.tar.* {
return 301 https://cf-builds.garudalinux.org$request_uri;
expires 2d;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/api/;
}
location /backend/ {
proxy_pass http://10.0.5.30:3000/;
}
location /logs/ {
proxy_pass http://127.0.0.1:8080/;
proxy_buffering off;
proxy_read_timeout 330s;
}
location / {
xslt_string_param path $uri;
xslt_string_param hostname "Chaotic-AUR main node - Temeraire 🐉";
xslt_stylesheet "${garuda-lib.xslt_style}";
location /iso {
expires 2d;
return 301 https://iso.builds.garudalinux.org$request_uri;
}
}
'';
http3 = true;
root = "/srv/http/";
};
"cf-builds.garudalinux.org" = {
extraConfig = ''
location ~* /repos/(chaotic-aur|garuda)/x86_64/(?!.*(chaotic-aur|garuda)\.(db|files)).+\.tar.* {
add_header Cache-Control "max-age=150, stale-while-revalidate=150, stale-if-error=86400";
}
location ~* /repos/(chaotic-aur|garuda)/x86_64/(chaotic-aur|garuda)\.db.* {
add_header Cache-Control 'no-cache';
}
location /repos/chaotic-aur {
expires 5m;
error_page 403 =301 https://builds.garudalinux.org$request_uri;
error_page 404 =301 https://builds.garudalinux.org$request_uri;
}
location /repos/garuda {
expires 5m;
error_page 403 =301 https://builds.garudalinux.org$request_uri;
error_page 404 =301 https://builds.garudalinux.org$request_uri;
}
location / {
expires 2d;
return 301 https://builds.garudalinux.org$request_uri;
}
'';
http3 = true;
root = "/srv/http/";
};
"iso.builds.garudalinux.org" = {
extraConfig = ''
autoindex on;
autoindex_format xml;
xslt_string_param path $uri;
xslt_string_param hostname "Garuda Linux ISO Builds";
'';
locations."/".return = "307 https://builds.garudalinux.org";
locations."/iso" = {
root = "/srv/http/";
extraConfig = ''
xslt_stylesheet "${garuda-lib.xslt_style}";
if ($symlink_target_rel != "") {
rewrite ^ https://$server_name/iso/$symlink_target_rel redirect;
}
if ($arg_sourceforge) {
rewrite ^/iso/(.*)$ https://sourceforge.net/projects/garuda-linux/files/$1? permanent;
}
if ($arg_r2) {
set $args "";
rewrite ^/iso/(.*)$ https://r2.garudalinux.org/iso/$1?r2request permanent;
}
break;
'';
};
};
};
};
# Rsyncd
services.rsyncd = {
enable = true;
settings = {
sections = {
chaotic = {
"read only" = "yes";
comment = "Chaotic-AUR repository";
exclude = "/chaotic-aur/archive/*** /garuda/archive/***";
path = "/srv/http/repos/";
};
chaotic-minimal = {
"read only" = "yes";
comment = "Chaotic-AUR repository minus largest packages";
exclude = "/chaotic-aur/archive/*** /garuda/archive/*** /chaotic-aur/x86_64/quartus* /chaotic-aur/x86_64/unrealtournament4* /chaotic-aur/x86_64/urbanterror*";
path = "/srv/http/repos/";
};
iso = {
path = "/srv/http/iso/";
comment = "ISO downloads";
"read only" = "yes";
};
};
globalSection = {
"max connections" = 80;
"max verbosity" = 3;
"transfer logging" = true;
"use chroot" = false;
gid = "nobody";
uid = "nobody";
};
};
};
# Push chaotic to r2 hourly automatically
services.garuda-rclone.chaotic = {
src = "/srv/http/repos/";
dest = "r2:/mirror/repos";
config = config.sops.secrets."cloudflare/r2_rclone".path;
args = "--s3-upload-cutoff 5G --s3-chunk-size 4G --fast-list --s3-no-head --s3-no-check-bucket --ignore-checksum --s3-disable-checksum -u --use-server-modtime --delete-during --delete-excluded --include /*/x86_64/*.pkg.tar.zst --include /*/lastupdate --order-by modtime,ascending --stats-log-level NOTICE";
startAt = "hourly";
};
systemd.services.chaotic-rclone-inotify = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
# Get all file changes, upload pkg.tar.zst. Not more than 5 per 5 seconds queued and only one uploaded at the same time. Queue dropped if uploading takes longer than 15 seconds.
# This prevents the queue from getting overloaded with nonsense requests if that ever were to happen. The hourly sync should take care of this.
script = ''
upload() {
operation="''${1%%|*}"
path="''${1#*|}"
relative="$(realpath --relative-to="." "$path")"
relative="''${relative#./}"
destpath="r2:/mirror/$relative"
if [ "$operation" != "MOVED_FROM" ]; then
${pkgs.flock}/bin/flock -w 30 /tmp/chaotic-rclone-inotify.lock \
${pkgs.rclone}/bin/rclone copyto "$path" "$destpath" --s3-upload-cutoff 5G --s3-chunk-size 4G --s3-no-head --no-check-dest --s3-no-check-bucket --ignore-checksum --s3-disable-checksum --config "${
config.sops.secrets."cloudflare/r2_rclone".path
}" --stats-one-line -v
else
${pkgs.flock}/bin/flock -w 30 /tmp/chaotic-rclone-inotify.lock ${pkgs.rclone}/bin/rclone deletefile "$destpath" --s3-no-head --no-check-dest --s3-no-check-bucket --config "${
config.sops.secrets."cloudflare/r2_rclone".path
}" --stats-one-line -v
(
${pkgs.flock}/bin/flock -w 200 -s 200
${pkgs.curl}/bin/curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_GARUDALINUX_ORG/purge_cache" -H "Authorization: Bearer $CF_CACHE_API_TOKEN" -H "Content-Type:application/json" --data "{\"files\":[\"https://r2.garudalinux.org/''${relative}\"]}"
sleep 0.5
) 200>/tmp/chaotic-rclone-inotify-invalidate.lock
fi
}
export -f upload
${pkgs.inotify-tools}/bin/inotifywait -m ./repos/*/x86_64 -e CLOSE_WRITE,MOVED_TO,MOVED_FROM --format "%e|%w%f" | \
${pkgs.gawk}/bin/awk '/\.pkg\.tar\.zst$/ { print $0; fflush(); }' | \
xargs -rP 0 -I % ${pkgs.bash}/bin/bash -c 'upload "%"'
'';
serviceConfig = {
EnvironmentFile = config.sops.secrets."cloudflare/api_keys".path;
Restart = "always";
WorkingDirectory = "/srv/http";
};
};
sops.secrets = {
"cloudflare/api_keys" = { };
"cloudflare/r2_rclone" = { };
"compose/chaotic-v4" = { };
"keypairs/syncthing/cert" = { };
"keypairs/syncthing/private" = { };
};
system.stateVersion = "25.05";
}
Docker containers
services:
chaotic-builder-1:
image: registry.gitlab.com/garuda-linux/tools/chaotic-manager/manager:latest
container_name: chaotic-builder
command: builder
deploy:
restart_policy:
condition: always
delay: 60s
tty: true
environment:
BUILDER_CLASS: 9
BUILDER_HOSTNAME: stormwing-1
BUILDER_TIMEOUT: 7200
REDIS_PASSWORD: ${REDIS_PASSWORD:-?err}
REDIS_SSH_HOST: ${REDIS_SSH_HOST:-?err}
REDIS_SSH_PORT: ${REDIS_SSH_PORT:-270}
REDIS_SSH_USER: package-deployer
SHARED_PATH: /var/garuda/compose-runner/chaotic-v4/shared
# Override the default database host
DATABASE_HOST: host.docker.internal
DATABASE_PORT: 22
volumes:
- ./shared:/shared
- ./sshkey:/app/sshkey
- /var/run/docker.sock:/var/run/docker.sock
extra_hosts: ["host.docker.internal:host-gateway"]
chaotic-builder-2:
image: registry.gitlab.com/garuda-linux/tools/chaotic-manager/manager:latest
container_name: chaotic-builder-2
command: builder
deploy:
restart_policy:
condition: always
delay: 60s
tty: true
environment:
BUILDER_CLASS: 6
BUILDER_HOSTNAME: stormwing-2
BUILDER_TIMEOUT: 7200
REDIS_PASSWORD: ${REDIS_PASSWORD:-?err}
REDIS_SSH_HOST: ${REDIS_SSH_HOST:-?err}
REDIS_SSH_PORT: ${REDIS_SSH_PORT:-270}
REDIS_SSH_USER: package-deployer
SHARED_PATH: /var/garuda/compose-runner/chaotic-v4/shared-2
BUILDER_SRCDEST_CACHE_OVERRIDE: /var/garuda/compose-runner/chaotic-v4/shared/srcdest_cache
# Override the default database host
DATABASE_HOST: host.docker.internal
DATABASE_PORT: 22
volumes:
- ./shared-2:/shared
- ./shared/srcdest_cache:/shared/srcdest_cache
- ./sshkey:/app/sshkey
- /var/run/docker.sock:/var/run/docker.sock
extra_hosts: ["host.docker.internal:host-gateway"]
chaotic-manager:
image: registry.gitlab.com/garuda-linux/tools/chaotic-manager/manager:latest
container_name: chaotic-manager
command: database --web-port 8080
deploy:
restart_policy:
condition: always
delay: 60s
tty: true
environment:
# Address published to outside world
DATABASE_HOST: builds.garudalinux.org
DATABASE_PORT: 210
CI_CODE_SKIP: 123
DATABASE_USER: package-deployer
GPG_PATH: /var/garuda/compose-runner/chaotic-v4/gnupg
LANDING_ZONE_PATH: /var/garuda/compose-runner/chaotic-v4/landing-zone
LOGS_URL: https://builds.garudalinux.org/logs/logs.html
REDIS_PASSWORD: ${REDIS_PASSWORD:-?err}
REDIS_SSH_HOST: ${REDIS_SSH_HOST:-?err}
REDIS_SSH_PORT: ${REDIS_SSH_PORT:-270}
REDIS_SSH_USER: package-deployer
REPO_PATH: /srv/http/repos
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-?err}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-?err}
PACKAGE_REPOS: >-
{
"chaotic-aur": {
"url": "https://gitlab.com/chaotic-aur/pkgbuilds"
},
"garuda": {
"url": "https://gitlab.com/garuda-linux/pkgbuilds"
}
}
PACKAGE_TARGET_REPOS: >-
{
"chaotic-aur": {
"extra_repos": [
{
"name": "chaotic-aur",
"servers": [
"https://builds.garudalinux.org/repos/chaotic-aur/x86_64"
]
}
],
"extra_keyrings": [
"https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst"
]
},
"garuda": {
"extra_repos": [
{
"name": "garuda",
"servers": [
"https://builds.garudalinux.org/repos/garuda/x86_64"
]
},
{
"name": "chaotic-aur",
"servers": [
"https://builds.garudalinux.org/repos/chaotic-aur/x86_64"
]
}
],
"extra_keyrings": [
"https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst"
]
}
}
PACKAGE_REPOS_NOTIFIERS: >-
{
"chaotic-aur": {
"id": "54867625",
"token": "${GITLAB_TOKEN_CX:-?err}",
"check_name": "chaotic-aur: %pkgbase%"
},
"garuda": {
"id": "48461689",
"token": "${GITLAB_TOKEN:-?err}",
"check_name": "garuda: %pkgbase%"
}
}
volumes:
- ./sshkey:/app/sshkey
- /var/run/docker.sock:/var/run/docker.sock
- /srv/http/repos:/repo_root
extra_hosts: ["host.docker.internal:host-gateway"]
ports: ["127.0.0.1:8080:8080", "127.0.0.1:3030:3030"]
# Automated container updates
watchtower:
image: containrrr/watchtower:latest
container_name: watchtower
deploy:
restart_policy:
condition: always
delay: 60s
command: --cleanup chaotic-builder chaotic-builder-2 chaotic-manager watchtower --interval 3600
volumes: ["/var/run/docker.sock:/var/run/docker.sock"]