feat(desec): add deSEC account + domain registration backend

Add the server-side deSEC (dedyn.io) free dynamic-DNS flow so users without a
domain can obtain one from the AIO interface.

- DesecManager drives the full flow against the deSEC API, matching its real
  semantics: account creation returns HTTP 202 + email verification (no token),
  a token is obtained via /auth/login/ only after the email is verified, domain
  registration handles 201/409, and a wildcard CNAME rrset is created for new
  accounts. Existing accounts can be used by supplying a password.
- The "awaiting verification" step is derived from the stored credentials (email
  + generated password but no token and no domain yet), not a separate flag.
  register() returns false for that state so the controller can re-render the
  awaiting-verification UI instead of surfacing it as an error.
- DesecController exposes POST /api/desec/register; DependencyInjection wires the
  manager; account credentials (email, generated password, token) are stored in
  the AIO configuration and DESEC_TOKEN is exposed to the caddy container.
- The dynamic-DNS record is refreshed with the current public IP on container
  start (DockerController) and via the cron path (Cron/UpdateDesecIp, cron.sh).
- Fix an undefined-variable bug in the desecToken/desecPassword config setters
  that prevented credentials from being persisted.

Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
This commit is contained in:
Simon L.
2026-06-18 13:19:16 +02:00
co-authored by szaimen Claude Opus 4.8
parent 03bc7ebded
commit f7987b26ac
7 changed files with 494 additions and 1 deletions
+32
View File
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace AIO\Controller;
use AIO\Desec\DesecManager;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
readonly class DesecController {
public function __construct(
private DesecManager $desecManager,
) {
}
public function Register(Request $request, Response $response, array $args): Response {
try {
$email = (string)($request->getParsedBody()['desec_email'] ?? '');
$slug = (string)($request->getParsedBody()['desec_slug'] ?? '');
$password = (string)($request->getParsedBody()['desec_password'] ?? '');
// register() returns false when a new account was created and we are now awaiting
// the user's email verification. That is a normal state transition, not an error:
// reload the page (201 + Location) so the awaiting-verification UI renders and
// explains the next step, exactly like the fully-registered success path.
$this->desecManager->register($email, $slug, $password);
return $response->withStatus(201)->withHeader('Location', '.');
} catch (\Exception $ex) {
$response->getBody()->write($ex->getMessage());
return $response->withStatus(422);
}
}
}
+6 -1
View File
@@ -6,6 +6,7 @@ namespace AIO\Controller;
use AIO\Container\Container;
use AIO\Container\ContainerState;
use AIO\ContainerDefinitionFetcher;
use AIO\Desec\DesecManager;
use AIO\Docker\DockerActionManager;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -19,7 +20,8 @@ readonly class DockerController {
public function __construct(
private DockerActionManager $dockerActionManager,
private ContainerDefinitionFetcher $containerDefinitionFetcher,
private ConfigurationManager $configurationManager
private ConfigurationManager $configurationManager,
private DesecManager $desecManager,
) {
}
@@ -264,6 +266,9 @@ readonly class DockerController {
// Stop domaincheck since apache would not be able to start otherwise
$this->StopDomaincheckContainer();
// Refresh the deSEC DNS record with the current public IP before starting containers
$this->desecManager->updateIpIfDesecDomain();
$id = self::TOP_CONTAINER;
$this->PerformRecursiveContainerStart($id, $pullImage, $addToStreamingResponseBody);