From 8d1555249a0c87f5e62510278c749517ad9cdba7 Mon Sep 17 00:00:00 2001 From: Bryan Joshua Pedini Date: Wed, 17 Dec 2025 10:49:43 +0100 Subject: [PATCH] go webserver template added --- .dockerignore | 6 ++++ config.yaml.example | 4 +++ deploy.sh | 39 +++++++++++++++++++++ dockerfile | 33 ++++++++++++++++++ main.go | 42 +++++++++++++++++++++++ makefile | 54 ++++++++++++++++++++++++++++++ routes.go | 7 ++++ templates/html/version.html | 12 +++++++ type_webserver.go | 67 +++++++++++++++++++++++++++++++++++++ version.go | 26 ++++++++++++++ version.sh | 29 ++++++++++++++++ 11 files changed, 319 insertions(+) create mode 100644 .dockerignore create mode 100644 config.yaml.example create mode 100644 deploy.sh create mode 100644 dockerfile create mode 100644 main.go create mode 100644 makefile create mode 100644 routes.go create mode 100644 templates/html/version.html create mode 100644 type_webserver.go create mode 100644 version.go create mode 100644 version.sh 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