From a9b07a7e1cab9119dd8720d59160a82c6b3c9b2f Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 13:27:59 +0800 Subject: [PATCH 01/17] start working on docker --- .dockerignore | 2 + .gitignore | 6 ++ .ssh/.gitkeep | 0 .ssh/dev/.gitkeep | 0 .ssh/dev/dev | 49 ++++++++++++++++ .ssh/dev/dev.pub | 1 + .ssh/prd/.gitkeep | 0 caddy/Caddyfile | 38 ++++++++++++ caddy/Dockerfile | 7 +++ config/makefile/build.mk | 4 ++ docker-compose.yml | 122 ++++++++++++++++++++++++--------------- docker/Dockerfile | 78 +++++++++++++++++++++++++ 12 files changed, 259 insertions(+), 48 deletions(-) create mode 100644 .ssh/.gitkeep create mode 100644 .ssh/dev/.gitkeep create mode 100644 .ssh/dev/dev create mode 100644 .ssh/dev/dev.pub create mode 100644 .ssh/prd/.gitkeep create mode 100644 caddy/Caddyfile create mode 100644 caddy/Dockerfile create mode 100644 docker/Dockerfile diff --git a/.dockerignore b/.dockerignore index ab7aae2c..66c8aa14 100644 --- a/.dockerignore +++ b/.dockerignore @@ -11,6 +11,7 @@ README.md .air.toml .editorconfig +.ssh/ .idea/ docs/ database/infra/ @@ -18,3 +19,4 @@ storage/media/ storage/logs/ tmp/ bin/ +caddy/ diff --git a/.gitignore b/.gitignore index a1feb6ce..f0b29a12 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,12 @@ database/infra/data tmp +# --- [API]: SSH +!.ssh/prd/.gitkeep +!.ssh/dev/.gitkeep +!.ssh/.gitkeep +.ssh/prd/*.* + # --- [API]: Bin bin !bin/.env diff --git a/.ssh/.gitkeep b/.ssh/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.ssh/dev/.gitkeep b/.ssh/dev/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.ssh/dev/dev b/.ssh/dev/dev new file mode 100644 index 00000000..02caceef --- /dev/null +++ b/.ssh/dev/dev @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/.ssh/dev/dev.pub b/.ssh/dev/dev.pub new file mode 100644 index 00000000..ebe3e879 --- /dev/null +++ b/.ssh/dev/dev.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkQOdgwSUfWE32wXOtv0FcV9USpDN+RQKxYrkPPZErLr60ezR6IT9w7CPjdqNzvIM4sQZAeJTRmi23ptmgfsa1L5rkXHNw2KWbX+g1WdySFQWfw+TW0poFiYxsMFaOCgDCfwbdVfR66VUbzWBCSi7s0K/lrGbhlVbcoppnfLx3v7ExKQz8rKOQz+JhHancUI5iBTSMxfI8IvJzWC2qYARluIpyYIQyGS/Af3gQEjQYK/HLKe5K25E16D+FRXGxFJIocfDzeEI8y0cx3oOKQurLsNKqGmY7uzNvfrMcipO7a3nclofKPFGP0ssts2HvuRoxB4x8JcW/71Xo0TfiGg1aOMCHQDdFdQRRrbSebTs5Y3gj1mo4P/gHCKA7sQcN8hJU91JN8AuetRgadogmEVN8wYiFjIawj0LvCBHHkZAbtvaEeS7208XuWcR0xMaTKiXqlHYRh851ZlaKk4jBvLUva8bi5an+sgXg73PgpD9UE76zpbmX5fibsC5Al0dj5yTfDPFd/H7ssVH50sC+MOylYVQYavDC/4jda5B5qBCnwP2oBpD0u4dQx5ilvPgAOcJzV3D37WFAbQnhuLrbH7+19MmZjxpiuzwHLJFvyoICAAKXBfnThRUUiFLqqXxxSUmCVQ5YX9V6TkiNuXUiEwGnXhVXg8zIAVp5mdfwQhQ3cQ== gustavoocanto@gmail.com diff --git a/.ssh/prd/.gitkeep b/.ssh/prd/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/caddy/Caddyfile b/caddy/Caddyfile new file mode 100644 index 00000000..88b552b4 --- /dev/null +++ b/caddy/Caddyfile @@ -0,0 +1,38 @@ +# Filename: caddy/Caddyfile +# This is a production-ready Caddyfile. +# For a real domain, replace "localhost" with "your-domain.com". +# Caddy will automatically provision a Let's Encrypt certificate for you. +{ + # Global options block + # Enable logging. By default, logs are sent to stdout in JSON format, + # which is ideal for containerized environments. + log + + # Enable OCSP stapling for better TLS performance and privacy. + ocsp_stapling off # Often needs tweaking in Docker, can be turned on if needed +} + +localhost { + # Enable compression to reduce bandwidth usage. + # Gzip is broadly supported. Zstd is newer and more efficient. + encode gzip zstd + + # Add security-related headers to protect against common attacks. + header { + # Enable HSTS to ensure browsers only connect via HTTPS. + Strict-Transport-Security "max-age=31536000;" + # Prevent clickjacking attacks. + X-Frame-Options "SAMEORIGIN" + # Prevent content type sniffing. + X-Content-Type-Options "nosniff" + # Enable browser's built-in XSS protection. + X-XSS-Protection "1; mode=block" + # Control which features and APIs can be used on the site. + Permissions-Policy "interest-cohort=()" + } + + # Reverse proxy all requests to the Go application service. + # 'go-app' is the service name defined in docker-compose.yml. + # Docker's internal DNS will resolve 'go-app' to the Go container's IP. + reverse_proxy go-app:8080 +} diff --git a/caddy/Dockerfile b/caddy/Dockerfile new file mode 100644 index 00000000..2e7dabfa --- /dev/null +++ b/caddy/Dockerfile @@ -0,0 +1,7 @@ +# Filename: caddy/Dockerfile +# Use the official Caddy image with the latest tag. +FROM caddy:latest + +# Copy your custom Caddyfile into the container. +# This overwrites the default Caddyfile. +COPY Caddyfile /etc/caddy/Caddyfile diff --git a/config/makefile/build.mk b/config/makefile/build.mk index 6210f9f1..6b90976a 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -48,3 +48,7 @@ build\:flush: build\:env: cp $(___BIN___ENV___FILE__TEMPLATE) $(___BIN___ENV___FILE) + +build\:api: + # ssh-add + DOCKER_BUILDKIT=1 docker-compose up --build -d diff --git a/docker-compose.yml b/docker-compose.yml index f47aad68..ca4eb820 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,53 +1,79 @@ networks: - gocanto: - name: gocanto - driver: bridge + caddy_net: + driver: bridge + gocanto: + name: gocanto + driver: bridge services: - postgres: - restart: always - image: postgres:17.4 - container_name: gocanto-db - env_file: - - .env - networks: - - gocanto - environment: - # --- Postgres CLI env vars. - PGUSER: ${ENV_DB_USER_NAME} - PGDATABASE: ${ENV_DB_DATABASE_NAME} - PGPASSWORD: ${ENV_DB_USER_PASSWORD} - # --- Docker postgres-image env vars. - POSTGRES_USER: ${ENV_DB_USER_NAME} - POSTGRES_DB: ${ENV_DB_DATABASE_NAME} - POSTGRES_PASSWORD: ${ENV_DB_USER_PASSWORD} - ports: - - "${ENV_DB_PORT}:${ENV_DB_PORT}" - volumes: - - ./database/infra/ssl/server.crt:/etc/ssl/certs/server.crt - - ./database/infra/ssl/server.key:/etc/ssl/private/server.key - - ./database/infra/data:/var/lib/postgresql/data - - ./database/infra/config/postgresql.conf:/etc/postgresql/postgresql.conf - - ./database/infra/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d - logging: - driver: "json-file" - options: - max-file: "20" - max-size: "10M" + # The Go application service + go-app: + # Build the image from the Dockerfile in the 'go-app' directory. + build: + context: . + dockerfile: ./docker/Dockerfile + args: + - SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY} - command: > - postgres -c config_file=/etc/postgresql/postgresql.conf + # Name the container for easier identification. + container_name: go_proxy_app + env_file: + - .env + # Restart the container automatically unless it is manually stopped. + restart: unless-stopped + # Expose the port to the internal Docker network. Caddy will connect to this. + expose: + - '8080' + # Set environment variables for the Go application. + environment: + - PORT=8080 + # Connect this service to our custom network. + networks: + - caddy_net - healthcheck: - interval: 10s - timeout: 5s - retries: 5 - test: [ - "CMD-SHELL", - "pg_isready", - "--username=${ENV_DB_USER_NAME}", - "--dbname=${ENV_DB_DATABASE_NAME}", - "--host=postgres", - "--port=${ENV_DB_PORT}", - "--version" - ] + postgres: + restart: always + image: postgres:17.4 + container_name: gocanto-db + env_file: + - .env + networks: + - gocanto + environment: + # --- Postgres CLI env vars. + PGUSER: ${ENV_DB_USER_NAME} + PGDATABASE: ${ENV_DB_DATABASE_NAME} + PGPASSWORD: ${ENV_DB_USER_PASSWORD} + # --- Docker postgres-image env vars. + POSTGRES_USER: ${ENV_DB_USER_NAME} + POSTGRES_DB: ${ENV_DB_DATABASE_NAME} + POSTGRES_PASSWORD: ${ENV_DB_USER_PASSWORD} + ports: + - "${ENV_DB_PORT}:${ENV_DB_PORT}" + volumes: + - ./database/infra/ssl/server.crt:/etc/ssl/certs/server.crt + - ./database/infra/ssl/server.key:/etc/ssl/private/server.key + - ./database/infra/data:/var/lib/postgresql/data + - ./database/infra/config/postgresql.conf:/etc/postgresql/postgresql.conf + - ./database/infra/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + logging: + driver: "json-file" + options: + max-file: "20" + max-size: "10M" + command: > + postgres -c config_file=/etc/postgresql/postgresql.conf + + healthcheck: + interval: 10s + timeout: 5s + retries: 5 + test: [ + "CMD-SHELL", + "pg_isready", + "--username=${ENV_DB_USER_NAME}", + "--dbname=${ENV_DB_DATABASE_NAME}", + "--host=postgres", + "--port=${ENV_DB_PORT}", + "--version" + ] diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..800eec76 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,78 @@ +# Filename: Dockerfile.go +# Use Docker BuildKit syntax for advanced features. +#syntax=docker/dockerfile:1.4 + +# --- Build Stage --- +# Use the official Go image, specifically version 1.24, as the builder. +# Using alpine for a smaller base image. +FROM golang:1.24-alpine AS builder + +# Install necessary build tools. 'git' is required for Go modules, +# 'openssh-client' for SSH, and 'tzdata' for timezone info. +RUN apk add --no-cache git tzdata openssh-client + +# Set the working directory inside the container. +WORKDIR /app + +# Before fetching dependencies, configure git to use SSH for GitHub URLs. +# This forces git to use SSH instead of HTTPS. +RUN git config --global url."ssh://git@github.com/".insteadOf "https://github.com/" + +# Add GitHub's public key to the known_hosts file to prevent +# "Host key verification failed" errors. +RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts + +# Declare a build argument for the SSH private key. +# This key will be passed in during the build process. +ARG SSH_PRIVATE_KEY + +# Write the provided SSH private key to the standard key location +# and set the correct file permissions (600). This is critical for SSH security. +RUN echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa && \ + chmod 600 ~/.ssh/id_rsa + +# Copy the Go module files from the build context (project root). +COPY go.mod go.sum ./ + +# Copy the application source code. +COPY main.go ./ + +# Run go mod tidy to sync dependencies. It will now use the id_rsa file for auth. +RUN go mod tidy + +# Download dependencies listed in go.mod. +RUN go mod download + +# Build the Go application. +# -o /app/server creates the binary named 'server' in the /app directory. +# CGO_ENABLED=0 disables Cgo, which is needed for a static binary. +# -ldflags="-s -w" strips debugging information, reducing the binary size. +RUN CGO_ENABLED=0 go build -o /app/server -ldflags="-s -w" . + +# --- Final Stage --- +# Use a minimal, non-root base image for the final container for security. +FROM alpine:latest + +# We'll create a non-root user for security purposes. +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + +# Set the working directory. +WORKDIR /home/appuser + +# Copy only the compiled binary from the builder stage. +COPY --from=builder /app/server . + +# Copy timezone data from the builder stage. +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo + +# Ensure the new user owns the application files. +RUN chown -R appuser:appgroup /home/appuser + +# Switch to the non-root user. +USER appuser + +# Expose the port the app runs on. +EXPOSE 8080 + +# The command to run when the container starts. +CMD ["./server"] From 246b17ab3bc859cfb6592d344e403fbc7ea2a0a3 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 13:41:32 +0800 Subject: [PATCH 02/17] revert this --- Makefile | 1 + config/makefile/build.mk | 4 ---- config/makefile/ssh.mk | 10 ++++++++++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 config/makefile/ssh.mk diff --git a/Makefile b/Makefile index d29cb5cb..9ab8ccff 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,7 @@ include ./config/makefile/db.mk include ./config/makefile/app.mk include ./config/makefile/logs.mk include ./config/makefile/build.mk +include ./config/makefile/ssh.mk # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # diff --git a/config/makefile/build.mk b/config/makefile/build.mk index 6b90976a..6210f9f1 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -48,7 +48,3 @@ build\:flush: build\:env: cp $(___BIN___ENV___FILE__TEMPLATE) $(___BIN___ENV___FILE) - -build\:api: - # ssh-add - DOCKER_BUILDKIT=1 docker-compose up --build -d diff --git a/config/makefile/ssh.mk b/config/makefile/ssh.mk new file mode 100644 index 00000000..b1fa83f6 --- /dev/null +++ b/config/makefile/ssh.mk @@ -0,0 +1,10 @@ +___SSH___ROOT___PATH := $(shell pwd) +___SSH___SSH___DEV___FILE := ___SSH___ROOT___PATH/.ssh/dev/key +___SSH___SSH___PRD___FILE := ___SSH___ROOT___PATH/.ssh/prd/key + +.ssh\:dev: + echo email + #ssh-keygen -t rsa -b 4096 -C (email) -f ___SSH___SSH___DEV___FILE + +.key\:prd: + ssh-keygen -t rsa -b 4096 -C (email) -f ___SSH___SSH___PRD___FILE From 215e4e4e86069be7ec2e84847752a65a359cf787 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 13:54:28 +0800 Subject: [PATCH 03/17] add ssh dev keys --- .ssh/dev/dev | 49 ------------------------------------------ .ssh/dev/dev.pub | 1 - Makefile | 4 ++++ config/makefile/ssh.mk | 17 +++++++++------ 4 files changed, 14 insertions(+), 57 deletions(-) delete mode 100644 .ssh/dev/dev delete mode 100644 .ssh/dev/dev.pub diff --git a/.ssh/dev/dev b/.ssh/dev/dev deleted file mode 100644 index 02caceef..00000000 --- a/.ssh/dev/dev +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -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 ------END OPENSSH PRIVATE KEY----- diff --git a/.ssh/dev/dev.pub b/.ssh/dev/dev.pub deleted file mode 100644 index ebe3e879..00000000 --- a/.ssh/dev/dev.pub +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCkQOdgwSUfWE32wXOtv0FcV9USpDN+RQKxYrkPPZErLr60ezR6IT9w7CPjdqNzvIM4sQZAeJTRmi23ptmgfsa1L5rkXHNw2KWbX+g1WdySFQWfw+TW0poFiYxsMFaOCgDCfwbdVfR66VUbzWBCSi7s0K/lrGbhlVbcoppnfLx3v7ExKQz8rKOQz+JhHancUI5iBTSMxfI8IvJzWC2qYARluIpyYIQyGS/Af3gQEjQYK/HLKe5K25E16D+FRXGxFJIocfDzeEI8y0cx3oOKQurLsNKqGmY7uzNvfrMcipO7a3nclofKPFGP0ssts2HvuRoxB4x8JcW/71Xo0TfiGg1aOMCHQDdFdQRRrbSebTs5Y3gj1mo4P/gHCKA7sQcN8hJU91JN8AuetRgadogmEVN8wYiFjIawj0LvCBHHkZAbtvaEeS7208XuWcR0xMaTKiXqlHYRh851ZlaKk4jBvLUva8bi5an+sgXg73PgpD9UE76zpbmX5fibsC5Al0dj5yTfDPFd/H7ssVH50sC+MOylYVQYavDC/4jda5B5qBCnwP2oBpD0u4dQx5ilvPgAOcJzV3D37WFAbQnhuLrbH7+19MmZjxpiuzwHLJFvyoICAAKXBfnThRUUiFLqqXxxSUmCVQ5YX9V6TkiNuXUiEwGnXhVXg8zIAVp5mdfwQhQ3cQ== gustavoocanto@gmail.com diff --git a/Makefile b/Makefile index 9ab8ccff..67ef3603 100644 --- a/Makefile +++ b/Makefile @@ -90,4 +90,8 @@ help: @printf "$(BOLD)$(BLUE)Log Commands:$(NC)\n" @printf " $(BOLD)$(GREEN)logs:fresh$(NC) : Clear application logs.\n" + @printf "$(BOLD)$(BLUE)SSH Commands:$(NC)\n" + @printf " $(BOLD)$(GREEN)ssh:dev$(NC) : Generate the ssh pairs keys for development.\n" + @printf " $(BOLD)$(GREEN)ssh:prd$(NC) : Generate the ssh pairs keys for production.\n" + @printf "$(NC)" diff --git a/config/makefile/ssh.mk b/config/makefile/ssh.mk index b1fa83f6..d485c743 100644 --- a/config/makefile/ssh.mk +++ b/config/makefile/ssh.mk @@ -1,10 +1,13 @@ ___SSH___ROOT___PATH := $(shell pwd) -___SSH___SSH___DEV___FILE := ___SSH___ROOT___PATH/.ssh/dev/key -___SSH___SSH___PRD___FILE := ___SSH___ROOT___PATH/.ssh/prd/key +___SSH___SSH___DEV___FILE := $(___SSH___ROOT___PATH)/.ssh/dev/key +___SSH___SSH___PRD___FILE := $(___SSH___ROOT___PATH)/.ssh/prd/key -.ssh\:dev: - echo email - #ssh-keygen -t rsa -b 4096 -C (email) -f ___SSH___SSH___DEV___FILE +ssh\:dev: + rm -rf ___SSH___SSH___DEV___FILE && \ + rm -rf "$(___SSH___SSH___DEV___FILE).pub" && \ + ssh-keygen -t rsa -b 4096 -C $(email) -f $(___SSH___SSH___DEV___FILE) -.key\:prd: - ssh-keygen -t rsa -b 4096 -C (email) -f ___SSH___SSH___PRD___FILE +ssh\:prd: + rm -rf ___SSH___SSH___PRD___FILE && \ + rm -rf "$(___SSH___SSH___PRD___FILE).pub" && \ + ssh-keygen -t rsa -b 4096 -C $(email) -f $(___SSH___SSH___PRD___FILE) From 8f3c845ccad6ad040da20d73b051b149971eb757 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 13:55:55 +0800 Subject: [PATCH 04/17] gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f0b29a12..422aab33 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ tmp !.ssh/prd/.gitkeep !.ssh/dev/.gitkeep !.ssh/.gitkeep -.ssh/prd/*.* +.ssh/prd/ # --- [API]: Bin bin From 011e7921863617d7b74c8e8b2dedf6ea09ce363a Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 14:20:24 +0800 Subject: [PATCH 05/17] move to secrets --- .env.example | 2 +- deploy_key | 49 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 28 +++++++++++++++++++++++--- docker/Dockerfile | 18 ++++++----------- 4 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 deploy_key diff --git a/.env.example b/.env.example index 732da9d9..08ba884e 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,7 @@ ENV_APP_LOGS_DATE_FORMAT="2006_02_01" ENV_HTTP_HOST=localhost ENV_HTTP_PORT=8080 -# --- App super admin credentials +# --- App token credentials ENV_APP_TOKEN_PUBLIC="" ENV_APP_TOKEN_PRIVATE="" diff --git a/deploy_key b/deploy_key new file mode 100644 index 00000000..3ed2f7cf --- /dev/null +++ b/deploy_key @@ -0,0 +1,49 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/docker-compose.yml b/docker-compose.yml index ca4eb820..451acbb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,8 @@ +# Define the secret. Docker Compose will load the content from the specified file. +secrets: + deploy_key: + file: ./.ssh/prd/key + networks: caddy_net: driver: bridge @@ -12,9 +17,8 @@ services: build: context: . dockerfile: ./docker/Dockerfile - args: - - SSH_PRIVATE_KEY=${SSH_PRIVATE_KEY} - + secrets: + - deploy_key # Name the container for easier identification. container_name: go_proxy_app env_file: @@ -31,6 +35,24 @@ services: networks: - caddy_net + # The Caddy reverse proxy service + caddy: + # Build the image from the Dockerfile in the 'caddy' directory. + build: + context: ./caddy + dockerfile: ./caddy/Dockerfile + container_name: go_proxy_caddy + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - caddy_data:/data + - caddy_config:/config + networks: + - caddy_net + - + postgres: restart: always image: postgres:17.4 diff --git a/docker/Dockerfile b/docker/Dockerfile index 800eec76..9f602b76 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,26 +22,20 @@ RUN git config --global url."ssh://git@github.com/".insteadOf "https://github.co # "Host key verification failed" errors. RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts -# Declare a build argument for the SSH private key. -# This key will be passed in during the build process. -ARG SSH_PRIVATE_KEY - -# Write the provided SSH private key to the standard key location -# and set the correct file permissions (600). This is critical for SSH security. -RUN echo "${SSH_PRIVATE_KEY}" > ~/.ssh/id_rsa && \ - chmod 600 ~/.ssh/id_rsa - # Copy the Go module files from the build context (project root). COPY go.mod go.sum ./ # Copy the application source code. COPY main.go ./ -# Run go mod tidy to sync dependencies. It will now use the id_rsa file for auth. -RUN go mod tidy +# Run go mod tidy to sync dependencies. +# The --mount=type=secret flag securely mounts the deploy_key file from the host +# directly to the standard SSH key location inside the container. +# This is the most secure way to handle private keys during a build. +RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 go mod tidy # Download dependencies listed in go.mod. -RUN go mod download +RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 go mod download # Build the Go application. # -o /app/server creates the binary named 'server' in the /app directory. From c1756047bdca1a7d853df1a00fe8f6b1d024c527 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 16:21:09 +0800 Subject: [PATCH 06/17] fix compilation --- .dockerignore | 4 -- .env.example | 4 ++ .gitignore | 6 -- .ssh/.gitkeep | 0 .ssh/dev/.gitkeep | 0 .ssh/prd/.gitkeep | 0 Makefile | 5 -- config/makefile/ssh.mk | 13 ----- deploy_key | 49 ---------------- docker-compose.yml | 47 +++------------ docker/Dockerfile | 72 ----------------------- docker/dockerfile-api | 127 +++++++++++++++++++++++++++++++++++++++++ go.mod | 2 + 13 files changed, 142 insertions(+), 187 deletions(-) delete mode 100644 .ssh/.gitkeep delete mode 100644 .ssh/dev/.gitkeep delete mode 100644 .ssh/prd/.gitkeep delete mode 100644 config/makefile/ssh.mk delete mode 100644 deploy_key delete mode 100644 docker/Dockerfile create mode 100644 docker/dockerfile-api diff --git a/.dockerignore b/.dockerignore index 66c8aa14..68d543d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,3 @@ -.env -.env.example -.env.production - .gitattributes .github/ .gitignore diff --git a/.env.example b/.env.example index 08ba884e..3bacd131 100644 --- a/.env.example +++ b/.env.example @@ -30,3 +30,7 @@ ENV_DB_URL="" # Example: postgresql://gocanto-user:gocanto-password@postgres:543 # --- Sentry ENV_SENTRY_DSN="" ENV_SENTRY_CSP="" + +# --- Docker +ENV_DOCKER_USER="" +ENV_DOCKER_USER_GROUP="" diff --git a/.gitignore b/.gitignore index 422aab33..a1feb6ce 100644 --- a/.gitignore +++ b/.gitignore @@ -4,12 +4,6 @@ database/infra/data tmp -# --- [API]: SSH -!.ssh/prd/.gitkeep -!.ssh/dev/.gitkeep -!.ssh/.gitkeep -.ssh/prd/ - # --- [API]: Bin bin !bin/.env diff --git a/.ssh/.gitkeep b/.ssh/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/.ssh/dev/.gitkeep b/.ssh/dev/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/.ssh/prd/.gitkeep b/.ssh/prd/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Makefile b/Makefile index 67ef3603..d29cb5cb 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,6 @@ include ./config/makefile/db.mk include ./config/makefile/app.mk include ./config/makefile/logs.mk include ./config/makefile/build.mk -include ./config/makefile/ssh.mk # -------------------------------------------------------------------------------------------------------------------- # # -------------------------------------------------------------------------------------------------------------------- # @@ -90,8 +89,4 @@ help: @printf "$(BOLD)$(BLUE)Log Commands:$(NC)\n" @printf " $(BOLD)$(GREEN)logs:fresh$(NC) : Clear application logs.\n" - @printf "$(BOLD)$(BLUE)SSH Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)ssh:dev$(NC) : Generate the ssh pairs keys for development.\n" - @printf " $(BOLD)$(GREEN)ssh:prd$(NC) : Generate the ssh pairs keys for production.\n" - @printf "$(NC)" diff --git a/config/makefile/ssh.mk b/config/makefile/ssh.mk deleted file mode 100644 index d485c743..00000000 --- a/config/makefile/ssh.mk +++ /dev/null @@ -1,13 +0,0 @@ -___SSH___ROOT___PATH := $(shell pwd) -___SSH___SSH___DEV___FILE := $(___SSH___ROOT___PATH)/.ssh/dev/key -___SSH___SSH___PRD___FILE := $(___SSH___ROOT___PATH)/.ssh/prd/key - -ssh\:dev: - rm -rf ___SSH___SSH___DEV___FILE && \ - rm -rf "$(___SSH___SSH___DEV___FILE).pub" && \ - ssh-keygen -t rsa -b 4096 -C $(email) -f $(___SSH___SSH___DEV___FILE) - -ssh\:prd: - rm -rf ___SSH___SSH___PRD___FILE && \ - rm -rf "$(___SSH___SSH___PRD___FILE).pub" && \ - ssh-keygen -t rsa -b 4096 -C $(email) -f $(___SSH___SSH___PRD___FILE) diff --git a/deploy_key b/deploy_key deleted file mode 100644 index 3ed2f7cf..00000000 --- a/deploy_key +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN OPENSSH PRIVATE KEY----- -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 ------END OPENSSH PRIVATE KEY----- diff --git a/docker-compose.yml b/docker-compose.yml index 451acbb8..d4adc39d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,3 @@ -# Define the secret. Docker Compose will load the content from the specified file. -secrets: - deploy_key: - file: ./.ssh/prd/key - networks: caddy_net: driver: bridge @@ -11,47 +6,23 @@ networks: driver: bridge services: - # The Go application service go-app: - # Build the image from the Dockerfile in the 'go-app' directory. + env_file: + - .env build: context: . - dockerfile: ./docker/Dockerfile - secrets: - - deploy_key - # Name the container for easier identification. + dockerfile: ./docker/dockerfile-api + args: + - APP_VERSION=v1.0.0-release + - APP_HOST_PORT=${ENV_HTTP_PORT} + - APP_USER=${ENV_DOCKER_USER} + - APP_GROUP=${ENV_DOCKER_USER_GROUP} container_name: go_proxy_app - env_file: - - .env - # Restart the container automatically unless it is manually stopped. restart: unless-stopped - # Expose the port to the internal Docker network. Caddy will connect to this. expose: - - '8080' - # Set environment variables for the Go application. - environment: - - PORT=8080 - # Connect this service to our custom network. - networks: - - caddy_net - - # The Caddy reverse proxy service - caddy: - # Build the image from the Dockerfile in the 'caddy' directory. - build: - context: ./caddy - dockerfile: ./caddy/Dockerfile - container_name: go_proxy_caddy - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - caddy_data:/data - - caddy_config:/config + - ${ENV_HTTP_PORT} networks: - caddy_net - - postgres: restart: always diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index 9f602b76..00000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -# Filename: Dockerfile.go -# Use Docker BuildKit syntax for advanced features. -#syntax=docker/dockerfile:1.4 - -# --- Build Stage --- -# Use the official Go image, specifically version 1.24, as the builder. -# Using alpine for a smaller base image. -FROM golang:1.24-alpine AS builder - -# Install necessary build tools. 'git' is required for Go modules, -# 'openssh-client' for SSH, and 'tzdata' for timezone info. -RUN apk add --no-cache git tzdata openssh-client - -# Set the working directory inside the container. -WORKDIR /app - -# Before fetching dependencies, configure git to use SSH for GitHub URLs. -# This forces git to use SSH instead of HTTPS. -RUN git config --global url."ssh://git@github.com/".insteadOf "https://github.com/" - -# Add GitHub's public key to the known_hosts file to prevent -# "Host key verification failed" errors. -RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts - -# Copy the Go module files from the build context (project root). -COPY go.mod go.sum ./ - -# Copy the application source code. -COPY main.go ./ - -# Run go mod tidy to sync dependencies. -# The --mount=type=secret flag securely mounts the deploy_key file from the host -# directly to the standard SSH key location inside the container. -# This is the most secure way to handle private keys during a build. -RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 go mod tidy - -# Download dependencies listed in go.mod. -RUN --mount=type=secret,id=deploy_key,target=/root/.ssh/id_rsa,mode=0600 go mod download - -# Build the Go application. -# -o /app/server creates the binary named 'server' in the /app directory. -# CGO_ENABLED=0 disables Cgo, which is needed for a static binary. -# -ldflags="-s -w" strips debugging information, reducing the binary size. -RUN CGO_ENABLED=0 go build -o /app/server -ldflags="-s -w" . - -# --- Final Stage --- -# Use a minimal, non-root base image for the final container for security. -FROM alpine:latest - -# We'll create a non-root user for security purposes. -RUN addgroup -S appgroup && adduser -S appuser -G appgroup - -# Set the working directory. -WORKDIR /home/appuser - -# Copy only the compiled binary from the builder stage. -COPY --from=builder /app/server . - -# Copy timezone data from the builder stage. -COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo - -# Ensure the new user owns the application files. -RUN chown -R appuser:appgroup /home/appuser - -# Switch to the non-root user. -USER appuser - -# Expose the port the app runs on. -EXPOSE 8080 - -# The command to run when the container starts. -CMD ["./server"] diff --git a/docker/dockerfile-api b/docker/dockerfile-api new file mode 100644 index 00000000..313abc2d --- /dev/null +++ b/docker/dockerfile-api @@ -0,0 +1,127 @@ +# API Docker File. + +# --- Build Arguments for Configuration --- +# Defines variables that make this Dockerfile more configurable and reusable. +# These can be overridden during the build process (e.g., via docker-compose). +ARG GO_VERSION=1.24 +ARG ALPINE_VERSION=latest + +ARG APP_VERSION="0.0.0-dev" +ARG BUILD_TAGS="posts,experience,profile,projects,social,talks,gus,gocanto" + +ARG BINARY_NAME=server +ARG APP_HOST_PORT=8080 +ARG APP_USER=appuser +ARG APP_GROUP=appgroup +ARG APP_HOME=/home/${APP_USER} + +ARG BUILD_DIR=/app +ARG STORAGE_DIR=storage +ARG LOGS_DIR=logs +ARG MEDIA_DIR=media +ARG FIXTURES_DIR=fixture + +# --- Build Stage --- +# This stage, named 'builder', is responsible for compiling the Go application. +# It uses a Go-specific base image that includes the necessary toolchain. +FROM golang:${GO_VERSION}-alpine AS builder + +# Forwards build-time arguments into this specific stage so they can be referenced. +ARG BUILD_DIR +ARG BINARY_NAME +ARG APP_VERSION +ARG BUILD_TAGS + +# Installs the timezone database package into the builder image. +# This is necessary because the base 'alpine' image is minimal and does not +# include this data by default. It is copied to the final image later. +RUN apk add --no-cache tzdata + +# Sets the primary working directory for this stage of the build. +WORKDIR ${BUILD_DIR} + +# Copies the Go module definition files into the builder. +# This is done first to leverage Docker's layer caching. The subsequent +# 'go mod download' step will only be re-run if these files have changed. +COPY go.mod go.sum ./ + +# Downloads the application's external dependencies as specified in the go.mod file. +# The 'replace' directive in go.mod is used to resolve local packages, so this +# command will not attempt to download them from the internet. +RUN go mod download + +# Copies the rest of the application's source code into the builder. +# This includes the main package and any local packages like 'boost'. +COPY . . + +# Compiles the Go application into a single, statically-linked binary. +# -tags: Applies build constraints, allowing for conditional compilation. +# -o: Specifies the output path and name for the compiled binary. +# -ldflags: Provides flags to the linker. +# -s: Strips the symbol table, reducing binary size. +# -w: Strips DWARF debugging information, further reducing size. +# -X: Injects a value into a string variable at build time. Here, it sets +# the application's version by targeting the 'Version' variable in the 'main' package. +RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -o ${BUILD_DIR}/${BINARY_NAME} -ldflags="-s -w -X main.Version=${APP_VERSION}" . + +# --- Final Stage --- +# This is the final, production-ready stage of the build. +# It uses a minimal 'alpine' base image to create a small and secure container. +FROM alpine:${ALPINE_VERSION} + +# Forwards build-time arguments into this final stage so they can be referenced. +ARG APP_USER +ARG APP_GROUP +ARG APP_HOME +ARG BUILD_DIR +ARG BINARY_NAME +ARG STORAGE_DIR +ARG LOGS_DIR +ARG MEDIA_DIR +ARG FIXTURES_DIR + +# Creates a dedicated, non-root user and group for the application. +# Running the application as a non-root user is a critical security best practice. +RUN addgroup -S ${APP_GROUP} && adduser -S ${APP_USER} -G ${APP_GROUP} + +# Sets the working directory for the final container. +WORKDIR ${APP_HOME} + +# Creates the necessary storage directories inside the container. +# These folders will be owned by the application user and can be used for runtime file generation. +RUN mkdir -p ${STORAGE_DIR}/${LOGS_DIR} ${STORAGE_DIR}/${MEDIA_DIR} + +# Copies the 'fixture' files from the local project directory into the container. +# This is useful for including seed data or other essential files with the application. +COPY ${STORAGE_DIR}/${FIXTURES_DIR} ./${STORAGE_DIR}/${FIXTURES_DIR}/ + +# Copies the compiled application binary from the 'builder' stage. +# This is the core of the multi-stage build pattern, ensuring the final image +# contains only the compiled application and not the Go toolchain or source code. +COPY --from=builder ${BUILD_DIR}/${BINARY_NAME} . + +# Copies the timezone database from the 'builder' stage. +# This ensures that time-related functions in the application work correctly. +COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo + +# Copies the .env file into the container. +# This allows the application to load its configuration from environment variables. +# For this to work, '.env' must not be in the .dockerignore file. +COPY .env . + +# Recursively sets the ownership of all files in the application's home directory. +# This ensures the non-root application user has the correct permissions to execute the binary +# and write to the storage directories. +RUN chown -R ${APP_USER}:${APP_GROUP} ${APP_HOME} + +# Switches the context of the container to run as the non-root user. +# Any subsequent commands (like the CMD) will be executed by this user. +USER ${APP_USER} + +# Exposes the application's port from the container. +# This does not publish the port; it serves as documentation for which port to map. +EXPOSE ${APP_HOST_PORT} + +# Defines the default command to execute when the container starts. +# This runs the compiled application binary. +CMD ["./server"] diff --git a/go.mod b/go.mod index e91df159..1150f536 100644 --- a/go.mod +++ b/go.mod @@ -32,3 +32,5 @@ require ( golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect ) + +replace github.com/oullin/boost => ./boost From 61552f129f7f836629f7d6d151c1cb1964d1fadd Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 16:22:56 +0800 Subject: [PATCH 07/17] format --- .dockerignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 68d543d3..2afadc93 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,7 +7,6 @@ README.md .air.toml .editorconfig -.ssh/ .idea/ docs/ database/infra/ From 7083cedb527f1580359e22e828ff09a8068c753e Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 17:12:11 +0800 Subject: [PATCH 08/17] keep working on docker --- caddy/Caddyfile | 6 +++--- config/makefile/build.mk | 3 +++ docker-compose.yml | 35 +++++++++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 88b552b4..4a64f7d7 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -32,7 +32,7 @@ localhost { } # Reverse proxy all requests to the Go application service. - # 'go-app' is the service name defined in docker-compose.yml. - # Docker's internal DNS will resolve 'go-app' to the Go container's IP. - reverse_proxy go-app:8080 + # 'api' is the service name defined in docker-compose.yml. + # Docker's internal DNS will resolve 'api' to the Go container's IP. + reverse_proxy api:8080 } diff --git a/config/makefile/build.mk b/config/makefile/build.mk index 6210f9f1..17e05e7c 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -48,3 +48,6 @@ build\:flush: build\:env: cp $(___BIN___ENV___FILE__TEMPLATE) $(___BIN___ENV___FILE) + +build\:caddy: + docker compose up --build -d caddy diff --git a/docker-compose.yml b/docker-compose.yml index d4adc39d..c82d5e9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,3 +1,7 @@ +volumes: + caddy_data: + caddy_config: + networks: caddy_net: driver: bridge @@ -6,9 +10,28 @@ networks: driver: bridge services: - go-app: + caddy: + build: + context: ./caddy + dockerfile: Dockerfile + container_name: oullin_proxy + restart: unless-stopped + depends_on: + - api + ports: + - "8080:80" + - "8443:443" + volumes: + - caddy_data:/data + - caddy_config:/config + networks: + - caddy_net + + api: env_file: - .env + environment: + - ENV_DB_HOST="postgres" build: context: . dockerfile: ./docker/dockerfile-api @@ -17,17 +40,21 @@ services: - APP_HOST_PORT=${ENV_HTTP_PORT} - APP_USER=${ENV_DOCKER_USER} - APP_GROUP=${ENV_DOCKER_USER_GROUP} - container_name: go_proxy_app + container_name: oullin_api restart: unless-stopped + depends_on: + postgres: + condition: service_healthy expose: - ${ENV_HTTP_PORT} networks: - caddy_net + - gocanto postgres: - restart: always + restart: unless-stopped image: postgres:17.4 - container_name: gocanto-db + container_name: oullin_db env_file: - .env networks: From 7927a279624dafa36b4b27223c3732bccffc6b38 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Tue, 24 Jun 2025 17:26:19 +0800 Subject: [PATCH 09/17] wip --- boost/factory.go | 43 +++++------ boost/ignite.go | 12 +-- cli/main.go | 2 +- database/seeder/main.go | 159 ++++++++++++++++++++-------------------- main.go | 5 +- 5 files changed, 111 insertions(+), 110 deletions(-) diff --git a/boost/factory.go b/boost/factory.go index ea7f2f5e..f309627a 100644 --- a/boost/factory.go +++ b/boost/factory.go @@ -9,6 +9,7 @@ import ( "github.com/oullin/pkg/auth" "github.com/oullin/pkg/llogs" "log" + "os" "strconv" "strings" "time" @@ -56,49 +57,49 @@ func MakeLogs(env *env.Environment) *llogs.Driver { return &lDriver } -func MakeEnv(values map[string]string, validate *pkg.Validator) *env.Environment { +func MakeEnv(validate *pkg.Validator) *env.Environment { errorSufix := "Environment: " - port, _ := strconv.Atoi(values["ENV_DB_PORT"]) + port, _ := strconv.Atoi(os.Getenv("ENV_DB_PORT")) token := auth.Token{ - Public: strings.TrimSpace(values["ENV_APP_TOKEN_PUBLIC"]), - Private: strings.TrimSpace(values["ENV_APP_TOKEN_PRIVATE"]), + Public: strings.TrimSpace(os.Getenv("ENV_APP_TOKEN_PUBLIC")), + Private: strings.TrimSpace(os.Getenv("ENV_APP_TOKEN_PRIVATE")), } app := env.AppEnvironment{ - Name: strings.TrimSpace(values["ENV_APP_NAME"]), - Type: strings.TrimSpace(values["ENV_APP_ENV_TYPE"]), + Name: strings.TrimSpace(os.Getenv("ENV_APP_NAME")), + Type: strings.TrimSpace(os.Getenv("ENV_APP_ENV_TYPE")), Credentials: token, } db := env.DBEnvironment{ - UserName: strings.TrimSpace(values["ENV_DB_USER_NAME"]), - UserPassword: strings.TrimSpace(values["ENV_DB_USER_PASSWORD"]), - DatabaseName: strings.TrimSpace(values["ENV_DB_DATABASE_NAME"]), + UserName: strings.TrimSpace(os.Getenv("ENV_DB_USER_NAME")), + UserPassword: strings.TrimSpace(os.Getenv("ENV_DB_USER_PASSWORD")), + DatabaseName: strings.TrimSpace(os.Getenv("ENV_DB_DATABASE_NAME")), Port: port, - Host: strings.TrimSpace(values["ENV_DB_HOST"]), + Host: strings.TrimSpace(os.Getenv("ENV_DB_HOST")), DriverName: "postgres", - BinDir: strings.TrimSpace(values["EN_DB_BIN_DIR"]), - URL: strings.TrimSpace(values["ENV_DB_URL"]), - SSLMode: strings.TrimSpace(values["ENV_DB_SSL_MODE"]), - TimeZone: strings.TrimSpace(values["ENV_DB_TIMEZONE"]), + BinDir: strings.TrimSpace(os.Getenv("EN_DB_BIN_DIR")), + URL: strings.TrimSpace(os.Getenv("ENV_DB_URL")), + SSLMode: strings.TrimSpace(os.Getenv("ENV_DB_SSL_MODE")), + TimeZone: strings.TrimSpace(os.Getenv("ENV_DB_TIMEZONE")), } logsCreds := env.LogsEnvironment{ - Level: strings.TrimSpace(values["ENV_APP_LOG_LEVEL"]), - Dir: strings.TrimSpace(values["ENV_APP_LOGS_DIR"]), - DateFormat: strings.TrimSpace(values["ENV_APP_LOGS_DATE_FORMAT"]), + Level: strings.TrimSpace(os.Getenv("ENV_APP_LOG_LEVEL")), + Dir: strings.TrimSpace(os.Getenv("ENV_APP_LOGS_DIR")), + DateFormat: strings.TrimSpace(os.Getenv("ENV_APP_LOGS_DATE_FORMAT")), } net := env.NetEnvironment{ - HttpHost: strings.TrimSpace(values["ENV_HTTP_HOST"]), - HttpPort: strings.TrimSpace(values["ENV_HTTP_PORT"]), + HttpHost: strings.TrimSpace(os.Getenv("ENV_HTTP_HOST")), + HttpPort: strings.TrimSpace(os.Getenv("ENV_HTTP_PORT")), } sentryEnvironment := env.SentryEnvironment{ - DSN: strings.TrimSpace(values["ENV_SENTRY_DSN"]), - CSP: strings.TrimSpace(values["ENV_SENTRY_CSP"]), + DSN: strings.TrimSpace(os.Getenv("ENV_SENTRY_DSN")), + CSP: strings.TrimSpace(os.Getenv("ENV_SENTRY_CSP")), } if _, err := validate.Rejects(app); err != nil { diff --git a/boost/ignite.go b/boost/ignite.go index 03d73e0e..c3e72402 100644 --- a/boost/ignite.go +++ b/boost/ignite.go @@ -6,14 +6,10 @@ import ( "github.com/oullin/pkg" ) -func Ignite(envPath string) (*env.Environment, *pkg.Validator) { - validate := pkg.GetDefaultValidator() - - envMap, err := godotenv.Read(envPath) - - if err != nil { - panic("failed to read the .env file: " + err.Error()) +func Ignite(envPath string, validate *pkg.Validator) *env.Environment { + if err := godotenv.Load(envPath); err != nil { + panic("failed to read the .env file/values: " + err.Error()) } - return MakeEnv(envMap, validate), validate + return MakeEnv(validate) } diff --git a/cli/main.go b/cli/main.go index 79996bee..57d9726b 100644 --- a/cli/main.go +++ b/cli/main.go @@ -16,7 +16,7 @@ var guard gate.Guard var environment *env.Environment func init() { - secrets, _ := boost.Ignite("./../.env") + secrets := boost.Ignite("./../.env", pkg.GetDefaultValidator()) environment = secrets guard = gate.MakeGuard(environment.App.Credentials) diff --git a/database/seeder/main.go b/database/seeder/main.go index a9669531..8d92de75 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -1,118 +1,119 @@ package main import ( - "github.com/oullin/boost" - "github.com/oullin/database" - "github.com/oullin/database/seeder/seeds" - "github.com/oullin/env" - "github.com/oullin/pkg/cli" - "sync" - "time" + "github.com/oullin/boost" + "github.com/oullin/database" + "github.com/oullin/database/seeder/seeds" + "github.com/oullin/env" + "github.com/oullin/pkg" + "github.com/oullin/pkg/cli" + "sync" + "time" ) var environment *env.Environment func init() { - secrets, _ := boost.Ignite("./.env") + secrets := boost.Ignite("./.env", pkg.GetDefaultValidator()) - environment = secrets + environment = secrets } func main() { - cli.ClearScreen() + cli.ClearScreen() - dbConnection := boost.MakeDbConnection(environment) - logs := boost.MakeLogs(environment) + dbConnection := boost.MakeDbConnection(environment) + logs := boost.MakeLogs(environment) - defer (*logs).Close() - defer (*dbConnection).Close() + defer (*logs).Close() + defer (*dbConnection).Close() - // [1] --- Create the Seeder Runner. - seeder := seeds.MakeSeeder(dbConnection, environment) + // [1] --- Create the Seeder Runner. + seeder := seeds.MakeSeeder(dbConnection, environment) - // [2] --- Truncate the db. - if err := seeder.TruncateDB(); err != nil { - panic(err) - } else { - cli.Successln("db Truncated successfully ...") - time.Sleep(2 * time.Second) - } + // [2] --- Truncate the db. + if err := seeder.TruncateDB(); err != nil { + panic(err) + } else { + cli.Successln("db Truncated successfully ...") + time.Sleep(2 * time.Second) + } - // [3] --- Seed users and posts sequentially because the below seeders depend on them. - UserA, UserB := seeder.SeedUsers() - posts := seeder.SeedPosts(UserA, UserB) + // [3] --- Seed users and posts sequentially because the below seeders depend on them. + UserA, UserB := seeder.SeedUsers() + posts := seeder.SeedPosts(UserA, UserB) - categoriesChan := make(chan []database.Category) - tagsChan := make(chan []database.Tag) + categoriesChan := make(chan []database.Category) + tagsChan := make(chan []database.Tag) - go func() { - defer close(categoriesChan) + go func() { + defer close(categoriesChan) - cli.Warningln("Seeding categories ...") - categoriesChan <- seeder.SeedCategories() - }() + cli.Warningln("Seeding categories ...") + categoriesChan <- seeder.SeedCategories() + }() - go func() { - defer close(tagsChan) + go func() { + defer close(tagsChan) - cli.Magentaln("Seeding tags ...") - tagsChan <- seeder.SeedTags() - }() + cli.Magentaln("Seeding tags ...") + tagsChan <- seeder.SeedTags() + }() - // [4] Use channels to concurrently seed categories and tags since they are main dependencies. - categories := <-categoriesChan - tags := <-tagsChan + // [4] Use channels to concurrently seed categories and tags since they are main dependencies. + categories := <-categoriesChan + tags := <-tagsChan - // [5] Use a WaitGroup to run independent seeding tasks concurrently. - var wg sync.WaitGroup - wg.Add(6) + // [5] Use a WaitGroup to run independent seeding tasks concurrently. + var wg sync.WaitGroup + wg.Add(6) - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Blueln("Seeding comments ...") - seeder.SeedComments(posts...) - }() + cli.Blueln("Seeding comments ...") + seeder.SeedComments(posts...) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Cyanln("Seeding likes ...") - seeder.SeedLikes(posts...) - }() + cli.Cyanln("Seeding likes ...") + seeder.SeedLikes(posts...) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Grayln("Seeding posts-categories ...") - seeder.SeedPostsCategories(categories, posts) - }() + cli.Grayln("Seeding posts-categories ...") + seeder.SeedPostsCategories(categories, posts) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Grayln("Seeding posts-tags ...") - seeder.SeedPostTags(tags, posts) - }() + cli.Grayln("Seeding posts-tags ...") + seeder.SeedPostTags(tags, posts) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Warningln("Seeding views ...") - seeder.SeedPostViews(posts, UserA, UserB) - }() + cli.Warningln("Seeding views ...") + seeder.SeedPostViews(posts, UserA, UserB) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - if err := seeder.SeedNewsLetters(); err != nil { - cli.Error(err.Error()) - } else { - cli.Successln("Seeding Newsletters ...") - } - }() + if err := seeder.SeedNewsLetters(); err != nil { + cli.Error(err.Error()) + } else { + cli.Successln("Seeding Newsletters ...") + } + }() - wg.Wait() + wg.Wait() - cli.Magentaln("db seeded as expected ....") + cli.Magentaln("db seeded as expected ....") } diff --git a/main.go b/main.go index 15f6adf4..046b47c1 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( _ "github.com/lib/pq" "github.com/oullin/boost" + "github.com/oullin/pkg" "log/slog" baseHttp "net/http" ) @@ -10,7 +11,9 @@ import ( var app *boost.App func init() { - secrets, validate := boost.Ignite("./.env") + validate := pkg.GetDefaultValidator() + + secrets := boost.Ignite("./.env", validate) app = boost.MakeApp(secrets, validate) } From 8246e36d7b383865cf88b56e39b0db6794c4e60c Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 11:06:27 +0800 Subject: [PATCH 10/17] use os for env access --- .env.example | 35 ++++----- boost/factory.go | 4 +- config/makefile/db.mk | 2 +- database/connection.go | 1 - database/model.go | 2 + database/seeder/main.go | 160 ++++++++++++++++++++-------------------- env/db.go | 4 +- 7 files changed, 100 insertions(+), 108 deletions(-) diff --git a/.env.example b/.env.example index 3bacd131..375dc374 100644 --- a/.env.example +++ b/.env.example @@ -1,36 +1,31 @@ -# --- App secrets +# --- App ENV_APP_NAME="Gus Blog" ENV_APP_ENV_TYPE=local ENV_APP_ENV=local - -# --- App Logs secrets ENV_APP_LOG_LEVEL=debug ENV_APP_LOGS_DIR="./storage/logs/logs_%s.log" ENV_APP_LOGS_DATE_FORMAT="2006_02_01" -# --- App Network secrets -ENV_HTTP_HOST=localhost -ENV_HTTP_PORT=8080 - -# --- App token credentials +# --- Auth ENV_APP_TOKEN_PUBLIC="" ENV_APP_TOKEN_PRIVATE="" -# --- App db secrets -ENV_DB_USER_NAME=gocanto-user -ENV_DB_USER_PASSWORD=gocanto-password -ENV_DB_DATABASE_NAME=gocanto-db +# --- DB +ENV_DB_USER_NAME="gus" +ENV_DB_USER_PASSWORD="password" +ENV_DB_DATABASE_NAME="oullin_db" ENV_DB_PORT=5432 ENV_DB_HOST=localhost ENV_DB_SSL_MODE=require ENV_DB_TIMEZONE="Asia/Singapore" -EN_DB_BIN_DIR="" # Local posgreSQL instalation directory. Example: /Library/PostgreSQL/17/bin/ -ENV_DB_URL="" # Example: postgresql://gocanto-user:gocanto-password@postgres:5432/gocanto-db?sslmode=require -# --- Sentry -ENV_SENTRY_DSN="" -ENV_SENTRY_CSP="" +# --- This flag is only needed/used for debugging purposes. +# Local posgreSQL instalation directory. +# Example: /Library/PostgreSQL/17/bin/ +EN_DB_BIN_DIR="" + +# --- This flag is only needed/used for docker purposes. +# The full db url value is only used for docker purposes given the application works in the -GORM DSN- +# Example: postgresql://gocanto-user:gocanto-password@postgres:5432/oullin_db?sslmode=require +ENV_DB_URL="" -# --- Docker -ENV_DOCKER_USER="" -ENV_DOCKER_USER_GROUP="" diff --git a/boost/factory.go b/boost/factory.go index f309627a..b6ff5bfe 100644 --- a/boost/factory.go +++ b/boost/factory.go @@ -79,9 +79,7 @@ func MakeEnv(validate *pkg.Validator) *env.Environment { DatabaseName: strings.TrimSpace(os.Getenv("ENV_DB_DATABASE_NAME")), Port: port, Host: strings.TrimSpace(os.Getenv("ENV_DB_HOST")), - DriverName: "postgres", - BinDir: strings.TrimSpace(os.Getenv("EN_DB_BIN_DIR")), - URL: strings.TrimSpace(os.Getenv("ENV_DB_URL")), + DriverName: database.DriverName, SSLMode: strings.TrimSpace(os.Getenv("ENV_DB_SSL_MODE")), TimeZone: strings.TrimSpace(os.Getenv("ENV_DB_TIMEZONE")), } diff --git a/config/makefile/db.mk b/config/makefile/db.mk index 5674e85a..3c8f30fc 100644 --- a/config/makefile/db.mk +++ b/config/makefile/db.mk @@ -5,7 +5,7 @@ # --- Docker DB_DOCKER_SERVICE_NAME := postgres -DB_DOCKER_CONTAINER_NAME := gocanto-db +DB_DOCKER_CONTAINER_NAME := oullin_db # --- Paths DB_SEEDER_ROOT_PATH := $(ROOT_PATH)/database/seeder diff --git a/database/connection.go b/database/connection.go index 5eeac376..d78c2181 100644 --- a/database/connection.go +++ b/database/connection.go @@ -25,7 +25,6 @@ func MakeConnection(env *env.Environment) (*Connection, error) { } return &Connection{ - url: dbEnv.URL, driver: driver, driverName: dbEnv.DriverName, env: env, diff --git a/database/model.go b/database/model.go index 59c2089b..b1e98c2a 100644 --- a/database/model.go +++ b/database/model.go @@ -6,6 +6,8 @@ import ( "time" ) +const DriverName = "postgres" + var schemaTables = []string{ "users", "posts", "categories", "post_categories", "tags", "post_tags", diff --git a/database/seeder/main.go b/database/seeder/main.go index 8d92de75..de915045 100644 --- a/database/seeder/main.go +++ b/database/seeder/main.go @@ -1,119 +1,119 @@ package main import ( - "github.com/oullin/boost" - "github.com/oullin/database" - "github.com/oullin/database/seeder/seeds" - "github.com/oullin/env" - "github.com/oullin/pkg" - "github.com/oullin/pkg/cli" - "sync" - "time" + "github.com/oullin/boost" + "github.com/oullin/database" + "github.com/oullin/database/seeder/seeds" + "github.com/oullin/env" + "github.com/oullin/pkg" + "github.com/oullin/pkg/cli" + "sync" + "time" ) var environment *env.Environment func init() { - secrets := boost.Ignite("./.env", pkg.GetDefaultValidator()) + secrets := boost.Ignite("./.env", pkg.GetDefaultValidator()) - environment = secrets + environment = secrets } func main() { - cli.ClearScreen() + cli.ClearScreen() - dbConnection := boost.MakeDbConnection(environment) - logs := boost.MakeLogs(environment) + dbConnection := boost.MakeDbConnection(environment) + logs := boost.MakeLogs(environment) - defer (*logs).Close() - defer (*dbConnection).Close() + defer (*logs).Close() + defer (*dbConnection).Close() - // [1] --- Create the Seeder Runner. - seeder := seeds.MakeSeeder(dbConnection, environment) + // [1] --- Create the Seeder Runner. + seeder := seeds.MakeSeeder(dbConnection, environment) - // [2] --- Truncate the db. - if err := seeder.TruncateDB(); err != nil { - panic(err) - } else { - cli.Successln("db Truncated successfully ...") - time.Sleep(2 * time.Second) - } + // [2] --- Truncate the db. + if err := seeder.TruncateDB(); err != nil { + panic(err) + } else { + cli.Successln("db Truncated successfully ...") + time.Sleep(2 * time.Second) + } - // [3] --- Seed users and posts sequentially because the below seeders depend on them. - UserA, UserB := seeder.SeedUsers() - posts := seeder.SeedPosts(UserA, UserB) + // [3] --- Seed users and posts sequentially because the below seeders depend on them. + UserA, UserB := seeder.SeedUsers() + posts := seeder.SeedPosts(UserA, UserB) - categoriesChan := make(chan []database.Category) - tagsChan := make(chan []database.Tag) + categoriesChan := make(chan []database.Category) + tagsChan := make(chan []database.Tag) - go func() { - defer close(categoriesChan) + go func() { + defer close(categoriesChan) - cli.Warningln("Seeding categories ...") - categoriesChan <- seeder.SeedCategories() - }() + cli.Warningln("Seeding categories ...") + categoriesChan <- seeder.SeedCategories() + }() - go func() { - defer close(tagsChan) + go func() { + defer close(tagsChan) - cli.Magentaln("Seeding tags ...") - tagsChan <- seeder.SeedTags() - }() + cli.Magentaln("Seeding tags ...") + tagsChan <- seeder.SeedTags() + }() - // [4] Use channels to concurrently seed categories and tags since they are main dependencies. - categories := <-categoriesChan - tags := <-tagsChan + // [4] Use channels to concurrently seed categories and tags since they are main dependencies. + categories := <-categoriesChan + tags := <-tagsChan - // [5] Use a WaitGroup to run independent seeding tasks concurrently. - var wg sync.WaitGroup - wg.Add(6) + // [5] Use a WaitGroup to run independent seeding tasks concurrently. + var wg sync.WaitGroup + wg.Add(6) - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Blueln("Seeding comments ...") - seeder.SeedComments(posts...) - }() + cli.Blueln("Seeding comments ...") + seeder.SeedComments(posts...) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Cyanln("Seeding likes ...") - seeder.SeedLikes(posts...) - }() + cli.Cyanln("Seeding likes ...") + seeder.SeedLikes(posts...) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Grayln("Seeding posts-categories ...") - seeder.SeedPostsCategories(categories, posts) - }() + cli.Grayln("Seeding posts-categories ...") + seeder.SeedPostsCategories(categories, posts) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Grayln("Seeding posts-tags ...") - seeder.SeedPostTags(tags, posts) - }() + cli.Grayln("Seeding posts-tags ...") + seeder.SeedPostTags(tags, posts) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - cli.Warningln("Seeding views ...") - seeder.SeedPostViews(posts, UserA, UserB) - }() + cli.Warningln("Seeding views ...") + seeder.SeedPostViews(posts, UserA, UserB) + }() - go func() { - defer wg.Done() + go func() { + defer wg.Done() - if err := seeder.SeedNewsLetters(); err != nil { - cli.Error(err.Error()) - } else { - cli.Successln("Seeding Newsletters ...") - } - }() + if err := seeder.SeedNewsLetters(); err != nil { + cli.Error(err.Error()) + } else { + cli.Successln("Seeding Newsletters ...") + } + }() - wg.Wait() + wg.Wait() - cli.Magentaln("db seeded as expected ....") + cli.Magentaln("db seeded as expected ....") } diff --git a/env/db.go b/env/db.go index de3dae6b..18b96c9d 100644 --- a/env/db.go +++ b/env/db.go @@ -5,12 +5,10 @@ import "fmt" type DBEnvironment struct { UserName string `validate:"required,lowercase,min=10"` UserPassword string `validate:"required,min=10"` - DatabaseName string `validate:"required,lowercase,min=10"` + DatabaseName string `validate:"required,lowercase,min=5"` Port int `validate:"required,numeric,gt=0"` Host string `validate:"required,lowercase,hostname"` DriverName string `validate:"required,lowercase,oneof=postgres"` - BinDir string - URL string `validate:"required,lowercase,startswith=postgres"` SSLMode string `validate:"required,lowercase,oneof=require"` TimeZone string `validate:"required"` } From e0a68bc8c40179b6ab9f0a6e8bc350c23a89a589 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 13:17:24 +0800 Subject: [PATCH 11/17] fix caddy config --- caddy/Caddyfile | 50 +++++++++++++++++----------------------------- docker-compose.yml | 19 +++++++++++------- env/network.go | 2 +- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/caddy/Caddyfile b/caddy/Caddyfile index 4a64f7d7..dcb83ea2 100644 --- a/caddy/Caddyfile +++ b/caddy/Caddyfile @@ -1,38 +1,24 @@ # Filename: caddy/Caddyfile -# This is a production-ready Caddyfile. -# For a real domain, replace "localhost" with "your-domain.com". -# Caddy will automatically provision a Let's Encrypt certificate for you. -{ - # Global options block - # Enable logging. By default, logs are sent to stdout in JSON format, - # which is ideal for containerized environments. - log - # Enable OCSP stapling for better TLS performance and privacy. - ocsp_stapling off # Often needs tweaking in Docker, can be turned on if needed +# This global options block explicitly disables Caddy's automatic HTTPS feature. +# This is the most reliable way to ensure Caddy acts as a simple HTTP proxy. +{ + auto_https off } -localhost { - # Enable compression to reduce bandwidth usage. - # Gzip is broadly supported. Zstd is newer and more efficient. - encode gzip zstd - - # Add security-related headers to protect against common attacks. - header { - # Enable HSTS to ensure browsers only connect via HTTPS. - Strict-Transport-Security "max-age=31536000;" - # Prevent clickjacking attacks. - X-Frame-Options "SAMEORIGIN" - # Prevent content type sniffing. - X-Content-Type-Options "nosniff" - # Enable browser's built-in XSS protection. - X-XSS-Protection "1; mode=block" - # Control which features and APIs can be used on the site. - Permissions-Policy "interest-cohort=()" - } +# This is a robust configuration for a containerized environment. +# It tells Caddy to listen on its internal port 80 for any incoming hostname. +# Docker Compose maps your host port (8080) to this container port. +:80 { + # Define a logging format for easier debugging. + log { + output stdout + format console + } - # Reverse proxy all requests to the Go application service. - # 'api' is the service name defined in docker-compose.yml. - # Docker's internal DNS will resolve 'api' to the Go container's IP. - reverse_proxy api:8080 + # Reverse proxy all incoming requests to the 'api' service. + # The service name 'api' is resolved by Docker's internal DNS to the + # correct container IP on the 'caddy_net' network. + # The API container listens on port 8080 (from your ENV_HTTP_PORT). + reverse_proxy api:8080 } diff --git a/docker-compose.yml b/docker-compose.yml index c82d5e9b..83c35ba9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,9 +4,10 @@ volumes: networks: caddy_net: + name: caddy_net driver: bridge - gocanto: - name: gocanto + oullin_net: + name: oullin_net driver: bridge services: @@ -31,7 +32,11 @@ services: env_file: - .env environment: - - ENV_DB_HOST="postgres" + # This ensures the API connects to the correct database container. + ENV_DB_HOST: postgres + # This ensures the Go web server listens for connections from other + # containers (like Caddy), not just from within itself. + ENV_HTTP_HOST: 0.0.0.0 build: context: . dockerfile: ./docker/dockerfile-api @@ -49,7 +54,7 @@ services: - ${ENV_HTTP_PORT} networks: - caddy_net - - gocanto + - oullin_net postgres: restart: unless-stopped @@ -58,7 +63,7 @@ services: env_file: - .env networks: - - gocanto + - oullin_net environment: # --- Postgres CLI env vars. PGUSER: ${ENV_DB_USER_NAME} @@ -79,8 +84,8 @@ services: logging: driver: "json-file" options: - max-file: "20" - max-size: "10M" + max-file: 20 + max-size: 10M command: > postgres -c config_file=/etc/postgresql/postgresql.conf diff --git a/env/network.go b/env/network.go index 195425dd..f66d91c6 100644 --- a/env/network.go +++ b/env/network.go @@ -1,7 +1,7 @@ package env type NetEnvironment struct { - HttpHost string `validate:"required,lowercase,min=8"` + HttpHost string `validate:"required,lowercase,min=7"` HttpPort string `validate:"required,numeric,oneof=8080"` } From d575df2fd9a44810cb9e9d43916299fdcde9c749 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 13:21:03 +0800 Subject: [PATCH 12/17] format --- docker/dockerfile-api | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/dockerfile-api b/docker/dockerfile-api index 313abc2d..6390d20a 100644 --- a/docker/dockerfile-api +++ b/docker/dockerfile-api @@ -22,8 +22,8 @@ ARG MEDIA_DIR=media ARG FIXTURES_DIR=fixture # --- Build Stage --- -# This stage, named 'builder', is responsible for compiling the Go application. -# It uses a Go-specific base image that includes the necessary toolchain. +# This stage, named 'builder', is responsible for compiling the Go application. +# It uses a Go-specific base image that includes the necessary toolchain. FROM golang:${GO_VERSION}-alpine AS builder # Forwards build-time arguments into this specific stage so they can be referenced. @@ -64,9 +64,9 @@ COPY . . # the application's version by targeting the 'Version' variable in the 'main' package. RUN CGO_ENABLED=0 go build -tags "${BUILD_TAGS}" -o ${BUILD_DIR}/${BINARY_NAME} -ldflags="-s -w -X main.Version=${APP_VERSION}" . -# --- Final Stage --- -# This is the final, production-ready stage of the build. -# It uses a minimal 'alpine' base image to create a small and secure container. +# --- Final Stage +# This is the final, production-ready stage of the build. +# It uses a minimal 'alpine' base image to create a small and secure container. FROM alpine:${ALPINE_VERSION} # Forwards build-time arguments into this final stage so they can be referenced. From d0ff71bc2e991ce6eb18e8cea9f97440962b804a Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 13:30:53 +0800 Subject: [PATCH 13/17] remove bin --- .air.toml | 1 - .dockerignore | 1 - .gitignore | 13 ---------- Makefile | 31 ++++++++++-------------- bin/.gitkeep | 0 config/makefile/app.mk | 6 ++--- config/makefile/build.mk | 51 +++------------------------------------- config/makefile/logs.mk | 2 -- 8 files changed, 18 insertions(+), 87 deletions(-) delete mode 100644 bin/.gitkeep diff --git a/.air.toml b/.air.toml index a77074ba..1be3f8c9 100644 --- a/.air.toml +++ b/.air.toml @@ -12,7 +12,6 @@ tmp_dir = "tmp" "tmp", "docs", "database/infra", - "bin", "storage/logs", "storage/media", "config/makefile", diff --git a/.dockerignore b/.dockerignore index 2afadc93..696032a2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -13,5 +13,4 @@ database/infra/ storage/media/ storage/logs/ tmp/ -bin/ caddy/ diff --git a/.gitignore b/.gitignore index a1feb6ce..e8588ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,19 +4,6 @@ database/infra/data tmp -# --- [API]: Bin -bin -!bin/.env -!bin/.gitkeep -bin/storage/logs/*.* -bin/storage/media/*.* -bin/storage/media/posts/*.* -bin/storage/media/users/*.* -!bin/storage/logs/.gitkeep -!bin/storage/media/.gitkeep -!bin/storage/media/posts/.gitkeep -!bin/storage/media/users/.gitkeep - # --- [API]: Storage storage/logs/*.* storage/media/*.* diff --git a/Makefile b/Makefile index d29cb5cb..597bfa18 100644 --- a/Makefile +++ b/Makefile @@ -26,8 +26,6 @@ SOURCE := go_bindata ROOT_PATH := $(shell pwd) APP_PATH := $(ROOT_PATH)/ STORAGE_PATH := $(ROOT_PATH)/storage -BIN_PATH := $(ROOT_PATH)/bin -BIN_LOGS_PATH := $(ROOT_PATH)/bin/storage/logs REPO_OWNER := $(shell cd .. && basename "$$(pwd)") VERSION := $(shell git describe --tags 2>/dev/null | cut -c 2-) @@ -36,7 +34,6 @@ VERSION := $(shell git describe --tags 2>/dev/null | cut -c 2-) include ./config/makefile/helpers.mk include ./config/makefile/env.mk - include ./config/makefile/db.mk include ./config/makefile/app.mk include ./config/makefile/logs.mk @@ -46,22 +43,18 @@ include ./config/makefile/build.mk # -------------------------------------------------------------------------------------------------------------------- # help: - @printf "$(BOLD)$(CYAN)Makefile Commands:$(NC)\n" + @printf "$(BOLD)$(CYAN)Applications Options$(NC)\n" @printf "$(WHITE)Usage:$(NC) make $(BOLD)$(YELLOW)$(NC)\n\n" @printf "$(BOLD)$(BLUE)General Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)fresh$(NC) : Clean and reset various project components (logs, build, etc.).\n" - @printf " $(BOLD)$(GREEN)audit$(NC) : Run code audits and checks.\n" - @printf " $(BOLD)$(GREEN)watch$(NC) : Start a file watcher process.\n" - @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n\n" + @printf " $(BOLD)$(GREEN)fresh$(NC) : Clean and reset various project components (logs, build, etc.).\n" + @printf " $(BOLD)$(GREEN)audit$(NC) : Run code audits and checks.\n" + @printf " $(BOLD)$(GREEN)watch$(NC) : Start a file watcher process.\n" + @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n\n" @printf "$(BOLD)$(BLUE)Build Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)build:app$(NC) : Build the main application executable.\n" - @printf " $(BOLD)$(GREEN)build:app:linux$(NC) : Build the application specifically for Linux.\n" - @printf " $(BOLD)$(GREEN)build:release$(NC) : Build a release version of the application.\n" - @printf " $(BOLD)$(GREEN)build:run$(NC) : Build and run the application.\n" - @printf " $(BOLD)$(GREEN)build:fresh$(NC) : Build and run a freshly instance of the application.\n" - @printf " $(BOLD)$(GREEN)build:flush$(NC) : Clean build artifacts and then build the application.\n\n" + @printf " $(BOLD)$(GREEN)build:api$(NC) : Build the main application.\n" + @printf " $(BOLD)$(GREEN)build:release$(NC) : Build a release version of the application.\n" @printf "$(BOLD)$(BLUE)Database Commands:$(NC)\n" @printf " $(BOLD)$(GREEN)db:local$(NC) : Set up or manage the local database environment.\n" @@ -81,12 +74,12 @@ help: @printf " $(BOLD)$(GREEN)db:migrate:force$(NC) : Force database migrations to run.\n\n" @printf "$(BOLD)$(BLUE)Environment Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)env:check$(NC) : Verify environment configuration.\n" - @printf " $(BOLD)$(GREEN)env:fresh$(NC) : Refresh environment settings.\n" - @printf " $(BOLD)$(GREEN)env:init$(NC) : Initialize environment settings.\n" - @printf " $(BOLD)$(GREEN)env:print$(NC) : Display current environment settings.\n\n" + @printf " $(BOLD)$(GREEN)env:check$(NC) : Verify environment configuration.\n" + @printf " $(BOLD)$(GREEN)env:fresh$(NC) : Refresh environment settings.\n" + @printf " $(BOLD)$(GREEN)env:init$(NC) : Initialize environment settings.\n" + @printf " $(BOLD)$(GREEN)env:print$(NC) : Display current environment settings.\n\n" @printf "$(BOLD)$(BLUE)Log Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)logs:fresh$(NC) : Clear application logs.\n" + @printf " $(BOLD)$(GREEN)logs:fresh$(NC) : Clear application logs.\n" @printf "$(NC)" diff --git a/bin/.gitkeep b/bin/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config/makefile/app.mk b/config/makefile/app.mk index c2425ab1..7c92242a 100644 --- a/config/makefile/app.mk +++ b/config/makefile/app.mk @@ -14,17 +14,17 @@ fresh: audit: $(call external_deps,'.') - $(call external_deps,'./bin/...') $(call external_deps,'./app/...') $(call external_deps,'./database/...') $(call external_deps,'./docs/...') watch: # --- Works with (air). - # https://github.com/air-verse/air + # https://github.com/air-verse/air cd $(APP_PATH) && air install-air: - # https://github.com/air-verse/air + # --- Works with (air). + # https://github.com/air-verse/air @echo "Installing air ..." @go install github.com/air-verse/air@latest diff --git a/config/makefile/build.mk b/config/makefile/build.mk index 17e05e7c..e16d345b 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -1,53 +1,8 @@ -.PHONY: build\:app build\:flush build\:app\:linux build\:release build\:run build\:fresh +.PHONY: build\:app build\:release -___BIN___ROOT__PATH := $(shell pwd) -___BIN___ENV___FILE__TEMPLATE := .env.production -___BIN___FULL__PATH := $(___BIN___ROOT__PATH)/bin -___BIN___ENV___FILE := $(___BIN___FULL__PATH)/.env -___BIN___APP___FILE := $(___BIN___FULL__PATH)/app - -# --- Storage -___BIN___STORAGE__PATH := $(___BIN___FULL__PATH)/storage -___BIN___LOGS__PATH := $(___BIN___STORAGE__PATH)/logs - - -build\:fresh: - make build:app && make build:run - -build\:run: - cd $(___BIN___FULL__PATH) && ./app - -# -build\:app\:linux: - @printf "\n$(BLUE)[BIN]$(NC) Building the app in [amd64] has started.\n" - make build:env && \ - make build:flush && \ - cd $(APP_PATH) && \ - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o "$(ROOT_PATH)/bin/app_linux" -ldflags='-X main.Version=$(VERSION) -extldflags "-static"' -tags '$(DATABASE) $(SOURCE)' $(APP_PATH) +build\:app: + docker compose up --build -d caddy build\:release: git tag v$(V) @read -p "Press enter to confirm and push to origin ..." && git push origin v$(V) - -build\:app: - @printf "\n$(BLUE)[BIN]$(NC) Building the app has started.\n" - make build:env && \ - make build:flush && \ - CGO_ENABLED=0 go build -a -ldflags='-X main.Version=$(VERSION)' -o "$(ROOT_PATH)/bin/app" -tags '$(DATABASE) $(SOURCE)' $(APP_PATH) - @printf "$(GREEN)[BIN]$(NC) Building the app has finished.\n\n" - -build\:flush: - @printf "$(BLUE)[BIN]$(NC) Flushing the previous builds has started.\n" - @sleep 1 - rm -f $(___BIN___ENV___FILE) - rm -f $(___BIN___APP___FILE) - rm -rf $(___BIN___LOGS__PATH) - mkdir -m 755 $(___BIN___LOGS__PATH) - touch $(___BIN___LOGS__PATH)/.gitkeep - @printf "$(GREEN)[BIN]$(NC) Flushing has finished.\n\n" - -build\:env: - cp $(___BIN___ENV___FILE__TEMPLATE) $(___BIN___ENV___FILE) - -build\:caddy: - docker compose up --build -d caddy diff --git a/config/makefile/logs.mk b/config/makefile/logs.mk index f5570eaf..b0a5af0e 100644 --- a/config/makefile/logs.mk +++ b/config/makefile/logs.mk @@ -2,5 +2,3 @@ logs\:fresh: find $(STORAGE_PATH)/logs -maxdepth 1 -type f -not -name ".gitkeep" -delete - - From 5a1dd789753f8290989a90a7f682d602be922be2 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 13:42:51 +0800 Subject: [PATCH 14/17] caddy local --- caddy/{Caddyfile => Caddyfile.local} | 0 caddy/Dockerfile | 2 +- config/makefile/build.mk | 6 +++--- docker-compose.yml | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) rename caddy/{Caddyfile => Caddyfile.local} (100%) diff --git a/caddy/Caddyfile b/caddy/Caddyfile.local similarity index 100% rename from caddy/Caddyfile rename to caddy/Caddyfile.local diff --git a/caddy/Dockerfile b/caddy/Dockerfile index 2e7dabfa..e3380214 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -4,4 +4,4 @@ FROM caddy:latest # Copy your custom Caddyfile into the container. # This overwrites the default Caddyfile. -COPY Caddyfile /etc/caddy/Caddyfile +COPY Caddyfile.local /etc/caddy/Caddyfile diff --git a/config/makefile/build.mk b/config/makefile/build.mk index e16d345b..bdb8f624 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -1,7 +1,7 @@ -.PHONY: build\:app build\:release +.PHONY: build\:local build\:release -build\:app: - docker compose up --build -d caddy +build\:local: + docker compose --profile local up --build -d build\:release: git tag v$(V) diff --git a/docker-compose.yml b/docker-compose.yml index 83c35ba9..7c9d4d18 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,11 +11,12 @@ networks: driver: bridge services: - caddy: + caddy_local: build: context: ./caddy dockerfile: Dockerfile - container_name: oullin_proxy + profiles: ["local"] + container_name: oullin_local_proxy restart: unless-stopped depends_on: - api @@ -25,6 +26,7 @@ services: volumes: - caddy_data:/data - caddy_config:/config + - ./caddy/Caddyfile.local:/etc/caddy/Caddyfile networks: - caddy_net From 65a4050a70d1b09c7959c185207666186a07a3b4 Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 13:51:24 +0800 Subject: [PATCH 15/17] caddy prod --- Makefile | 3 ++- caddy/Caddyfile.prod | 21 +++++++++++++++++++++ config/makefile/build.mk | 5 ++++- docker-compose.yml | 22 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 caddy/Caddyfile.prod diff --git a/Makefile b/Makefile index 597bfa18..044be022 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,8 @@ help: @printf " $(BOLD)$(GREEN)format$(NC) : Automatically format code.\n\n" @printf "$(BOLD)$(BLUE)Build Commands:$(NC)\n" - @printf " $(BOLD)$(GREEN)build:api$(NC) : Build the main application.\n" + @printf " $(BOLD)$(GREEN)build:local$(NC) : Build the main application for development.\n" + @printf " $(BOLD)$(GREEN)build:prod$(NC) : Build the main application for production.\n" @printf " $(BOLD)$(GREEN)build:release$(NC) : Build a release version of the application.\n" @printf "$(BOLD)$(BLUE)Database Commands:$(NC)\n" diff --git a/caddy/Caddyfile.prod b/caddy/Caddyfile.prod new file mode 100644 index 00000000..7a9b7c71 --- /dev/null +++ b/caddy/Caddyfile.prod @@ -0,0 +1,21 @@ +# Filename: caddy/Caddyfile.prod +# Caddy will automatically provision a Let's Encrypt certificate. + +gocanto.dev { + # Enable compression to reduce bandwidth usage. + encode gzip zstd + + # Add security-related headers to protect against common attacks. + header { + # Enable HSTS to ensure browsers only connect via HTTPS. + Strict-Transport-Security "max-age=31536000;" + # Prevent clickjacking attacks. + X-Frame-Options "SAMEORIGIN" + # Prevent content type sniffing. + X-Content-Type-Options "nosniff" + } + + # Reverse proxy all requests to the Go application service. + # 'api' is the service name defined in docker-compose.yml. + reverse_proxy api:8080 +} diff --git a/config/makefile/build.mk b/config/makefile/build.mk index bdb8f624..64724fc3 100644 --- a/config/makefile/build.mk +++ b/config/makefile/build.mk @@ -1,8 +1,11 @@ -.PHONY: build\:local build\:release +.PHONY: build\:local build\:prod build\:release build\:local: docker compose --profile local up --build -d +build\:prod: + docker compose --profile prod up --build -d + build\:release: git tag v$(V) @read -p "Press enter to confirm and push to origin ..." && git push origin v$(V) diff --git a/docker-compose.yml b/docker-compose.yml index 7c9d4d18..a8d60195 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,10 +11,32 @@ networks: driver: bridge services: + caddy_prod: + build: + context: ./caddy + dockerfile: Dockerfile + # This service will only run when the 'prod' profile is active. + profiles: ["prod"] + container_name: oullin_proxy_prod + restart: unless-stopped + depends_on: + - api + ports: + - "80:80" + - "443:443" + - "443:443/udp" # Required for HTTP/3 + volumes: + - caddy_data:/data + - caddy_config:/config + - ./caddy/Caddyfile.prod:/etc/caddy/Caddyfile + networks: + - caddy_net + caddy_local: build: context: ./caddy dockerfile: Dockerfile + # This service will only run when the 'local' profile is active. profiles: ["local"] container_name: oullin_local_proxy restart: unless-stopped From 4796c9557332e6ae8ea2b544e3c8d9c83f97d68e Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 14:26:35 +0800 Subject: [PATCH 16/17] caddy version + tweaks --- boost/factory.go | 38 ++++++++++++++++++-------------------- caddy/Caddyfile.prod | 2 ++ caddy/Dockerfile | 8 +++++++- docker-compose.yml | 4 ++++ env/env.go | 9 +++++++++ 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/boost/factory.go b/boost/factory.go index b6ff5bfe..a1fddf32 100644 --- a/boost/factory.go +++ b/boost/factory.go @@ -9,9 +9,7 @@ import ( "github.com/oullin/pkg/auth" "github.com/oullin/pkg/llogs" "log" - "os" "strconv" - "strings" "time" ) @@ -60,44 +58,44 @@ func MakeLogs(env *env.Environment) *llogs.Driver { func MakeEnv(validate *pkg.Validator) *env.Environment { errorSufix := "Environment: " - port, _ := strconv.Atoi(os.Getenv("ENV_DB_PORT")) + port, _ := strconv.Atoi(env.GetEnvVar("ENV_DB_PORT")) token := auth.Token{ - Public: strings.TrimSpace(os.Getenv("ENV_APP_TOKEN_PUBLIC")), - Private: strings.TrimSpace(os.Getenv("ENV_APP_TOKEN_PRIVATE")), + Public: env.GetEnvVar("ENV_APP_TOKEN_PUBLIC"), + Private: env.GetEnvVar("ENV_APP_TOKEN_PRIVATE"), } app := env.AppEnvironment{ - Name: strings.TrimSpace(os.Getenv("ENV_APP_NAME")), - Type: strings.TrimSpace(os.Getenv("ENV_APP_ENV_TYPE")), + Name: env.GetEnvVar("ENV_APP_NAME"), + Type: env.GetEnvVar("ENV_APP_ENV_TYPE"), Credentials: token, } db := env.DBEnvironment{ - UserName: strings.TrimSpace(os.Getenv("ENV_DB_USER_NAME")), - UserPassword: strings.TrimSpace(os.Getenv("ENV_DB_USER_PASSWORD")), - DatabaseName: strings.TrimSpace(os.Getenv("ENV_DB_DATABASE_NAME")), + UserName: env.GetEnvVar("ENV_DB_USER_NAME"), + UserPassword: env.GetEnvVar("ENV_DB_USER_PASSWORD"), + DatabaseName: env.GetEnvVar("ENV_DB_DATABASE_NAME"), Port: port, - Host: strings.TrimSpace(os.Getenv("ENV_DB_HOST")), + Host: env.GetEnvVar("ENV_DB_HOST"), DriverName: database.DriverName, - SSLMode: strings.TrimSpace(os.Getenv("ENV_DB_SSL_MODE")), - TimeZone: strings.TrimSpace(os.Getenv("ENV_DB_TIMEZONE")), + SSLMode: env.GetEnvVar("ENV_DB_SSL_MODE"), + TimeZone: env.GetEnvVar("ENV_DB_TIMEZONE"), } logsCreds := env.LogsEnvironment{ - Level: strings.TrimSpace(os.Getenv("ENV_APP_LOG_LEVEL")), - Dir: strings.TrimSpace(os.Getenv("ENV_APP_LOGS_DIR")), - DateFormat: strings.TrimSpace(os.Getenv("ENV_APP_LOGS_DATE_FORMAT")), + Level: env.GetEnvVar("ENV_APP_LOG_LEVEL"), + Dir: env.GetEnvVar("ENV_APP_LOGS_DIR"), + DateFormat: env.GetEnvVar("ENV_APP_LOGS_DATE_FORMAT"), } net := env.NetEnvironment{ - HttpHost: strings.TrimSpace(os.Getenv("ENV_HTTP_HOST")), - HttpPort: strings.TrimSpace(os.Getenv("ENV_HTTP_PORT")), + HttpHost: env.GetEnvVar("ENV_HTTP_HOST"), + HttpPort: env.GetEnvVar("ENV_HTTP_PORT"), } sentryEnvironment := env.SentryEnvironment{ - DSN: strings.TrimSpace(os.Getenv("ENV_SENTRY_DSN")), - CSP: strings.TrimSpace(os.Getenv("ENV_SENTRY_CSP")), + DSN: env.GetEnvVar("ENV_SENTRY_DSN"), + CSP: env.GetEnvVar("ENV_SENTRY_CSP"), } if _, err := validate.Rejects(app); err != nil { diff --git a/caddy/Caddyfile.prod b/caddy/Caddyfile.prod index 7a9b7c71..b6dfb0bb 100644 --- a/caddy/Caddyfile.prod +++ b/caddy/Caddyfile.prod @@ -13,6 +13,8 @@ gocanto.dev { X-Frame-Options "SAMEORIGIN" # Prevent content type sniffing. X-Content-Type-Options "nosniff" + # Enhances user privacy. + Referrer-Policy "strict-origin-when-cross-origin" } # Reverse proxy all requests to the Go application service. diff --git a/caddy/Dockerfile b/caddy/Dockerfile index e3380214..f868dd16 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -1,6 +1,12 @@ # Filename: caddy/Dockerfile +# This Dockerfile builds a Caddy image using a specific, stable version number. + +# Define a build argument for the Caddy version with a sensible default. +# This allows the version to be easily overridden from the docker-compose.yml file. +ARG CADDY_VERSION=2.10.0 + # Use the official Caddy image with the latest tag. -FROM caddy:latest +FROM caddy:2.10.0 # Copy your custom Caddyfile into the container. # This overwrites the default Caddyfile. diff --git a/docker-compose.yml b/docker-compose.yml index a8d60195..757468a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,8 @@ services: build: context: ./caddy dockerfile: Dockerfile + args: + - CADDY_VERSION=2.10.0 # This service will only run when the 'prod' profile is active. profiles: ["prod"] container_name: oullin_proxy_prod @@ -36,6 +38,8 @@ services: build: context: ./caddy dockerfile: Dockerfile + args: + - CADDY_VERSION=latest # This service will only run when the 'local' profile is active. profiles: ["local"] container_name: oullin_local_proxy diff --git a/env/env.go b/env/env.go index dfc4e5da..6bb4c657 100644 --- a/env/env.go +++ b/env/env.go @@ -1,5 +1,10 @@ package env +import ( + "os" + "strings" +) + type Environment struct { App AppEnvironment DB DBEnvironment @@ -7,3 +12,7 @@ type Environment struct { Network NetEnvironment Sentry SentryEnvironment } + +func GetEnvVar(key string) string { + return strings.TrimSpace(os.Getenv(key)) +} From 07b702ab4d05d35b841c1ab04672b3e519a8cc9d Mon Sep 17 00:00:00 2001 From: Gustavo Ocanto Date: Wed, 25 Jun 2025 14:45:27 +0800 Subject: [PATCH 17/17] use arg --- caddy/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/caddy/Dockerfile b/caddy/Dockerfile index f868dd16..0e83db19 100644 --- a/caddy/Dockerfile +++ b/caddy/Dockerfile @@ -6,7 +6,7 @@ ARG CADDY_VERSION=2.10.0 # Use the official Caddy image with the latest tag. -FROM caddy:2.10.0 +FROM caddy:${CADDY_VERSION} # Copy your custom Caddyfile into the container. # This overwrites the default Caddyfile.