- mailer/: standalone Express+nodemailer service on port 3000 with
validation, honeypot (company field), per-IP rate limiting (5/min),
Fastmail SMTP. Returns 400 on bad input, 429 on rate limit, 502 on
SMTP failure, 200 {ok:true} on success or honeypot hit.
- Dockerfile: multi-stage nginx build for humanai-landing container.
- mailer/Dockerfile: node:22-alpine, no exposed host ports.
- deploy.sh: idempotent deploy of both containers to piha.local via
Docker context 'piha', with smoke tests (400 on empty body, 200 on
honeypot fill).
- src/i18n/: minimal pl/en translation files; useT() hook picks
language from navigator.language.
- CTASection: replaced dead onSubmit with fetch POST /api/contact,
added hidden honeypot field, success/error states using i18n strings.
- README: Mailer section with two manual steps (credentials + NPM proxy).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
88 lines
3.1 KiB
Bash
Executable file
88 lines
3.1 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
CONTEXT="piha"
|
|
REMOTE="ssh://oskar@piha.local"
|
|
NETWORK="nginxproxymanager_default"
|
|
|
|
# Create docker context if missing
|
|
docker context inspect "$CONTEXT" &>/dev/null || \
|
|
docker context create "$CONTEXT" --docker "host=$REMOTE"
|
|
|
|
D="docker --context $CONTEXT"
|
|
|
|
# ── Landing ────────────────────────────────────────────────────────────────────
|
|
echo "==> Building humanai-landing"
|
|
$D build -t humanai-landing .
|
|
|
|
echo "==> Deploying humanai-landing"
|
|
$D rm -f humanai-landing 2>/dev/null || true
|
|
$D run -d \
|
|
--name humanai-landing \
|
|
--network "$NETWORK" \
|
|
--restart unless-stopped \
|
|
humanai-landing
|
|
|
|
# ── Mailer ─────────────────────────────────────────────────────────────────────
|
|
echo "==> Building humanai-mailer"
|
|
$D build -t humanai-mailer ./mailer
|
|
|
|
echo "==> Deploying humanai-mailer"
|
|
$D rm -f humanai-mailer 2>/dev/null || true
|
|
$D run -d \
|
|
--name humanai-mailer \
|
|
--network "$NETWORK" \
|
|
--env-file mailer.env \
|
|
--restart unless-stopped \
|
|
humanai-mailer
|
|
|
|
# ── Smoke tests ────────────────────────────────────────────────────────────────
|
|
echo "==> Waiting for mailer to start..."
|
|
sleep 3
|
|
|
|
echo "==> Smoke test 1: empty body must return 400"
|
|
STATUS=$($D exec humanai-mailer node -e "
|
|
const http = require('http');
|
|
const body = '{}';
|
|
const req = http.request({
|
|
host: 'localhost', port: 3000, path: '/api/contact', method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
}, res => { console.log(res.statusCode); });
|
|
req.on('error', e => { console.error(e.message); process.exit(1); });
|
|
req.write(body);
|
|
req.end();
|
|
")
|
|
if [ "$STATUS" != "400" ]; then
|
|
echo "FAIL: expected 400, got $STATUS"
|
|
exit 1
|
|
fi
|
|
echo " OK: got 400"
|
|
|
|
echo "==> Smoke test 2: honeypot fill must return 200 {ok:true} (no email sent)"
|
|
RESP=$($D exec humanai-mailer node -e "
|
|
const http = require('http');
|
|
const body = JSON.stringify({name:'Bot',email:'bot@example.com',message:'hello world',company:'acme'});
|
|
const chunks = [];
|
|
const req = http.request({
|
|
host: 'localhost', port: 3000, path: '/api/contact', method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
|
|
}, res => {
|
|
res.on('data', d => chunks.push(d));
|
|
res.on('end', () => console.log(res.statusCode + ' ' + Buffer.concat(chunks).toString()));
|
|
});
|
|
req.on('error', e => { console.error(e.message); process.exit(1); });
|
|
req.write(body);
|
|
req.end();
|
|
")
|
|
if [[ "$RESP" != 200* ]]; then
|
|
echo "FAIL: expected 200, got $RESP"
|
|
exit 1
|
|
fi
|
|
echo " OK: got $RESP"
|
|
|
|
echo ""
|
|
echo "Deploy complete."
|
|
echo "Next steps (manual):"
|
|
echo " 1. Set SMTP_USER/SMTP_PASS in mailer.env on this machine"
|
|
echo " 2. In NPM: Proxy Host gethumanai.pl → Custom Location /api → http://humanai-mailer:3000"
|