diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8dc8322 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +LICENSE +dockerfile +makefile +.makeVars +*.md +*.sh diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 0000000..91db3ed --- /dev/null +++ b/config.yaml.example @@ -0,0 +1,4 @@ +--- +listen: + address: 0.0.0.0 + port: 80 diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..1376042 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Convert deployment paths into array +ENVIRONMENTS=($DEPLOYMENT_PATHS) + +# Check if the DEPLOYMENT_PATH is not already set +if [ -z "${DEPLOYMENT_PATH}" ]; then + # Print and ask for deployment environment (if more than one) + if [ "${#ENVIRONMENTS[@]}" -gt 1 ]; then + for i in "${!ENVIRONMENTS[@]}"; do + echo "$i: ${ENVIRONMENTS[$i]}" + done + read -p "Deployment environment: " DEPLOYMENT_ENVIRONMENT + fi + if [ -z "${DEPLOYMENT_ENVIRONMENT}" ]; then + DEPLOYMENT_ENVIRONMENT=0 + fi + # Select correct path + DEPLOYMENT_PATH="${ENVIRONMENTS[$DEPLOYMENT_ENVIRONMENT]}" +fi + +# Check if the DEPLOYMENT_VERSION is not already set +if [ -z "${DEPLOYMENT_VERSION}" ]; then + # Ask for deployment version + read -p "Version [latest]: " DEPLOYMENT_VERSION + if [ -z "${DEPLOYMENT_VERSION}" ]; then + DEPLOYMENT_VERSION=latest + fi +fi + +echo "${DEPLOYMENT_PATH}" +echo "${DEPLOYMENT_VERSION}" + +ssh $DEPLOYMENT_HOST \ + "cd ${DEPLOYMENT_PATH} && \ + git pull && \ + sed -i "s/VERSION=.*/VERSION=${DEPLOYMENT_VERSION}/" .env && \ + docker compose pull && \ + docker compose up -d" diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000..1084547 --- /dev/null +++ b/dockerfile @@ -0,0 +1,33 @@ +# Stage 1 · go builder +ARG GO_BUILDER=golang +ARG GO_VERSION=latest +FROM ${GO_BUILDER}:${GO_VERSION} AS build + +ARG GO_OS +ARG GO_ARCH +ARG GIT_HOST +ARG REPO_ORG +ARG REPO_NAME +ARG APP_VERSION + +# Copy the project inside the builder container +WORKDIR $GOPATH/src/${GIT_HOST}/$REPO_ORG/$REPO_NAME/ +COPY . . + +# Build the binary +RUN CGO_ENABLED=0 GOOS=${GO_OS} GOARCH=${GO_ARCH} \ + go build \ + -installsuffix cgo \ + -ldflags="-w -s -X 'main.APP_VERSION=${APP_VERSION}' -X 'main.COMMIT_ID=$(git log HEAD --oneline | awk '{print $1}' | head -n1)'" \ + --o /app + +# Stage 2 · scratch image +FROM scratch + +# Copy the necessary stuff from the build stage +COPY --from=build /app /app +# Copy the certificates - in case of fetches +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem + +# Execute the binary +ENTRYPOINT ["/app"] diff --git a/main.go b/main.go new file mode 100644 index 0000000..d089051 --- /dev/null +++ b/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +var APP_VERSION string = "latest" +var COMMIT_ID string = "undefined" +var ws *WebServer + +func main() { + // Create a channel to receive the OS signals + sc := make(chan os.Signal, 1) + signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM) + + // Initialize the WebService structure + ws = new(WebServer) + ws.Initialize() + + // Start the WebService + go ws.Start() + + // Wait for a signal + <-sc + fmt.Println("Shutting down...") + + // Create a context with a timeout for graceful shutdown + shCtx, shCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer shCancel() + + // Shutdown the HTTP server + err := ws.HTTPServer.Shutdown(shCtx) + if err != nil { + fmt.Printf("Server shutdown error: %s", err) + os.Exit(1) + } +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..4458508 --- /dev/null +++ b/makefile @@ -0,0 +1,54 @@ +#!make +include .vars + +default: version + +clean: + if [ "$$(docker images "$${CONTAINER_ORG}/$${CONTAINER_IMAGE}" --format "{{.Repository}}:{{.Tag}}")" != "" ]; then \ + docker image rm $$(docker images "$${CONTAINER_ORG}/$${CONTAINER_IMAGE}" --all --format "{{.Repository}}:{{.Tag}}"); \ + fi + +docker: clean + docker build \ + --build-arg GO_BUILDER=$${GO_BUILDER} \ + --build-arg GO_VERSION=$${GO_VERSION} \ + --build-arg GO_OS=$${GO_OS} \ + --build-arg GO_ARCH=$${GO_ARCH} \ + --build-arg GIT_HOST=$${GIT_HOST} \ + --build-arg REPO_ORG=$${REPO_ORG} \ + --build-arg REPO_NAME=$${REPO_NAME} \ + --build-arg APP_VERSION=$${APP_VERSION} \ + -t $${CONTAINER_ORG}/$${CONTAINER_IMAGE}:$${APP_VERSION} .; \ + if [ "$$(docker images --filter "dangling=true" --quiet --no-trunc)" != "" ]; then \ + docker image rm $$(docker images --filter "dangling=true" --quiet --no-trunc); \ + fi + +dockerpush: + docker push \ + $${CONTAINER_ORG}/$${CONTAINER_IMAGE}:$${APP_VERSION} + +deploy: + bash -c "./deploy.sh" + +version: + bash -c "./version.sh" + +run: + docker run \ + --rm \ + --tty \ + --interactive \ + --publish $${CONTAINER_IP}:$${CONTAINER_PORT}:80 \ + --workdir /go/src/$${GIT_HOST}/$${REPO_ORG}/$${REPO_NAME} \ + --volume $(shell pwd):/go/src/$${GIT_HOST}/$${REPO_ORG}/$${REPO_NAME} \ + $${GO_BUILDER}:$${GO_VERSION} \ + go run . + +dockerrun: + docker run \ + --rm \ + --tty \ + --interactive \ + --publish $${CONTAINER_IP}:$${CONTAINER_PORT}:80 \ + --volume $(shell pwd)/config.yml:/config.yml \ + $${CONTAINER_ORG}/$${CONTAINER_IMAGE}:latest diff --git a/routes.go b/routes.go new file mode 100644 index 0000000..eedf5b6 --- /dev/null +++ b/routes.go @@ -0,0 +1,7 @@ +package main + +import "github.com/gorilla/mux" + +func (s *WebServer) Routes(r *mux.Router) { + r.HandleFunc("/version", handleVersion).Methods("GET") +} diff --git a/templates/html/version.html b/templates/html/version.html new file mode 100644 index 0000000..e4f30ee --- /dev/null +++ b/templates/html/version.html @@ -0,0 +1,12 @@ + + + + {{.Name}} + + +

