From 108a166ee063f27ed8c132d816591d0e28457aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Tue, 29 Sep 2026 00:32:44 +0200 Subject: [PATCH] feat(server-setup): replace nginx with caddy Assisted-by: GLM 5.3 Flash --- README.md | 2 +- server-setup.pl | 53 +++++++++++++++++++++++++++++++++++-------- tests/server-setup.pl | 19 ++++++++++++++++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e7f8c1f..a90ea62 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ a change would do without doing it. | `network-diag.pl` | 2.0.0 | Network latency, DNS, MTU, packet loss and dual-stack on Linux and FreeBSD | | `system-diag.pl` | 2.0.0 | System health with a grade from A to F. Linux only | | `security-audit.pl` | 2.0.0 | Security posture with a grade from A to F and an exit code for cron. Linux only | -| `server-setup.pl` | 2.0.0 | Server initial setup for Fedora, CentOS Stream and openEuler | +| `server-setup.pl` | 2.1.0 | Server initial setup for Fedora, CentOS Stream and openEuler | | `workstation-setup.pl` | 2.0.0 | Fedora desktop setup | | `sglang-deploy.pl` | 2.1.0 | SGLang inference server on an AMD GPU, behind Caddy with HTTPS | | `system-optimise.pl` | 2.0.0 | System cleanup; refuses rpm-ostree systems | diff --git a/server-setup.pl b/server-setup.pl index c27b22f..cb00a25 100644 --- a/server-setup.pl +++ b/server-setup.pl @@ -44,7 +44,7 @@ use strict; use warnings; -my $VERSION = '2.0.0'; +my $VERSION = '2.1.0'; my $BOLD = "\033[1m"; my $RED = "\033[31m"; @@ -67,16 +67,39 @@ my %SUPPORTED_OS = ( ); # Base packages installed on Fedora, CentOS Stream and openEuler alike. +# caddy is the reverse proxy the deploy scripts serve the endpoints through; +# openEuler ships no package for it, and install_packages says so and leaves +# it to the script that needs it. my @BASE_PACKAGES = qw( - nano curl wget htop tmux rsync nginx openssl jq fastfetch + nano curl wget htop tmux rsync caddy openssl jq fastfetch ); # Services to open in firewalld by default. ssh is mandatory: losing it means # locking out remote administration. my @FIREWALL_SERVICES = ('ssh'); -# Opened in firewalld only when nginx is installed, which it is by default. -my @NGINX_FIREWALL_SERVICES = ('http', 'https'); +# Opened in firewalld only when caddy is installed, which it is by default +# wherever the package exists. +my @CADDY_FIREWALL_SERVICES = ('http', 'https'); + +# The one base package a distribution's repositories do not carry, with the +# reason and who provides it instead. +my %UNPACKAGED = ( + openeuler => { + caddy => 'openEuler ships no caddy package; the scripts that need the ' + . 'proxy install the release binary themselves', + }, +); + +# Test-visible accessors: the catalogue is lexical to this file, so the checks +# under tests/ read the base package list and the unpackaged map through these. +sub base_packages { return @BASE_PACKAGES; } + +sub unpackaged_for { + my ($os_id) = @_; + return () unless exists $UNPACKAGED{$os_id}; + return %{ $UNPACKAGED{$os_id} }; +} # dnf-automatic configuration file. dnf 4 ships it with defaults; dnf 5 reads host # overrides from this path (its own defaults live in /usr/share/dnf5). @@ -565,19 +588,29 @@ sub ensure_epel { # --------------------------------------------------------------------------- sub install_packages { - my ($dry_run) = @_; - my (@installed, @skipped, @failed, @to_install); + my ($os_id, $dry_run) = @_; + my (@installed, @skipped, @failed, @to_install, @unpackaged); _status('Checking base packages'); for my $pkg (@BASE_PACKAGES) { + # exists and not a bare lookup: descending into a missing os key + # would autovivify it. + if (exists $UNPACKAGED{$os_id} && exists $UNPACKAGED{$os_id}{$pkg}) { + push @unpackaged, [$pkg, $UNPACKAGED{$os_id}{$pkg}]; + next; + } if (rpm_installed($pkg)) { push @skipped, $pkg } else { push @to_install, $pkg } } my $already = scalar @skipped; my $missing = scalar @to_install; - my $total = scalar @BASE_PACKAGES; + my $total = scalar @BASE_PACKAGES - scalar @unpackaged; _status_done("$already/$total already installed"); + for my $entry (@unpackaged) { + my ($pkg, $reason) = @$entry; + _info("$pkg is not packaged on this distribution: $reason"); + } if (!@to_install) { _ok('All base packages already present'); @@ -663,9 +696,9 @@ sub setup_firewall { $info{installed} = 1; } - # ssh is mandatory; http and https only when nginx is present. + # ssh is mandatory; http and https only when caddy is present. my @services = @FIREWALL_SERVICES; - push @services, @NGINX_FIREWALL_SERVICES if rpm_installed('nginx'); + push @services, @CADDY_FIREWALL_SERVICES if rpm_installed('caddy'); # --- Permanent rules first, through the offline client, which needs no daemon my $permanent_changed = 0; @@ -1776,7 +1809,7 @@ sub main { # 3. Base packages print STDERR "\n${BOLD}── Base Packages ──$RESET\n"; if (!$opt{skip_packages}) { - $results{packages} = install_packages($opt{dry_run}); + $results{packages} = install_packages($os_id, $opt{dry_run}); if (@{ $results{packages}{failed} }) { push @warnings, 'Some packages failed to install: ' . join(', ', @{ $results{packages}{failed} }); diff --git a/tests/server-setup.pl b/tests/server-setup.pl index 0832026..da9111a 100644 --- a/tests/server-setup.pl +++ b/tests/server-setup.pl @@ -124,6 +124,25 @@ my %defaults = parse_args(); is($defaults{dry_run}, 0, 'args: dry run is off by default'); @ARGV = @saved; +# --------------------------------------------------------------------------- +# The base package catalogue +# --------------------------------------------------------------------------- + +# The proxy of the collection is caddy everywhere; nginx left the baseline when +# sglang-deploy.pl moved to Caddy, and the name survives only in its own legacy +# clean-up. +check((grep { $_ eq 'caddy' } base_packages()) == 1, 'packages: caddy is a base package'); +check((grep { $_ eq 'nginx' } base_packages()) == 0, 'packages: nginx is not'); +is(join('|', base_packages()), + 'nano|curl|wget|htop|tmux|rsync|caddy|openssl|jq|fastfetch', + 'packages: the whole list, in order'); +my %openeuler_gaps = unpackaged_for('openeuler'); +is(exists $openeuler_gaps{caddy} ? 'yes' : 'no', 'yes', + 'packages: the openEuler gap is named in the unpackaged map'); +is(scalar(unpackaged_for('fedora')) // 0, 0, 'packages: Fedora has no unpackaged entry'); +is(scalar(unpackaged_for('centos')) // 0, 0, + 'packages: CentOS Stream has no unpackaged entry, EPEL carries caddy'); + # --------------------------------------------------------------------------- # The command runner # ---------------------------------------------------------------------------