############################################################################## # Arguments ############################################################################## VERSION= TEST_TARGET?=./... ############################################################################## # Variables ############################################################################## # This Makefile's directory SCRIPT_DIR=$(abspath $(dir $(lastword $(MAKEFILE_LIST)))) # Remote repository for the project REMOTE_REPO_URL=https://github.com/dagu-org/dagu # Directories for miscellaneous files for the local environment LOCAL_DIR=$(SCRIPT_DIR)/.local LOCAL_BIN_DIR=$(LOCAL_DIR)/bin # Configuration directory CONFIG_DIR=$(SCRIPT_DIR)/config # Local build settings BIN_DIR=$(SCRIPT_DIR)/.local/bin BUILD_VERSION=$(shell git describe --tags) DATE=$(shell date +'%y%m%d%H%M%S') LDFLAGS=-X 'main.version=$(BUILD_VERSION)-$(DATE)' # Application name APP_NAME=dagu # Docker image build configuration DOCKER_CMD := docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm64/v8 --builder container --build-arg LDFLAGS="$(LDFLAGS)" --push --no-cache # Arguments for the tests GOTESTSUM_ARGS=--format=standard-quiet # Go test flags (see https://github.com/golang/go/issues/61229#issuecomment-1988965927) GO_TEST_FLAGS=-v --race -ldflags=-extldflags=-Wl # OpenAPI configuration OAPI_SPEC_DIR_V2=./api/v2 OAPI_SPEC_FILE_V2=${OAPI_SPEC_DIR_V2}/api.yaml OAPI_CONFIG_FILE_V2=${OAPI_SPEC_DIR_V2}/config.yaml OAPI_SPEC_DIR_V1=./api/v1 OAPI_SPEC_FILE_V1=${OAPI_SPEC_DIR_V1}/api.yaml OAPI_CONFIG_FILE_V1=${OAPI_SPEC_DIR_V1}/config.yaml # Frontend directories FE_DIR=./internal/service/frontend FE_GEN_DIR=${FE_DIR}/gen FE_ASSETS_DIR=${FE_DIR}/assets FE_BUILD_DIR=./ui/dist FE_BUNDLE_JS=${FE_ASSETS_DIR}/bundle.js # Colors for the output COLOR_GREEN=\033[0;32m COLOR_RESET=\033[0m COLOR_RED=\033[0;31m # Go packages for the tools PKG_golangci_lint=github.com/golangci/golangci-lint/v2/cmd/golangci-lint PKG_gotestsum=gotest.tools/gotestsum PKG_addlicense=github.com/google/addlicense PKG_changelog-from-release=github.com/rhysd/changelog-from-release/v3@latest PKG_oapi_codegen=github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen PKG_kin_openapi_validate=github.com/getkin/kin-openapi/cmd/validate PKG_protolint=github.com/yoheimuta/protolint/cmd/protolint PKG_protoc_gen_go=google.golang.org/protobuf/cmd/protoc-gen-go PKG_protoc_gen_go_grpc=google.golang.org/grpc/cmd/protoc-gen-go-grpc # Certificates for the development environment CERTS_DIR=${LOCAL_DIR}/certs DEV_CERT_SUBJ_CA="/C=TR/ST=ASIA/L=TOKYO/O=DEV/OU=DAGU/CN=*.dagu.dev/emailAddress=ca@dev.com" DEV_CERT_SUBJ_SERVER="/C=TR/ST=ASIA/L=TOKYO/O=DEV/OU=SERVER/CN=*.server.dev/emailAddress=server@dev.com" DEV_CERT_SUBJ_CLIENT="/C=TR/ST=ASIA/L=TOKYO/O=DEV/OU=CLIENT/CN=*.client.dev/emailAddress=client@dev.com" DEV_CERT_SUBJ_ALT="subjectAltName=DNS:localhost" CA_CERT_FILE=${CERTS_DIR}/ca-cert.pem CA_KEY_FILE=${CERTS_DIR}/ca-key.pem SERVER_CERT_REQ=${CERTS_DIR}/server-req.pem SERVER_CERT_FILE=${CERTS_DIR}/server-cert.pem SERVER_KEY_FILE=${CERTS_DIR}/server-key.pem CLIENT_CERT_REQ=${CERTS_DIR}/client-req.pem CLIENT_CERT_FILE=${CERTS_DIR}/client-cert.pem CLIENT_KEY_FILE=${CERTS_DIR}/client-key.pem OPENSSL_CONF=${CONFIG_DIR}/openssl.local.conf # Variables for installing protoc # Detect the OS OS := $(shell uname -s) ifeq (${OS},Darwin) OS := osx else OS := linux endif # Detect the Architecture ARCH := $(shell uname -m) ifeq (${ARCH},arm64) ARCH := aarch_64 else ARCH := x86_64 endif # Protobuf release URL PB_RELEASE_URL=https://github.com/protocolbuffers/protobuf/releases PB_VERSION=31.1 PB_RELEASE_NAME=protoc-${PB_VERSION}-${OS}-${ARCH} ############################################################################## # Targets ############################################################################## # run starts the frontend server and the scheduler. .PHONY: run run: ${FE_BUNDLE_JS} @echo "${COLOR_GREEN}Starting the frontend server and the scheduler...${COLOR_RESET}" @go run ./cmd start-all # server build the binary and start the server. .PHONY: run-server run-server: golangci-lint bin @echo "${COLOR_GREEN}Starting the server...${COLOR_RESET}" ${LOCAL_BIN_DIR}/${APP_NAME} server # scheduler build the binary and start the scheduler. .PHONY: run-scheduler run-scheduler: golangci-lint bin @echo "${COLOR_GREEN}Starting the scheduler...${COLOR_RESET}" ${LOCAL_BIN_DIR}/${APP_NAME} scheduler # check if the frontend assets are built. ${FE_BUNDLE_JS}: @echo "${COLOR_RED}Error: frontend assets are not built.${COLOR_RESET}" @echo "${COLOR_RED}Please run 'make ui' before starting the server.${COLOR_RESET}" # https starts the server with the HTTPS protocol. .PHONY: run-server-https run-server-https: ${SERVER_CERT_FILE} ${SERVER_KEY_FILE} @echo "${COLOR_GREEN}Starting the server with HTTPS...${COLOR_RESET}" @DAGU_CERT_FILE=${SERVER_CERT_FILE} \ DAGU_KEY_FILE=${SERVER_KEY_FILE} \ go run ./cmd start-all # test runs all tests. .PHONY: test test: bin @echo "${COLOR_GREEN}Running tests...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_gotestsum} @go clean -testcache @${LOCAL_BIN_DIR}/gotestsum ${GOTESTSUM_ARGS} -- ${GO_TEST_FLAGS} ${TEST_TARGET} # test-coverage runs all tests with coverage. .PHONY: test-coverage test-coverage: @echo "${COLOR_GREEN}Running tests with coverage...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_gotestsum} @${LOCAL_BIN_DIR}/gotestsum ${GOTESTSUM_ARGS} -- ${GO_TEST_FLAGS} -coverprofile="coverage.out" -covermode=atomic ${TEST_TARGET} @go tool cover -html=coverage.out # lint runs the linter. .PHONY: lint lint: golangci-lint # api generates the server code from the OpenAPI specification. .PHONY: api api: api-validate @echo "${COLOR_GREEN}Generating API...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_oapi_codegen} @${LOCAL_BIN_DIR}/oapi-codegen --config=${OAPI_CONFIG_FILE_V2} ${OAPI_SPEC_FILE_V2} # api-validate validates the OpenAPI specification. .PHONY: api-validate api-validate: @echo "${COLOR_GREEN}Validating API...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_kin_openapi_validate} @${LOCAL_BIN_DIR}/validate ${OAPI_SPEC_FILE_V2} # api-v1 generates the server code from the OpenAPI specification. .PHONY: apiv1 apiv1: apiv1-validate @echo "${COLOR_GREEN}Generating API...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_oapi_codegen} @${LOCAL_BIN_DIR}/oapi-codegen --config=${OAPI_CONFIG_FILE_V1} ${OAPI_SPEC_FILE_V1} # api-validate-v1 validates the OpenAPI specification. .PHONY: apiv1-validate apiv1-validate: @echo "${COLOR_GREEN}Validating API...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_kin_openapi_validate} @${LOCAL_BIN_DIR}/validate ${OAPI_SPEC_FILE_V1} # api generates the swagger server code. .PHONY: swagger swagger: clean-swagger gen-swagger .PHONY: proto proto: protolint protoc # Lint proto files protolint: @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_protolint} @echo "${GREEN}Linting proto files...${NC}" @${LOCAL_BIN_DIR}/protolint lint ${SCRIPT_DIR}/proto # Generate Go code from proto files protoc: ${LOCAL_DIR}/${PB_RELEASE_NAME} @ln -sf ${LOCAL_DIR}/${PB_RELEASE_NAME}/bin/protoc ${LOCAL_BIN_DIR}/protoc @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_protoc_gen_go} @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_protoc_gen_go_grpc} @echo "${GREEN}Generating Go code from proto files...${NC}" @env PATH="${LOCAL_BIN_DIR}:/usr/local/bin:/usr/bin:/bin" ${LOCAL_BIN_DIR}/protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ proto/coordinator/v1/*.proto # Download protoc ${LOCAL_DIR}/${PB_RELEASE_NAME}: @echo "${GREEN}Downloading protoc...${NC}" @mkdir -p ${LOCAL_BIN_DIR} @curl -L ${PB_RELEASE_URL}/download/v${PB_VERSION}/${PB_RELEASE_NAME}.zip -o ${LOCAL_DIR}/${PB_RELEASE_NAME}.zip @unzip ${LOCAL_DIR}/${PB_RELEASE_NAME} -d ${LOCAL_DIR}/${PB_RELEASE_NAME} @rm ${LOCAL_DIR}/${PB_RELEASE_NAME}.zip # certs generates the certificates to use in the development environment. .PHONY: certs certs: ${CERTS_DIR} ${SERVER_CERT_FILE} ${CLIENT_CERT_FILE} certs-check # build build the binary. .PHONY: build build: ui bin # build-image build the docker image and push to the registry. # VERSION should be set via the argument as follows: # ```sh # make build-image VERSION={version} # ``` # {version} should be the version number such as "1.13.0". .PHONY: build-image build-image: build-image-version .PHONY: build-image-version build-image-version: @if [ -z "$(VERSION)" ]; then \ echo "${COLOR_RED}Error: VERSION is not set${COLOR_RESET}"; \ echo "Usage: make build-image VERSION={version}"; \ exit 1; \ fi @echo "${COLOR_GREEN}Building the docker image with the version $(VERSION)...${COLOR_RESET}" @$(DOCKER_CMD) -t ghcr.io/dagu-org/${APP_NAME}:$(VERSION) . # build-image-latest build the docker image with the latest tag and push to # the registry. .PHONY: build-image-latest build-image-latest: @echo "${COLOR_GREEN}Building the docker image...${COLOR_RESET}" @$(DOCKER_CMD) -t ghcr.io/dagu-org/${APP_NAME}:latest . ${LOCAL_DIR}/merged: @mkdir -p ${LOCAL_DIR}/merged # addlicense adds license header to all files. .PHONY: addlicense addlicense: @echo "${COLOR_GREEN}Adding license headers...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install ${PKG_addlicense} @${LOCAL_BIN_DIR}/addlicense \ -ignore "**/node_modules/**" \ -ignore "./**/gen/**" \ -ignore "Dockerfile" \ -ignore "ui/*" \ -ignore "ui/**/*" \ -ignore "bin/*" \ -ignore "local/*" \ -ignore "docs/**" \ -ignore "**/examples/*" \ -ignore ".github/*" \ -ignore ".github/**/*" \ -ignore ".*" \ -ignore "**/*.yml" \ -ignore "**/*.yaml" \ -ignore "**/filenotify/*" \ -ignore "**/testdata/**" \ -c "Yota Hamada" \ -f scripts/header.txt \ . # cpuprof opens the CPU profile in the browser. cpuprof: @go tool pprof -http=:9999 cpu.prof ############################################################################## # Internal targets ############################################################################## # bin builds the go application. .PHONY: bin bin: @echo "${COLOR_GREEN}Building the binary...${COLOR_RESET}" @mkdir -p ${BIN_DIR} @go build -ldflags="$(LDFLAGS)" -o ${BIN_DIR}/${APP_NAME} ./cmd # build-keepalive builds the keepalive binary for all architectures using Zig. .PHONY: build-keepalive build-keepalive: @echo "${COLOR_GREEN}Building keepalive binaries with Zig for all architectures...${COLOR_RESET}" @mkdir -p internal/container/assets @rm -rf internal/container/assets/keepalive_* @cd internal/container/keepalive && \ echo "Building Darwin binaries..." && \ zig build-exe main.zig -target x86_64-macos -O ReleaseSmall -femit-bin=../assets/keepalive_darwin_amd64 && \ zig build-exe main.zig -target aarch64-macos -O ReleaseSmall -femit-bin=../assets/keepalive_darwin_arm64 && \ echo "Building Linux binaries..." && \ zig build-exe main.zig -target x86-linux-musl -O ReleaseSmall -femit-bin=../assets/keepalive_linux_386 && \ zig build-exe main.zig -target x86_64-linux-musl -O ReleaseSmall -femit-bin=../assets/keepalive_linux_amd64 && \ zig build-exe main.zig -target aarch64-linux-musl -O ReleaseSmall -femit-bin=../assets/keepalive_linux_arm64 && \ zig build-exe main.zig -target arm-linux-musleabihf -O ReleaseSmall -mcpu=generic+v7a -femit-bin=../assets/keepalive_linux_armv7 && \ zig build-exe main.zig -target arm-linux-musleabi -O ReleaseSmall -mcpu=generic+v6 -femit-bin=../assets/keepalive_linux_armv6 && \ zig build-exe main.zig -target powerpc64le-linux-musl -O ReleaseSmall -femit-bin=../assets/keepalive_linux_ppc64le && \ zig build-exe main.zig -target s390x-linux-musl -O ReleaseSmall -femit-bin=../assets/keepalive_linux_s390x && \ echo "Skipping BSD targets (require additional setup)..." @chmod +x internal/container/assets/keepalive_* 2>/dev/null || true @echo "${COLOR_GREEN}Cleaning up build artifacts...${COLOR_RESET}" @rm -f internal/container/assets/keepalive_*.o internal/container/assets/keepalive_*.obj @echo "${COLOR_GREEN}Generating checksums...${COLOR_RESET}" @cd internal/container/assets && \ if command -v sha256sum >/dev/null 2>&1; then \ sha256sum keepalive_* > keepalive_checksums.txt; \ else \ shasum -a 256 keepalive_* > keepalive_checksums.txt; \ fi @echo "${COLOR_GREEN}Done! Checksums saved to internal/container/assets/keepalive_checksums.txt${COLOR_RESET}" .PHONY: ui # ui builds the frontend codes. ui: clean-ui build-ui cp-assets # build-ui builds the frontend codes. .PHONY: build-ui build-ui: @echo "${COLOR_GREEN}Building UI...${COLOR_RESET}" @cd ui; \ pnpm install; \ NODE_OPTIONS="--max-old-space-size=8192" pnpm webpack --config webpack.dev.js --progress --color; \ pnpm webpack --config webpack.prod.js --progress --color @echo "${COLOR_GREEN}Waiting for the build to finish...${COLOR_RESET}" @sleep 3 # wait for the build to finish # build-ui-prod builds the frontend codes for production. .PHONY: cp-assets cp-assets: @echo "${COLOR_GREEN}Copying UI assets...${COLOR_RESET}" @rm -f ${FE_ASSETS_DIR}/* @cp ${FE_BUILD_DIR}/* ${FE_ASSETS_DIR} # clean-ui removes the UI build cache. .PHONY: clean-ui clean-ui: @echo "${COLOR_GREEN}Cleaning UI build cache...${COLOR_RESET}" @cd ui; \ rm -rf node_modules; \ rm -rf .cache; # golangci-lint run linting tool. .PHONY: golangci-lint golangci-lint: @echo "${COLOR_GREEN}Running linter...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install $(PKG_golangci_lint) @${LOCAL_BIN_DIR}/golangci-lint run --fix ./... # changelog generates a changelog from the releases. .PHONY: changelog changelog: @echo "${COLOR_GREEN}Running changelog...${COLOR_RESET}" @GOBIN=${LOCAL_BIN_DIR} go install $(PKG_changelog-from-release) @${LOCAL_BIN_DIR}/changelog-from-release -r ${REMOTE_REPO_URL} -c > CHANGELOG.md ############################################################################## # Certificates ############################################################################## ${CA_CERT_FILE}: @echo "${COLOR_GREEN}Generating CA certificates...${COLOR_RESET}" @openssl req -x509 -newkey rsa:4096 \ -nodes -days 365 -keyout ${CA_KEY_FILE} \ -out ${CA_CERT_FILE} \ -subj "$(DEV_CERT_SUBJ_CA)" ${SERVER_KEY_FILE}: @echo "${COLOR_GREEN}Generating server key...${COLOR_RESET}" @openssl req -newkey rsa:4096 -nodes -keyout ${SERVER_KEY_FILE} \ -out ${SERVER_CERT_REQ} \ -subj "$(DEV_CERT_SUBJ_SERVER)" ${SERVER_CERT_FILE}: ${CA_CERT_FILE} ${SERVER_KEY_FILE} @echo "${COLOR_GREEN}Generating server certificate...${COLOR_RESET}" @openssl x509 -req -in ${SERVER_CERT_REQ} -CA ${CA_CERT_FILE} -CAkey ${CA_KEY_FILE} \ -CAcreateserial -out ${SERVER_CERT_FILE} \ -extfile ${OPENSSL_CONF} ${CLIENT_KEY_FILE}: @echo "${COLOR_GREEN}Generating client key...${COLOR_RESET}" @openssl req -newkey rsa:4096 -nodes -keyout ${CLIENT_KEY_FILE} \ -out ${CLIENT_CERT_REQ} \ -subj "$(DEV_CERT_SUBJ_CLIENT)" ${CLIENT_CERT_FILE}: ${CA_CERT_FILE} ${CLIENT_KEY_FILE} @echo "${COLOR_GREEN}Generating client certificate...${COLOR_RESET}" @openssl x509 -req -in ${CLIENT_CERT_REQ} -days 60 -CA ${CA_CERT_FILE} \ -CAkey ${CA_KEY_FILE} -CAcreateserial -out ${CLIENT_CERT_FILE} \ -extfile ${OPENSSL_CONF} ${CERTS_DIR}: @echo "${COLOR_GREEN}Creating the certificates directory...${COLOR_RESET}" @mkdir -p ${CERTS_DIR} .PHONY: certs-check certs-check: @echo "${COLOR_GREEN}Checking CA certificate...${COLOR_RESET}" @openssl x509 -in ${SERVER_CERT_FILE} -noout -text