# www.kenjim.com KenJim Technologies marketing site — React SPA with Express contact form backend, deployed on **zet.home.arpa** behind nginx SSL termination. ## Deployment Architecture ```mermaid flowchart TD subgraph internet["Internet"] browser(["Client browser"]) godaddy["GoDaddy DNS\nwww.kenjim.com CNAME → lair.kenjim.com\nlair.kenjim.com A → WAN IP (dynamic)"] le["Let's Encrypt CA"] end subgraph pfsense["pfSense · home gateway"] ddns["DDNS client\nupdates lair.kenjim.com A record\nwhen WAN IP changes"] nat["NAT / Port Forward\nWAN :80 / :443 → zet :80 / :443"] unbound["Unbound DNS Resolver\nwww / git / kenji.kenjim.com\n→ 172.27.0.35 (split DNS — bypasses NAT)"] end subgraph zet["zet.home.arpa · 172.27.0.35"] subgraph ssl["SSL — acme.sh + Let's Encrypt"] acme["acme.sh\nDNS-01 challenge via GoDaddy API"] cert["/etc/nginx/ssl/kenjim.com/\nfullchain.pem · key.pem\nauto-renewed by cron"] end subgraph nginx_host["nginx · host process"] nginxsrv[":80 → redirect HTTPS\n:443 · SSL termination\nproxy_pass → localhost:8080"] end subgraph docker["Docker · network_mode: host"] fe["www-kenjim-frontend\nnginx :8080\nReact SPA (pre-built image)\n/api/* proxied → :3001"] api["www-kenjim-api\nExpress.js :3001\nPOST /contact handler"] end bridge["Proton Mail Bridge\n127.0.0.1:1025 SMTP relay"] gitea[("Gitea · git.kenjim.com\nkenjim/www.kenjim.com")] end protonmail["Proton Mail\ninfo@kenjim.com → kenji@kenjim.com"] browser -->|"DNS lookup"| godaddy browser -->|"HTTPS :443"| nat nat -->|"port forward"| nginxsrv unbound -.->|"LAN clients resolve direct"| nginxsrv nginxsrv -->|"proxy_pass :8080"| fe fe -->|"proxy /api/*"| api api -->|"SMTP 127.0.0.1:1025"| bridge bridge -->|"relay via Proton servers"| protonmail acme <-->|"set DNS TXT record\nfor domain validation"| godaddy acme <-->|"certificate issuance"| le acme -->|"writes cert"| cert cert -->|"loaded by"| nginxsrv ddns -->|"keeps A record current"| godaddy gitea -->|"git pull · docker compose up --build"| fe gitea -->|"git pull · docker compose up --build"| api ``` ## Structure ``` www.kenjim.com/ ├── docker-compose.yml # both services run network_mode: host ├── frontend/ │ ├── Dockerfile # node:20 build → nginx:alpine serve │ ├── nginx.conf # :8080, SPA fallback, /api/ proxy → :3001 │ └── src/ # React source (Vite) └── backend/ ├── Dockerfile └── server.js # Express, POST /contact → nodemailer ``` ## Development Workflow Edit on Mac → push to Gitea → pull + rebuild on zet: ``` Mac Gitea (git.kenjim.com) zet.home.arpa │ git push │ │ ├──────────────────────────────►│ │ │ │ git pull │ │ ├─────────────────────────►│ │ │ │ docker compose up --build -d ``` ## Running on zet ```bash cd ~/workspace/src/personal/www.kenjim.com docker compose up --build -d # build images and start docker compose down # stop docker compose logs -f # watch logs docker logs www-kenjim-api # backend only docker logs www-kenjim-frontend # frontend only ``` ## Environment `.env` lives on zet only (not in git). Required vars: | Variable | Example | Notes | |---|---|---| | `SMTP_HOST` | `127.0.0.1` | Proton Bridge SMTP host | | `SMTP_PORT` | `1025` | Proton Bridge SMTP port | | `SMTP_SECURE` | `false` | Bridge uses STARTTLS, not SSL-on-connect | | `SMTP_USER` | `kenji@kenjim.com` | Bridge auth credential | | `SMTP_PASS` | `` | From Bridge SMTP settings | | `FROM_EMAIL` | `info@kenjim.com` | Sender address shown in emails | | `CONTACT_TO` | `info@kenjim.com` | Destination for contact form submissions | ## SSL Certificate Issued by Let's Encrypt via DNS-01 challenge using the GoDaddy API. Managed by acme.sh on zet: ```bash # manual renewal (cron handles this automatically) ~/.acme.sh/acme.sh --renew -d kenjim.com --wildcard --dns dns_gd ``` Cert location: `/etc/nginx/ssl/kenjim.com/` ## Mac Setup (first time) ```bash git clone http://kenjim@git.kenjim.com/kenjim/www.kenjim.com.git cd www.kenjim.com # edit frontend/src, then: git add . && git commit -m "..." && git push ```