Add MIT license and enable Let's Encrypt certificate services#5
Conversation
- Add MIT LICENSE (CodeLifter LLC) and update README license section,
replacing "Private - All rights reserved".
- Enable the previously-disabled Let's Encrypt certificate services:
- Rename CertificateService and CertificateRenewalBackgroundService
from .disabled and register them in DI.
- Add IAcmeChallengeStore + map GET /.well-known/acme-challenge/{token}
so HTTP-01 validation can actually succeed (the original code never
served the challenge response).
- Poll the authorization (not the order) for validation success, and
clean up the published challenge afterward.
- Trigger issuance on save and broaden the background service to cover
initial issuance, not just renewal.
- Default to the Let's Encrypt staging directory; LetsEncrypt__UseProduction
opts into production. Documented in .env.example.
- Remove the Cloudflare Tunnel recommendation from the home-hosting guide
(CGNAT workaround now points to a non-CGNAT IP or VPS instead).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🤖 Augment PR SummarySummary: This PR makes the repository license explicit and enables functional Let's Encrypt/ACME certificate issuance for reverse-proxy hosts. Changes:
Technical Notes: Issuance requires the public domain to reach the app over HTTP at 🤖 Was this summary useful? React with 👍 or 👎 |
| LetsEncrypt__UseProduction=false | ||
|
|
||
| # Example project with real SMTP data | ||
| # StageZero No newline at end of file |
There was a problem hiding this comment.
| // during certificate issuance; we serve the key authorization published by | ||
| // CertificateService. Must remain reachable over plain HTTP (Let's Encrypt | ||
| // follows the 301 from UseHttpsRedirection, but the route itself is anonymous). | ||
| app.MapGet("/.well-known/acme-challenge/{token}", (string token, IAcmeChallengeStore store) => |
There was a problem hiding this comment.
StageZero/Program.cs:380: Since HTTP-01 validation can hit this endpoint multiple times from different vantage points, consider explicitly preventing caching of challenge responses (e.g., via response headers) to avoid any intermediary serving stale content. This is especially relevant if the app is deployed behind a CDN/reverse-proxy that might cache 200 responses by default.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| // Publish the challenge response so /.well-known/acme-challenge/{token} | ||
| // can serve it when Let's Encrypt validates the domain. | ||
| challengeToken = httpChallenge.Token; | ||
| _challengeStore.AddChallenge(challengeToken, keyAuthz); |
There was a problem hiding this comment.
StageZero.ReverseProxy/Services/CertificateService.cs:84: proxyHost.DomainName is later used to construct cert/key file paths, so any unexpected characters (path separators, .., etc.) could allow writing outside the intended certificates directory. Consider validating/sanitizing DomainName before using it in any filesystem path now that this service is enabled.
Severity: high
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| // Publish the challenge response so /.well-known/acme-challenge/{token} | ||
| // can serve it when Let's Encrypt validates the domain. | ||
| challengeToken = httpChallenge.Token; | ||
| _challengeStore.AddChallenge(challengeToken, keyAuthz); |
There was a problem hiding this comment.
StageZero.ReverseProxy/Services/CertificateService.cs:84: This issuance flow writes a private key to disk; relying on default file permissions can be risky if the host/umask is permissive. Consider ensuring the .key file is created with restrictive permissions since it contains TLS private key material.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| // within the 30-day renewal window. | ||
| var pendingHosts = await _dbContext.Set<ProxyHost>() | ||
| .Where(p => p.UseLetsEncrypt | ||
| && (p.SslCertificateExpiry == null || p.SslCertificateExpiry < expiringDate)) |
There was a problem hiding this comment.
StageZero.ReverseProxy/Services/CertificateService.cs:188: RenewExpiringCertificatesAsync() will select hosts with UseLetsEncrypt even if LetsEncryptEmail is missing, which will cause repeated failed attempts and misleading "not enabled" logs. Consider filtering out incomplete Let's Encrypt configurations (or logging a more specific message) to avoid noisy retries.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
Summary
Two product-repo cleanup items, plus a follow-up requested in the same session.
1. MIT LICENSE
LICENSE(MIT, © 2026 CodeLifter LLC) so the open-source / MIT claim is true.2. Enable Let's Encrypt certificate services
The cert services existed only as
.disabledfiles and were incomplete — they never served the ACME challenge, so validation would always have failed. This makes them actually work:CertificateServiceandCertificateRenewalBackgroundServicefrom.disabled; register in DI (Program.cs).IAcmeChallengeStore(singleton) and mapGET /.well-known/acme-challenge/{token}so HTTP-01 validation can succeed.finally.LetsEncrypt__UseProduction=trueto go live. Documented in.env.example.3. Remove Cloudflare Tunnel from the home-hosting guide
Per request to drop Cloudflare Tunnels project-wide. The code was already Tunnel-free; the only reference was a CGNAT workaround sentence in
StageZero-Home-Hosting-Guide.docx, now reworded to a non-CGNAT IP / VPS alternative. (The Cloudflare DNS provider — the app's core feature — is unrelated and untouched.)Testing
StageZeroandStageZero.ReverseProxybuild clean (the pre-existing solution-wide build break from the missingLifted.BlazorAuth.AspNetIdentityproject is unrelated).Notes for reviewers
/.well-known/acme-challenge/— verify the ingress path passes it through.DatabaseProxyConfigProvider.cs.disabled(the third disabled file) is intentionally left disabled — it's the YARP routing provider, a separate concern that needs full YARP integration.🤖 Generated with Claude Code