feat: contact form backend for linux and freebsd servers
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
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
Assisted-by: GLM 5.3 Flash
This commit is contained in:
Executable
+173
@@ -0,0 +1,173 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user