mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-21 10:50:10 +00:00
Compare commits
28 Commits
copilot/de
...
copilot/im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b342b0b8d | ||
|
|
d58a34b605 | ||
|
|
44b257a2b5 | ||
|
|
2b78dcc9cc | ||
|
|
11d8050085 | ||
|
|
1c6ca098d5 | ||
|
|
5343353bb5 | ||
|
|
f0fb065dc2 | ||
|
|
b71afd933b | ||
|
|
24f8a126cb | ||
|
|
b0b997ac42 | ||
|
|
fb6112f174 | ||
|
|
ce857a5588 | ||
|
|
6b362f2f5d | ||
|
|
03e3b90ced | ||
|
|
5d85a156b0 | ||
|
|
e0305457fd | ||
|
|
7281c9b7c8 | ||
|
|
cb48bc5db0 | ||
|
|
83129d6a55 | ||
|
|
68df1dd857 | ||
|
|
7598f6534d | ||
|
|
a12f505dcd | ||
|
|
7a825f2025 | ||
|
|
6aa4f8d672 | ||
|
|
5b72d17438 | ||
|
|
7c5abc978d | ||
|
|
f23d8276ff |
17
Containers/dnsmasq/Dockerfile
Normal file
17
Containers/dnsmasq/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# syntax=docker/dockerfile:latest
|
||||
FROM alpine:3.21
|
||||
|
||||
RUN apk add --no-cache dnsmasq iproute2
|
||||
|
||||
COPY --chmod=755 start.sh /start.sh
|
||||
|
||||
ENTRYPOINT ["/start.sh"]
|
||||
|
||||
LABEL com.centurylinklabs.watchtower.enable="false" \
|
||||
wud.watch="false" \
|
||||
org.opencontainers.image.title="Dnsmasq for Nextcloud AIO" \
|
||||
org.opencontainers.image.description="Lightweight DNS server that resolves NC_DOMAIN to the local server IP for LAN devices" \
|
||||
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/dnsmasq/readme.md"
|
||||
40
Containers/dnsmasq/start.sh
Normal file
40
Containers/dnsmasq/start.sh
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
if [ -z "$NC_DOMAIN" ]; then
|
||||
echo "ERROR: NC_DOMAIN is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
LOCAL_IP=""
|
||||
|
||||
# Determine the server's primary LAN IP - use the source address chosen by the kernel
|
||||
# for a route to a well-known public IP (1.1.1.1 is used purely to query the routing table;
|
||||
# no traffic is sent there).
|
||||
LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") {print $(i+1); exit}}')
|
||||
|
||||
if [ -z "$LOCAL_IP" ]; then
|
||||
LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
fi
|
||||
|
||||
if [ -z "$LOCAL_IP" ]; then
|
||||
echo "ERROR: Could not determine local IP address" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Nextcloud AIO dnsmasq: resolving $NC_DOMAIN -> $LOCAL_IP"
|
||||
echo "Configure your router's DHCP to hand out $LOCAL_IP as the DNS server for LAN clients."
|
||||
|
||||
mkdir -p /etc/dnsmasq.d
|
||||
|
||||
cat > /etc/dnsmasq.d/nextcloud-aio.conf << EOF
|
||||
# Auto-generated by Nextcloud AIO dnsmasq container.
|
||||
# Resolves NC_DOMAIN (and all its subdomains) to this server's local IP.
|
||||
address=/$NC_DOMAIN/$LOCAL_IP
|
||||
|
||||
# Bind only to the LAN interface to avoid conflicts with any system DNS resolver.
|
||||
bind-interfaces
|
||||
listen-address=$LOCAL_IP
|
||||
EOF
|
||||
|
||||
exec dnsmasq --no-daemon --log-queries --conf-dir=/etc/dnsmasq.d
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
"NC_DOMAIN=%NC_DOMAIN%",
|
||||
"APACHE_PORT=%APACHE_PORT%",
|
||||
"APACHE_IP_BINDING=%APACHE_IP_BINDING%",
|
||||
"NEXTCLOUD_EXPORTER_CADDY_PASSWORD=%NEXTCLOUD_EXPORTER_CADDY_PASSWORD%"
|
||||
"NEXTCLOUD_EXPORTER_CADDY_PASSWORD=%NEXTCLOUD_EXPORTER_CADDY_PASSWORD%",
|
||||
"DESEC_TOKEN=%DESEC_TOKEN%"
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
|
||||
17
community-containers/dnsmasq/dnsmasq.json
Normal file
17
community-containers/dnsmasq/dnsmasq.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"aio_services_v1": [
|
||||
{
|
||||
"container_name": "nextcloud-aio-dnsmasq",
|
||||
"display_name": "Dnsmasq (Local DNS)",
|
||||
"documentation": "https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq",
|
||||
"image": "ghcr.io/nextcloud-releases/aio-dnsmasq",
|
||||
"image_tag": "%AIO_CHANNEL%",
|
||||
"internal_port": "host",
|
||||
"restart": "unless-stopped",
|
||||
"environment": [
|
||||
"NC_DOMAIN=%NC_DOMAIN%",
|
||||
"TZ=%TIMEZONE%"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
31
community-containers/dnsmasq/readme.md
Normal file
31
community-containers/dnsmasq/readme.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Dnsmasq (Local DNS) community container
|
||||
|
||||
This container runs [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) pre-configured to resolve your Nextcloud domain (`NC_DOMAIN`) to the server's local LAN IP address.
|
||||
|
||||
## Why is this needed?
|
||||
|
||||
By default, all devices on your LAN reach Nextcloud via the public internet (or require hairpin NAT on your router). With this container, LAN clients can resolve `NC_DOMAIN` directly to the server's private LAN IP, making local access faster and independent of your internet connection.
|
||||
|
||||
This container is automatically enabled when you register a deSEC domain through the AIO interface.
|
||||
|
||||
## How it works
|
||||
|
||||
On startup the container:
|
||||
1. Detects the server's primary LAN IP address automatically.
|
||||
2. Configures dnsmasq to resolve `NC_DOMAIN` (and all its subdomains) to that IP.
|
||||
3. Forwards all other DNS queries to the upstream nameservers from the host's `/etc/resolv.conf`.
|
||||
4. Listens only on the LAN interface to avoid conflicts with any system DNS resolver (e.g. `systemd-resolved`).
|
||||
|
||||
## Required router configuration
|
||||
|
||||
⚠️ **You must change your router's DHCP settings** for this to take effect for LAN clients:
|
||||
|
||||
Set the **DNS server** handed out by DHCP to the **local IP address of this server** (the same IP that is printed in the container logs on startup). After saving the change, LAN devices need to renew their DHCP lease (or be rebooted) before the new DNS setting takes effect.
|
||||
|
||||
Most routers expose this under **DHCP settings → Primary DNS** or **LAN → DNS Server**.
|
||||
|
||||
## Notes
|
||||
|
||||
- The container runs in **host network mode** so it can bind directly to port 53 on the LAN interface. No additional port-forwarding is required.
|
||||
- If `systemd-resolved` (or another DNS resolver) is already listening on port 53 on the LAN IP, there will be a conflict. In that case you need to disable or reconfigure that resolver first.
|
||||
- IPv6 addresses are not handled by this container; extend the dnsmasq configuration manually if needed.
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
# WATCHTOWER_DOCKER_SOCKET_PATH: /var/run/docker.sock # Needs to be specified if the docker socket on the host is not located in the default '/var/run/docker.sock'. Otherwise mastercontainer updates will fail. For macos it needs to be '/var/run/docker.sock'
|
||||
|
||||
# # Optional: Caddy reverse proxy. See https://github.com/nextcloud/all-in-one/discussions/575
|
||||
# # Alternatively, use Tailscale if you don't have a domain yet. See https://github.com/nextcloud/all-in-one/discussions/6817
|
||||
# # Alternatively, if you don't have a domain yet, use the built-in deSEC free domain registration in the AIO interface, or use Tailscale. See https://github.com/nextcloud/all-in-one#how-to-get-a-free-domain-via-desec and https://github.com/nextcloud/all-in-one/discussions/6817
|
||||
# # Hint: You need to uncomment APACHE_PORT: 11000 above, adjust cloud.example.com to your domain and uncomment the necessary docker volumes at the bottom of this file in order to make it work
|
||||
# # You can find further examples here: https://github.com/nextcloud/all-in-one/discussions/588
|
||||
# caddy:
|
||||
|
||||
@@ -2,16 +2,27 @@
|
||||
It is possible due to several reasons that you do not want or cannot open Nextcloud to the public internet. Perhaps you were hoping to access AIO directly from an `ip.add.r.ess` (unsupported) or without a valid domain. However, AIO requires a valid certificate to work correctly. Below is discussed how you can achieve both: Having a valid certificate for Nextcloud and only using it locally.
|
||||
|
||||
### Content
|
||||
- [1. Tailscale](#1-tailscale)
|
||||
- [2. The normal way](#2-the-normal-way)
|
||||
- [3. Use the ACME DNS-challenge](#3-use-the-acme-dns-challenge)
|
||||
- [4. Use Cloudflare](#4-use-cloudflare)
|
||||
- [5. Buy a certificate and use that](#5-buy-a-certificate-and-use-that)
|
||||
- [1. deSEC free domain (recommended)](#1-desec-free-domain-recommended)
|
||||
- [2. Tailscale](#2-tailscale)
|
||||
- [3. The normal way](#3-the-normal-way)
|
||||
- [4. Use the ACME DNS-challenge](#4-use-the-acme-dns-challenge)
|
||||
- [5. Use Cloudflare](#5-use-cloudflare)
|
||||
- [6. Buy a certificate and use that](#6-buy-a-certificate-and-use-that)
|
||||
|
||||
## 1. Tailscale
|
||||
This is the recommended way. For a reverse proxy example guide for Tailscale, see this guide by [@Perseus333](https://github.com/Perseus333): https://github.com/nextcloud/all-in-one/discussions/6817
|
||||
## 1. deSEC free domain (recommended)
|
||||
[deSEC](https://desec.io) offers free dynamic-DNS subdomains under `dedyn.io`. AIO can register an account and a subdomain for you automatically — directly from the domain-entry page of the AIO interface. After registration:
|
||||
- The [Caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container is enabled automatically as a reverse proxy and handles TLS via Let's Encrypt.
|
||||
- The [dnsmasq](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) community container is enabled automatically so that LAN clients resolve your Nextcloud domain to the server's local IP address — no separate Pi-hole or local DNS server required.
|
||||
- The mastercontainer keeps the DNS record up to date automatically when your public IP changes.
|
||||
|
||||
## 2. The normal way
|
||||
**How to set it up:** Open the AIO interface, expand the **"Don't have a domain? Get a free one from deSEC"** section, enter your email and an optional subdomain slug, and click **Register free domain via deSEC**. See the full documentation at [How to get a free domain via deSEC](https://github.com/nextcloud/all-in-one#how-to-get-a-free-domain-via-desec).
|
||||
|
||||
After registration, follow the [dnsmasq documentation](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) to point your router's DHCP DNS server to the AIO host so that all LAN devices resolve the domain locally.
|
||||
|
||||
## 2. Tailscale
|
||||
For a reverse proxy example guide for Tailscale, see this guide by [@Perseus333](https://github.com/Perseus333): https://github.com/nextcloud/all-in-one/discussions/6817
|
||||
|
||||
## 3. The normal way
|
||||
The normal way is the following:
|
||||
1. Set up your domain correctly to point to your home network
|
||||
1. Set up a reverse proxy by following the [reverse proxy documentation](./reverse-proxy.md) but only open port 80 (which is needed for the ACME challenge to work - however no real traffic will use this port).
|
||||
@@ -21,12 +32,12 @@ The normal way is the following:
|
||||
|
||||
**Hint:** You may have a look at [this video](https://youtu.be/zk-y2wVkY4c) for a more complete but possibly outdated example.
|
||||
|
||||
## 3. Use the ACME DNS-challenge
|
||||
## 4. Use the ACME DNS-challenge
|
||||
You can alternatively use the ACME DNS-challenge to get a valid certificate for Nextcloud. Here is described how to set it up using an external caddy reverse proxy: https://github.com/nextcloud/all-in-one#how-to-get-nextcloud-running-using-the-acme-dns-challenge
|
||||
|
||||
## 4. Use Cloudflare
|
||||
## 5. Use Cloudflare
|
||||
If you do not have any control over the network, you may think about using Cloudflare Tunnel to get a valid certificate for your Nextcloud. However it will be opened to the public internet then. See https://github.com/nextcloud/all-in-one#how-to-run-nextcloud-behind-a-cloudflare-tunnel how to set this up.
|
||||
|
||||
## 5. Buy a certificate and use that
|
||||
## 6. Buy a certificate and use that
|
||||
If none of the above ways work for you, you may simply buy a certificate from an issuer for your domain. You then download the certificate onto your server, configure AIO in [reverse proxy mode](./reverse-proxy.md) and use the certificate for your domain in your reverse proxy config.
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ $app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin
|
||||
$app->get('/api/auth/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin');
|
||||
$app->post('/api/auth/logout', AIO\Controller\LoginController::class . ':Logout');
|
||||
$app->post('/api/configuration', \AIO\Controller\ConfigurationController::class . ':SetConfig');
|
||||
$app->post('/api/desec/register', \AIO\Controller\DesecController::class . ':Register');
|
||||
|
||||
// Views
|
||||
$app->get('/containers', function (Request $request, Response $response, array $args) use ($container) {
|
||||
@@ -180,6 +181,10 @@ $app->get('/containers', function (Request $request, Response $response, array $
|
||||
'community_containers' => $configurationManager->listAvailableCommunityContainers(),
|
||||
'community_containers_enabled' => $configurationManager->aioCommunityContainers,
|
||||
'bypass_container_update' => $bypass_container_update,
|
||||
'desec_email' => $configurationManager->desecEmail,
|
||||
'desec_password' => $configurationManager->desecPassword,
|
||||
'is_desec_domain' => $configurationManager->isDesecDomain(),
|
||||
'desec_account_registered' => $configurationManager->isDesecAccountRegistered(),
|
||||
]);
|
||||
})->setName('profile');
|
||||
$app->get('/login', function (Request $request, Response $response, array $args) use ($container) {
|
||||
|
||||
28
php/src/Controller/DesecController.php
Normal file
28
php/src/Controller/DesecController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?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'] ?? '');
|
||||
$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,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;
|
||||
@@ -18,7 +19,8 @@ readonly class DockerController {
|
||||
public function __construct(
|
||||
private DockerActionManager $dockerActionManager,
|
||||
private ContainerDefinitionFetcher $containerDefinitionFetcher,
|
||||
private ConfigurationManager $configurationManager
|
||||
private ConfigurationManager $configurationManager,
|
||||
private DesecManager $desecManager,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -263,6 +265,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);
|
||||
|
||||
17
php/src/Cron/UpdateDesecIp.php
Normal file
17
php/src/Cron/UpdateDesecIp.php
Normal 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\Desec\DesecManager $desecManager */
|
||||
$desecManager = $container->get(\AIO\Desec\DesecManager::class);
|
||||
|
||||
$desecManager->updateIpIfDesecDomain();
|
||||
@@ -8,6 +8,8 @@ use AIO\Controller\DockerController;
|
||||
|
||||
class ConfigurationManager
|
||||
{
|
||||
public const string DEDYN_SUFFIX = '.dedyn.io';
|
||||
|
||||
private array $secrets = [];
|
||||
|
||||
private array $config = [];
|
||||
@@ -198,6 +200,43 @@ class ConfigurationManager
|
||||
set { $this->set('turn_domain', $value); }
|
||||
}
|
||||
|
||||
public string $desecEmail {
|
||||
get => $this->get('desec_email', '');
|
||||
set { $this->set('desec_email', $value); }
|
||||
}
|
||||
|
||||
public string $desecToken {
|
||||
get {
|
||||
$s = $this->get('secrets', []);
|
||||
return isset($s['DESEC_TOKEN']) && is_string($s['DESEC_TOKEN']) ? $s['DESEC_TOKEN'] : '';
|
||||
}
|
||||
set {
|
||||
$s = $this->get('secrets', []);
|
||||
$s['DESEC_TOKEN'] = $value;
|
||||
$this->set('secrets', $s);
|
||||
}
|
||||
}
|
||||
|
||||
public string $desecPassword {
|
||||
get {
|
||||
$s = $this->get('secrets', []);
|
||||
return isset($s['DESEC_PASSWORD']) && is_string($s['DESEC_PASSWORD']) ? $s['DESEC_PASSWORD'] : '';
|
||||
}
|
||||
set {
|
||||
$s = $this->get('secrets', []);
|
||||
$s['DESEC_PASSWORD'] = $value;
|
||||
$this->set('secrets', $s);
|
||||
}
|
||||
}
|
||||
|
||||
public function isDesecDomain(): bool {
|
||||
return str_ends_with($this->domain, self::DEDYN_SUFFIX) && $this->desecToken !== '';
|
||||
}
|
||||
|
||||
public function isDesecAccountRegistered(): bool {
|
||||
return $this->desecToken !== '' && $this->desecEmail !== '' && $this->domain === '';
|
||||
}
|
||||
|
||||
public string $apachePort {
|
||||
get => $this->getEnvironmentalVariableOrConfig('APACHE_PORT', 'apache_port', '443');
|
||||
set { $this->set('apache_port', $value); }
|
||||
@@ -1109,6 +1148,7 @@ class ConfigurationManager
|
||||
'CADDY_IP_ADDRESS' => in_array('caddy', $this->aioCommunityContainers, true) ? gethostbyname('nextcloud-aio-caddy') : '',
|
||||
'WHITEBOARD_ENABLED' => $this->isWhiteboardEnabled ? 'yes' : '',
|
||||
'AIO_VERSION' => $this->getAioVersion(),
|
||||
'DESEC_TOKEN' => $this->desecToken,
|
||||
default => $this->getRegisteredSecret($placeholder),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ class DependencyInjection
|
||||
$container->get(GitHubContainerRegistryManager::class)
|
||||
)
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Desec\DesecManager::class,
|
||||
new \AIO\Desec\DesecManager(
|
||||
$container->get(\AIO\Data\ConfigurationManager::class),
|
||||
)
|
||||
);
|
||||
$container->set(
|
||||
\AIO\Auth\PasswordGenerator::class,
|
||||
new \AIO\Auth\PasswordGenerator()
|
||||
|
||||
310
php/src/Desec/DesecManager.php
Normal file
310
php/src/Desec/DesecManager.php
Normal file
@@ -0,0 +1,310 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace AIO\Desec;
|
||||
|
||||
use AIO\Data\ConfigurationManager;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\TransferException;
|
||||
|
||||
class DesecManager {
|
||||
private const string DESEC_API_BASE = 'https://desec.io/api/v1';
|
||||
private const int MAX_SLUG_ATTEMPTS = 5;
|
||||
private const int SLUG_BYTES = 5; // bin2hex → 10-char slug
|
||||
private const string SLUG_PATTERN = '/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/';
|
||||
|
||||
private Client $guzzleClient;
|
||||
|
||||
public function __construct(
|
||||
private readonly ConfigurationManager $configurationManager,
|
||||
) {
|
||||
$this->guzzleClient = new Client([
|
||||
'timeout' => 15,
|
||||
'connect_timeout' => 10,
|
||||
'http_errors' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full registration flow: validates inputs, creates an account if needed,
|
||||
* registers the domain, enables required containers, and updates the DNS record.
|
||||
*
|
||||
* When $password is non-empty the user is logging into an existing deSEC account
|
||||
* rather than creating a new one. When $password is empty a new account is created
|
||||
* with a randomly generated password (unless an account was already registered in a
|
||||
* previous attempt).
|
||||
*
|
||||
* @throws \Exception on any validation or API error
|
||||
*/
|
||||
public function register(string $email, string $slug, string $password = ''): void {
|
||||
if ($this->configurationManager->domain !== '') {
|
||||
throw new \Exception('A domain is already configured. Reset the AIO instance first to register a new domain.');
|
||||
}
|
||||
|
||||
$accountAlreadyRegistered = $this->configurationManager->isDesecAccountRegistered();
|
||||
$token = $accountAlreadyRegistered
|
||||
? $this->configurationManager->desecToken
|
||||
: null;
|
||||
|
||||
$validatedSlug = $this->validateSlug($slug);
|
||||
|
||||
if (!$accountAlreadyRegistered) {
|
||||
$validatedEmail = $this->validateEmail($email);
|
||||
$validatedPassword = trim($password);
|
||||
|
||||
if ($validatedPassword !== '') {
|
||||
// The user supplied their existing deSEC password — log in instead of registering.
|
||||
// Store an empty password: the token is all we need; the user's password must not be persisted.
|
||||
$token = $this->loginAccount($validatedEmail, $validatedPassword);
|
||||
$this->saveAccountCredentials($token, '', $validatedEmail);
|
||||
} else {
|
||||
// 24 random bytes → 48-char hex password; satisfies deSEC's minimum length
|
||||
// and lets the user log in at desec.io if they ever need to.
|
||||
$generatedPassword = bin2hex(random_bytes(24));
|
||||
$token = $this->registerAccount($validatedEmail, $generatedPassword);
|
||||
$this->saveAccountCredentials($token, $generatedPassword, $validatedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
$isNewAccount = !$accountAlreadyRegistered && trim($password) === '';
|
||||
|
||||
$domain = $this->registerDomain($token, $validatedSlug);
|
||||
|
||||
if ($isNewAccount) {
|
||||
$this->createWildcardCname($token, $domain);
|
||||
}
|
||||
|
||||
$this->enableDesecContainers();
|
||||
$this->configurationManager->setDomain($domain, true);
|
||||
$this->updateIpIfDesecDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an email address string.
|
||||
*
|
||||
* @throws \Exception if the email is empty or syntactically invalid
|
||||
*/
|
||||
private function validateEmail(string $email): string {
|
||||
$email = trim($email);
|
||||
if ($email === '' || filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
throw new \Exception('Please provide a valid email address.');
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an optional subdomain slug.
|
||||
* Returns an empty string when the caller wants a randomly generated slug.
|
||||
*
|
||||
* @throws \Exception if the slug is non-empty but does not match the allowed pattern
|
||||
*/
|
||||
private function validateSlug(string $slug): string {
|
||||
$slug = trim($slug);
|
||||
if ($slug !== '' && !preg_match(self::SLUG_PATTERN, $slug)) {
|
||||
throw new \Exception(
|
||||
'The desired subdomain must contain only lowercase letters, digits and hyphens, '
|
||||
. 'be between 1 and 63 characters long, and must not start or end with a hyphen.'
|
||||
);
|
||||
}
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new deSEC account and returns the API token issued for it.
|
||||
*
|
||||
* @throws \Exception on network failure or an unexpected HTTP response
|
||||
*/
|
||||
public function registerAccount(string $email, string $password): string {
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/auth/', [
|
||||
'json' => ['email' => $email, 'password' => $password],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
$body = $res->getBody()->getContents();
|
||||
|
||||
if ($code === 400) {
|
||||
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (is_array($data) && isset($data['email'])) {
|
||||
throw new \Exception(
|
||||
'This email address is already registered at deSEC. '
|
||||
. 'If this is your account, please enter your deSEC password in the password field and try again.',
|
||||
);
|
||||
}
|
||||
throw new \Exception('Registration at deSEC failed (HTTP 400): ' . $body);
|
||||
}
|
||||
|
||||
if ($code !== 201) {
|
||||
throw new \Exception('Unexpected response from deSEC during account registration (HTTP ' . $code . '): ' . $body);
|
||||
}
|
||||
|
||||
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($data) || !isset($data['token']['token']) || !is_string($data['token']['token'])) {
|
||||
throw new \Exception('Could not extract the API token from the deSEC response. Please try again.');
|
||||
}
|
||||
|
||||
return $data['token']['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates with an existing deSEC account and returns the API token issued for it.
|
||||
*
|
||||
* @throws \Exception on invalid credentials, network failure, or an unexpected HTTP response
|
||||
*/
|
||||
public function loginAccount(string $email, string $password): string {
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/auth/login/', [
|
||||
'json' => ['email' => $email, 'password' => $password],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
$body = $res->getBody()->getContents();
|
||||
|
||||
if ($code === 400 || $code === 403) {
|
||||
throw new \Exception('Could not log in to deSEC: invalid email address or password.');
|
||||
}
|
||||
|
||||
if ($code !== 200 && $code !== 201) {
|
||||
throw new \Exception('Unexpected response from deSEC during login (HTTP ' . $code . '): ' . $body);
|
||||
}
|
||||
|
||||
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
|
||||
if (!is_array($data) || !isset($data['token']) || !is_string($data['token'])) {
|
||||
throw new \Exception('Could not extract the API token from the deSEC login response. Please try again.');
|
||||
}
|
||||
|
||||
return $data['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a dedyn.io domain for the authenticated account.
|
||||
* When $slug is empty a random 10-character slug is tried up to MAX_SLUG_ATTEMPTS times.
|
||||
*
|
||||
* @return string the fully-qualified domain name that was registered
|
||||
* @throws \Exception if the slug is taken, on network failure, or after exhausting random attempts
|
||||
*/
|
||||
public function registerDomain(string $token, string $slug): string {
|
||||
$random = $slug === '';
|
||||
$attempts = $random ? self::MAX_SLUG_ATTEMPTS : 1;
|
||||
|
||||
for ($i = 0; $i < $attempts; $i++) {
|
||||
$domain = ($random ? bin2hex(random_bytes(self::SLUG_BYTES)) : $slug) . ConfigurationManager::DEDYN_SUFFIX;
|
||||
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/domains/', [
|
||||
'headers' => ['Authorization' => 'Token ' . $token],
|
||||
'json' => ['name' => $domain],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
throw new \Exception('Could not reach the deSEC API: ' . $e->getMessage());
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
|
||||
if ($code === 201) {
|
||||
return $domain;
|
||||
}
|
||||
|
||||
if ($code === 409) {
|
||||
if (!$random) {
|
||||
throw new \Exception('"' . $domain . '" is already taken. Please choose a different subdomain and try again.');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new \Exception('Unexpected response from deSEC during domain registration (HTTP ' . $code . '): ' . $res->getBody()->getContents());
|
||||
}
|
||||
|
||||
throw new \Exception('Could not register a free dedyn.io domain after ' . self::MAX_SLUG_ATTEMPTS . ' attempts. Please try again.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a wildcard CNAME rrset (*.domain → domain.) for a newly registered domain.
|
||||
* Errors are logged but do not abort the overall registration.
|
||||
*/
|
||||
private function createWildcardCname(string $token, string $domain): void {
|
||||
try {
|
||||
$res = $this->guzzleClient->post(self::DESEC_API_BASE . '/domains/' . $domain . '/rrsets/', [
|
||||
'headers' => ['Authorization' => 'Token ' . $token],
|
||||
'json' => [
|
||||
'subname' => '*',
|
||||
'type' => 'CNAME',
|
||||
'ttl' => 3600,
|
||||
'records' => [$domain . '.'],
|
||||
],
|
||||
]);
|
||||
} catch (TransferException $e) {
|
||||
error_log('Could not create wildcard CNAME for ' . $domain . ': ' . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$code = $res->getStatusCode();
|
||||
if ($code !== 201) {
|
||||
error_log('Unexpected response when creating wildcard CNAME for ' . $domain . ' (HTTP ' . $code . '): ' . $res->getBody()->getContents());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists deSEC account credentials to the AIO configuration atomically.
|
||||
*/
|
||||
public function saveAccountCredentials(string $token, string $password, string $email): void {
|
||||
$this->configurationManager->startTransaction();
|
||||
$this->configurationManager->desecToken = $token;
|
||||
$this->configurationManager->desecPassword = $password;
|
||||
$this->configurationManager->desecEmail = $email;
|
||||
$this->configurationManager->commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the caddy and dnsmasq community containers are enabled.
|
||||
*/
|
||||
public function enableDesecContainers(): void {
|
||||
$this->configurationManager->startTransaction();
|
||||
$enabled = array_values(array_filter(
|
||||
$this->configurationManager->aioCommunityContainers,
|
||||
fn(string $cc): bool => $cc !== '',
|
||||
));
|
||||
if (!in_array('caddy', $enabled, true)) {
|
||||
$enabled[] = 'caddy';
|
||||
}
|
||||
if (!in_array('dnsmasq', $enabled, true)) {
|
||||
$enabled[] = 'dnsmasq';
|
||||
}
|
||||
$this->configurationManager->aioCommunityContainers = $enabled;
|
||||
$this->configurationManager->commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the deSEC dynamic-DNS record with the current public IP.
|
||||
* Does nothing when the configured domain is not a deSEC-managed dedyn.io domain.
|
||||
*/
|
||||
public function updateIpIfDesecDomain(): void {
|
||||
if (!$this->configurationManager->isDesecDomain()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = $this->configurationManager->domain;
|
||||
$token = $this->configurationManager->desecToken;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -867,7 +867,15 @@ readonly class DockerActionManager {
|
||||
// Add a secondary alias for domaincheck container, to keep it as similar to actual apache controller as possible.
|
||||
// If a reverse-proxy is relying on container name as hostname this allows it to operate as usual and still validate the domain
|
||||
// The domaincheck container and apache container are never supposed to be active at the same time because they use the same APACHE_PORT anyway, so this doesn't add any new constraints.
|
||||
$alias = ($container->identifier === 'nextcloud-aio-domaincheck') ? 'nextcloud-aio-apache' : '';
|
||||
if ($container->identifier === 'nextcloud-aio-domaincheck') {
|
||||
$alias = 'nextcloud-aio-apache';
|
||||
}
|
||||
|
||||
// Add NC_DOMAIN as a Docker network alias so that intra-network traffic for the Nextcloud
|
||||
// domain is forwarded directly to the aio-caddy container without leaving the Docker network.
|
||||
if ($container->identifier === 'nextcloud-aio-caddy') {
|
||||
$alias = $this->configurationManager->domain;
|
||||
}
|
||||
|
||||
$this->ConnectContainerIdToNetwork($container->identifier, $container->internalPorts, alias: $alias);
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
<p>Make sure that this server is reachable on port 443 (port 443/tcp is open/forwarded in your firewall/router and 443/udp as well if you want to enable http3) and that you've correctly set up the DNS config for the domain that you enter (set the A record to your public ipv4-address and if you need ipv6, set the AAAA record to your public ipv6-address. A CNAME record is, of course, also possible). You should see hints on what went wrong in the top right corner if your domain is not accepted.</p>
|
||||
<details>
|
||||
<summary>Click here for further hints</summary>
|
||||
<p>If you do not have a domain yet, you can get one for free e.g. from duckdns.org and others. Recommended is to use <a target="_blank" href="https://github.com/nextcloud/all-in-one/discussions/6817">Tailscale</a></p>
|
||||
<p>If you do not have a domain yet, the easiest option is to use the <strong>deSEC free domain</strong> registration below. You can also get a free domain from duckdns.org or others, or use <a target="_blank" href="https://github.com/nextcloud/all-in-one/discussions/6817">Tailscale</a>.</p>
|
||||
<p>If you have a dynamic public IP-address, you can use e.g. <a target="_blank" href="https://ddclient.net/">DDclient</a> with a compatible domain provider for DNS updates.</p>
|
||||
<p>If you only want to install AIO locally without exposing it to the public internet or if you cannot do so, feel free to follow <a target="_blank" href="https://github.com/nextcloud/all-in-one/blob/main/local-instance.md">this documentation</a>.</p>
|
||||
<p>If you should be using Cloudflare Proxy for your domain, make sure to disable the Proxy feature temporarily as it might block the domain validation attempts.</p>
|
||||
@@ -132,6 +132,7 @@
|
||||
{% endif %}
|
||||
<p><strong>Hint:</strong> If the domain validation fails but you are completely sure that you've configured everything correctly, you may skip the domain validation by following <a target="_blank" href="https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation">this documentation</a>.</p>
|
||||
</details>
|
||||
{% include 'includes/desec-register.twig' %}
|
||||
{% endif %}
|
||||
|
||||
<h2>Restore former AIO instance from backup</h2>
|
||||
@@ -375,6 +376,14 @@
|
||||
|
||||
{% if was_start_button_clicked == true %}
|
||||
|
||||
{% if is_desec_domain %}
|
||||
<h2>deSEC account credentials</h2>
|
||||
<p>Your domain <strong>{{ domain }}</strong> is managed via <a target="_blank" href="https://desec.io">deSEC</a>. Below are your deSEC account credentials. You can use them to log in at <a target="_blank" href="https://desec.io">desec.io</a> to manage your domain directly.</p>
|
||||
<p>Email: <strong>{{ desec_email }}</strong></p>
|
||||
<p>Password: <details style="display:inline"><summary>Reveal deSEC password</summary><strong>{{ desec_password }}</strong></details></p>
|
||||
<p>Please save these credentials in a safe place.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if is_backup_section_enabled == false %}
|
||||
<h2>Backup and restore</h2>
|
||||
<p>The backup section is disabled via environmental variable.</p>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<h2>Community Containers</h2>
|
||||
<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> community container has been automatically enabled as a reverse proxy and the <strong>dnsmasq</strong> container has been automatically enabled so that LAN devices can resolve your Nextcloud domain to the server's local IP address. Please <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq"><strong>read the dnsmasq documentation</strong></a> for the required router change.</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>
|
||||
{% else %}
|
||||
|
||||
26
php/templates/includes/desec-register.twig
Normal file
26
php/templates/includes/desec-register.twig
Normal file
@@ -0,0 +1,26 @@
|
||||
<details{% if desec_account_registered %} open{% endif %}>
|
||||
<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>caddy</strong> community container will be enabled as a reverse proxy, the <strong>dnsmasq</strong> container will be enabled for local DNS resolution, and the mastercontainer will keep your DNS record up to date automatically.</p>
|
||||
{% if desec_account_registered %}
|
||||
<p>Your deSEC account (<strong>{{ desec_email }}</strong>) was registered successfully but the domain could not be registered. Please enter a desired subdomain slug (the part before <code>.dedyn.io</code>) and try again, or leave it blank for a random one.</p>
|
||||
<p>Your deSEC login credentials (for <a target="_blank" href="https://desec.io">desec.io</a>): Email: <strong>{{ desec_email }}</strong>. <details style="display:inline"><summary>Reveal deSEC password</summary><strong>{{ desec_password }}</strong></details>. Please save these in a safe place.</p>
|
||||
<form method="POST" action="api/desec/register" class="xhr">
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="text" name="desec_slug" placeholder="my-nextcloud (optional)" pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Only lowercase letters, digits and hyphens (1–63 characters). No leading or trailing hyphen." />
|
||||
<input type="submit" value="Register free domain via deSEC" />
|
||||
</form>
|
||||
{% else %}
|
||||
<p>Please enter your email address. You can also enter a desired subdomain slug (the part before <code>.dedyn.io</code>); leave it blank for a random one.</p>
|
||||
<p>If you already have a deSEC account for this email address, enter your deSEC password in the optional password field below to log in with it instead of creating a new account.</p>
|
||||
<form method="POST" action="api/desec/register" class="xhr">
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="email" name="desec_email" placeholder="your@email.com" required />
|
||||
<input type="password" name="desec_password" placeholder="deSEC password (only if already registered)" autocomplete="current-password" />
|
||||
<input type="text" name="desec_slug" placeholder="my-nextcloud (optional)" pattern="[a-z0-9]([a-z0-9\-]{0,61}[a-z0-9])?" title="Only lowercase letters, digits and hyphens (1–63 characters). No leading or trailing hyphen." />
|
||||
<input type="submit" value="Register free domain via deSEC" />
|
||||
</form>
|
||||
<p><strong>Note:</strong> By submitting this form you agree to the <a target="_blank" href="https://desec.io/terms">deSEC terms of service</a>. The registered domain and your deSEC account credentials are stored in the AIO configuration. After registration, set your router's DHCP DNS server to this machine's local IP address so LAN devices resolve the domain locally (see the <a target="_blank" href="https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq">dnsmasq documentation</a>). Alternatively adjust the hosts files on your clients so that they can reach the server using the local ip-address.</p>
|
||||
{% endif %}
|
||||
</details>
|
||||
30
readme.md
30
readme.md
@@ -61,6 +61,7 @@ Included are:
|
||||
- [Mail server can be added](https://github.com/nextcloud/all-in-one#mail-server)
|
||||
- Nextcloud can be [accessed locally via the domain](https://github.com/nextcloud/all-in-one#how-can-i-access-nextcloud-locally)
|
||||
- Can [be installed locally](https://github.com/nextcloud/all-in-one/blob/main/local-instance.md) (if you don't want or cannot make the instance publicly reachable)
|
||||
- Free [deSEC](https://desec.io) dynamic-DNS domain (`*.dedyn.io`) can be registered directly from the AIO interface — no external domain needed
|
||||
- [IPv6-ready](https://github.com/nextcloud/all-in-one/blob/main/docker-ipv6-support.md)
|
||||
- Can be used with [Docker rootless](https://github.com/nextcloud/all-in-one/blob/main/docker-rootless.md) (good for additional security)
|
||||
- Runs on all platforms Docker supports (e.g. also on Windows and Macos)
|
||||
@@ -248,6 +249,9 @@ https://your-domain-that-points-to-this-server.tld:8443
|
||||
|
||||
5. If you enable Nextcloud Talk, open port `3478/TCP` and `3478/UDP` in your firewall/router for the Talk (TURN) container.
|
||||
|
||||
> [!TIP]
|
||||
> Don't have a domain yet? AIO can register a free dynamic-DNS subdomain under `dedyn.io` for you via [deSEC](https://desec.io) — no external setup needed. Look for the **"Don't have a domain? Get a free one from deSEC"** section on the AIO interface when you are asked to enter your domain. AIO will automatically register the domain, keep the DNS record up to date, and enable the [Caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container as a reverse proxy as well as the [dnsmasq](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) container for local DNS resolution.
|
||||
|
||||
# FAQ
|
||||
- [TOC](#faq)
|
||||
- [Where can I find additional documentation?](#where-can-i-find-additional-documentation)
|
||||
@@ -261,6 +265,7 @@ https://your-domain-that-points-to-this-server.tld:8443
|
||||
- [Notes on Cloudflare (proxy/tunnel)](#notes-on-cloudflare-proxytunnel)
|
||||
- [How to run Nextcloud behind a Cloudflare Tunnel?](#how-to-run-nextcloud-behind-a-cloudflare-tunnel)
|
||||
- [How to run Nextcloud via Tailscale?](#how-to-run-nextcloud-via-tailscale)
|
||||
- [How to get a free domain via deSEC?](#how-to-get-a-free-domain-via-desec)
|
||||
- [How to get Nextcloud running using the ACME DNS-challenge?](#how-to-get-nextcloud-running-using-the-acme-dns-challenge)
|
||||
- [How to run Nextcloud locally? No domain wanted, or wanting intranet access within your LAN.](#how-to-run-nextcloud-locally-no-domain-wanted-or-wanting-intranet-access-within-your-lan)
|
||||
- [Can I use an ip-address for Nextcloud instead of a domain?](#can-i-use-an-ip-address-for-nextcloud-instead-of-a-domain)
|
||||
@@ -390,7 +395,7 @@ Only those (if you access the Mastercontainer Interface internally via port 8080
|
||||
- `3478/TCP` and `3478/UDP`: will be used by the Turnserver inside the Talk container and needs to be open/forwarded in your firewall/router
|
||||
|
||||
### Notes on Cloudflare (proxy/tunnel)
|
||||
Since Cloudflare Proxy/Tunnel comes with a lot of limitations which are listed below, it is rather recommended to switch to [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817) if possible.
|
||||
Since Cloudflare Proxy/Tunnel comes with a lot of limitations which are listed below, it is rather recommended to use the [built-in deSEC domain registration](#how-to-get-a-free-domain-via-desec) or switch to [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817) if possible.
|
||||
- Cloudflare Proxy and Cloudflare Tunnel both require Cloudflare to perform TLS termination on their side and thus decrypt all the traffic on their infrastructure. This is a privacy concern and you will need to look for other solutions if it's unacceptable for you.
|
||||
- Using Cloudflare Tunnel might potentially slow down Nextcloud since local access via the configured domain is not possible because TLS termination is in that case offloaded to Cloudflare's infrastructure. There is no way to disable this behavior in Cloudflare Tunnel.
|
||||
- It is known that the domain validation may not work correctly behind Cloudflare since Cloudflare might block the validation attempt. You can simply skip it in that case by following: https://github.com/nextcloud/all-in-one#how-to-skip-the-domain-validation
|
||||
@@ -410,6 +415,19 @@ Although it does not seems like it is the case but from AIO perspective a Cloudf
|
||||
### How to run Nextcloud via Tailscale?
|
||||
For a reverse proxy example guide for Tailscale, see this guide by [@Perseus333](https://github.com/Perseus333): https://github.com/nextcloud/all-in-one/discussions/6817
|
||||
|
||||
### How to get a free domain via deSEC?
|
||||
[deSEC](https://desec.io) offers free dynamic-DNS subdomains under `dedyn.io`. AIO integrates the registration process directly:
|
||||
|
||||
1. Open the AIO interface and expand the **"Don't have a domain? Get a free one from deSEC"** section on the domain-entry page.
|
||||
2. Enter your email address and, optionally, a desired subdomain slug (the part before `.dedyn.io`). Leave the slug blank for a random one.
|
||||
3. Click **Register free domain via deSEC**. AIO will create a deSEC account, register the subdomain and store the credentials in its configuration.
|
||||
|
||||
After successful registration:
|
||||
- AIO sets the registered `*.dedyn.io` domain as the Nextcloud domain automatically.
|
||||
- The [Caddy](https://github.com/nextcloud/all-in-one/tree/main/community-containers/caddy) community container is enabled as a reverse proxy.
|
||||
- The [dnsmasq](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) community container is enabled so that LAN devices can resolve the domain to the server's local IP. Follow the [dnsmasq documentation](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) for the required router change.
|
||||
- The mastercontainer keeps the DNS record up to date automatically when your public IP changes.
|
||||
|
||||
### How to get Nextcloud running using the ACME DNS-challenge?
|
||||
You can install AIO behind an external reverse proxy where is also documented how to get it running using the ACME DNS-challenge for getting a valid certificate for AIO. See the [reverse proxy documentation](./reverse-proxy.md). (Meant is the `Caddy with ACME DNS-challenge` section). Also see https://github.com/dani-garcia/vaultwarden/wiki/Running-a-private-vaultwarden-instance-with-Let%27s-Encrypt-certs#getting-a-custom-caddy-build for additional docs on this topic.
|
||||
|
||||
@@ -417,22 +435,22 @@ You can install AIO behind an external reverse proxy where is also documented ho
|
||||
If you do not want to open Nextcloud to the public internet, you may have a look at the following documentation on how to set it up locally: [local-instance.md](./local-instance.md), but keep in mind you're still required to have https working properly.
|
||||
|
||||
### Can I use an ip-address for Nextcloud instead of a domain?
|
||||
No and it will not be added. If you only want to run it locally, you may have a look at the following documentation: [local-instance.md](./local-instance.md). Recommended is to use [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817).
|
||||
No and it will not be added. If you only want to run it locally, you may have a look at the following documentation: [local-instance.md](./local-instance.md). Recommended is to use the [built-in deSEC domain registration](#how-to-get-a-free-domain-via-desec) to get a free domain automatically, or alternatively [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817).
|
||||
|
||||
### Can I run AIO offline or in an airgapped system?
|
||||
No. This is not possible and will not be added due to multiple reasons: update checks, app installs via app-store, downloading additional docker images on demand and more.
|
||||
|
||||
### Are self-signed certificates supported for Nextcloud?
|
||||
No and they will not be. If you want to run it locally, without opening Nextcloud to the public internet, please have a look at the [local instance documentation](./local-instance.md). Recommended is to use [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817).
|
||||
No and they will not be. If you want to run it locally, without opening Nextcloud to the public internet, please have a look at the [local instance documentation](./local-instance.md). Recommended is to use the [built-in deSEC domain registration](#how-to-get-a-free-domain-via-desec) to obtain a free domain with a valid certificate automatically, or alternatively [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817).
|
||||
|
||||
### Can I use AIO with multiple domains?
|
||||
No and it will not be added. However you can use [this feature](https://github.com/nextcloud/all-in-one/blob/main/multiple-instances.md) in order to create multiple AIO instances, one for each domain.
|
||||
|
||||
### Are other ports than the default 443 for Nextcloud supported?
|
||||
No and they will not be. If port 443 and/or 80 is blocked for you, you may use [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817) if you want to publish it online. If you already run a different service on port 443, please use a dedicated domain for Nextcloud and set it up correctly by following the [reverse proxy documentation](./reverse-proxy.md). However in all cases the Nextcloud interface will redirect you to port 443.
|
||||
No and they will not be. If port 443 and/or 80 is blocked for you, you may use the [built-in deSEC domain registration](#how-to-get-a-free-domain-via-desec) (which uses the Caddy community container on port 443) or [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817) if you want to publish it online. If you already run a different service on port 443, please use a dedicated domain for Nextcloud and set it up correctly by following the [reverse proxy documentation](./reverse-proxy.md). However in all cases the Nextcloud interface will redirect you to port 443.
|
||||
|
||||
### Can I run Nextcloud in a subdirectory on my domain?
|
||||
No and it will not be added. Please use a dedicated (sub-)domain for Nextcloud and set it up correctly by following the [reverse proxy documentation](./reverse-proxy.md). Alternatively, you may use [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817) if you want to publish it online.
|
||||
No and it will not be added. Please use a dedicated (sub-)domain for Nextcloud and set it up correctly by following the [reverse proxy documentation](./reverse-proxy.md). If you don't have a domain yet, use the [built-in deSEC domain registration](#how-to-get-a-free-domain-via-desec) to get one for free, or alternatively [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817).
|
||||
|
||||
### How can I access Nextcloud locally?
|
||||
Please note that local access is not possible if you are running AIO behind Cloudflare Tunnel since TLS proxying is in that case offloaded to Cloudflares infrastructure. You can fix this by setting up your own reverse proxy that handles TLS proxying locally and will make the steps below work.
|
||||
@@ -446,6 +464,8 @@ Now that this is out of the way, the recommended way how to access Nextcloud loc
|
||||
- https://dockerlabs.collabnix.com/intermediate/networking/Configuring_DNS.html
|
||||
Apart from that there is now a community container that can be added to the AIO stack: https://github.com/nextcloud/all-in-one/tree/main/community-containers/pi-hole
|
||||
|
||||
If you registered your domain via the built-in [deSEC integration](#how-to-get-a-free-domain-via-desec), the [dnsmasq](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) community container is automatically enabled and resolves your Nextcloud domain to the server's local IP for LAN clients. You only need to point your router's DHCP DNS server to the AIO host — see the [dnsmasq documentation](https://github.com/nextcloud/all-in-one/tree/main/community-containers/dnsmasq) for details.
|
||||
|
||||
### How to overwrite the local DNS resolution for some domains or add extra hosts to the containers?
|
||||
|
||||
For some use cases, you might need to overwrite the local DNS resolution of some domains inside the containers. On Linux, you can do so either by using a local DNS server as described in the section above and add a local DNS entry into the dns server and make your containers use that DNS server.
|
||||
|
||||
@@ -104,7 +104,7 @@ To make your Nextcloud AIO instance accessible from the public Internet (not jus
|
||||
## Configuration and Deployment
|
||||
|
||||
> [!NOTE]
|
||||
> These instructions assume you already have a domain name pointing to your server's public IP address. If you don't have a domain yet, see the recommendations below.
|
||||
> These instructions assume you already have a domain name pointing to your server's public IP address. If you don't have a domain yet, AIO can register a free dynamic-DNS subdomain under `dedyn.io` for you via the built-in [deSEC integration](https://github.com/nextcloud/all-in-one#how-to-get-a-free-domain-via-desec) — no external setup needed. Alternatively, see the recommendations below.
|
||||
|
||||
### Quick overview
|
||||
|
||||
@@ -117,7 +117,7 @@ To run Nextcloud AIO behind an external reverse proxy or secure tunneling/proxyi
|
||||
The sections below provide detailed instructions for each step.
|
||||
|
||||
> [!TIP]
|
||||
> If you don't have a domain yet, we recommend using [an approach using Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817). If you don't have an external reverse proxy yet, we recommend [Caddy](https://github.com/nextcloud/all-in-one/discussions/575).
|
||||
> If you don't have a domain yet, AIO can register a free `*.dedyn.io` subdomain for you via [deSEC](https://desec.io) directly from the AIO interface — see [How to get a free domain via deSEC](https://github.com/nextcloud/all-in-one#how-to-get-a-free-domain-via-desec). This is the recommended option. Alternatively, you can use [Tailscale](https://github.com/nextcloud/all-in-one/discussions/6817). If you don't have an external reverse proxy yet, we recommend [Caddy](https://github.com/nextcloud/all-in-one/discussions/575).
|
||||
|
||||
### Step-by-Step Instructions
|
||||
|
||||
|
||||
@@ -9,6 +9,13 @@ For the below to work, it is important that you have a domain that you point ont
|
||||
- [ ] Entering `10.0.0.1` should report that ip-addresses are not supported
|
||||
- [ ] Entering `nextcloud.com` should report that the domain does not point to this server
|
||||
- [ ] Entering the domain that does point to your server e.g. `yourdomain.com` should finally redirect you to the next screen (if you did not configure your domain yet or did not open port 443, it should report that to you)
|
||||
- [ ] Below the domain input box there should be a collapsed `<details>` element labelled **"Don't have a domain? Get a free one from deSEC"**
|
||||
- [ ] Expanding it should show a form with fields for email address and an optional subdomain slug, plus a **Register free domain via deSEC** submit button
|
||||
- [ ] Submitting with an empty email should show an error asking for a valid email address
|
||||
- [ ] Submitting with an invalid slug (e.g. `-bad-`) should show a validation error about the allowed slug format
|
||||
- [ ] Submitting with a valid email and no slug should register a random `*.dedyn.io` domain, set it as the Nextcloud domain and redirect to the next screen
|
||||
- [ ] After successful registration, the `caddy` and `dnsmasq` community containers should be listed as enabled in the Community Containers section
|
||||
- [ ] After successful registration, there should be a **deSEC account credentials** section in the AIO interface that shows the registered email and allows revealing the password
|
||||
- [ ] Now you should see a button `Start containers` and an explanation which points out that clicking on the button will start the containers and that this can take a long time.
|
||||
- [ ] Below that you should see a section `Optional addons` which shows a checkbox list with addons that can be enabled or disabled.
|
||||
- [ ] Collabora, Imaginary, Talk and Whiteboard should be enabled, the rest disabled
|
||||
|
||||
Reference in New Issue
Block a user