diff --git a/systemd/README.md b/systemd/README.md new file mode 100644 index 0000000..5af4cb5 --- /dev/null +++ b/systemd/README.md @@ -0,0 +1,28 @@ +# Example systemd unit files + +To use these, first review the speedtest.* unit files, and then: + + cp ../speedtest /usr/local/bin/ + mkdir -p /usr/local/share/speedtest /usr/local/etc + cp -aR ../web/assets /usr/local/share/speedtest/assets + cp speedtest-settings.toml /usr/local/etc + cp speedtest.* /etc/systemd/system/ + systemctl daemon-reload + +If you wish to use the bolt database type: + + # Create static system user and group + adduser --system --group --no-create-home --disabled-password speedtest + mkdir -p /usr/local/var/speedtest + touch /usr/local/var/speedtest/speedtest.db + chown speedtest. /usr/local/var/speedtest/speedtest.db + +To start (and enable at boot-up): + + systemctl enable --now speedtest.socket + +speedtest-go should now be listening for http request on port 80 on the local +machine. + +You will need to customise the html files e.g. edit +`/usr/local/share/speedtest/assets/index.html` to suit your site. diff --git a/systemd/speedtest-settings.toml b/systemd/speedtest-settings.toml new file mode 100644 index 0000000..8b2ba7d --- /dev/null +++ b/systemd/speedtest-settings.toml @@ -0,0 +1,31 @@ +# bind address, use empty string to bind to all interfaces, or when using socket activation +#bind_address="" +# backend listen port. Set this to "" when using socket activation +listen_port="" +# proxy protocol port, use 0 to disable +proxyprotocol_port=0 +# Server location +server_lat=50.82589 +server_lng=-0.141391 +# ipinfo.io API key, if applicable +ipinfo_api_key="" + +# assets directory path, defaults to `assets` in the same directory +assets_path="/usr/local/share/speedtest/assets" + +# password for logging into statistics page +statistics_password="PASSWORD" +# redact IP addresses +redact_ip_addresses=false + +# database type for statistics data, currently supports: none, memory, bolt, mysql, postgresql +# if none is specified, no telemetry/stats will be recorded, and no result PNG will be generated +#database_type="bolt" +database_type="memory" +database_hostname="" +database_name="" +database_username="" +database_password="" + +# if you use `bolt` as database, set database_file to database file location +database_file="/usr/local/var/speedtest/speedtest.db" diff --git a/systemd/speedtest.service b/systemd/speedtest.service new file mode 100644 index 0000000..c99e963 --- /dev/null +++ b/systemd/speedtest.service @@ -0,0 +1,135 @@ +# Systemd unit file for speedtest-go. The defaults below are suitable for +# running all configurations in a medium-security environment. See comments +# below for addtional caveats - particularly those labelled "IMPORTANT". + +# You can edit this file, or alternatively you may prefer to use systemd's +# "override" mechanisms, to avoid editing this file e.g. using: + +# systemctl edit speedtest.service + +[Unit] +Description=Speedtest-go Server +After=syslog.target network.target + +# Default to using socket activation (see accompanying socket unit file to +# configure the bind address etc.). +Requires=speedtest.socket +After=speedtest.socket + +[Service] +Type=simple +# The paths to the installed binary and configuration file: + +ExecStart=/usr/local/bin/speedtest -c /usr/local/etc/speedtest-settings.toml +#WorkingDirectory=/usr/local/share/speedtest +#Restart=always +#RestartSec=5 + +# IMPORTANT! +# If you use a database file (not server), then you will need to disable the +# DynamicUser setting, and manually create the UNIX user and group specified +# below, to ensure the file is accessible across multiple invocations of the +# service. +DynamicUser=true + +# You may prefer to use a different user or group name on your system. +User=speedtest +Group=speedtest + + +# The following options will work for all configurations, but are not the +# most secure, so you are advised to customise them as described below: + +# If NOT using socket activation, or if using socket activation AND +# connecting to an external database server (MySQL, postgres) via TCP: +RestrictAddressFamilies=AF_INET AF_INET6 + +# If connecting to an external database via unix domain sockets (MySQL +# default to this mode of operation): +RestrictAddressFamilies=AF_UNIX + +# If using 'none', 'memory', or 'bolt' database types, and socket activation +# then the process will not need to bind to any new sockets, so we can remove +# the earlier AF_UNIX option again. In systemd versions before 249 this is +# the only way to say "Restrict the use of all address families": +RestrictAddressFamilies=AF_UNIX +RestrictAddressFamilies=~AF_UNIX +# ...in systemd version 249 and later, we can instead use the much clearer: +#RestrictAddressFamilies=none + +# The following options are available (in systemd v247) to restrict the +# actions of the speedtest server for reasons of increased security. + +# As a whole, the purpose of these are to provide an additional layer of +# security by mitigating any unknown security vulnerabilities which may exist +# in speedtest or in the libraries, tools and operating system components +# which it relies upon. + +# IMPORTANT! +# The following line must be customised to your individual requirements. +# e.g. if using the 'bolt' in-process database type: +ReadWritePaths=/usr/local/var/speedtest + +# Makes created files group-readable, but inaccessible by others +UMask=027 + +# Many of the following options are desribed in the systemd.resource-control(5) +# manual page. + +# The following may be useful in your environment: +#IPAddressDeny= +#IPAddressAllow= +#IPAccounting=true +#IPIngressFilterPath= +#SocketBindAllow= + +# If your system doesn't support all of the features below (e.g. because of +# the use of a version of systemd older than 247), you may need to comment-out +# some of the following lines. + +# n.b. It may be possible to further restrict speedtest, but this is a good +# start, and will guard against many potential zero-day vulnerabilities. + +# See the output of `systemd-analyze security speedtest.service` for further +# opportunities. Patches welcome! + +CapabilityBoundingSet= +LockPersonality=true +MemoryDenyWriteExecute=true +NoNewPrivileges=yes +PrivateTmp=yes +PrivateDevices=true +PrivateUsers=true +ProtectSystem=strict +ProtectHome=yes +ProtectClock=true +ProtectControlGroups=true +ProtectKernelLogs=true +ProtectKernelModules=true +ProtectKernelTunables=true +ProtectProc=invisible +ProtectHostname=true +RemoveIPC=true +RestrictNamespaces=true +RestrictSUIDSGID=true +RestrictRealtime=true +SystemCallArchitectures=native +SystemCallFilter=@system-service + +# Additionally, you may wish to use some of the systemd options documented in +# systemd.resource-control(5) to limit the CPU, memory, file-system I/O and +# network I/O that the speedtest server is permitted to consume according to +# the individual requirements of your installation. + +#CPUQuota=25% +#MemoryMax=bytes +#MemorySwapMax=bytes +#TasksMax=N +#IOReadBandwidthMax=device bytes +#IOWriteBandwidthMax=device bytes +#IOReadIOPSMax=device IOPS, IOWriteIOPSMax=device IOPS +#IPAccounting=true +#IPAddressAllow= + +[Install] +WantedBy=multi-user.target diff --git a/systemd/speedtest.socket b/systemd/speedtest.socket new file mode 100644 index 0000000..7412409 --- /dev/null +++ b/systemd/speedtest.socket @@ -0,0 +1,11 @@ +# Socket listener systemd unit file for speedtest-go. See the +# systemd.socket(5) manual page for many more options. +[Unit] +Description=Speedtest Web Server (http port 80) Socket + +[Socket] +ListenStream=80 +Accept=no + +[Install] +WantedBy=sockets.target diff --git a/web/web.go b/web/web.go index 990c83d..a63da3d 100644 --- a/web/web.go +++ b/web/web.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" + "github.com/coreos/go-systemd/activation" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" @@ -64,8 +65,6 @@ func ListenAndServe(conf *config.Config) error { assetFS = justFilesFilesystem{fs: http.Dir(conf.AssetsPath), readDirBatchSize: 2} } - addr := net.JoinHostPort(conf.BindAddress, conf.Port) - log.Infof("Starting backend server on %s", addr) r.Get("/*", pages(assetFS)) r.HandleFunc("/empty", empty) r.HandleFunc("/backend/empty", empty) @@ -95,7 +94,31 @@ func ListenAndServe(conf *config.Config) error { r.HandleFunc("/backend/stats.php", results.Stats) go listenProxyProtocol(conf, r) - return http.ListenAndServe(addr, r) + + // See if systemd socket activation has been used when starting our process + listeners, err := activation.Listeners() + if err != nil { + log.Fatalf("Error whilst checking for systemd socket activation %s", err) + } + + var s error + + switch len(listeners) { + case 0: + addr := net.JoinHostPort(conf.BindAddress, conf.Port) + log.Infof("Starting backend server on %s", addr) + s = http.ListenAndServe(addr, r) + case 1: + log.Info("Starting backend server on inherited file descriptor via systemd socket activation") + if (conf.BindAddress != "" || conf.Port != "") { + log.Errorf("Both an address/port (%s:%s) has been specificed in the config AND externally configured socket activation has been detected", conf.BindAddress, conf.Port) + log.Fatal(`Please deconfigure socket activation (e.g. in systemd unit files), or set both 'bind_address' and 'listen_port' to ''`) + } + s = http.Serve(listeners[0], r) + default: + log.Fatalf("Asked to listen on %s sockets via systemd activation. Sorry we currently only support listening on 1 socket.", len(listeners)) + } + return s } func listenProxyProtocol(conf *config.Config, r *chi.Mux) {