Compare commits

..

79 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
6b342b0b8d feat: create wildcard CNAME rrset after new deSEC account domain registration
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/2390d12f-4776-4f9a-8382-c10f090dadcb

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-27 00:03:59 +00:00
Simon L.
d58a34b605 Revert "feat: show deSEC password field only after email-already-registered failure, via POST /containers and Twig (no sessions, no query params)"
This reverts commit 44b257a2b5.
2026-04-26 18:37:19 +02:00
copilot-swe-agent[bot]
44b257a2b5 feat: show deSEC password field only after email-already-registered failure, via POST /containers and Twig (no sessions, no query params)
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/9eadc186-a642-409b-871d-f2bbb47f20ce

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-26 16:36:06 +00:00
Simon L.
2b78dcc9cc Revert "feat: hide deSEC password field until email-already-registered 422 error"
This reverts commit f0fb065dc2.
2026-04-26 18:25:43 +02:00
Simon L.
11d8050085 Revert "refactor: move deSEC password-reveal logic from JS to Twig (PRG pattern)"
This reverts commit 1c6ca098d5.
2026-04-26 18:25:39 +02:00
copilot-swe-agent[bot]
1c6ca098d5 refactor: move deSEC password-reveal logic from JS to Twig (PRG pattern)
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/159fc9de-4eb7-4131-8dee-9166045156e6

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 16:43:49 +00:00
copilot-swe-agent[bot]
5343353bb5 feat: clear manually-entered deSEC password after successful login
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/902f3119-a4ee-4fa5-8865-510513cc4046

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 16:35:22 +00:00
copilot-swe-agent[bot]
f0fb065dc2 feat: hide deSEC password field until email-already-registered 422 error
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/d4f48d74-6e53-474c-b5bf-a9705525de45

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 16:32:31 +00:00
copilot-swe-agent[bot]
b71afd933b refactor: extract deSEC registration form into includes/desec-register.twig
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/5799217f-4a9b-4e23-9f0f-4bd0d37999b2

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 16:26:23 +00:00
copilot-swe-agent[bot]
24f8a126cb feat: allow using existing deSEC account by supplying a password
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/57f233da-439b-4992-888c-82fad2dfa2cc

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 16:21:46 +00:00
copilot-swe-agent[bot]
b0b997ac42 refactor: move remaining DesecController helpers into DesecManager
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/47f426be-1843-41f2-85ff-8fcac1e6f3d6

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:44:26 +00:00
copilot-swe-agent[bot]
fb6112f174 refactor: extract DesecManager from DesecController
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/7109ac88-02d1-44bf-8602-5f15873a67e5

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:37:53 +00:00
copilot-swe-agent[bot]
ce857a5588 docs: recommend deSEC by default over Tailscale across all files
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/987af94f-dbff-4efd-968c-f6993c7c4dc8

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:25:38 +00:00
copilot-swe-agent[bot]
6b362f2f5d docs: mention deSEC feature in readme, reverse-proxy, local-instance and QA docs
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/987af94f-dbff-4efd-968c-f6993c7c4dc8

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:24:03 +00:00
copilot-swe-agent[bot]
03e3b90ced refactor(DesecController): extract helper methods and add docblocks
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/7b6645ba-dcbd-46cc-a9ae-695c40c2fff1

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:13:22 +00:00
copilot-swe-agent[bot]
5d85a156b0 extract DEDYN_SUFFIX to ConfigurationManager constant; improve password comment
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/10d22120-1d15-4613-bcb4-a7ca9f3705ce

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:03:46 +00:00
copilot-swe-agent[bot]
e0305457fd rewrite DesecController; convert desecToken/desecPassword to virtual properties
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/10d22120-1d15-4613-bcb4-a7ca9f3705ce

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-25 12:02:21 +00:00
copilot-swe-agent[bot]
7281c9b7c8 mask deSEC password behind details reveal; add hex length comment
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/4e99bcbc-4f32-45e6-af08-5026ce4b1f45

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 21:49:26 +00:00
copilot-swe-agent[bot]
cb48bc5db0 store and display deSEC password for user login at desec.io
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/4e99bcbc-4f32-45e6-af08-5026ce4b1f45

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 21:48:19 +00:00
copilot-swe-agent[bot]
83129d6a55 feat: retry deSEC domain registration without re-entering email when account already exists
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/f779f575-e0fb-4e25-8e5c-5d5cb7668a91

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 21:40:06 +00:00
copilot-swe-agent[bot]
68df1dd857 feat: pass DESEC_TOKEN to caddy container for xcaddy DNS-01 plugin
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/cf1a6a21-0e8e-4912-9eee-465670eaf1d4

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 21:17:11 +00:00
copilot-swe-agent[bot]
7598f6534d feat: allow custom subdomain slug for deSEC registration, fix slug validation regex
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/60fbe9fa-94d1-4fea-9f6e-d1fb56861388

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 21:05:19 +00:00
Simon L.
a12f505dcd Apply suggestions from code review
Co-authored-by: Simon L. <szaimen@e.mail.de>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-24 22:56:46 +02:00
Simon L.
7a825f2025 Apply suggestion from @szaimen
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-24 22:53:09 +02:00
Simon L.
6aa4f8d672 Apply suggestions from code review
Co-authored-by: Simon L. <szaimen@e.mail.de>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-24 22:52:19 +02:00
copilot-swe-agent[bot]
5b72d17438 feat: add dnsmasq community container for LAN DNS, remove ddclient, add NC_DOMAIN Docker alias
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/7bd0c60a-c5df-404a-a8a5-5cbb97c7a48c

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-24 20:42:47 +00:00
copilot-swe-agent[bot]
7c5abc978d 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>
2026-04-24 19:24:14 +00:00
copilot-swe-agent[bot]
f23d8276ff switch to Guzzle in DesecController (pre-refactor checkpoint)
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>
2026-04-24 19:21:11 +00:00
Simon L.
39f30a6609 docs: add mermaid architecture diagrams to readme files (#7992) 2026-04-24 19:42:03 +02:00
copilot-swe-agent[bot]
5cf4580a86 docs: add mermaid architecture diagrams to readme files
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-24 19:40:05 +02:00
Simon L.
da88c7d25b Yaml updates (#7989) 2026-04-24 16:13:44 +02:00
szaimen
f113f2c155 Yaml updates
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-24 12:18:05 +00:00
Simon L.
f1dacad6b3 adjust the docs for NEXTCLOUD_STARTUP_APPS in manual-install
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-24 11:58:22 +02:00
Simon L.
30cb79bb62 postgres and nextcloud: Allow arbitrary characters in passwords (no binary required) (#7898) 2026-04-24 11:34:23 +02:00
Simon L.
c20bae5a0f Nextcloud: Prevent accidental Nextcloud reinstall when PHP fails to report installed version (#7987) 2026-04-23 20:30:39 +02:00
copilot-swe-agent[bot]
e76ccf4f3c Guard against empty installed_version when php -r fails in nextcloud entrypoint.sh
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/48f0714b-3f49-4e28-b48e-d58fea2e881e

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-23 14:36:46 +00:00
Simon L.
14dc5b7729 build(deps): bump astral-sh/setup-uv from 8.0.0 to 8.1.0 in /.github/workflows (#7986) 2026-04-23 15:21:20 +02:00
dependabot[bot]
d46b222c4e build(deps): bump astral-sh/setup-uv in /.github/workflows
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.0.0 to 8.1.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](cec208311d...08807647e7)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 12:22:41 +00:00
Simon L.
5e0ee16b9e nextcloud: adjust phpredis session timeouts to match Nextclouds config (#7985) 2026-04-23 13:04:09 +02:00
Simon L.
fadbdc5c78 nextcloud: adjust phpredis session timeouts to match Nextclouds config
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-23 13:01:54 +02:00
Simon L.
f8274028ea nextcloud: allow to configure memcache_customprefix for redis (#7984) 2026-04-23 12:54:39 +02:00
Simon L.
e05bdaeca0 nextcloud: allow to configure memcache_customprefix for redis
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-23 12:29:54 +02:00
Simon L.
7699ac9c12 nextcloud: enable taskprocessing:worker as standard service (#7981) 2026-04-23 11:22:10 +02:00
Simon L.
84d8d78106 some general fixes to the code base (#7979) 2026-04-23 11:08:50 +02:00
copilot-swe-agent[bot]
546474346f some general fixes to the code base
- Fix return vs continue in CreateVolumes: using return caused all
  subsequent volumes to be skipped when nextcloud_aio_nextcloud_datadir
  or nextcloud_aio_backupdir appeared in the volume list

- Fix GetLogs parsing loop: the while loop checked $line before
  reassigning it so the false sentinel from strtok was always processed,
  appending a spurious extra empty line to the output

- Fix getRegisteredSecret unsafe array access: accessing
  $this->secrets[$secretId] without isset() can trigger an undefined
  array key warning; use isset() instead

- Remove redundant startTransaction() call in setDomain(): the method
  called startTransaction() twice without an intervening commitTransaction(),
  making the second call a no-op that was misleading

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/19424687-dda1-4510-8f70-068c8d3efd41
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-23 10:50:59 +02:00
Simon L.
54ca36ec46 nextcloud: adjust redis timeouts (#7980) 2026-04-23 10:46:53 +02:00
copilot-swe-agent[bot]
8a5440134b nextcloud: adjust redis timeouts
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/1f9b7c14-6c7a-4008-b690-5167db1f90e6
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-23 10:45:02 +02:00
Simon L.
177c093dd9 notify-push & imaginary: terminate containers on SIGTERM (#7982) 2026-04-23 10:35:52 +02:00
Simon L.
2eee2eac53 fix notify-push start.sh script
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-23 10:33:55 +02:00
derhagen
7bf5b18d5e Terminate aio-containers on SIGTERM
Signed-off-by: derhagen <2806328+derhagen@users.noreply.github.com>
2026-04-22 14:23:41 +02:00
Simon L.
9d33eb29d2 remove link to the pickup speed guide
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-22 13:25:42 +02:00
Simon L.
b2e02da46b nextcloud: enable taskprocessing:worker as standard service
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-22 13:20:14 +02:00
Simon L.
954f250ac8 nextcloud-entrypoint.sh: make log-type configurable (#7901) 2026-04-22 10:54:35 +02:00
Simon L.
0cea791a64 build(deps): bump actions/github-script from 8.0.0 to 9.0.0 in /.github/workflows (#7924) 2026-04-21 18:43:46 +02:00
Simon L.
49afd85adc build(deps): bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in /.github/workflows (#7858) 2026-04-21 18:43:34 +02:00
Simon L.
9f6b0c2fe9 build(deps): bump azure/setup-helm from 4.3.1 to 5.0.0 in /.github/workflows (#7834) 2026-04-21 18:43:17 +02:00
Simon L.
8de44cf6aa aio-interface: preserve old PHPSESSID session during cookie migration to survive 502s on mastercontainer update (#7971) 2026-04-21 17:55:54 +02:00
Simon L.
45a1cd73b6 headers.Caddyfile: adjust the Permission Policy again (#7976) 2026-04-21 15:41:07 +02:00
Simon L.
304dc97ad6 address review
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-21 14:41:49 +02:00
Simon L.
0af87295a6 Yaml updates (#7977) 2026-04-21 14:25:33 +02:00
szaimen
b71408af98 Yaml updates
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-04-21 12:24:43 +00:00
Simon L.
d6e4b83c51 apps.config.php: allow to disable the internal app store (#7864) 2026-04-21 14:15:09 +02:00
Simon L.
563a047a9a headers.Caddyfile: adjust the Permission Policy like suggested by @Zoey2936
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-21 14:10:55 +02:00
Simon L.
7c40f57f36 Revert "nextcloud: switch PHP-FPM to dynamic mode and add max_requests to recycle stale workers" (#7975) 2026-04-21 13:51:18 +02:00
Simon L.
f91d26115b Revert "nextcloud: switch PHP-FPM to dynamic mode and add max_requests to recycle stale workers"
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-21 13:42:47 +02:00
Simon L.
180ea67cbb build(deps): bump docker from 29.4.0-cli to 29.4.1-cli in /Containers/mastercontainer (#7974) 2026-04-21 09:33:52 +02:00
dependabot[bot]
ce2b4c6b87 build(deps): bump docker in /Containers/mastercontainer
Bumps docker from 29.4.0-cli to 29.4.1-cli.

---
updated-dependencies:
- dependency-name: docker
  dependency-version: 29.4.1-cli
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 04:17:11 +00:00
Copilot
953a5fdf1e Configurationmanager.php: atomic write for configuration.json to prevent truncation on low disk (#7888)
* fix: use atomic temp-file write to prevent configuration.json truncation

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/ea144d2f-2533-4001-8d10-d17168bb8bec

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>

* fix: improve error messages with specific file paths for config write failures

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/ea144d2f-2533-4001-8d10-d17168bb8bec

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Pablo Zmdl <57864086+pabzm@users.noreply.github.com>
Signed-off-by: Simon L. <szaimen@e.mail.de>

---------

Signed-off-by: Simon L. <szaimen@e.mail.de>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
Co-authored-by: Simon L. <szaimen@e.mail.de>
Co-authored-by: Pablo Zmdl <57864086+pabzm@users.noreply.github.com>
2026-04-20 18:44:33 +02:00
Simon L.
bce78168e4 feat(clamav): reduce RAM usage via ConcurrentDatabaseReload no and MaxThreads 2 (#7972) 2026-04-20 17:25:02 +02:00
copilot-swe-agent[bot]
7031310257 feat(clamav): reduce RAM usage via ConcurrentDatabaseReload no and MaxThreads 2
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/4a5841ac-069e-4d40-8bd1-e557b685802c

docs(clamav): add comments explaining ConcurrentDatabaseReload and MaxThreads changes

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/3b943282-bf8e-469a-acee-2a0b0d699112
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-20 17:22:48 +02:00
copilot-swe-agent[bot]
301f42d2a0 aio-interface: preserve old PHPSESSID session during cookie migration to survive 502s on mastercontainer update
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/aadfe06c-fde4-4a01-953a-42abd110b416

fix: rename $oldSessionTime to $oldSessionTimestamp for clarity

Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/aadfe06c-fde4-4a01-953a-42abd110b416
Co-Authored-By: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-20 17:10:23 +02:00
Simon L.
5fe6adc62a imaginary: revert the change to the healthcheck
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-20 16:53:00 +02:00
Simon L.
5c016d5d35 mastercontainer: fix enabling opcache
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-20 16:28:57 +02:00
dependabot[bot]
fed44e0010 build(deps): bump actions/github-script in /.github/workflows
Bumps [actions/github-script](https://github.com/actions/github-script) from 8.0.0 to 9.0.0.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](ed597411d8...3a2844b7e9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: 9.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-16 12:06:54 +00:00
Simon L.
47733776b9 nextcloud-entrypoint.sh: make log-type configurable
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-13 15:43:35 +02:00
copilot-swe-agent[bot]
44148ba4a3 Fix password string handling without Go binaries using PGPASSWORD env var and psql variable interpolation
Agent-Logs-Url: https://github.com/nextcloud/all-in-one/sessions/6baec41e-95d9-4966-8cf8-4b12727139fd

Co-authored-by: szaimen <42591237+szaimen@users.noreply.github.com>
2026-04-13 10:03:01 +00:00
Simon L.
f99a59fb58 apps.config.php: allow to disable the internal app store
Signed-off-by: Simon L. <szaimen@e.mail.de>
2026-04-07 15:08:42 +02:00
dependabot[bot]
a79f637251 build(deps): bump astral-sh/setup-uv in /.github/workflows
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.6.0 to 8.0.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](37802adc94...cec208311d)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 12:07:26 +00:00
dependabot[bot]
640b5b7d6d build(deps): bump azure/setup-helm in /.github/workflows
Bumps [azure/setup-helm](https://github.com/azure/setup-helm) from 4.3.1 to 5.0.0.
- [Release notes](https://github.com/azure/setup-helm/releases)
- [Changelog](https://github.com/Azure/setup-helm/blob/main/CHANGELOG.md)
- [Commits](1a275c3b69...dda3372f75)

---
updated-dependencies:
- dependency-name: azure/setup-helm
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-31 12:09:01 +00:00
48 changed files with 888 additions and 87 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: "Check latest published release isn't a prerelease"
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v6
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v6
with:
script: |
const tags = await github.rest.repos.listTags({

View File

@@ -32,7 +32,7 @@ jobs:
# See https://github.com/helm/chart-releaser-action/issues/6
- name: Set up Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with:
version: v3.6.3

View File

@@ -16,7 +16,7 @@ jobs:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
with:
version: v3.11.1

View File

@@ -36,7 +36,7 @@ jobs:
line-length: warning
- name: Install the latest version of uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- name: Check GitHub actions
run: uvx zizmor --min-severity medium .github/workflows/*.yml

View File

@@ -13,6 +13,15 @@ RUN set -ex; \
sed -i "s|#\?PCREMaxFileSize.*|PCREMaxFileSize 2000M|g" /etc/clamav/clamd.conf; \
# StreamMaxLength must be synced with av_stream_max_length inside the Nextcloud files_antivirus plugin
sed -i "s|#\?StreamMaxLength.*|StreamMaxLength 2000M|g" /etc/clamav/clamd.conf; \
# By default clamd keeps the old signature database in RAM while loading the new one,
# briefly doubling memory usage (~1 GB extra) during each freshclam update cycle.
# Setting ConcurrentDatabaseReload to "no" makes clamd unload the old database first,
# eliminating that transient peak and significantly reducing maximum RAM consumption.
sed -i "s|#\?ConcurrentDatabaseReload.*|ConcurrentDatabaseReload no|g" /etc/clamav/clamd.conf; \
# The default thread pool is 10-12 threads, each reserving its own stack and scan buffers.
# The Nextcloud antivirus plugin sends one file at a time, so 2 threads are sufficient
# and avoids the idle per-thread memory overhead of the larger default pool.
sed -i "s|#\?MaxThreads.*|MaxThreads 2|g" /etc/clamav/clamd.conf; \
sed -i "s|#\?TCPSocket|TCPSocket|g" /etc/clamav/clamd.conf; \
sed -i "s|^LocalSocket .*|LocalSocket /tmp/clamd.sock|g" /etc/clamav/clamd.conf; \
sed -i "s|Example| |g" /etc/clamav/clamav-milter.conf; \

View File

@@ -1,6 +1,5 @@
[supervisord]
nodaemon=true
nodaemon=true
logfile=/var/log/supervisord/supervisord.log
pidfile=/var/run/supervisord/supervisord.pid
childlogdir=/var/log/supervisord/

View 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"

View 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

View File

@@ -1,3 +1,3 @@
#!/bin/bash
wget -q -O /dev/null "http://127.0.0.1:${PORT}/health" || exit 1
nc -z 127.0.0.1 "$PORT" || exit 1

View File

@@ -8,4 +8,4 @@ if [ -n "$IMAGINARY_SECRET" ]; then
IMAGINARY_ARGS+=(-key "$IMAGINARY_SECRET")
fi
imaginary "${IMAGINARY_ARGS[@]}" "$@"
exec imaginary "${IMAGINARY_ARGS[@]}" "$@"

View File

@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:latest
# Docker CLI is a requirement
FROM docker:29.4.0-cli AS docker
FROM docker:29.4.1-cli AS docker
ARG CADDY_REMOTE_HOST_HASH=b21775afa730ffb52a24ddff310c8a6d1fd37276

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

@@ -23,7 +23,7 @@ header {
Cross-Origin-Resource-Policy "same-origin"; # Only allow the same origin to load resources. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cross-Origin_Resource_Policy
# Permissions-Policy disables browser features that AIO does not use. Since there is no "deny all" option, all known features need to be listed explicitly. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), aria-notify=(), attribution-reporting=(), autoplay=(), battery=(), bluetooth=(), browsing-topics=(), camera=(), captured-surface-control=(), ch-ua-high-entropy-values=(), compute-pressure=(), cross-origin-isolated=(), deferred-fetch=(), deferred-fetch-minimal=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), language-detector=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), on-device-speech-recognition=(), otp-credentials=(), payment=(), picture-in-picture=(), private-state-token-issuance=(), private-state-token-redemption=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), summarizer=(), translator=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()"
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), aria-notify=(), attribution-reporting=(), autoplay=(), bluetooth=(), browsing-topics=(), camera=(), captured-surface-control=(), ch-ua-high-entropy-values=(), compute-pressure=(), cross-origin-isolated=(), deferred-fetch=(), deferred-fetch-minimal=(), display-capture=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), hid=(), identity-credentials-get=(), idle-detection=(), local-fonts=(), local-network=(), local-network-access=(), loopback-network=(), magnetometer=(), microphone=(), midi=(), on-device-speech-recognition=(), otp-credentials=(), payment=(), picture-in-picture=(), private-state-token-issuance=(), private-state-token-redemption=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), storage-access=(), summarizer=(), usb=(), web-share=(), window-management=(), xr-spatial-tracking=()"
-Server
-X-Powered-By

View File

@@ -142,7 +142,7 @@ RUN set -ex; \
\
{ \
echo 'session.save_handler = redis'; \
echo 'session.save_path = "tcp://${REDIS_HOST}:${REDIS_PORT}?database=${REDIS_DB_INDEX}${REDIS_USER_AUTH}&auth[]=${REDIS_HOST_PASSWORD}"'; \
echo 'session.save_path = "tcp://${REDIS_HOST}:${REDIS_PORT}?database=${REDIS_DB_INDEX}${REDIS_USER_AUTH}&auth[]=${REDIS_HOST_PASSWORD}&timeout=3.0&read_timeout=10.0"'; \
echo 'redis.session.locking_enabled = 1'; \
echo 'redis.session.lock_retries = -1'; \
echo '; 100ms in microseconds - prevents timeout on long requests such as large file uploads'; \
@@ -244,27 +244,12 @@ RUN set -ex; \
imagemagick-tiff \
coreutils; \
\
# Use dynamic pm mode: spare workers stay alive between requests so every request is served immediately
# without waiting for a new process to spawn (unlike ondemand which forks on every request when idle).
# pm.max_children: upper bound on worker processes; synced with max DB connections and MaxRequestWorkers.
# Set high so users never hit an artificial limit under peak load — spare-server bounds keep idle memory usage low.
grep -q '^pm = dynamic' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's/^pm = dynamic/pm = ondemand/' /usr/local/etc/php-fpm.d/www.conf; \
# Sync this with max db connections and MaxRequestWorkers
# We don't actually expect so many children but don't want to limit it artificially because people will report issues otherwise.
# Also children will usually be terminated again after the process is done due to the ondemand setting
sed -i 's/^pm.max_children =.*/pm.max_children = 5000/' /usr/local/etc/php-fpm.d/www.conf; \
# pm.start_servers: number of workers pre-forked at container startup.
# Having 2 workers ready immediately means the first requests after boot are served without any spawn delay.
sed -i '/^;pm.start_servers/s/^;//' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's/^pm.start_servers =.*/pm.start_servers = 2/' /usr/local/etc/php-fpm.d/www.conf; \
# pm.min_spare_servers: floor of idle workers kept alive at all times.
# Guarantees at least 1 ready worker so a sudden burst of requests is handled without any fork wait.
sed -i '/^;pm.min_spare_servers/s/^;//' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's/^pm.min_spare_servers =.*/pm.min_spare_servers = 1/' /usr/local/etc/php-fpm.d/www.conf; \
# pm.max_spare_servers: ceiling of idle workers kept alive during quiet periods.
# Capping at 3 limits idle memory consumption while still keeping a small ready pool.
sed -i '/^;pm.max_spare_servers/s/^;//' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's/^pm.max_spare_servers =.*/pm.max_spare_servers = 3/' /usr/local/etc/php-fpm.d/www.conf; \
# pm.max_requests: recycle each worker after handling 500 requests.
# PHP extensions and apps can leak memory over time; recycling prevents those leaks from accumulating indefinitely.
sed -i '/^;pm.max_requests/s/^;//' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's/^pm.max_requests =.*/pm.max_requests = 500/' /usr/local/etc/php-fpm.d/www.conf; \
sed -i 's|access.log = /proc/self/fd/2|access.log = /proc/self/fd/1|' /usr/local/etc/php-fpm.d/docker.conf; \
\
echo "[ -n \"\$TERM\" ] && [ -f /root.motd ] && cat /root.motd" >> /root/.bashrc; \

View File

@@ -16,6 +16,12 @@ $CONFIG = array (
if (getenv('APPS_ALLOWLIST')) {
$CONFIG['appsallowlist'] = explode(" ", getenv('APPS_ALLOWLIST'));
}
if (getenv('NEXTCLOUD_APP_STORE_URL')) {
$CONFIG['appstoreurl'] = getenv('NEXTCLOUD_APP_STORE_URL');
$appStoreUrl = getenv('NEXTCLOUD_APP_STORE_URL');
if ($appStoreUrl) {
if ($appStoreUrl === 'no') {
$CONFIG['appstoreenabled '] = false;
} else {
$CONFIG['appstoreurl'] = getenv('NEXTCLOUD_APP_STORE_URL');
}
}

View File

@@ -7,8 +7,8 @@ if (getenv('REDIS_MODE') !== 'rediscluster') {
if (getenv('REDIS_HOST')) {
$CONFIG['redis']['host'] = (string) getenv('REDIS_HOST');
$CONFIG['redis']['timeout'] = 1.5;
$CONFIG['redis']['read_timeout'] = 1.5;
$CONFIG['redis']['timeout'] = 3.0;
$CONFIG['redis']['read_timeout'] = 10.0;
}
if (getenv('REDIS_HOST_PASSWORD')) {
@@ -23,6 +23,10 @@ if (getenv('REDIS_MODE') !== 'rediscluster') {
$CONFIG['redis']['dbindex'] = (int) getenv('REDIS_DB_INDEX');
}
if (getenv('REDIS_PREFIX')) {
$CONFIG['redis']['memcache_customprefix'] = getenv('REDIS_PREFIX');
}
if (getenv('REDIS_USER_AUTH')) {
$CONFIG['redis']['user'] = str_replace("&auth[]=", "", getenv('REDIS_USER_AUTH'));
}
@@ -60,6 +64,10 @@ if (getenv('REDIS_MODE') !== 'rediscluster') {
$CONFIG['redis.cluster']['user'] = str_replace("&auth[]=", "", getenv('REDIS_USER_AUTH'));
}
if (getenv('REDIS_PREFIX')) {
$CONFIG['redis.cluster']['memcache_customprefix'] = getenv('REDIS_PREFIX');
}
if (getenv('NEXTCLOUD_TRUSTED_CERTIFICATES_REDIS')) {
$CONFIG['redis.cluster']['ssl_context']['cafile'] = '/var/www/html/data/certificates/ca-bundle.crt';
}

View File

@@ -115,6 +115,11 @@ rm -f "$test_file"
if [ -f /var/www/html/version.php ]; then
# shellcheck disable=SC2016
installed_version="$(php -r 'require "/var/www/html/version.php"; echo implode(".", $OC_Version);')"
if [ -z "$installed_version" ]; then
echo "Could not determine the installed Nextcloud version via php -r. The PHP installation might be broken."
echo "Please check the container logs and your PHP installation."
exit 1
fi
else
installed_version="0.0.0.0"
fi
@@ -438,11 +443,19 @@ EOF
echo "Applying default settings..."
mkdir -p /var/www/html/data
php /var/www/html/occ config:system:set loglevel --value="2" --type=integer
php /var/www/html/occ config:system:set log_type --value="file"
php /var/www/html/occ config:system:set logfile --value="/var/www/html/data/nextcloud.log"
if [ "$NEXTCLOUD_LOG_TYPE" = "errorlog" ]; then
php /var/www/html/occ config:system:set log_type --value="errorlog"
php /var/www/html/occ config:system:set log_type_audit --value="errorlog"
php /var/www/html/occ app:disable logreader
else
php /var/www/html/occ config:system:set log_type --value="file"
php /var/www/html/occ config:system:set log_type_audit --value="file"
php /var/www/html/occ app:enable logreader
php /var/www/html/occ config:system:set logfile --value="/var/www/html/data/nextcloud.log"
php /var/www/html/occ config:system:set logfile_audit --value="/var/www/html/data/audit.log"
fi
php /var/www/html/occ config:system:set log_rotate_size --value="10485760" --type=integer
php /var/www/html/occ app:enable admin_audit
php /var/www/html/occ config:app:set admin_audit logfile --value="/var/www/html/data/audit.log"
php /var/www/html/occ config:system:set log.condition apps 0 --value="admin_audit"
# Apply preview settings
@@ -640,8 +653,17 @@ fi
# Adjusting log files to be stored on a volume
echo "Adjusting log files..."
php /var/www/html/occ config:system:set upgrade.cli-upgrade-link --value="https://github.com/nextcloud/all-in-one/discussions/2726"
php /var/www/html/occ config:system:set logfile --value="/var/www/html/data/nextcloud.log"
php /var/www/html/occ config:app:set admin_audit logfile --value="/var/www/html/data/audit.log"
if [ "$NEXTCLOUD_LOG_TYPE" = "errorlog" ]; then
php /var/www/html/occ config:system:set log_type --value="errorlog"
php /var/www/html/occ config:system:set log_type_audit --value="errorlog"
php /var/www/html/occ app:disable logreader
else
php /var/www/html/occ config:system:set log_type --value="file"
php /var/www/html/occ config:system:set log_type_audit --value="file"
php /var/www/html/occ app:enable logreader
php /var/www/html/occ config:system:set logfile --value="/var/www/html/data/nextcloud.log"
php /var/www/html/occ config:system:set logfile_audit --value="/var/www/html/data/audit.log"
fi
php /var/www/html/occ config:system:set updatedirectory --value="/nc-updater"
if [ -n "$NEXTCLOUD_SKELETON_DIRECTORY" ]; then
if [ "$NEXTCLOUD_SKELETON_DIRECTORY" = "empty" ]; then

View File

@@ -25,7 +25,7 @@ fi
# Fix false database connection on old instances
if [ -f "/var/www/html/config/config.php" ]; then
sleep 2
while ! sudo -E -u www-data psql -d "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB" -c "select now()"; do
while ! sudo -E -u www-data env PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "select now()"; do
echo "Waiting for the database to start..."
sleep 5
done

View File

@@ -25,6 +25,14 @@ stderr_logfile_maxbytes=0
command=/cron.sh
user=www-data
[program:taskprocessing-worker]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=php /var/www/html/occ taskprocessing:worker --timeout 300
user=www-data
[program:run-exec-commands]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0

View File

@@ -39,8 +39,6 @@ fi
echo "notify-push was started"
# Run it
/var/www/html/custom_apps/notify_push/bin/"$CPU_ARCH"/notify_push \
exec /var/www/html/custom_apps/notify_push/bin/"$CPU_ARCH"/notify_push \
--port 7867 \
/var/www/html/config/config.php
exec "$@"

View File

@@ -2,6 +2,6 @@
test -f "/mnt/data/backup-is-running" && exit 0
psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:11000/$POSTGRES_DB" -c "select now()" && exit 0
PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -p 11000 -U "oc_$POSTGRES_USER" -d "$POSTGRES_DB" -c "select now()" && exit 0
psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:5432/$POSTGRES_DB" -c "select now()" || exit 1
PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -p 5432 -U "oc_$POSTGRES_USER" -d "$POSTGRES_DB" -c "select now()" || exit 1

View File

@@ -3,8 +3,9 @@ set -ex
touch "$DUMP_DIR/initialization.failed"
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER "oc_$POSTGRES_USER" WITH PASSWORD '$POSTGRES_PASSWORD' CREATEDB;
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \
-v "pg_new_password=$POSTGRES_PASSWORD" <<-EOSQL
CREATE USER "oc_$POSTGRES_USER" WITH PASSWORD :'pg_new_password' CREATEDB;
ALTER DATABASE "$POSTGRES_DB" OWNER TO "oc_$POSTGRES_USER";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "oc_$POSTGRES_USER";
GRANT ALL PRIVILEGES ON SCHEMA public TO "oc_$POSTGRES_USER";

View File

@@ -85,7 +85,7 @@ if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSIO
exec docker-entrypoint.sh postgres &
# Wait for creation
while ! psql -d "postgresql://oc_$POSTGRES_USER:$POSTGRES_PASSWORD@127.0.0.1:11000/$POSTGRES_DB" -c "select now()"; do
while ! psql -h 127.0.0.1 -p 11000 -U "oc_$POSTGRES_USER" -d "$POSTGRES_DB" -c "select now()"; do
echo "Waiting for the database to start."
sleep 5
done
@@ -107,8 +107,9 @@ if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSIO
exit 1
elif [ "$DB_OWNER" != "oc_$POSTGRES_USER" ]; then
DIFFERENT_DB_OWNER=1
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER "$DB_OWNER" WITH PASSWORD '$POSTGRES_PASSWORD' CREATEDB;
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" \
-v "pg_new_password=$POSTGRES_PASSWORD" <<-EOSQL
CREATE USER "$DB_OWNER" WITH PASSWORD :'pg_new_password' CREATEDB;
ALTER DATABASE "$POSTGRES_DB" OWNER TO "$DB_OWNER";
GRANT ALL PRIVILEGES ON DATABASE "$POSTGRES_DB" TO "$DB_OWNER";
GRANT ALL PRIVILEGES ON SCHEMA public TO "$DB_OWNER";

View File

@@ -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": [
{

View 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%"
]
}
]
}

View 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.

View File

@@ -5,7 +5,6 @@ This container bundles Local AI and auto-configures it for you. It support hardw
Documentation is available on the container repository. This documentation is regularly updated and is intended to be as simple and detailed as possible. Thanks for all your feedback!
- See https://github.com/docjyJ/aio-local-ai-vulkan#getting-started for getting start with this container.
- See [this guide](https://github.com/nextcloud/all-in-one/discussions/5430) for how to improve AI task pickup speed
- Note that Nextcloud supports only one server for AI queries, so this container cannot be used at the same time as other AI containers.
- See https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers how to add it to the AIO stack

View File

@@ -4,6 +4,61 @@ This directory features containers that are built for AIO which allows to add ad
## Disclaimers
All containers that are in this directory are community maintained so the responsibility is on the community to keep them updated and secure. There is no guarantee that this will be the case in the future.
## Overview
```mermaid
flowchart TD
%% ── Styles ───────────────────────────────────────────────────────────────────
classDef community fill:#FDEBD0,stroke:#E67E22,color:#222
classDef group fill:#FEF9E7,stroke:#F39C12,color:#333
subgraph COMM_AI["🤖 AI & Language"]
LAI(["🧠 Local AI\nPrivate AI assistant"]):::community
LT(["✏️ LanguageTool\nSpell & grammar check"]):::community
LTRANS(["🌐 LibreTranslate\nTranslation engine"]):::community
FACE(["🙂 FaceRecognition\nPhoto AI processor"]):::community
end
subgraph COMM_BACKUP["💾 Backup & Storage"]
BV(["📦 Borgbackup Viewer\nBrowse backups"]):::community
CALB(["📅 CalCardBackup\nCalendar/Contacts backup"]):::community
MINIO(["🗃️ MinIO\nS3-compatible storage"]):::community
SMB(["📂 SMB Server\nWindows file sharing"]):::community
end
subgraph COMM_EXTRA["🌟 Extra Services"]
HA(["🏠 Home Assistant\nHome automation"]):::community
STLW(["📧 Stalwart\nMail server"]):::community
NOCO(["🗂️ NocoDB\nNo-code database"]):::community
JSER(["🎯 Jellyseerr\nMedia request manager"]):::community
NOTIF(["📣 Notifications\nExternal push notify"]):::community
end
subgraph COMM_MEDIA["🎬 Media"]
PLEX(["🎞️ Plex\nMedia Server"]):::community
JELLY(["🎬 Jellyfin\nMedia Server"]):::community
DLNA(["📡 DLNA\nMedia Streaming"]):::community
MEMTR(["📸 Memories\nVideo Transcoder"]):::community
MKV(["💿 MakeMKV\nBlu-ray / DVD rip"]):::community
end
subgraph COMM_MONITOR["📊 Monitoring & Infra"]
GLAN(["📈 Glances\nSystem monitoring"]):::community
EXP(["📊 Nextcloud Exporter\nPrometheus metrics"]):::community
SCRU(["💽 Scrutiny\nDisk health (S.M.A.R.T.)"]):::community
CMGMT(["🐳 Container Mgmt\nDocker web console"]):::community
end
subgraph COMM_SEC["🔒 Security & Network"]
F2B(["🚫 Fail2ban\nBrute-force protection"]):::community
PIH(["🕳️ Pi-hole\nAd & DNS blocker"]):::community
VW(["🔐 Vaultwarden\nPassword Manager"]):::community
LDAP(["👥 LLDAP\nLight LDAP / Users"]):::community
CADDY(["🌐 Caddy\nReverse Proxy + Geoblocking"]):::community
NPMPLUS(["🌐 NPMplus\nNginx Proxy Manager"]):::community
end
```
## How to use this?
Starting with v11 of AIO, the management of Community Containers is done via the AIO interface (it is the last section in the AIO interface, so only visible if you scroll down).
⚠️⚠️⚠️ Please review the folder for documentation on each of the containers before adding them! Not reviewing the documentation for each of them first might break starting the AIO containers because e.g. fail2ban only works on Linux and not on Docker Desktop! **Hint:** If the containers where running already, in order to actually start the added container, you need to click on `Stop containers` and the `Update and start containers` in order to actually start it.

View File

@@ -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:

View File

@@ -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.

View File

@@ -186,6 +186,7 @@ services:
- WHITEBOARD_ENABLED
stop_grace_period: 600s
restart: unless-stopped
shm_size: 134217728
cap_drop:
- NET_RAW

View File

@@ -34,7 +34,7 @@ NEXTCLOUD_DATADIR=nextcloud_aio_nextcloud_data # You can change this to
NEXTCLOUD_MAX_TIME=3600 # This allows to change the upload time limit of the Nextcloud container
NEXTCLOUD_MEMORY_LIMIT=512M # This allows to change the PHP memory limit of the Nextcloud container
NEXTCLOUD_MOUNT=/mnt/ # This allows the Nextcloud container to access directories on the host. It must never be equal to the value of NEXTCLOUD_DATADIR!
NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time
NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time. You can also disable apps by using a hyphen in front of them. E.g. "-app_api"
NEXTCLOUD_TRUSTED_CACERTS_DIR=/usr/local/share/ca-certificates/my-custom-ca # Nextcloud container will trust all the Certification Authorities, whose certificates are included in the given directory.
NEXTCLOUD_UPLOAD_LIMIT=16G # This allows to change the upload limit of the Nextcloud container
REMOVE_DISABLED_APPS=yes # Setting this to no keep Nextcloud apps that are disabled via their switch and not uninstall them if they should be installed in Nextcloud.

View File

@@ -101,7 +101,7 @@ sed -i 's|NEXTCLOUD_PASSWORD=|NEXTCLOUD_PASSWORD= # TODO! This is the p
sed -i 's|TIMEZONE=|TIMEZONE=Europe/Berlin # TODO! This is the timezone that your containers will use.|' sample.conf
sed -i 's|COLLABORA_SECCOMP_POLICY=|COLLABORA_SECCOMP_POLICY=--o:security.seccomp=true # Changing the value to false allows to disable the seccomp feature of the Collabora container.|' sample.conf
sed -i 's|FULLTEXTSEARCH_JAVA_OPTIONS=|FULLTEXTSEARCH_JAVA_OPTIONS="-Xms512M -Xmx512M" # Allows to adjust the fulltextsearch java options.|' sample.conf
sed -i 's|NEXTCLOUD_STARTUP_APPS=|NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time|' sample.conf
sed -i 's|NEXTCLOUD_STARTUP_APPS=|NEXTCLOUD_STARTUP_APPS="deck twofactor_totp tasks calendar contacts notes" # Allows to modify the Nextcloud apps that are installed on starting AIO the first time. You can also disable apps by using a hyphen in front of them. E.g. "-app_api"|' sample.conf
sed -i 's|NEXTCLOUD_ADDITIONAL_APKS=|NEXTCLOUD_ADDITIONAL_APKS=imagemagick # This allows to add additional packages to the Nextcloud container permanently. Default is imagemagick but can be overwritten by modifying this value.|' sample.conf
sed -i 's|NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS=|NEXTCLOUD_ADDITIONAL_PHP_EXTENSIONS=imagick # This allows to add additional php extensions to the Nextcloud container permanently. Default is imagick but can be overwritten by modifying this value.|' sample.conf
sed -i 's|INSTALL_LATEST_MAJOR=|INSTALL_LATEST_MAJOR=no # Setting this to yes will install the latest Major Nextcloud version upon the first installation|' sample.conf

View File

@@ -45,12 +45,16 @@ $container->set(Guard::class, function () use ($responseFactory) {
// This is needed because the session cookie was renamed in a previous release. Without this,
// users that were logged in before the update would be logged out after the container restarts.
$wasAuthenticated = false;
$oldSessionTimestamp = null;
if (!isset($_COOKIE['__Host-Http-PHPSESSID']) && isset($_COOKIE['PHPSESSID'])) {
session_name('PHPSESSID');
if (session_start(['save_path' => $dataConst->GetSessionDirectory(), 'use_strict_mode' => true])) {
$wasAuthenticated = isset($_SESSION[\AIO\Auth\AuthManager::SESSION_KEY]) && $_SESSION[\AIO\Auth\AuthManager::SESSION_KEY] === true;
session_unset();
session_destroy();
$oldSessionTimestamp = isset($_SESSION['date_time']) ? (int)$_SESSION['date_time'] : null;
// Do not destroy the old session: if the response carrying the new __Host-Http-PHPSESSID
// cookie is lost (e.g., due to a 502 during a mastercontainer update), the client can
// retry with the old PHPSESSID cookie and still be authenticated.
session_write_close();
}
}
@@ -68,7 +72,15 @@ session_start([
]);
if ($wasAuthenticated) {
$container->get(\AIO\Auth\AuthManager::class)->SetAuthState(true);
if ($oldSessionTimestamp !== null) {
// Use MigrateAuthState to preserve the original login timestamp. This prevents the
// session deduplicator from running and keeps the old PHPSESSID session file alive,
// so the client can retry with the old cookie if the 502 response causes the new
// __Host-Http-PHPSESSID cookie to not be received.
$container->get(\AIO\Auth\AuthManager::class)->MigrateAuthState($oldSessionTimestamp);
} else {
$container->get(\AIO\Auth\AuthManager::class)->SetAuthState(true);
}
}
$app->add(Guard::class);
@@ -97,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) {
@@ -168,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) {

View File

@@ -42,6 +42,18 @@ readonly class AuthManager {
$_SESSION[self::SESSION_KEY] = $isLoggedIn;
}
/**
* Migrates the authenticated state from an old session (different cookie name) to the new session.
* Unlike SetAuthState, this method preserves the original login timestamp and does not update
* the session_date_file, so the session deduplicator is not triggered. This keeps the old session
* file alive in case the response carrying the new cookie is lost (e.g., due to a 502 error during
* a mastercontainer update), allowing the client to retry with the old cookie.
*/
public function MigrateAuthState(int $oldTimestamp) : void {
$_SESSION[self::SESSION_KEY] = true;
$_SESSION['date_time'] = $oldTimestamp;
}
public function IsAuthenticated() : bool {
return isset($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY] === true;
}

View 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);
}
}
}

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;
@@ -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);

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\Desec\DesecManager $desecManager */
$desecManager = $container->get(\AIO\Desec\DesecManager::class);
$desecManager->updateIpIfDesecDomain();

View File

@@ -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); }
@@ -302,6 +341,9 @@ class ConfigurationManager
if ($this->config === [] && file_exists(DataConst::GetConfigFile()))
{
$configContent = (string)file_get_contents(DataConst::GetConfigFile());
if ($configContent === '') {
throw new \RuntimeException("The config file " . DataConst::GetConfigFile() . " is empty. It may have been truncated due to low disk space. Please restore it from a backup.");
}
$this->config = json_decode($configContent, true, 512, JSON_THROW_ON_ERROR);
}
@@ -360,7 +402,7 @@ class ConfigurationManager
}
public function getRegisteredSecret(string $secretId) : string {
if ($this->secrets[$secretId]) {
if (isset($this->secrets[$secretId])) {
return $this->getAndGenerateSecret($secretId);
}
throw new \Exception("The secret " . $secretId . " was not registered. Please check if it is defined in secrets of containers.json.");
@@ -560,7 +602,6 @@ class ConfigurationManager
$this->set('domain', $domain);
// Reset the borg restore password when setting the domain
$this->borgRestorePassword = '';
$this->startTransaction();
$this->commitTransaction();
}
@@ -702,7 +743,21 @@ class ConfigurationManager
if ($df !== false && (int)$df < $size) {
throw new InvalidSettingConfigurationException(DataConst::GetDataDirectory() . " does not have enough space for writing the config file! Not writing it back!");
}
file_put_contents(DataConst::GetConfigFile(), $content);
// Write to a temp file first to avoid truncating the config file if the
// disk fills up mid-write. rename() is atomic on POSIX filesystems, so the
// original config is never touched until the new content is fully on disk.
$tempFile = DataConst::GetConfigFile() . '.tmp';
if (file_put_contents($tempFile, $content) === false) {
// The file probably wasn't created, but better check nonetheless.
if (file_exists($tempFile)) {
unlink($tempFile);
}
throw new InvalidSettingConfigurationException("Failed to write temporary config file: " . $tempFile);
}
if (!rename($tempFile, DataConst::GetConfigFile())) {
unlink($tempFile);
throw new InvalidSettingConfigurationException("Failed to rename " . $tempFile . " to " . DataConst::GetConfigFile());
}
$this->config = [];
}
@@ -1093,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),
};
}

View File

@@ -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()

View 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());
}
}
}

View File

@@ -157,11 +157,12 @@ readonly class DockerActionManager {
$response = "";
$separator = "\r\n";
$line = strtok($responseBody, $separator);
$response = substr((string)$line, 8) . $separator;
if ($line !== false) {
$response = substr($line, 8) . $separator;
}
while ($line !== false) {
$line = strtok($separator);
$response .= substr((string)$line, 8) . $separator;
while (($line = strtok($separator)) !== false) {
$response .= substr($line, 8) . $separator;
}
return $response;
@@ -187,7 +188,7 @@ readonly class DockerActionManager {
];
if ($volume->name === 'nextcloud_aio_nextcloud_datadir' || $volume->name === 'nextcloud_aio_backupdir') {
return;
continue;
}
$firstChar = substr($volume->name, 0, 1);
@@ -866,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);

View File

@@ -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>

View File

@@ -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 %}

View 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 (163 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 (163 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>

View File

@@ -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)
@@ -87,6 +88,70 @@ Included are:
|---|---|
| ![image](https://github.com/user-attachments/assets/6ef5d7b5-86f2-402c-bc6c-b633af2ca7dd) | ![image](https://github.com/user-attachments/assets/939d0fdf-436f-433d-82d3-27548263a040) |
## Architecture overview
```mermaid
flowchart TB
%% ── Styles ───────────────────────────────────────────────────────────────────
classDef user fill:#FFF3CD,stroke:#F0AD4E,color:#333
classDef master fill:#E8D5F5,stroke:#9B59B6,color:#222
classDef core fill:#D6EAF8,stroke:#2E86C1,color:#222
classDef opt fill:#D5F5E3,stroke:#27AE60,color:#222
classDef community fill:#FDEBD0,stroke:#E67E22,color:#222
classDef access fill:#EAFAF1,stroke:#1E8449,color:#222
%% ── Top row: people ─────────────────────────────────────────────────────────
YOU(["🧑‍💻 Admin\n(You)"]):::user
ENDUSER(["🧑‍🤝‍🧑 Users\n(family / team)"]):::user
%% ── Everything that runs inside AIO ─────────────────────────────────────────
subgraph AIO[" 🐳 Nextcloud AIO "]
MC(["🧠 Mastercontainer\n─────────────────────────\n▸ AIO interface :8080 / :8443\n▸ Manages & updates containers\n▸ Handles backups"]):::master
subgraph CORE[" 📦 Core Stack (auto-started) "]
PROXY(["🔀 Apache\nProxy & HTTPS"]):::core
NC(["☁️ Nextcloud\nApp Server"]):::core
DB(["🗄️ PostgreSQL\nDatabase"]):::core
CACHE(["⚡ Redis\nCache"]):::core
PUSH(["🔔 Notify Push\nReal-time sync"]):::core
end
subgraph OPT[" 🧩 Optional Built-in Containers (enable in AIO interface) "]
COLLA(["📄 Nextcloud Office"]):::opt
OO(["📄 OnlyOffice\nDocument Server"]):::opt
TALK(["🎙️ Talk\nVideo & Voice calls"]):::opt
TALKREC(["🎬 Talk Recording"]):::opt
FTS(["🔎 Full-text Search\n(Elasticsearch)"]):::opt
IMAG(["🖼️ Imaginary\nImage previews"]):::opt
CLAM(["🦠 ClamAV\nAntivirus"]):::opt
WB(["🖊️ Whiteboard"]):::opt
end
COMM(["🌍 Community Containers\n──────────────────────\n30+ optional add-ons\nSee community-containers/"]):::community
click COMM "https://github.com/nextcloud/all-in-one/tree/main/community-containers#community-containers" "Browse community containers"
end
%% ── Result node (outside AIO) ────────────────────────────────────────────────
NC_URL(["🌐 https://your-domain.com\n✅ Nextcloud — ready to use!"]):::access
%% ── Flows ────────────────────────────────────────────────────────────────────
YOU -->|"① open :8080 or :8443 in browser"| MC
MC -->|"② auto-starts & wires up"| CORE
MC -. "③ enable in AIO interface" .-> OPT
MC -. "④ add via AIO interface" .-> COMM
PROXY --> NC
NC --- DB
NC --- CACHE
NC --- PUSH
OPT -->|"integrate with"| NC
COMM -.->|"integrate with"| NC
CORE -->|"⑤ stack is ready!"| NC_URL
ENDUSER -->|"⑥ browser / app"| NC_URL
YOU -->|"⑥ use normally"| NC_URL
```
## How to use this?
The steps below are written for Linux. For platform-specific guidance see:
@@ -184,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)
@@ -197,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)
@@ -326,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
@@ -346,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.
@@ -353,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.
@@ -382,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.

View File

@@ -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

View File

@@ -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