200 lines
9.4 KiB
Makefile
200 lines
9.4 KiB
Makefile
SHELL := /bin/bash
|
|
.SHELLFLAGS := -eu -o pipefail -c
|
|
.DEFAULT_GOAL := help
|
|
.DELETE_ON_ERROR:
|
|
|
|
# Non-secret deployment settings. Runtime secrets remain in the shell-compatible
|
|
# .envrc and remote/setup/.env files instead of being parsed by Make.
|
|
-include config.mk
|
|
|
|
define load_envrc
|
|
set -a; [[ ! -f ./.envrc ]] || source ./.envrc; set +a;
|
|
endef
|
|
|
|
# ==================================================================================== #
|
|
# HELPERS
|
|
# ==================================================================================== #
|
|
|
|
## help: print this help message
|
|
.PHONY: help
|
|
help:
|
|
@echo 'Usage:'
|
|
@sed -n 's/^##//p' $(firstword $(MAKEFILE_LIST)) | column -t -s ':' | sed -e 's/^/ /'
|
|
|
|
.PHONY: confirm
|
|
confirm:
|
|
@read -r -p "Are you sure? [y/N] " answer && [[ "$${answer:-N}" == y ]]
|
|
|
|
## config/init: create missing local configuration files from their examples
|
|
.PHONY: config/init
|
|
config/init:
|
|
@if [[ -e ./.env ]]; then echo 'keep .env'; else install -m 0600 ./.env.example ./.env; echo 'create .env'; fi
|
|
@if [[ -e ./.envrc ]]; then echo 'keep .envrc'; else install -m 0600 ./.envrc.example ./.envrc; echo 'create .envrc'; fi
|
|
@if [[ -e ./config.mk ]]; then echo 'keep config.mk'; else install -m 0600 ./config.mk.example ./config.mk; echo 'create config.mk'; fi
|
|
@if [[ -e ./remote/setup/.env ]]; then echo 'keep remote/setup/.env'; else install -m 0600 ./remote/setup/.env.example ./remote/setup/.env; echo 'create remote/setup/.env'; fi
|
|
|
|
# ==================================================================================== #
|
|
# DEVELOPMENT
|
|
# ==================================================================================== #
|
|
|
|
## run/api: run the cmd/api application
|
|
.PHONY: run/api
|
|
run/api:
|
|
@${load_envrc} go run ./cmd/api
|
|
|
|
## run/web: run the cmd/web application
|
|
.PHONY: run/web
|
|
run/web:
|
|
@${load_envrc} go run ./cmd/web
|
|
|
|
## run/cli: run the cmd/cli administration tool (use ARGS='...')
|
|
.PHONY: run/cli
|
|
run/cli:
|
|
@${load_envrc} go run ./cmd/cli ${ARGS}
|
|
|
|
## db/psql: connect to the database using psql
|
|
.PHONY: db/psql
|
|
db/psql:
|
|
@${load_envrc} test -n "$${GARDOMATIC_DB_DSN:-}" || { echo 'GARDOMATIC_DB_DSN is required' >&2; exit 1; }; psql "$$GARDOMATIC_DB_DSN"
|
|
|
|
## db/migrations/new name=$1: create a new database migration
|
|
.PHONY: db/migrations/new
|
|
db/migrations/new:
|
|
@[[ "${name}" =~ ^[a-z0-9_]+$$ ]] || { echo 'name must contain lowercase letters, digits, or underscores' >&2; exit 1; }
|
|
migrate create -seq -ext=.sql -dir=./internal/storage/postgres/migrations "${name}"
|
|
|
|
## db/migrations/up: apply all up database migrations
|
|
.PHONY: db/migrations/up
|
|
db/migrations/up:
|
|
@${load_envrc} test -n "$${GARDOMATIC_DB_DSN:-}" || { echo 'GARDOMATIC_DB_DSN is required' >&2; exit 1; }; migrate -path ./internal/storage/postgres/migrations -database "$$GARDOMATIC_DB_DSN" up
|
|
|
|
## db/migrations/down: apply all down database migrations
|
|
.PHONY: db/migrations/down
|
|
db/migrations/down: confirm
|
|
@${load_envrc} test -n "$${GARDOMATIC_DB_DSN:-}" || { echo 'GARDOMATIC_DB_DSN is required' >&2; exit 1; }; migrate -path ./internal/storage/postgres/migrations -database "$$GARDOMATIC_DB_DSN" down
|
|
|
|
# ==================================================================================== #
|
|
# QUALITY CONTROL
|
|
# ==================================================================================== #
|
|
|
|
## tidy: tidy module dependencies and format all Go files
|
|
.PHONY: tidy
|
|
tidy:
|
|
go mod tidy
|
|
go mod verify
|
|
go fmt ./...
|
|
|
|
## audit: run quality control checks
|
|
.PHONY: audit
|
|
audit:
|
|
go mod tidy -diff
|
|
go mod verify
|
|
go vet ./...
|
|
go tool staticcheck ./...
|
|
go test -race -vet=off ./...
|
|
|
|
## test/integration: migrate the configured PostgreSQL database and run integration tests
|
|
.PHONY: test/integration
|
|
test/integration:
|
|
@${load_envrc} test -n "$${GARDOMATIC_TEST_DB_DSN:-}" || { echo 'GARDOMATIC_TEST_DB_DSN is required and must point to a disposable test database' >&2; exit 1; }; migrate -path ./internal/storage/postgres/migrations -database "$$GARDOMATIC_TEST_DB_DSN" up; go test -count=1 ./internal/storage/postgres
|
|
|
|
# ==================================================================================== #
|
|
# BUILD
|
|
# ==================================================================================== #
|
|
|
|
.PHONY: build/dirs
|
|
build/dirs:
|
|
mkdir -p ./bin ${PRODUCTION_BUILD_DIR}
|
|
|
|
## build/api: build the cmd/api application
|
|
.PHONY: build/api
|
|
build/api: build/dirs
|
|
go build -ldflags='-s' -o=./bin/api ./cmd/api
|
|
|
|
## build/web: build the cmd/web application
|
|
.PHONY: build/web
|
|
build/web: build/dirs
|
|
go build -ldflags='-s' -o=./bin/web ./cmd/web
|
|
|
|
## build/cli: build the cmd/cli administration tool
|
|
.PHONY: build/cli
|
|
build/cli: build/dirs
|
|
go build -ldflags='-s' -o=./bin/cli ./cmd/cli
|
|
|
|
## build/production: cross-compile all production binaries
|
|
.PHONY: build/production
|
|
build/production: build/dirs
|
|
GOOS=${PRODUCTION_GOOS} GOARCH=${PRODUCTION_GOARCH} go build -trimpath -ldflags='-s -w' -o=${PRODUCTION_BUILD_DIR}/api ./cmd/api
|
|
GOOS=${PRODUCTION_GOOS} GOARCH=${PRODUCTION_GOARCH} go build -trimpath -ldflags='-s -w' -o=${PRODUCTION_BUILD_DIR}/web ./cmd/web
|
|
GOOS=${PRODUCTION_GOOS} GOARCH=${PRODUCTION_GOARCH} go build -trimpath -ldflags='-s -w' -o=${PRODUCTION_BUILD_DIR}/cli ./cmd/cli
|
|
|
|
## code/stats: print source lines and their estimated number of book pages
|
|
.PHONY: code/stats
|
|
code/stats:
|
|
@lines=$$(git ls-files -z -- '*.go' '*.tmpl' '*.html' '*.css' '*.js' '*.sql' '*.sh' \
|
|
':(exclude)vendor/**' ':(exclude)**/*.min.css' ':(exclude)**/*.min.js' \
|
|
| xargs -0 awk 'END { print NR }'); \
|
|
pages=$$((($$lines + 49) / 50)); \
|
|
printf 'Codeumfang: %s Zeilen\nBuchumfang: ca. %s Seiten (bei 50 Codezeilen pro Seite)\n' "$$lines" "$$pages"
|
|
|
|
## build/all: build API, web, and administration CLI, then print code statistics
|
|
.PHONY: build/all
|
|
build/all: build/api build/web build/cli
|
|
@$(MAKE) --no-print-directory code/stats
|
|
|
|
# ==================================================================================== #
|
|
# PRODUCTION
|
|
# ==================================================================================== #
|
|
|
|
PRODUCTION_HOST ?=
|
|
PRODUCTION_SSH_USER ?= root
|
|
PRODUCTION_SSH_PORT ?= 22
|
|
PRODUCTION_SSH_IDENTITY_FILE ?=
|
|
PRODUCTION_GOOS ?= linux
|
|
PRODUCTION_GOARCH ?= amd64
|
|
PRODUCTION_BUILD_DIR = ./bin/${PRODUCTION_GOOS}_${PRODUCTION_GOARCH}
|
|
|
|
production_target = ${PRODUCTION_SSH_USER}@${PRODUCTION_HOST}
|
|
production_ssh_options = -p ${PRODUCTION_SSH_PORT}
|
|
production_scp_options = -P ${PRODUCTION_SSH_PORT}
|
|
ifneq ($(strip ${PRODUCTION_SSH_IDENTITY_FILE}),)
|
|
production_ssh_options += -i ${PRODUCTION_SSH_IDENTITY_FILE}
|
|
production_scp_options += -i ${PRODUCTION_SSH_IDENTITY_FILE}
|
|
endif
|
|
|
|
.PHONY: production/check-config
|
|
production/check-config:
|
|
@test -n "${PRODUCTION_HOST}" || { echo 'PRODUCTION_HOST is required; configure it in config.mk' >&2; exit 1; }
|
|
@test -n "${PRODUCTION_SSH_USER}" || { echo 'PRODUCTION_SSH_USER is required' >&2; exit 1; }
|
|
@[[ "${PRODUCTION_SSH_PORT}" =~ ^[0-9]+$$ ]] && (( ${PRODUCTION_SSH_PORT} >= 1 && ${PRODUCTION_SSH_PORT} <= 65535 )) || { echo 'PRODUCTION_SSH_PORT must be between 1 and 65535' >&2; exit 1; }
|
|
@[[ "${PRODUCTION_GOOS}" == linux ]] || { echo 'PRODUCTION_GOOS must be linux' >&2; exit 1; }
|
|
@[[ "${PRODUCTION_GOARCH}" == amd64 || "${PRODUCTION_GOARCH}" == arm64 ]] || { echo 'PRODUCTION_GOARCH must be amd64 or arm64' >&2; exit 1; }
|
|
@if [[ -n "${PRODUCTION_SSH_IDENTITY_FILE}" && ! -f "${PRODUCTION_SSH_IDENTITY_FILE}" ]]; then echo 'PRODUCTION_SSH_IDENTITY_FILE does not exist' >&2; exit 1; fi
|
|
|
|
## production/connect: connect to the production server
|
|
.PHONY: production/connect
|
|
production/connect: production/check-config
|
|
ssh ${production_ssh_options} ${production_target}
|
|
|
|
## production/provision: provision a fresh Ubuntu server (root or passwordless sudo required)
|
|
.PHONY: production/provision
|
|
production/provision: production/check-config
|
|
@test -f ./remote/setup/.env || { echo 'Copy remote/setup/.env.example to remote/setup/.env and configure it first' >&2; exit 1; }
|
|
@permissions=$$(stat -c '%a' ./remote/setup/.env); (( (8#$$permissions & 077) == 0 )) || { echo 'remote/setup/.env must have mode 0600' >&2; exit 1; }
|
|
scp ${production_scp_options} ./remote/setup/provision-server.sh ${production_target}:/tmp/gardomatic-provision-server.sh
|
|
ssh ${production_ssh_options} ${production_target} 'status=0; chmod 700 /tmp/gardomatic-provision-server.sh && /tmp/gardomatic-provision-server.sh --env-file - || status=$$?; rm -f /tmp/gardomatic-provision-server.sh; exit $$status' < ./remote/setup/.env
|
|
|
|
## production/deploy: deploy API and web to production
|
|
.PHONY: production/deploy
|
|
production/deploy: production/check-config build/production
|
|
ssh ${production_ssh_options} ${production_target} 'mkdir -p "$$HOME/gardomatic-deploy/migrations"'
|
|
rsync -P -e "ssh ${production_ssh_options}" ${PRODUCTION_BUILD_DIR}/api ${PRODUCTION_BUILD_DIR}/web ${PRODUCTION_BUILD_DIR}/cli ${production_target}:gardomatic-deploy/
|
|
rsync -rP --delete -e "ssh ${production_ssh_options}" ./internal/storage/postgres/migrations/ ${production_target}:gardomatic-deploy/migrations/
|
|
rsync -P -e "ssh ${production_ssh_options}" ./remote/production/api.service ./remote/production/web.service ./remote/production/deploy-server.sh ./remote/setup/create-admin.sh ${production_target}:gardomatic-deploy/
|
|
ssh -t ${production_ssh_options} ${production_target} 'chmod 700 "$$HOME/gardomatic-deploy/deploy-server.sh" && "$$HOME/gardomatic-deploy/deploy-server.sh"'
|
|
|
|
## production/create-admin: interactively create an active application administrator
|
|
.PHONY: production/create-admin
|
|
production/create-admin: production/check-config
|
|
ssh -t ${production_ssh_options} ${production_target} /usr/local/sbin/gardomatic-create-admin
|