This commit is contained in:
@@ -3,8 +3,8 @@ SHELL := /bin/bash
|
||||
.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.
|
||||
# Non-secret deployment settings. Runtime secrets remain in separate shell-compatible
|
||||
# files below remote/setup instead of being parsed by Make.
|
||||
-include config.mk
|
||||
|
||||
define load_envrc
|
||||
@@ -32,6 +32,8 @@ config/init:
|
||||
@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
|
||||
@if [[ -e ./remote/setup/.env.testserver ]]; then echo 'keep remote/setup/.env.testserver'; else install -m 0600 ./remote/setup/.env.example ./remote/setup/.env.testserver; echo 'create remote/setup/.env.testserver'; fi
|
||||
@if [[ -e ./remote/setup/.env.demo ]]; then echo 'keep remote/setup/.env.demo'; else install -m 0600 ./remote/setup/.env.example ./remote/setup/.env.demo; echo 'create remote/setup/.env.demo'; fi
|
||||
|
||||
# ==================================================================================== #
|
||||
# DEVELOPMENT
|
||||
@@ -104,7 +106,7 @@ test/integration:
|
||||
|
||||
.PHONY: build/dirs
|
||||
build/dirs:
|
||||
mkdir -p ./bin ${PRODUCTION_BUILD_DIR}
|
||||
mkdir -p ./bin
|
||||
|
||||
## build/api: build the cmd/api application
|
||||
.PHONY: build/api
|
||||
@@ -121,12 +123,21 @@ build/web: build/dirs
|
||||
build/cli: build/dirs
|
||||
go build -ldflags='-s' -o=./bin/cli ./cmd/cli
|
||||
|
||||
## build/production: cross-compile all production binaries
|
||||
DEPLOYMENT_GOOS ?= linux
|
||||
DEPLOYMENT_GOARCH ?= amd64
|
||||
DEPLOYMENT_BUILD_DIR = ./bin/${DEPLOYMENT_GOOS}_${DEPLOYMENT_GOARCH}
|
||||
|
||||
.PHONY: build/deployment
|
||||
build/deployment: build/dirs
|
||||
mkdir -p ${DEPLOYMENT_BUILD_DIR}
|
||||
GOOS=${DEPLOYMENT_GOOS} GOARCH=${DEPLOYMENT_GOARCH} go build -trimpath -ldflags='-s -w' -o=${DEPLOYMENT_BUILD_DIR}/api ./cmd/api
|
||||
GOOS=${DEPLOYMENT_GOOS} GOARCH=${DEPLOYMENT_GOARCH} go build -trimpath -ldflags='-s -w' -o=${DEPLOYMENT_BUILD_DIR}/web ./cmd/web
|
||||
GOOS=${DEPLOYMENT_GOOS} GOARCH=${DEPLOYMENT_GOARCH} go build -trimpath -ldflags='-s -w' -o=${DEPLOYMENT_BUILD_DIR}/cli ./cmd/cli
|
||||
|
||||
## build/production: cross-compile all production binaries (compatibility alias)
|
||||
.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
|
||||
build/production:
|
||||
@$(MAKE) --no-print-directory build/deployment DEPLOYMENT_GOOS=${PRODUCTION_GOOS} DEPLOYMENT_GOARCH=${PRODUCTION_GOARCH}
|
||||
|
||||
## code/stats: print source lines and their estimated number of book pages
|
||||
.PHONY: code/stats
|
||||
@@ -143,7 +154,7 @@ build/all: build/api build/web build/cli
|
||||
@$(MAKE) --no-print-directory code/stats
|
||||
|
||||
# ==================================================================================== #
|
||||
# PRODUCTION
|
||||
# REMOTE DEPLOYMENTS
|
||||
# ==================================================================================== #
|
||||
|
||||
PRODUCTION_HOST ?=
|
||||
@@ -152,48 +163,72 @@ 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
|
||||
TESTSERVER_HOST ?=
|
||||
TESTSERVER_SSH_USER ?= root
|
||||
TESTSERVER_SSH_PORT ?= 22
|
||||
TESTSERVER_SSH_IDENTITY_FILE ?=
|
||||
TESTSERVER_GOOS ?= linux
|
||||
TESTSERVER_GOARCH ?= amd64
|
||||
|
||||
.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
|
||||
DEMO_HOST ?=
|
||||
DEMO_SSH_USER ?= root
|
||||
DEMO_SSH_PORT ?= 22
|
||||
DEMO_SSH_IDENTITY_FILE ?=
|
||||
DEMO_GOOS ?= linux
|
||||
DEMO_GOARCH ?= amd64
|
||||
|
||||
## production/connect: connect to the production server
|
||||
.PHONY: production/connect
|
||||
production/connect: production/check-config
|
||||
ssh ${production_ssh_options} ${production_target}
|
||||
deployments := production testserver demo
|
||||
deployment_prefix.production := PRODUCTION
|
||||
deployment_prefix.testserver := TESTSERVER
|
||||
deployment_prefix.demo := DEMO
|
||||
deployment_env.production := ./remote/setup/.env
|
||||
deployment_env.testserver := ./remote/setup/.env.testserver
|
||||
deployment_env.demo := ./remote/setup/.env.demo
|
||||
|
||||
## 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
|
||||
deployment_setting = $($(deployment_prefix.$1)_$2)
|
||||
deployment_target = $(call deployment_setting,$1,SSH_USER)@$(call deployment_setting,$1,HOST)
|
||||
deployment_identity_option = $(if $(strip $(call deployment_setting,$1,SSH_IDENTITY_FILE)),-i $(call deployment_setting,$1,SSH_IDENTITY_FILE))
|
||||
deployment_ssh_options = -p $(call deployment_setting,$1,SSH_PORT) $(call deployment_identity_option,$1)
|
||||
deployment_scp_options = -P $(call deployment_setting,$1,SSH_PORT) $(call deployment_identity_option,$1)
|
||||
deployment_build_dir = ./bin/$(call deployment_setting,$1,GOOS)_$(call deployment_setting,$1,GOARCH)
|
||||
|
||||
## 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"'
|
||||
deployment_check_targets := $(addsuffix /check-config,$(deployments))
|
||||
deployment_connect_targets := $(addsuffix /connect,$(deployments))
|
||||
deployment_provision_targets := $(addsuffix /provision,$(deployments))
|
||||
deployment_deploy_targets := $(addsuffix /deploy,$(deployments))
|
||||
deployment_admin_targets := $(addsuffix /create-admin,$(deployments))
|
||||
|
||||
## 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
|
||||
.PHONY: $(deployment_check_targets) $(deployment_connect_targets) $(deployment_provision_targets) $(deployment_deploy_targets) $(deployment_admin_targets)
|
||||
|
||||
$(deployment_check_targets): %/check-config:
|
||||
@test -n "$(call deployment_setting,$*,HOST)" || { echo '$(deployment_prefix.$*)_HOST is required; configure it in config.mk' >&2; exit 1; }
|
||||
@test -n "$(call deployment_setting,$*,SSH_USER)" || { echo '$(deployment_prefix.$*)_SSH_USER is required' >&2; exit 1; }
|
||||
@[[ "$(call deployment_setting,$*,SSH_PORT)" =~ ^[0-9]+$$ ]] && (( $(call deployment_setting,$*,SSH_PORT) >= 1 && $(call deployment_setting,$*,SSH_PORT) <= 65535 )) || { echo '$(deployment_prefix.$*)_SSH_PORT must be between 1 and 65535' >&2; exit 1; }
|
||||
@[[ "$(call deployment_setting,$*,GOOS)" == linux ]] || { echo '$(deployment_prefix.$*)_GOOS must be linux' >&2; exit 1; }
|
||||
@[[ "$(call deployment_setting,$*,GOARCH)" == amd64 || "$(call deployment_setting,$*,GOARCH)" == arm64 ]] || { echo '$(deployment_prefix.$*)_GOARCH must be amd64 or arm64' >&2; exit 1; }
|
||||
@if [[ -n "$(call deployment_setting,$*,SSH_IDENTITY_FILE)" && ! -f "$(call deployment_setting,$*,SSH_IDENTITY_FILE)" ]]; then echo '$(deployment_prefix.$*)_SSH_IDENTITY_FILE does not exist' >&2; exit 1; fi
|
||||
|
||||
$(deployment_connect_targets): %/connect: %/check-config
|
||||
ssh $(call deployment_ssh_options,$*) $(call deployment_target,$*)
|
||||
|
||||
$(deployment_provision_targets): %/provision: %/check-config
|
||||
@test -f $(deployment_env.$*) || { echo 'Copy remote/setup/.env.example to $(deployment_env.$*) and configure it first' >&2; exit 1; }
|
||||
@permissions=$$(stat -c '%a' $(deployment_env.$*)); (( (8#$$permissions & 077) == 0 )) || { echo '$(deployment_env.$*) must have mode 0600' >&2; exit 1; }
|
||||
scp $(call deployment_scp_options,$*) ./remote/setup/provision-server.sh $(call deployment_target,$*):/tmp/gardomatic-provision-server.sh
|
||||
ssh $(call deployment_ssh_options,$*) $(call deployment_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' < $(deployment_env.$*)
|
||||
|
||||
$(deployment_deploy_targets): %/deploy: %/check-config
|
||||
@$(MAKE) --no-print-directory build/deployment DEPLOYMENT_GOOS=$(call deployment_setting,$*,GOOS) DEPLOYMENT_GOARCH=$(call deployment_setting,$*,GOARCH)
|
||||
ssh $(call deployment_ssh_options,$*) $(call deployment_target,$*) 'mkdir -p "$$HOME/gardomatic-deploy/migrations"'
|
||||
rsync -P -e "ssh $(call deployment_ssh_options,$*)" $(call deployment_build_dir,$*)/api $(call deployment_build_dir,$*)/web $(call deployment_build_dir,$*)/cli $(call deployment_target,$*):gardomatic-deploy/
|
||||
rsync -rP --delete -e "ssh $(call deployment_ssh_options,$*)" ./internal/storage/postgres/migrations/ $(call deployment_target,$*):gardomatic-deploy/migrations/
|
||||
rsync -P -e "ssh $(call deployment_ssh_options,$*)" ./remote/production/api.service ./remote/production/web.service ./remote/production/demo-reset.service ./remote/production/demo-reset.timer ./remote/production/deploy-server.sh ./remote/setup/create-admin.sh $(call deployment_target,$*):gardomatic-deploy/
|
||||
ssh -t $(call deployment_ssh_options,$*) $(call deployment_target,$*) 'chmod 700 "$$HOME/gardomatic-deploy/deploy-server.sh" && "$$HOME/gardomatic-deploy/deploy-server.sh"'
|
||||
|
||||
$(deployment_admin_targets): %/create-admin: %/check-config
|
||||
ssh -t $(call deployment_ssh_options,$*) $(call deployment_target,$*) /usr/local/sbin/gardomatic-create-admin
|
||||
|
||||
## production/connect|provision|deploy|create-admin: manage the production server
|
||||
## testserver/connect|provision|deploy|create-admin: manage the test server
|
||||
## demo/connect|provision|deploy|create-admin: manage the public demo server
|
||||
|
||||
Reference in New Issue
Block a user