Initial commit

This commit is contained in:
2026-02-10 19:19:25 -03:00
commit 7bf6ea5c26
8 changed files with 523 additions and 0 deletions

126
.woodpecker/build.yaml Normal file
View File

@@ -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]

59
.woodpecker/deploy.yaml Normal file
View File

@@ -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]

14
Dockerfile Normal file
View File

@@ -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

149
docker-compose.yml Normal file
View File

@@ -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

View File

@@ -0,0 +1,5 @@
version: STSv1
mode: enforce
mx: mail.mifi.holdings
mx: mx02.mail.mifi.holdings
max_age: 604800

143
html/index.html Normal file
View File

@@ -0,0 +1,143 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MTA-STS Policy Server</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
color: #333;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 600px;
width: 100%;
padding: 48px;
text-align: center;
}
.icon {
font-size: 64px;
margin-bottom: 24px;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
h1 {
font-size: 32px;
font-weight: 700;
color: #667eea;
margin-bottom: 16px;
}
.subtitle {
font-size: 18px;
color: #666;
margin-bottom: 32px;
line-height: 1.6;
}
.info-box {
background: #f8f9fa;
border-radius: 12px;
padding: 24px;
margin-top: 24px;
text-align: left;
}
.info-box h2 {
font-size: 18px;
font-weight: 600;
color: #333;
margin-bottom: 12px;
}
.info-box p {
font-size: 14px;
color: #666;
line-height: 1.6;
}
.badge {
display: inline-block;
background: #10b981;
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 600;
margin-top: 24px;
}
a {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
a:hover {
text-decoration: underline;
}
@media (max-width: 640px) {
.container {
padding: 32px 24px;
}
h1 {
font-size: 24px;
}
.subtitle {
font-size: 16px;
}
}
</style>
</head>
<body>
<div class="container">
<div class="icon">🔒</div>
<h1>MTA-STS Policy Server</h1>
<p class="subtitle">
This domain serves MTA-STS (Mail Transfer Agent Strict Transport Security) policies to ensure secure email delivery.
</p>
<div class="badge">✓ Active & Protected</div>
<div class="info-box">
<h2>What is MTA-STS?</h2>
<p>
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.
</p>
</div>
<div class="info-box">
<h2>Policy Location</h2>
<p>
The MTA-STS policy file is available at:<br>
<a href="/.well-known/mta-sts.txt">/.well-known/mta-sts.txt</a>
</p>
</div>
</div>
</body>
</html>

10
nginx/default.conf Normal file
View File

@@ -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;
}
}

17
package.json Normal file
View File

@@ -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"
}