mastercontainer updates deSEC IP directly; ddclient auto-configures from env vars

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/fc6803fd-5743-438d-86b8-068ce48b1411

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-04-24 19:24:14 +00:00
committed by GitHub
parent f23d8276ff
commit 7c5abc978d
10 changed files with 110 additions and 36 deletions

View File

@@ -0,0 +1,16 @@
# syntax=docker/dockerfile:latest
FROM ghcr.io/linuxserver/ddclient:latest
# Auto-configure ddclient for deSEC when NC_DOMAIN and DESEC_TOKEN are provided.
# The linuxserver base image executes all scripts in /custom-cont-init.d/ before
# the main service starts, which lets us generate ddclient.conf without any manual step.
COPY --chmod=755 ddclient-config-gen.sh /custom-cont-init.d/ddclient-config-gen.sh
LABEL com.centurylinklabs.watchtower.enable="false" \
wud.watch="false" \
org.opencontainers.image.title="DDclient for Nextcloud AIO" \
org.opencontainers.image.description="DDclient with automatic deSEC configuration for Nextcloud All-in-One" \
org.opencontainers.image.url="https://github.com/nextcloud/all-in-one" \
org.opencontainers.image.source="https://github.com/nextcloud/all-in-one" \
org.opencontainers.image.vendor="Nextcloud" \
org.opencontainers.image.documentation="https://github.com/nextcloud/all-in-one/blob/main/community-containers/ddclient/readme.md"

View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Automatically generate /config/ddclient.conf for deSEC dynamic DNS when
# NC_DOMAIN and DESEC_TOKEN are provided and no config file exists yet.
#
# This script is executed by the linuxserver base image from /custom-cont-init.d/
# before ddclient starts, so no manual configuration step is required.
if [[ -n "${NC_DOMAIN}" && -n "${DESEC_TOKEN}" && ! -f /config/ddclient.conf ]]; then
{
printf 'daemon=300\nsyslog=yes\nssl=yes\n\n'
printf 'use=web, web=https://checkipv4.dedyn.io/\n\n'
printf 'protocol=dyndns2\nserver=update.dedyn.io\n'
printf 'login=%s\npassword=%s\n%s\n' \
"${NC_DOMAIN}" "${DESEC_TOKEN}" "${NC_DOMAIN}"
} > /config/ddclient.conf
echo "deSEC ddclient config auto-generated for domain ${NC_DOMAIN}"
fi

View File

@@ -51,6 +51,9 @@ while true; do
# Check if AIO is outdated
sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/OutdatedNotification.php
# Update deSEC DNS IP record (no-op when IP is unchanged or deSEC is not configured)
sudo -E -u www-data php /var/www/docker-aio/php/src/Cron/UpdateDesecIp.php
# Remove sessions older than 24h
find "/mnt/docker-aio-config/session/" -mindepth 1 -mmin +1440 -delete

View File