+ Version: {{.Version}}
+ Commit ID: {{.CommitId}} +

+ + diff --git a/type_webserver.go b/type_webserver.go new file mode 100644 index 0000000..51bc643 --- /dev/null +++ b/type_webserver.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "net/http" + "os" + + "github.com/gorilla/mux" + "gopkg.in/yaml.v2" +) + +type WebServer struct { + HTTPServer *http.Server + Listen WSListen `yaml:"listen"` +} + +type WSListen struct { + Address string `yaml:"address"` + Port string `yaml:"port"` +} + +func (s *WebServer) Initialize() { + // Initialize default values + s.Listen = WSListen{ + Address: "0.0.0.0", + Port: "80", + } + + // Attempt to read the config file + configFile, err := os.ReadFile("config.yml") + if err != nil { + if os.IsNotExist(err) { + // File does not exist, log and use default config + fmt.Println("Config file not found, using default settings.") + } else { + // Some other error occurred when trying to read the file, exit + fmt.Println("Error reading config file:", err) + os.Exit(1) + } + } else { + // If the file exists, unmarshal it into the ServiceSettings struct + err = yaml.Unmarshal(configFile, &s) + if err != nil { + fmt.Println("Error parsing config file:", err) + os.Exit(1) + } + } +} + +func (s *WebServer) Start() error { + // Create a new MUX router and an HTTP server + r := mux.NewRouter() + s.HTTPServer = &http.Server{ + Addr: s.Listen.Address + ":" + s.Listen.Port, + Handler: r, + } + + // Associate the various handlers (routes) + s.Routes(r) + + // Start the server + fmt.Println("Listening on", s.Listen.Address+":"+s.Listen.Port) + err := s.HTTPServer.ListenAndServe() + + // Return error, or nil + return err +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..7383cf4 --- /dev/null +++ b/version.go @@ -0,0 +1,26 @@ +package main + +import ( + _ "embed" + "html/template" + "net/http" +) + +//go:embed templates/html/version.html +var versionTemplate string + +func handleVersion(w http.ResponseWriter, r *http.Request) { + type SiteInfo struct { + CommitId string + Name string + Version string + } + + tmpl, _ := template.New("version.html").Parse(versionTemplate) + // Return (write) the version to the response body + tmpl.Execute(w, SiteInfo{ + CommitId: COMMIT_ID, + Name: "YASKM", + Version: APP_VERSION, + }) +} diff --git a/version.sh b/version.sh new file mode 100644 index 0000000..8643b00 --- /dev/null +++ b/version.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# Get version from user +read -p "Version [latest]: " VERSIONINPUT +# If version was not provided, use the latest commit short hash as version +if [ -z ${VERSIONINPUT} ]; then + APP_VERSION="latest" +else + APP_VERSION=${VERSIONINPUT} +fi + +# Get docker push option from user +read -p "Docker push? [n]: " DOCKERPUSH +if [ -z ${DOCKERPUSH} ]; then + DOCKERPUSH=n +fi + +# Create version tag (if provided) +if [ ! -z ${VERSIONINPUT} ]; then + git tag ${APP_VERSION} +fi + +# Build the app +export APP_VERSION +make docker +# If wanted, push the docker image +if [ ${DOCKERPUSH} = "y" ]; then + make dockerpush +fi