From f8bdeb52f69adf094f060624a76df831fe9243c9 Mon Sep 17 00:00:00 2001 From: Rick Beton <1358735+rickb777@users.noreply.github.com> Date: Fri, 6 Dec 2019 00:53:40 +0000 Subject: [PATCH] SystemD installation (#1445) * First implementation; older systemd is not yet supported; SysV is not supported at all * small correction * more documentation; manual testing confirms that Athens is working as a SystemD service * documentation updates * removed differences from author/master branch --- .gitignore | 3 +- DEVELOPMENT.md | 48 +++++++-- Makefile | 8 ++ scripts/service/athens.service | 65 +++++++++++++ scripts/systemd.sh | 173 +++++++++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 7 deletions(-) create mode 100644 scripts/service/athens.service create mode 100755 scripts/systemd.sh diff --git a/.gitignore b/.gitignore index 76089c2b..9a27c27f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,11 +19,12 @@ tmp/* vgp bin/* .envrc +athens cmd/proxy/proxy +cmd/proxy/bin test-keys.json tmp .vs -cmd/proxy/bin .idea .DS_Store .vscode diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 547279c6..0699f0c5 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -27,15 +27,16 @@ GO_BINARY_PATH=go1.11.X # Run the Proxy -We provide three ways to run the proxy on your local machine: +We provide four ways to run the proxy on your local machine: 1. Using [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) (_we suggest this one if you're getting started_) -2. Natively on your host -3. Using [Sail](https://sail.dev) +2. Natively on your host as a service (only SystemD is currently supported) +3. Natively on your host as a simple binary +4. Using [Sail](https://sail.dev) See below for instructions for each option! -## Using Docker +## Run Using Docker As we said above, we suggest that you use this approach because it simulates a more realistic Athens deployment. This technique does the following, completely inside containers: @@ -79,7 +80,42 @@ When you're ready to stop Athens and all its dependencies, run this command: $ make run-docker-teardown ``` -## Natively on Your Host +## Run Natively on Your Host as a Service + +There are many service execution environments. On Linux, two important ones are SystemD and SysV, but at the moment only SystemD is supported; others may follow soon. For other systems, please see the next section. + +### SystemD on Linux + +If you're inside GOPATH, make sure `GO111MODULE=on`, if you're outside GOPATH, then Go Modules are on by default. + +The Makefile builds the necessary `athens` binary. Then a script sets up the service for you. + +```console +$ make athens +$ sudo ./scripts/systemd.sh install +``` + +After the server starts, you can manage it as usual via `systemctl`, e.g.: + +```console +sudo systemctl status athens +``` +which is the same as + +```console +$ sudo ./scripts/systemd.sh status +``` + +The `systemd.sh` script also has a `remove` option to uninstall the service. + +SystemD allows logs to be collected and inspected; more information is in +[this tutorial by Digital Ocean](https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs), amongst others. So tailing the logs can be done like this: + +```console +$ sudo journalctl -u athens --since today --follow +``` + +## Run Natively on Your Host If you're inside GOPATH, make sure `GO111MODULE=on`, if you're outside GOPATH, then Go Modules are on by default. @@ -97,7 +133,7 @@ After the server starts, you'll see some console output like: Starting application at 127.0.0.1:3000 ``` -## Using Sail +## Run Using Sail Follow instructions at [sail.dev](https://sail.dev) to setup the sail CLI. Then simply run: diff --git a/Makefile b/Makefile index 724f9f17..babc4cae 100644 --- a/Makefile +++ b/Makefile @@ -9,9 +9,13 @@ endif build: ## build the athens proxy cd cmd/proxy && go build +.PHONY: build-ver build-ver: ## build the athens proxy with version number GO111MODULE=on CGO_ENABLED=0 GOPROXY="https://proxy.golang.org" go build -ldflags "-X github.com/gomods/athens/pkg/build.version=$(VERSION) -X github.com/gomods/athens/pkg/build.buildDate=$(DATE)" -o athens ./cmd/proxy +athens: + $(MAKE) build-ver + # The build-image step creates a docker image that has all the tools required # to perform some CI build steps, instead of relying on them being installed locally .PHONY: build-image @@ -110,6 +114,10 @@ down: dev-teardown: docker-compose -p athensdev down -v +.PHONY: clean +clean: ## delete all locally-built artefacts (not including docker images) + rm -f athens cmd/proxy/proxy + .PHONY: help help: ## display help page @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/scripts/service/athens.service b/scripts/service/athens.service new file mode 100644 index 00000000..06de46a3 --- /dev/null +++ b/scripts/service/athens.service @@ -0,0 +1,65 @@ +[Unit] +Description=Athens Go module proxy +Documentation=https://docs.gomods.io/ +After=network-online.target +Wants=network-online.target + +[Service] +Restart=on-abnormal +Nice=5 +;StartLimitInterval=86400 +;StartLimitBurst=5 + +; User and group the process will run as. +User=www-data +Group=www-data + +Environment=ATHENS_DISK_STORAGE_ROOT=/var/run/athens + +; The full path and the arguments of the command to be executed to start the process. +ExecStart=/usr/local/bin/athens -config_file=/etc/athens/config.toml + +; The command necessary to stop the service. +;ExecStop=/bin/kill -INT $MAINPID + +; The command necessary to reload the configuration of the service if available. +;ExecReload=/bin/kill -USR1 $MAINPID + +; Use graceful shutdown with a reasonable timeout +KillMode=mixed +KillSignal=SIGINT +TimeoutStopSec=5s + +; Limit the number of file descriptors; see `man systemd.exec` for more limit settings. +LimitNOFILE=1048576 +; Unmodified athens is not expected to use more than that. +LimitNPROC=512 + +; Use private /tmp and /var/tmp, which are discarded after athens stops. +PrivateTmp=true + +; Use a minimal /dev +PrivateDevices=true + +; Prevent alteration of /home, /root, and /run/user. +ProtectHome=read-only + +; Make /usr, /boot, /etc and possibly some more folders read-only. +ProtectSystem=full + +; … except /var/run/athens, because we want disk storage here +; This merely retains r/w access rights, it does not add any new. Must still be writable on the host! +ReadWritePaths=/var/run/athens + +; The following additional security directives only work with systemd v229 or later. +; They further retrict privileges that can be gained by athens. Uncomment if you like. +; Note that you may have to add capabilities required by any plugins in use. +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true + +; Ensure /var/log subdirectories are available. This is a space-separated list. +;LogsDirectory= + +[Install] +WantedBy=multi-user.target diff --git a/scripts/systemd.sh b/scripts/systemd.sh new file mode 100755 index 00000000..7bb4c2dd --- /dev/null +++ b/scripts/systemd.sh @@ -0,0 +1,173 @@ +#!/bin/bash -e +# This script manages installation using SystemD, if available, or SysV as a fall-back. +# +# Usage: +# systemd.sh [ install | remove | status | log ] + +cd $(dirname $0)/.. +[[ -n $VERSION ]] || export VERSION=unset + +function ensureSystemdIsPresent +{ + if [[ $( uname -s ) != "Linux" ]]; then + echo "SystemD can only be used on Linux systems." + exit 1 + fi + + id=$(uname -msn) + + if command -v systemctl >/dev/null; then + echo -n "$id has " + systemctl --version | head -1 + + elif [ -d /etc/init.d ]; then + echo "$id has SysV." + + else + echo "Neither SystemD nor SysV is available in this operating system." + lsb_release -a + exit 1 + + fi +} + +# doInstallConfig installs the config file +function doInstallConfig +{ + if [ ! -r config.toml ]; then + echo "Missing: config.toml" + echo "Copy & modify config.dev.toml as needed." + exit 1 + fi + sudo mkdir -p /etc/athens + sudo install -v -o root -g root -m 644 config.toml /etc/athens + + # if storage is on disk, this is where the database goes (see scripts/service/athens.service) + ATHENS_DISK_STORAGE_ROOT=/var/run/athens + sudo mkdir -p $ATHENS_DISK_STORAGE_ROOT + sudo chown www-data $ATHENS_DISK_STORAGE_ROOT + sudo chgrp www-data $ATHENS_DISK_STORAGE_ROOT +} + +# doInstallBinary copies the Athens binary to /usr/local/bin with the necessary settings. +function doInstallBinary +{ + [ -f athens ] || make athens + + if [ ! -x /usr/local/bin/athens -o athens -nt /usr/local/bin/athens ]; then + + [ -f /etc/systemd/system/athens.service ] && sudo systemctl stop athens + [ -f /etc/init.d/athens ] && sudo /etc/init.d/athens stop + + sudo install -v -o root -g root athens /usr/local/bin + fi + + # Give the athens binary the ability to bind to privileged ports (e.g. 80, 443) as a non-root user: + command -v setcap >/dev/null && sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/athens +} + +# doInstallSystemd sets up the SystemD service unit. +function doInstallSystemd +{ + sudo install -v -o root -g root -m 644 scripts/service/athens.service /etc/systemd/system + sudo systemctl daemon-reload + sudo systemctl enable athens + sudo systemctl start athens +} + +# doInstall builds and installs the binary as a SystemD unit +function doInstall +{ + doInstallConfig + doInstallBinary + doInstallSystemd +} + +# doRemove deletes the SystemD unit and cleans up log files etc +function doRemove +{ + if [ -f /etc/systemd/system/athens.service ]; then + sudo systemctl stop athens + sudo rm -f /etc/systemd/system/athens.service + # Reset systemctl + sudo systemctl daemon-reload + echo "SystemD installation was removed." + + elif [ -f /etc/init.d/athens ]; then + sudo service athens stop + sudo update-rc.d athens remove + sudo rm -f /etc/init.d/athens + echo "SysV installation was removed." + + fi + + sudo rm -rf /etc/athens /etc/ssl/athens /usr/local/bin/athens /var/log/athens.log /var/www/.athens +} + +# doStatus shows what is installed, if anything, and whether it is running +function doStatus +{ + if [ -x /usr/local/bin/athens ]; then + echo "Athens is /usr/local/bin/athens" + /usr/local/bin/athens --version + else + echo "Athens is absent (no /usr/local/bin/athens)." + exit 0 + fi + + if [ -f /etc/systemd/system/athens.service ]; then + echo + echo "SystemD: /etc/systemd/system/athens.service exists." + sudo systemctl status athens ||: + + elif [ -f /etc/init.d/athens ]; then + echo + echo "SysV: /etc/init.d/athens exists." + sudo service athens status ||: + + else + echo "Athens is not installed as a service." + fi +} + +# showLog shows the relevant lines in syslog +function showLog +{ + if [ -x /usr/local/bin/athens ]; then + echo "Athens is /usr/local/bin/athens" + /usr/local/bin/athens --version + else + echo "Athens is absent (no /usr/local/bin/athens)." + exit 0 + fi + + if [ -f /etc/systemd/system/athens.service ]; then + fgrep athens /var/log/syslog | fgrep "$(date '+%b %d')" + + elif [ -f /etc/init.d/athens ]; then + fgrep athens /var/log/syslog | fgrep "$(date '+%b %d')" + + else + echo "Athens is not installed as a service." + fi +} + +### Main script ###doStatus + +case $1 in + install) + ensureSystemdIsPresent; doInstall ;; + + remove|uninstall) + ensureSystemdIsPresent; doRemove ;; + + status) + ensureSystemdIsPresent; doStatus ;; + + log) + ensureSystemdIsPresent; showLog ;; + + *) + echo "Usage: $0 [ install | remove | status | log" + exit 1 ;; +esac