diff --git a/projects/greenlight/Makefile b/projects/greenlight/Makefile index e06b118..6cf939c 100644 --- a/projects/greenlight/Makefile +++ b/projects/greenlight/Makefile @@ -93,3 +93,40 @@ build/api: go build -ldflags=${linker_flags} -o=./bin/api ./cmd/api GOOS=linux GOARCH=amd64 go build -ldflags=${linker_flags} -o=./bin/linux_amd64/api ./cmd/api + +# ==================================================================================== # +# PRODUCTION +# ==================================================================================== # + +production_host_ip = 'xx.xx.xx.xx' + +## production/connect: connect to the production server +.PHONY: production/connect +production/connect: + ssh greenlight@${production_host_ip} + +## production/deploy/api: deploy the api to production +.PHONY: production/deploy/api +production/deploy/api: + rsync -rP --delete ./bin/linux_amd64/api ./migrations greenlight@${production_host_ip}:~ + ssh -t greenlight@${production_host_ip} 'migrate -path ~/migrations -database $$GREENLIGHT_DB_DSN up' + + +## production/configure/api.service: configure the production systemd api.service file +.PHONY: production/configure/api.service +production/configure/api.service: + rsync -P ./remote/production/api.service greenlight@${production_host_ip}:~ + ssh -t greenlight@${production_host_ip} '\ + sudo mv ~/api.service /etc/systemd/system/ \ + && sudo systemctl enable api \ + && sudo systemctl restart api \ + ' + +## production/configure/caddyfile: configure the production Caddyfile +.PHONY: production/configure/caddyfile +production/configure/caddyfile: + rsync -P ./remote/production/Caddyfile greenlight@${production_host_ip}:~ + ssh -t greenlight@${production_host_ip} '\ + sudo mv ~/Caddyfile /etc/caddy/ \ + && sudo systemctl reload caddy \ + ' diff --git a/projects/greenlight/bin/api b/projects/greenlight/bin/api index 9d27f66..1b6e587 100755 Binary files a/projects/greenlight/bin/api and b/projects/greenlight/bin/api differ diff --git a/projects/greenlight/remote/production/Caddyfile b/projects/greenlight/remote/production/Caddyfile new file mode 100644 index 0000000..ab7643a --- /dev/null +++ b/projects/greenlight/remote/production/Caddyfile @@ -0,0 +1,12 @@ +# Set the email address that should be used to contact you if there is a problem with +# your TLS certificates. +{ + email you@example.com +} + +# Remove the http:// prefix from your site address. +xxx.xxx.net { + respond /debug/* "Not Permitted" 403 + reverse_proxy localhost:4000 +} + diff --git a/projects/greenlight/remote/production/api.service b/projects/greenlight/remote/production/api.service new file mode 100644 index 0000000..27f3191 --- /dev/null +++ b/projects/greenlight/remote/production/api.service @@ -0,0 +1,34 @@ +[Unit] +# Description is a human-readable name for the service. +Description=Greenlight API service + +# Wait until PostgreSQL is running and the network is "up" before starting the service. +After=postgresql.service +After=network-online.target +Wants=network-online.target + +# Configure service start rate limiting. If the service is (re)started more than 5 times +# in 600 seconds then don't permit it to start anymore. +StartLimitIntervalSec=600 +StartLimitBurst=5 + +[Service] +# Execute the API binary as the greenlight user, loading the environment variables from +# /etc/environment and using the working directory /home/greenlight. +Type=exec +User=greenlight +Group=greenlight +EnvironmentFile=/etc/environment +WorkingDirectory=/home/greenlight +ExecStart=/home/greenlight/api -port=4000 -db-dsn=${GREENLIGHT_DB_DSN} -env=production + +# Automatically restart the service after a 5-second wait if it exits with a non-zero +# exit code. If it restarts more than 5 times in 600 seconds, then the rate limit we +# configured above will be hit and it won't be restarted anymore. +Restart=on-failure +RestartSec=5 + +[Install] +# Start the service automatically at boot time (the 'multi-user.target' describes a boot +# state when the system will accept logins). +WantedBy=multi-user.target diff --git a/projects/greenlight/remote/setup/01.sh b/projects/greenlight/remote/setup/01.sh new file mode 100644 index 0000000..f4fd052 --- /dev/null +++ b/projects/greenlight/remote/setup/01.sh @@ -0,0 +1,83 @@ +#!/bin/bash +set -eu + +# ==================================================================================== # +# VARIABLES +# ==================================================================================== # + +# Set the timezone for the server. A full list of available timezones can be found by +# running timedatectl list-timezones. +TIMEZONE=America/New_York + +# Set the name of the new user to create. +USERNAME=greenlight + +# Prompt to enter a password for the PostgreSQL greenlight user (rather than hard-coding +# a password in this script). +read -p "Enter password for greenlight DB user: " DB_PASSWORD + +# Force all output to be presented in en_US for the duration of this script. This avoids +# any "setting locale failed" errors while this script is running, before we have +# installed support for all locales. Do not change this setting! +export LC_ALL=en_US.UTF-8 + +# ==================================================================================== # +# SCRIPT LOGIC +# ==================================================================================== # + +# Enable the "universe" repository. +add-apt-repository --yes universe + +# Update all software packages. Using the --force-confnew flag means that configuration +# files will be replaced if newer ones are available. +apt update +apt --yes -o Dpkg::Options::="--force-confnew" upgrade + +# Set the system timezone and install all locales. +timedatectl set-timezone ${TIMEZONE} +apt --yes install locales-all + +# Add the new user (and give them sudo privileges). +useradd --create-home --shell "/bin/bash" --groups sudo "${USERNAME}" + +# Force a password to be set for the new user the first time they log in. +passwd --delete "${USERNAME}" +chage --lastday 0 "${USERNAME}" + +# Copy the SSH keys from the root user to the new user. +rsync --archive --chown=${USERNAME}:${USERNAME} /root/.ssh /home/${USERNAME} + +# Configure the firewall to allow SSH, HTTP and HTTPS traffic. +ufw allow 22 +ufw allow 80/tcp +ufw allow 443/tcp +ufw --force enable + +# Install fail2ban. +apt --yes install fail2ban + +# Install the migrate CLI tool. +curl -L https://github.com/golang-migrate/migrate/releases/download/v4.14.1/migrate.linux-amd64.tar.gz | tar xvz +mv migrate.linux-amd64 /usr/local/bin/migrate + +# Install PostgreSQL. +apt --yes install postgresql + +# Set up the greenlight DB and create a user account with the password entered earlier. +sudo -i -u postgres psql -c "CREATE DATABASE greenlight" +sudo -i -u postgres psql -d greenlight -c "CREATE EXTENSION IF NOT EXISTS citext" +sudo -i -u postgres psql -d greenlight -c "CREATE ROLE greenlight WITH LOGIN PASSWORD '${DB_PASSWORD}'" + +# Add a DSN for connecting to the greenlight database to the system-wide environment +# variables in the /etc/environment file. +echo "GREENLIGHT_DB_DSN='postgres://greenlight:${DB_PASSWORD}@localhost/greenlight'" >>/etc/environment + +# Install Caddy (see https://caddyserver.com/docs/install#debian-ubuntu-raspbian). +apt --yes install -y debian-keyring debian-archive-keyring apt-transport-https +curl -L https://dl.cloudsmith.io/public/caddy/stable/gpg.key | sudo apt-key add - +curl -L https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list +apt update +apt --yes install caddy + +echo "Script complete! Rebooting..." +reboot