Files
nuntius/scripts/install.pl
T
petrbalvin 3a38f00dc0
Test / test (push) Successful in 2m1s
Release / gates (push) Successful in 1m57s
Release / build (amd64, freebsd) (push) Successful in 1m26s
Release / build (amd64, linux) (push) Successful in 1m30s
Release / build (arm64, freebsd) (push) Successful in 1m28s
Release / build (arm64, linux) (push) Successful in 1m49s
Release / build (loong64, linux) (push) Successful in 1m30s
Release / build (riscv64, linux) (push) Successful in 1m29s
Release / release (push) Successful in 41s
feat: contact form backend for linux and freebsd servers
Assisted-by: GLM 5.3 Flash
2026-09-29 00:32:56 +02:00

174 lines
5.9 KiB
Perl
Executable File

#!/usr/bin/env perl
# Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
# SPDX-License-Identifier: MIT
#
# install.pl: install nuntius as a systemd service.
#
# nuntius ships as a single static binary. Build it on your workstation, upload
# the binary, the systemd unit, and the .env template to the server, then run
# this script there as root from that directory:
#
# scp bin/nuntius nuntius.service .env.example user@host:/tmp/nuntius/
# ssh user@host
# cd /tmp/nuntius && sudo perl install.pl
#
# It expects ./nuntius and ./nuntius.service (and optionally ./.env.example) in
# the current working directory. Idempotent: re-running is safe. It skips the
# user, config, and .env when they already exist, and never starts the service.
use strict;
use warnings;
my $nuntius_user = 'nuntius';
my $bin_dest = '/usr/local/bin/nuntius';
my $config_dir = '/etc/nuntius';
my $config_file = '/etc/nuntius/config.toml';
my $env_file = '/etc/nuntius/.env';
my $data_dir = '/var/lib/nuntius';
my $service_dest = '/etc/systemd/system/nuntius.service';
sub note { print qq{==> $_[0]\n} }
sub warn_ { print qq{WARN: $_[0]\n} }
sub fail { print qq{ERROR: $_[0]\n}; exit 1 }
# run executes one child in list form, so no argument is ever word-split or
# globbed, and dies naming the attempt when the child fails.
sub run {
my (@cmd) = @_;
system(@cmd) == 0
or die qq{ERROR: could not run '$cmd[0]': exit code } . ($? >> 8) . qq{\n};
}
# silenced runs a block with STDOUT and STDERR pointed at /dev/null, restored
# afterwards, so the caller sees only what it decides to print.
sub silenced(&) {
open(my $save_out, '>&', \*STDOUT) or die qq{ERROR: could not save STDOUT: $!\n};
open(my $save_err, '>&', \*STDERR) or die qq{ERROR: could not save STDERR: $!\n};
open(STDOUT, '>', '/dev/null') or die qq{ERROR: could not silence STDOUT: $!\n};
open(STDERR, '>&', \*STDOUT) or die qq{ERROR: could not silence STDERR: $!\n};
my $result = $_[0]->();
open(STDOUT, '>&', $save_out) or die qq{ERROR: could not restore STDOUT: $!\n};
open(STDERR, '>&', $save_err) or die qq{ERROR: could not restore STDERR: $!\n};
return $result;
}
sub require_root {
$> == 0 or fail('Run as root: sudo perl install.pl');
}
sub require_files {
-f './nuntius'
or fail('./nuntius binary not found: copy it here first (e.g. rsync bin/nuntius).');
-f './nuntius.service'
or fail('./nuntius.service not found: copy it from the repository root.');
}
sub create_user {
if (silenced { system('id', $nuntius_user) == 0 }) {
note("User $nuntius_user already exists.");
return;
}
note("Creating system user $nuntius_user...");
run('useradd', '--system', '--shell', '/usr/sbin/nologin',
'--home-dir', $data_dir, '--user-group', $nuntius_user);
}
sub create_data_dir {
note("Setting up $data_dir...");
unless (-d $data_dir) {
mkdir($data_dir, 0750) or die qq{ERROR: could not create $data_dir: $!\n};
}
my $uid = getpwnam($nuntius_user)
or die qq{ERROR: could not look up user $nuntius_user\n};
my $gid = getgrnam($nuntius_user)
or die qq{ERROR: could not look up group $nuntius_user\n};
chown($uid, $gid, $data_dir) or die qq{ERROR: could not chown $data_dir: $!\n};
chmod(0750, $data_dir) or die qq{ERROR: could not chmod $data_dir: $!\n};
}
sub install_binary {
note("Installing binary to $bin_dest...");
run('install', '-m', '0755', './nuntius', $bin_dest);
}
sub install_service {
note("Installing systemd unit $service_dest...");
run('install', '-m', '0644', './nuntius.service', $service_dest);
}
sub generate_config {
if (-f $config_file) {
note("Config $config_file already exists; leaving it untouched.");
return;
}
note("Generating $config_file...");
unless (-d $config_dir) {
mkdir($config_dir, 0755) or die qq{ERROR: could not create $config_dir: $!\n};
}
# nuntius writes the template config on first start; run it briefly, then
# let `timeout` send SIGTERM (graceful shutdown). The write happens at
# startup, before the timeout elapses. timeout exits 124 on SIGTERM and
# 137 on SIGKILL, so its status is deliberately not checked here.
silenced { system('timeout', '-k', '5s', '4s', $bin_dest) };
if (!-f $config_file) {
warn_("nuntius did not create $config_file; check the uploaded binary.");
}
# Fix ownership in case nuntius wrote files as root.
run('chown', '-R', "$nuntius_user:$nuntius_user", $data_dir);
}
sub install_env {
if (-f $env_file) {
note("$env_file already exists; leaving it untouched.");
}
elsif (-f './.env.example') {
note("Creating $env_file from .env.example (edit it!)...");
run('install', '-m', '0600', './.env.example', $env_file);
run('chown', "root:$nuntius_user", $env_file);
}
else {
warn_(".env.example not found; skipping $env_file. Create it before starting.");
}
}
sub enable_service {
note('Reloading systemd and enabling nuntius...');
run('systemctl', 'daemon-reload');
run('systemctl', 'enable', 'nuntius.service');
}
sub final_notes {
print <<"END_NOTES";
============================================================
nuntius installed and enabled, but NOT started.
Next manual steps:
1. Set the SMTP password:
sudo \$EDITOR $env_file
2. Edit the auto-generated config (smtp.host, smtp.user, allowed_origins):
sudo \$EDITOR $config_file
3. Start the service:
sudo systemctl start nuntius
sudo journalctl -u nuntius -f
4. Add the Caddy reverse_proxy directive (see README.md), then:
sudo caddy validate && sudo systemctl reload caddy
============================================================
END_NOTES
}
require_root();
require_files();
create_user();
create_data_dir();
install_binary();
install_service();
generate_config();
install_env();
enable_service();
final_notes();