@@ -4,8 +4,8 @@
"container_name": "nextcloud-aio-ddclient",
"display_name": "DDclient (deSEC DDNS)",
"documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/ddclient",
"image": "ghcr.io/linuxserver/ddclient",
"image_tag": "latest",
"image": "ghcr.io/nextcloud-releases/aio-ddclient",
"image_tag": "%AIO_CHANNEL%",
"restart": "unless-stopped",
"environment": [
"TZ=%TIMEZONE%",

View File

@@ -2,17 +2,11 @@
This container runs [DDclient](https://ddclient.net/) pre-configured for use with [deSEC](https://desec.io/) dynamic DNS.
When you register a free dedyn.io domain through the AIO interface, the `NC_DOMAIN` and `DESEC_TOKEN` environment variables are automatically set from the stored credentials. On first start the linuxserver/ddclient image creates an empty `/config/ddclient.conf` in the `nextcloud_aio_ddclient` volume; you need to populate that file once as described below.
## How it works
## One-time configuration
When you register a free dedyn.io domain through the AIO interface the `NC_DOMAIN` and `DESEC_TOKEN` environment variables are automatically populated from the stored credentials.
After the container has started for the first time, run:
```bash
docker exec -it nextcloud-aio-ddclient sh
```
Then create `/config/ddclient.conf` with the following content (replace the placeholders with the values printed in the container's environment):
On first start, if `/config/ddclient.conf` does not yet exist and both `NC_DOMAIN` and `DESEC_TOKEN` are set, the container automatically generates a ready-to-use `ddclient.conf`:
```
daemon=300
@@ -23,28 +17,20 @@ use=web, web=https://checkipv4.dedyn.io/
protocol=dyndns2
server=update.dedyn.io
login=<value of NC_DOMAIN>
password=<value of DESEC_TOKEN>
<value of NC_DOMAIN>
login=<NC_DOMAIN>
password=<DESEC_TOKEN>
<NC_DOMAIN>
```
You can read the values from the running container:
No manual configuration step is required.
```bash
docker exec nextcloud-aio-ddclient printenv NC_DOMAIN
docker exec nextcloud-aio-ddclient printenv DESEC_TOKEN
```
## Relationship to the AIO mastercontainer
Once the file is saved, restart the container:
```bash
docker restart nextcloud-aio-ddclient
```
DDclient will now update the DNS record for your domain every 5 minutes.
The AIO mastercontainer already updates the deSEC DNS record every time containers are started and once per cron cycle (roughly every minute). This container adds a second, independent layer of DNS updates that runs continuously every 5 minutes via the ddclient daemon — useful if the host IP can change while the containers are running between cron cycles.
## Notes
- The config volume (`nextcloud_aio_ddclient`) is included in AIO backups, so the configuration persists across updates and restores.
- A derivative image that auto-generates the config from `NC_DOMAIN` and `DESEC_TOKEN` without any manual step will be created in a dedicated repository in the future.
- For IPv6 support add a second `use` block pointing to `https://checkipv6.dedyn.io/` in the config file. See the [ddclient documentation](https://ddclient.net/protocols/dyndns2.html) for details.
- If the config file already exists (e.g., you customised it previously), it will **not** be overwritten on restart.
- For IPv6 support, add a second `use` block pointing to `https://checkipv6.dedyn.io/` in the config file. See the [ddclient documentation](https://ddclient.net/protocols/dyndns2.html) for details.
- This image is derived from [ghcr.io/linuxserver/ddclient](https://github.com/linuxserver/docker-ddclient) and adds the auto-configuration script via the linuxserver `/custom-cont-init.d/` mechanism.

View File

@@ -49,7 +49,7 @@ readonly class DesecController {
// Register a free dedyn.io subdomain
$domain = $this->registerDesecDomain($token);
// Persist the credentials and auto-enable the companion community containers
// Persist the credentials and auto-enable caddy as the reverse proxy
$this->configurationManager->startTransaction();
$this->configurationManager->setDesecToken($token);
$this->configurationManager->desecEmail = $email;
@@ -57,10 +57,8 @@ readonly class DesecController {
$this->configurationManager->aioCommunityContainers,
fn(string $cc): bool => $cc !== '',
));
foreach (['caddy', 'ddclient'] as $cc) {
if (!in_array($cc, $enabled, true)) {
$enabled[] = $cc;
}
if (!in_array('caddy', $enabled, true)) {
$enabled[] = 'caddy';
}
$this->configurationManager->aioCommunityContainers = $enabled;
$this->configurationManager->commitTransaction();
@@ -69,6 +67,9 @@ readonly class DesecController {
// created and DNS propagation may not have completed yet.
$this->configurationManager->setDomain($domain, true);
// Perform the first DNS IP update so the record is populated immediately
$this->updateIpIfDesecDomain();
return $response->withStatus(201)->withHeader('Location', '.');
} catch (InvalidSettingConfigurationException $ex) {
$response->getBody()->write($ex->getMessage());
@@ -79,6 +80,36 @@ readonly class DesecController {
}
}
/**
* Updates the deSEC DNS A/AAAA record with the current public IP of this host.
* Uses deSEC's DynDNS2-compatible update endpoint, which auto-detects the requester's IP.
* Safe to call frequently; the endpoint returns "nochg" when the IP has not changed.
* Errors are logged but never thrown, so callers are not interrupted.
*/
public function updateIpIfDesecDomain(): void {
if (!$this->configurationManager->isDesecDomain()) {
return;
}
$domain = $this->configurationManager->domain;
$token = $this->configurationManager->getDesecToken();
try {
$res = $this->guzzleClient->get('https://update.dedyn.io/', [
'query' => ['hostname' => $domain],
'headers' => ['Authorization' => 'Token ' . $token],
]);
$status = trim($res->getBody()->getContents());
if (str_starts_with($status, 'good') || str_starts_with($status, 'nochg')) {
error_log('deSEC IP update for ' . $domain . ': ' . $status);
} else {
error_log('deSEC IP update for ' . $domain . ' returned unexpected response: ' . $status);
}
} catch (\Exception $e) {
error_log('Could not update deSEC DNS record for ' . $domain . ': ' . $e->getMessage());
}
}
/**
* Creates a new deSEC account and returns the API token from the response.
*

View File

@@ -18,7 +18,8 @@ readonly class DockerController {
public function __construct(
private DockerActionManager $dockerActionManager,
private ContainerDefinitionFetcher $containerDefinitionFetcher,
private ConfigurationManager $configurationManager
private ConfigurationManager $configurationManager,
private DesecController $desecController,
) {
}
@@ -263,6 +264,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->desecController->updateIpIfDesecDomain();
$id = self::TOP_CONTAINER;
$this->PerformRecursiveContainerStart($id, $pullImage, $addToStreamingResponseBody);

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
// increase memory limit to 2GB
ini_set('memory_limit', '2048M');
// Log whole log messages
ini_set('log_errors_max_len', '0');
require __DIR__ . '/../../vendor/autoload.php';
$container = \AIO\DependencyInjection::GetContainer();
/** @var \AIO\Controller\DesecController $desecController */
$desecController = $container->get(\AIO\Controller\DesecController::class);
$desecController->updateIpIfDesecDomain();

View File

@@ -134,7 +134,7 @@
</details>
<details>
<summary>Don't have a domain? Get a free one from deSEC</summary>
<p><a target="_blank" href="https://desec.io">deSEC</a> offers free dynamic DNS subdomains under <strong>dedyn.io</strong>. AIO can register an account and a subdomain for you automatically. The <strong>ddclient</strong> and <strong>caddy</strong> community containers will be enabled so that your IP address is kept up to date and your traffic is routed through a reverse proxy.</p>
<p><a target="_blank" href="https://desec.io">deSEC</a> offers free dynamic DNS subdomains under <strong>dedyn.io</strong>. AIO can register an account and a subdomain for you automatically. The <strong>caddy</strong> community container will be enabled as a reverse proxy, and the mastercontainer will keep your DNS record up to date automatically. You can additionally enable the <strong>ddclient</strong> community container for continuous DNS monitoring between cron cycles.</p>
<p><strong>Requirements:</strong> Your server must be reachable from the internet (a public IP address is needed). Port 80 and 443 must be open/forwarded in your firewall/router.</p>
<p>Please enter your email address. A deSEC account and a random <em>subdomain.dedyn.io</em> domain will be created for you.</p>
<form method="POST" action="api/desec/register" class="xhr">

View File

@@ -2,7 +2,7 @@
<p>In this section you can enable or disable optional Community Containers that are not included by default in the main installation. These containers are provided by the community and can be useful for various purposes and are automatically integrated in AIOs backup solution and update mechanisms.</p>
<p><strong>⚠️ Caution: </strong>Community Containers are maintained by the community and not officially by Nextcloud. Some containers may not be compatible with your system, may not work as expected or may discontinue. Use them at your own risk. Please read the documentation for each container first before adding any as some are also incompatible between each other! Never add all of them at the same time!</p>
{% if is_desec_domain == true %}
<p> Your Nextcloud domain (<strong>{{ domain }}</strong>) was registered via deSEC. The <strong>caddy</strong> and <strong>ddclient</strong> community containers have been automatically enabled. Please follow the <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/ddclient"><strong>ddclient documentation</strong></a> to finish configuring DNS updates for your domain.</p>
<p> Your Nextcloud domain (<strong>{{ domain }}</strong>) was registered via deSEC. The <strong>caddy</strong> community container has been automatically enabled as a reverse proxy. The mastercontainer keeps the DNS record up to date; you can optionally also enable the <strong>ddclient</strong> container for continuous DNS monitoring between cron cycles. Please see its <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/ddclient"><strong>documentation</strong></a> for details.</p>
{% endif %}
{% if isAnyRunning == true %}
<p><strong>Please note:</strong> You can enable or disable the options below only when your containers are stopped.</p>