From 7bf6ea5c26845be1020da57bdc41cdf94a82d003 Mon Sep 17 00:00:00 2001 From: mifi Date: Tue, 10 Feb 2026 19:19:25 -0300 Subject: [PATCH] Initial commit --- .woodpecker/build.yaml | 126 +++++++++++++++++++++++++++++ .woodpecker/deploy.yaml | 59 ++++++++++++++ Dockerfile | 14 ++++ docker-compose.yml | 149 +++++++++++++++++++++++++++++++++++ html/.well-known/mta-sts.txt | 5 ++ html/index.html | 143 +++++++++++++++++++++++++++++++++ nginx/default.conf | 10 +++ package.json | 17 ++++ 8 files changed, 523 insertions(+) create mode 100644 .woodpecker/build.yaml create mode 100644 .woodpecker/deploy.yaml create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 html/.well-known/mta-sts.txt create mode 100644 html/index.html create mode 100644 nginx/default.conf create mode 100644 package.json diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml new file mode 100644 index 0000000..b0a8384 --- /dev/null +++ b/.woodpecker/build.yaml @@ -0,0 +1,126 @@ +# Deploy: build image, push to registry, trigger Portainer stack redeploy. +# Runs on push/tag/manual to main only, after ci workflow succeeds. +when: + - branch: main + event: [push, tag, manual] + - event: deployment + evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "production"' + +steps: + - name: Docker image build + image: docker:latest + environment: + REGISTRY_REPO: git.mifi.dev/mifi-holdings/mta-sts + DOCKER_API_VERSION: '1.43' + DOCKER_BUILDKIT: '1' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - set -e + - echo "=== Building Docker image (BuildKit) ===" + - 'echo "Commit SHA: ${CI_COMMIT_SHA:0:8}"' + - 'echo "Registry repo: $REGISTRY_REPO"' + - | + build() { + docker build \ + --progress=plain \ + --tag $REGISTRY_REPO:${CI_COMMIT_SHA} \ + --tag $REGISTRY_REPO:latest \ + --label "git.commit=${CI_COMMIT_SHA}" \ + --label "git.branch=${CI_COMMIT_BRANCH}" \ + . + } + for attempt in 1 2 3; do + echo "Build attempt $attempt/3" + if build; then + echo "✓ Docker image built successfully" + exit 0 + fi + echo "Build attempt $attempt failed, retrying in 30s..." + sleep 30 + done + echo "All build attempts failed" + exit 1 + + - name: Send Docker Image Build Status Notification (success) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Docker image build success 🎉"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Docker image build + when: + - status: [success] + + - name: Send Docker Image Build Status Notification (failure) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Docker image build failure 💩"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Docker image build + when: + - status: [failure] + + - name: Push to registry + image: docker:latest + environment: + DOCKER_API_VERSION: '1.43' + REGISTRY_URL: git.mifi.dev + REGISTRY_REPO: git.mifi.dev/mifi-holdings/mta-sts + REGISTRY_USERNAME: + from_secret: gitea_registry_username + REGISTRY_PASSWORD: + from_secret: gitea_package_token + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - set -e + - echo "=== Pushing to registry ===" + - 'echo "Registry: $REGISTRY_URL"' + - 'echo "Repository: $REGISTRY_REPO"' + - | + echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" \ + -u "$REGISTRY_USERNAME" \ + --password-stdin + - docker push $REGISTRY_REPO:${CI_COMMIT_SHA} + - docker push $REGISTRY_REPO:latest + - echo "✓ Images pushed successfully" + depends_on: + - Docker image build + + - name: Send Push to Registry Status Notification (success) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Push to registry success 🎉"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Push to registry + when: + - status: [success] + + - name: Send Push to Registry Status Notification (failure) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Push to registry failure 💩"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Push to registry + when: + - status: [failure] diff --git a/.woodpecker/deploy.yaml b/.woodpecker/deploy.yaml new file mode 100644 index 0000000..fb3dcd1 --- /dev/null +++ b/.woodpecker/deploy.yaml @@ -0,0 +1,59 @@ +# Deploy: build image, push to registry, trigger Portainer stack redeploy. +# Runs on push/tag/manual to main only, after ci workflow succeeds. +when: + - branch: main + event: [push, tag, manual] + - event: deployment + evaluate: 'CI_PIPELINE_DEPLOY_TARGET == "production"' + +depends_on: + - build + +steps: + - name: Trigger Portainer stack redeploy + image: curlimages/curl:latest + environment: + PORTAINER_WEBHOOK_URL: + from_secret: portainer_webhook_url + commands: + - set -e + - echo "=== Triggering Portainer stack redeploy ===" + - | + resp=$(curl -s -w "\n%{http_code}" -X POST "$PORTAINER_WEBHOOK_URL") + body=$(echo "$resp" | head -n -1) + code=$(echo "$resp" | tail -n 1) + if [ "$code" != "200" ] && [ "$code" != "204" ]; then + echo "Webhook failed (HTTP $code): $body" + exit 1 + fi + echo "✓ Portainer redeploy triggered (HTTP $code)" + depends_on: + - Push to registry + + - name: Send Deploy Status Notification (success) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Production Deploy success 🎉"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Trigger Portainer stack redeploy + when: + - status: [success] + + - name: Send Deploy Status Notification (failure) + image: curlimages/curl + environment: + DISCORD_WEBHOOK_URL: + from_secret: discord_webhook_url + commands: + - | + BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Production Deploy failure 💩"}' "$CI_REPO" "$CI_PIPELINE_NUMBER") + curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL" + depends_on: + - Trigger Portainer stack redeploy + when: + - status: [failure] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..28c3b6e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM nginx:alpine + +# Copy nginx configuration +COPY nginx/default.conf /etc/nginx/conf.d/default.conf + +# Copy static content +COPY html/ /usr/share/nginx/html/ + +# Expose port 80 +EXPOSE 80 + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \ + CMD wget --spider -q http://localhost || exit 1 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6406512 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,149 @@ +services: + mta-sts: + image: git.mifi.dev/mifi-holdings/mta-sts:latest + container_name: mta-sts + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://localhost"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + networks: + - traefik + labels: + - "traefik.enable=true" + - "traefik.docker.network=traefik" + + # mifi.holdings + - "traefik.http.routers.mta-sts-mifi-holdings.rule=Host(`mta-sts.mifi.holdings`)" + - "traefik.http.routers.mta-sts-mifi-holdings.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-holdings.tls=true" + - "traefik.http.routers.mta-sts-mifi-holdings.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-holdings.service=mta-sts-mifi-holdings" + - "traefik.http.services.mta-sts-mifi-holdings.loadbalancer.server.port=80" + + # mifi.com.br + - "traefik.http.routers.mta-sts-mifi-com-br.rule=Host(`mta-sts.mifi.com.br`)" + - "traefik.http.routers.mta-sts-mifi-com-br.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-com-br.tls=true" + - "traefik.http.routers.mta-sts-mifi-com-br.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-com-br.service=mta-sts-mifi-com-br" + - "traefik.http.services.mta-sts-mifi-com-br.loadbalancer.server.port=80" + + # mifi.dev + - "traefik.http.routers.mta-sts-mifi-dev.rule=Host(`mta-sts.mifi.dev`)" + - "traefik.http.routers.mta-sts-mifi-dev.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-dev.tls=true" + - "traefik.http.routers.mta-sts-mifi-dev.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-dev.service=mta-sts-mifi-dev" + - "traefik.http.services.mta-sts-mifi-dev.loadbalancer.server.port=80" + + # mifi.ventures + - "traefik.http.routers.mta-sts-mifi-ventures.rule=Host(`mta-sts.mifi.ventures`)" + - "traefik.http.routers.mta-sts-mifi-ventures.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-ventures.tls=true" + - "traefik.http.routers.mta-sts-mifi-ventures.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-ventures.service=mta-sts-mifi-ventures" + - "traefik.http.services.mta-sts-mifi-ventures.loadbalancer.server.port=80" + + # mifi.vix.br + - "traefik.http.routers.mta-sts-mifi-vix-br.rule=Host(`mta-sts.mifi.vix.br`)" + - "traefik.http.routers.mta-sts-mifi-vix-br.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-vix-br.tls=true" + - "traefik.http.routers.mta-sts-mifi-vix-br.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-vix-br.service=mta-sts-mifi-vix-br" + - "traefik.http.services.mta-sts-mifi-vix-br.loadbalancer.server.port=80" + + # mifi.me + - "traefik.http.routers.mta-sts-mifi-me.rule=Host(`mta-sts.mifi.me`)" + - "traefik.http.routers.mta-sts-mifi-me.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mifi-me.tls=true" + - "traefik.http.routers.mta-sts-mifi-me.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mifi-me.service=mta-sts-mifi-me" + - "traefik.http.services.mta-sts-mifi-me.loadbalancer.server.port=80" + + # blackice.vix.br + - "traefik.http.routers.mta-sts-blackice-vix-br.rule=Host(`mta-sts.blackice.vix.br`)" + - "traefik.http.routers.mta-sts-blackice-vix-br.entrypoints=websecure" + - "traefik.http.routers.mta-sts-blackice-vix-br.tls=true" + - "traefik.http.routers.mta-sts-blackice-vix-br.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-blackice-vix-br.service=mta-sts-blackice-vix-br" + - "traefik.http.services.mta-sts-blackice-vix-br.loadbalancer.server.port=80" + + # fitz.guru + - "traefik.http.routers.mta-sts-fitz-guru.rule=Host(`mta-sts.fitz.guru`)" + - "traefik.http.routers.mta-sts-fitz-guru.entrypoints=websecure" + - "traefik.http.routers.mta-sts-fitz-guru.tls=true" + - "traefik.http.routers.mta-sts-fitz-guru.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-fitz-guru.service=mta-sts-fitz-guru" + - "traefik.http.services.mta-sts-fitz-guru.loadbalancer.server.port=80" + + # umlautpress.com + - "traefik.http.routers.mta-sts-umlautpress-com.rule=Host(`mta-sts.umlautpress.com`)" + - "traefik.http.routers.mta-sts-umlautpress-com.entrypoints=websecure" + - "traefik.http.routers.mta-sts-umlautpress-com.tls=true" + - "traefik.http.routers.mta-sts-umlautpress-com.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-umlautpress-com.service=mta-sts-umlautpress-com" + - "traefik.http.services.mta-sts-umlautpress-com.loadbalancer.server.port=80" + + # camilla-rena.com + - "traefik.http.routers.mta-sts-camilla-rena-com.rule=Host(`mta-sts.camilla-rena.com`)" + - "traefik.http.routers.mta-sts-camilla-rena-com.entrypoints=websecure" + - "traefik.http.routers.mta-sts-camilla-rena-com.tls=true" + - "traefik.http.routers.mta-sts-camilla-rena-com.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-camilla-rena-com.service=mta-sts-camilla-rena-com" + - "traefik.http.services.mta-sts-camilla-rena-com.loadbalancer.server.port=80" + + # officelift.net + - "traefik.http.routers.mta-sts-officelift-net.rule=Host(`mta-sts.officelift.net`)" + - "traefik.http.routers.mta-sts-officelift-net.entrypoints=websecure" + - "traefik.http.routers.mta-sts-officelift-net.tls=true" + - "traefik.http.routers.mta-sts-officelift-net.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-officelift-net.service=mta-sts-officelift-net" + - "traefik.http.services.mta-sts-officelift-net.loadbalancer.server.port=80" + + # mylocalpro.biz + - "traefik.http.routers.mta-sts-mylocalpro-biz.rule=Host(`mta-sts.mylocalpro.biz`)" + - "traefik.http.routers.mta-sts-mylocalpro-biz.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mylocalpro-biz.tls=true" + - "traefik.http.routers.mta-sts-mylocalpro-biz.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mylocalpro-biz.service=mta-sts-mylocalpro-biz" + - "traefik.http.services.mta-sts-mylocalpro-biz.loadbalancer.server.port=80" + + # mylocalpro.online + - "traefik.http.routers.mta-sts-mylocalpro-online.rule=Host(`mta-sts.mylocalpro.online`)" + - "traefik.http.routers.mta-sts-mylocalpro-online.entrypoints=websecure" + - "traefik.http.routers.mta-sts-mylocalpro-online.tls=true" + - "traefik.http.routers.mta-sts-mylocalpro-online.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-mylocalpro-online.service=mta-sts-mylocalpro-online" + - "traefik.http.services.mta-sts-mylocalpro-online.loadbalancer.server.port=80" + + # happybeardedcarpenter.com + - "traefik.http.routers.mta-sts-happybeardedcarpenter-com.rule=Host(`mta-sts.happybeardedcarpenter.com`)" + - "traefik.http.routers.mta-sts-happybeardedcarpenter-com.entrypoints=websecure" + - "traefik.http.routers.mta-sts-happybeardedcarpenter-com.tls=true" + - "traefik.http.routers.mta-sts-happybeardedcarpenter-com.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-happybeardedcarpenter-com.service=mta-sts-happybeardedcarpenter-com" + - "traefik.http.services.mta-sts-happybeardedcarpenter-com.loadbalancer.server.port=80" + + # thenewenglandpalletguy.com + - "traefik.http.routers.mta-sts-thenewenglandpalletguy-com.rule=Host(`mta-sts.thenewenglandpalletguy.com`)" + - "traefik.http.routers.mta-sts-thenewenglandpalletguy-com.entrypoints=websecure" + - "traefik.http.routers.mta-sts-thenewenglandpalletguy-com.tls=true" + - "traefik.http.routers.mta-sts-thenewenglandpalletguy-com.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-thenewenglandpalletguy-com.service=mta-sts-thenewenglandpalletguy-com" + - "traefik.http.services.mta-sts-thenewenglandpalletguy-com.loadbalancer.server.port=80" + + # dining-it.com + - "traefik.http.routers.mta-sts-dining-it-com.rule=Host(`mta-sts.dining-it.com`)" + - "traefik.http.routers.mta-sts-dining-it-com.entrypoints=websecure" + - "traefik.http.routers.mta-sts-dining-it-com.tls=true" + - "traefik.http.routers.mta-sts-dining-it-com.tls.certresolver=letsencrypt" + - "traefik.http.routers.mta-sts-dining-it-com.service=mta-sts-dining-it-com" + - "traefik.http.services.mta-sts-dining-it-com.loadbalancer.server.port=80" + + restart: unless-stopped + +networks: + traefik: + external: true diff --git a/html/.well-known/mta-sts.txt b/html/.well-known/mta-sts.txt new file mode 100644 index 0000000..19b2a6c --- /dev/null +++ b/html/.well-known/mta-sts.txt @@ -0,0 +1,5 @@ +version: STSv1 +mode: enforce +mx: mail.mifi.holdings +mx: mx02.mail.mifi.holdings +max_age: 604800 diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..080e2c5 --- /dev/null +++ b/html/index.html @@ -0,0 +1,143 @@ + + + + + + MTA-STS Policy Server + + + +
+
🔒
+

MTA-STS Policy Server

+

+ This domain serves MTA-STS (Mail Transfer Agent Strict Transport Security) policies to ensure secure email delivery. +

+ +
✓ Active & Protected
+ +
+

What is MTA-STS?

+

+ MTA-STS is a security standard that helps protect email in transit by requiring mail servers to use TLS encryption and validate certificates. This prevents downgrade attacks and ensures your emails are delivered securely. +

+
+ +
+

Policy Location

+

+ The MTA-STS policy file is available at:
+ /.well-known/mta-sts.txt +

+
+
+ + diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..c3f5fe1 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,10 @@ +server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + root /usr/share/nginx/html; + index index.html; + location / { + try_files $uri $uri/ =404; + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6dc8378 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "mta-sts", + "version": "1.0.0", + "description": "MTA-STS static site with nginx", + "scripts": { + "build": "docker build -t git.mifi.dev/mifi-holdings/mta-sts:latest .", + "push": "docker push git.mifi.dev/mifi-holdings/mta-sts:latest", + "build:push": "pnpm build && pnpm push" + }, + "keywords": [ + "mta-sts", + "nginx", + "docker" + ], + "author": "", + "license": "ISC" +}