feat(server-setup): replace nginx with caddy
Test / test (push) Successful in 1m14s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-29 00:32:44 +02:00
parent 37e06d090c
commit 108a166ee0
3 changed files with 63 additions and 11 deletions
+1 -1
View File
@@ -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 |
+43 -10
View File
@@ -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} });
+19
View File
@@ -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
# ---------------------------------------------------------------------------