Fix: Let's Encrypt status detection and auto-generate .env

- AdminController: Detect existing SSL certificates from /etc/letsencrypt
  and update database status automatically (fixes status showing "none"
  when cert was obtained via cloud-init)
- docker-compose.prod.yml: Mount /etc/letsencrypt to backend container
- cloud-init: Auto-generate .env with secure random secrets on first boot
  (DB_PASSWORD, JWT_SECRET, REDIS_PASSWORD, OME_API_TOKEN, NAKAMA keys)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
doomtube 2026-01-06 04:49:00 -05:00
parent 118629549e
commit 0fc49d0032
3 changed files with 149 additions and 10 deletions

View file

@ -2956,10 +2956,13 @@ void AdminController::getSSLSettings(const HttpRequestPtr &req,
*dbClient << "SELECT domain, acme_email, certificate_status, certificate_expiry, "
"last_renewal_attempt, last_renewal_error, auto_renewal_enabled, updated_at "
"FROM ssl_settings WHERE id = 1"
>> [callback](const Result& r) {
>> [callback, dbClient](const Result& r) {
Json::Value resp;
resp["success"] = true;
std::string dbStatus = "none";
std::string domain = "";
if (r.empty()) {
// Return defaults if no settings exist
resp["domain"] = "";
@ -2971,9 +2974,12 @@ void AdminController::getSSLSettings(const HttpRequestPtr &req,
resp["autoRenewalEnabled"] = true;
} else {
const auto& row = r[0];
resp["domain"] = row["domain"].isNull() ? "" : row["domain"].as<std::string>();
domain = row["domain"].isNull() ? "" : row["domain"].as<std::string>();
dbStatus = row["certificate_status"].as<std::string>();
resp["domain"] = domain;
resp["acmeEmail"] = row["acme_email"].isNull() ? "" : row["acme_email"].as<std::string>();
resp["certificateStatus"] = row["certificate_status"].as<std::string>();
resp["certificateStatus"] = dbStatus;
if (!row["certificate_expiry"].isNull()) {
resp["certificateExpiry"] = row["certificate_expiry"].as<std::string>();
@ -2996,6 +3002,50 @@ void AdminController::getSSLSettings(const HttpRequestPtr &req,
resp["autoRenewalEnabled"] = row["auto_renewal_enabled"].as<bool>();
}
// Check for existing certificates on disk if DB shows "none"
// This handles the case where certbot ran during cloud-init
if (dbStatus == "none") {
try {
// Check /etc/letsencrypt/live directory for any certificates
std::string letsencryptDir = "/etc/letsencrypt/live";
if (std::filesystem::exists(letsencryptDir) && std::filesystem::is_directory(letsencryptDir)) {
for (const auto& entry : std::filesystem::directory_iterator(letsencryptDir)) {
if (entry.is_directory()) {
std::string certPath = entry.path().string() + "/fullchain.pem";
if (std::filesystem::exists(certPath)) {
std::string detectedDomain = entry.path().filename().string();
LOG_INFO << "Detected existing SSL certificate for: " << detectedDomain;
// Update response to show active certificate
resp["certificateStatus"] = "active";
if (resp["domain"].asString().empty()) {
resp["domain"] = detectedDomain;
}
// Update database to reflect the existing certificate
*dbClient << "INSERT INTO ssl_settings (id, domain, certificate_status, updated_at) "
"VALUES (1, $1, 'active', CURRENT_TIMESTAMP) "
"ON CONFLICT (id) DO UPDATE SET "
"certificate_status = 'active', "
"domain = CASE WHEN ssl_settings.domain = '' THEN $1 ELSE ssl_settings.domain END, "
"updated_at = CURRENT_TIMESTAMP"
<< detectedDomain
>> [](const Result&) {
LOG_INFO << "Updated ssl_settings with detected certificate";
}
>> [](const DrogonDbException& e) {
LOG_ERROR << "Failed to update ssl_settings: " << e.base().what();
};
break;
}
}
}
}
} catch (const std::exception& e) {
LOG_DEBUG << "Error checking for certificates: " << e.what();
}
}
callback(jsonResp(resp));
}
>> [callback](const DrogonDbException& e) {