# cert-helper — Setup Guide

Full TLS certificate lifecycle for CommuniGate Pro. Discovers domains, checks eligibility, orders certs from Let's Encrypt via Cloudflare DNS-01, installs them back into CGP, renews before expiry, and notifies on failure.

Ships as a single static binary (Windows / Linux) plus one TOML config. Runs as a Windows Service or a systemd unit. No manual OpenSSL. No GUI clicks.

---

## What you need before you start

- **A CGP admin login** for the CLI on port 106. `postmaster@<main-domain>` is the standard choice. It needs `Domain Administrator` rights on every domain the helper will manage.
- **A Cloudflare API token** scoped to `Zone.DNS:Edit` on the zone that holds your CGP domains. Create in Cloudflare dashboard → *My Profile → API Tokens → Create Token → template "Edit zone DNS" → Zone Resources = your zone*.
- **The zone ID** for that same zone (right sidebar of the zone's Cloudflare overview page).
- **A license file** (`license.key`) covering `cert-helper` in its `Helpers` array. Contact Stalker Software or use the license generator (`license-generator-v2.py`) if you have the private key. Without a license the helper runs in beta mode until 2026-12-31, then refuses to start.
- **Optional but recommended:** a local SMTP relay on the CGP host (usually already there) so postmaster notifications on failure can reach you.

---

## Install

### Windows

```powershell
# 1. Create the install directory
New-Item -ItemType Directory C:\CommuniGate\Helpers\cert-helper -Force

# 2. Drop the binary + license
Copy-Item cert-helper.exe   C:\CommuniGate\Helpers\cert-helper\
Copy-Item license.key       C:\CommuniGate\Helpers\cert-helper\

# 3. Create the config from the template
Copy-Item cert-helper.toml.example C:\CommuniGate\Helpers\cert-helper\cert-helper.toml
# then edit cert-helper.toml — see the section below

# 4. Register as a Windows Service (auto-start, restart-on-crash)
cd C:\CommuniGate\Helpers\cert-helper
.\cert-helper.exe --install-service
sc.exe start cert-helper
```

### Linux (Fedora / RHEL / Debian / Ubuntu)

```bash
sudo mkdir -p /opt/cert-helper
sudo install -m 0755 cert-helper       /opt/cert-helper/cert-helper
sudo install -m 0600 license.key       /opt/cert-helper/license.key
sudo install -m 0600 cert-helper.toml  /opt/cert-helper/cert-helper.toml
# then edit /opt/cert-helper/cert-helper.toml

# systemd unit
sudo tee /etc/systemd/system/cert-helper.service <<'EOF'
[Unit]
Description=CGP Certificate Helper
After=network-online.target CGPro.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/opt/cert-helper/cert-helper --config /opt/cert-helper/cert-helper.toml
WorkingDirectory=/opt/cert-helper
Restart=always
RestartSec=30
User=root

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now cert-helper
sudo systemctl status cert-helper
```

---

## Configure — `cert-helper.toml`

The full annotated template ships as `cert-helper.toml.example`. The sections you must review before first run:

```toml
[cgp]
host       = "localhost"                # where the CGP CLI listens
port       = 106
admin_user = "postmaster@mx.rhone.ch"   # your main-domain admin
admin_pass = "REPLACE_ME"

[db]
path = "/var/lib/cert-helper/state.sqlite"

[log]
dir   = "/var/log/cert-helper"
file  = "cert-helper.log"
level = "info"

[policy]
# Eligibility filter — which domains should get a public cert.
# Rhone.ch-style default: DenyAllExcept means only listed domains are eligible.
# Every OTHER domain will be marked ineligible (and, if auto_disable=true,
# will get CertificateType=Disabled pushed into CGP — replaces any external
# keep-applying-Disabled script).
mode         = "deny_all_except"
list         = ["mail.example.com", "webmail.example.com"]
# The mailserver's OWN public IPs. Only domains whose DNS resolves here are
# eligible for a public cert.
local_ips    = ["203.0.113.10", "203.0.113.11"]
auto_disable = true

[acme]
# START ON STAGING. Only switch to prod after you've seen a successful cycle.
directory_url = "https://acme-staging-v02.api.letsencrypt.org/directory"
contact_email = "postmaster@example.com"
http01_dir    = "/var/lib/cert-helper/acme-challenges"  # not used for DNS-01

[renewal]
enabled          = true    # background scheduler on
interval_seconds = 86400   # 24h
threshold_days   = 30      # renew when < 30d to expiry

[smtp]
enabled        = true
host           = "localhost"
port           = 25
helo           = "cert-helper.example.com"
from           = "cert-helper@example.com"
recipients     = ["postmaster@example.com"]
subject_prefix = "[cert-helper]"

[smtp.events]
issuance_failed = true
sans_shrunk     = true
no_dns          = true
not_local       = true

[web]
enabled = true
addr    = "127.0.0.1:8765"

[cloudflare]
# DNS-01 responder. Use a scoped Zone.DNS:Edit token (NOT the global key).
api_token = "REPLACE_WITH_SCOPED_API_TOKEN"
zone_id   = "REPLACE_WITH_ZONE_ID"
```

**Safe defaults** — the master switches all default OFF:
- `[smtp] enabled = false` — no mail sent until you opt in
- `[policy] auto_disable = false` — no CGP mutation for ineligible domains until you opt in
- `[acme] directory_url` defaults to Let's Encrypt **staging** — no rate-limit burn on your first attempts

---

## First run — smoke test

1. Open the dashboard at `http://127.0.0.1:8765`. You should see:
   - Your loaded config summary
   - License status: `valid: DRIFT-…`
   - Domain count: 0 (empty before first discovery)

2. Click **Run discover**. Wait a few seconds. The domain list populates from CGP. Each row shows the outcome of the eligibility filter for that domain.

3. Pick one eligible domain, click **Test renewal**. This runs a dry-run of the challenge mechanism (writes a real TXT via CF API, polls for propagation, verifies, then deletes) without burning an LE order. **This is safe to run any time.**

4. When the probe passes, click **Renew now** on the same domain. Watch the log tail on the dashboard. You should see:
   - `creating ACME account` (first run only)
   - `cloudflare TXT created`
   - `cloudflare TXT propagation wait complete` (45s)
   - `set_ready` for the challenge
   - Order status polls, then `finalize`
   - `install verified` with fingerprint
   - Row flips to `active`, `not_after` shows ~90d out

5. When the staging cycle works end-to-end, **switch the ACME URL to production**:
   ```toml
   directory_url = "https://acme-v02.api.letsencrypt.org/directory"
   ```
   Delete the staging account so a fresh production one is created:
   ```bash
   sqlite3 /var/lib/cert-helper/state.sqlite "DELETE FROM config WHERE key='acme_account_credentials'"
   ```
   Restart the service, click **Renew now** again.

6. Turn the scheduler on (`[renewal] enabled = true`). From now on, every 24h the helper walks the DB, finds domains expiring in the next 30d, and renews them silently. Failures go to postmaster.

---

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| Dashboard shows `license: degraded` | No `license.key` next to the binary | Copy it in; restart. Beta mode works until 2026-12-31 without it. |
| Discover returns 0 domains | Wrong `admin_user` / `admin_pass` | Check `cert-helper.log` for `401` on the USER/PASS exchange |
| Renew fails with `NXDOMAIN … _acme-challenge.<domain>` | CF token has wrong scope or wrong zone | Verify with `curl -H "Authorization: Bearer $TOKEN" https://api.cloudflare.com/client/v4/user/tokens/verify` |
| Renew fails with `rate limit exceeded` | You hit LE production rate limits | Switch back to staging until you understand the limit; the CT log records every issue |
| Cert installs but browsers show old one | Restart CGP after install: `sc restart CommuniGatePro` (Win) or `systemctl restart CGPro` (Linux) |
| Row stuck in `failed` after 5 attempts | Manual reset needed | Click **Renew now** on that row — resets `attempt_count` and re-attempts once |

---

## Where things live at runtime

| Purpose | Path (Windows) | Path (Linux) |
|---|---|---|
| Binary | `C:\CommuniGate\Helpers\cert-helper\cert-helper.exe` | `/opt/cert-helper/cert-helper` |
| Config | `…\cert-helper.toml` | `/opt/cert-helper/cert-helper.toml` |
| License | `…\license.key` | `/opt/cert-helper/license.key` |
| State DB | `…\state.sqlite` (or per config) | `/var/lib/cert-helper/state.sqlite` |
| Log file | `…\cert-helper.log` (or per config) | `/var/log/cert-helper/cert-helper.log` |
| ACME challenge dir (HTTP-01 only) | not used for CF DNS-01 | not used for CF DNS-01 |

---

## Uninstall

### Windows

```powershell
sc.exe stop cert-helper
cd C:\CommuniGate\Helpers\cert-helper
.\cert-helper.exe --uninstall-service
Remove-Item -Recurse C:\CommuniGate\Helpers\cert-helper
```

### Linux

```bash
sudo systemctl stop cert-helper
sudo systemctl disable cert-helper
sudo rm /etc/systemd/system/cert-helper.service
sudo systemctl daemon-reload
sudo rm -r /opt/cert-helper /var/lib/cert-helper /var/log/cert-helper
```

Removing the helper doesn't touch the certs it installed — those stay in CGP until the next real renewal (which, without the helper, means manual admin work).

---

Questions or a broken install? Mail **info@cgatepro.de** with your `cert-helper.log` attached.
