mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-21 19:00:33 +00:00
Compare commits
93 Commits
copilot/in
...
copilot/cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da88c7d25b | ||
|
|
f113f2c155 | ||
|
|
f1dacad6b3 | ||
|
|
30cb79bb62 | ||
|
|
c20bae5a0f | ||
|
|
e76ccf4f3c | ||
|
|
14dc5b7729 | ||
|
|
d46b222c4e | ||
|
|
5e0ee16b9e | ||
|
|
fadbdc5c78 | ||
|
|
f8274028ea | ||
|
|
e05bdaeca0 | ||
|
|
7699ac9c12 | ||
|
|
84d8d78106 | ||
|
|
546474346f | ||
|
|
54ca36ec46 | ||
|
|
8a5440134b | ||
|
|
177c093dd9 | ||
|
|
2eee2eac53 | ||
|
|
7bf5b18d5e | ||
|
|
9d33eb29d2 | ||
|
|
b2e02da46b | ||
|
|
954f250ac8 | ||
|
|
0cea791a64 | ||
|
|
49afd85adc | ||
|
|
9f6b0c2fe9 | ||
|
|
8de44cf6aa | ||
|
|
45a1cd73b6 | ||
|
|
304dc97ad6 | ||
|
|
0af87295a6 | ||
|
|
b71408af98 | ||
|
|
d6e4b83c51 | ||
|
|
563a047a9a | ||
|
|
7c40f57f36 | ||
|
|
f91d26115b | ||
|
|
180ea67cbb | ||
|
|
ce2b4c6b87 | ||
|
|
953a5fdf1e | ||
|
|
bce78168e4 | ||
|
|
7031310257 | ||
|
|
301f42d2a0 | ||
|
|
5fe6adc62a | ||
|
|
5c016d5d35 | ||
|
|
d7030396cb | ||
|
|
8a9c3c4512 | ||
|
|
49a24272f6 | ||
|
|
7bae058dbe | ||
|
|
66236c1a2e | ||
|
|
091fb8e814 | ||
|
|
ee4088744c | ||
|
|
ac38ea38b7 | ||
|
|
ada407751a | ||
|
|
845d08ba09 | ||
|
|
65a3244a2f | ||
|
|
7c8433d07a | ||
|
|
7e628b1200 | ||
|
|
8ebd624aa8 | ||
|
|
fceec6f23e | ||
|
|
ff86c6d066 | ||
|
|
ed672fb99c | ||
|
|
ef87e82f13 | ||
|
|
144c91ae02 | ||
|
|
5ead361c04 | ||
|
|
2b5998e57d | ||
|
|
3c17a6af36 | ||
|
|
a465baa259 | ||
|
|
457f7bfee9 | ||
|
|
aade77437c | ||
|
|
ccda322888 | ||
|
|
c428bc3b71 | ||
|
|
479f68d69e | ||
|
|
e1ae6444e5 | ||
|
|
47ee453719 | ||
|
|
e378f7faca | ||
|
|
4679c6c38d | ||
|
|
91e9e58c39 | ||
|
|
5624dde376 | ||
|
|
d0b0bde4c8 | ||
|
|
310429c5fd | ||
|
|
98a8861690 | ||
|
|
d2ecff2e06 | ||
|
|
29bac9dbf9 | ||
|
|
7cd0450dae | ||
|
|
b5dad7927a | ||
|
|
fcc4d9502d | ||
|
|
80ea0c1151 | ||
|
|
f25f588295 | ||
|
|
fed44e0010 | ||
|
|
47733776b9 | ||
|
|
44148ba4a3 | ||
|
|
f99a59fb58 | ||
|
|
a79f637251 | ||
|
|
640b5b7d6d |
2
.github/workflows/fail-on-prerelease.yml
vendored
2
.github/workflows/fail-on-prerelease.yml
vendored
@@ -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({
|
||||
|
||||
2
.github/workflows/helm-release.yml
vendored
2
.github/workflows/helm-release.yml
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/lint-helm.yml
vendored
2
.github/workflows/lint-helm.yml
vendored
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/lint-yaml.yml
vendored
2
.github/workflows/lint-yaml.yml
vendored
@@ -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
|
||||
|
||||
@@ -4,9 +4,9 @@ FROM alpine:3.23.4
|
||||
RUN set -ex; \
|
||||
apk upgrade --no-cache -a
|
||||
|
||||
LABEL org.opencontainers.image.title="Alpine for Nextcloud AIO"
|
||||
org.opencontainers.image.description="Minimal Alpine Linux base image for Nextcloud All-in-One"
|
||||
org.opencontainers.image.url="https://github.com/nextcloud/all-in-one"
|
||||
org.opencontainers.image.source="https://github.com/nextcloud/all-in-one"
|
||||
org.opencontainers.image.vendor="Nextcloud"
|
||||
LABEL org.opencontainers.image.title="Alpine for Nextcloud AIO" \
|
||||
org.opencontainers.image.description="Minimal Alpine Linux image for Nextcloud All-in-One" \
|
||||
org.opencontainers.image.url="https://github.com/nextcloud/all-in-one" \
|
||||
org.opencontainers.image.source="https://github.com/nextcloud/all-in-one" \
|
||||
org.opencontainers.image.vendor="Nextcloud" \
|
||||
org.opencontainers.image.documentation="https://github.com/nextcloud/all-in-one/blob/main/readme.md"
|
||||
|
||||
@@ -60,6 +60,19 @@ RUN set -ex; \
|
||||
grep -q '<IfModule mpm_event_module>' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
# ServerLimit needs to be set to MaxRequestWorkers divided by ThreadsPerChild which is set to 25 by default
|
||||
sed -i '/<IfModule mpm_event_module>/a\ \ \ \ ServerLimit 200' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
# Pin ThreadsPerChild so the value is deterministic regardless of the httpd base-image
|
||||
# defaults; 25 threads per process balances concurrency against per-process memory use.
|
||||
sed -i 's|ThreadsPerChild.*|ThreadsPerChild 25|' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
# Start two server processes on boot to absorb the first requests without spawning
|
||||
# new processes on the critical path, while avoiding unnecessary memory overhead.
|
||||
sed -i 's|StartServers.*|StartServers 2|' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
# Keep at least 25 idle threads (one full process worth) so traffic bursts can be
|
||||
# absorbed immediately without triggering new process creation.
|
||||
sed -i 's|MinSpareThreads.*|MinSpareThreads 25|' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
# Retire idle threads above 50 to reclaim memory during quiet periods. 50 is the
|
||||
# minimum valid value (MinSpareThreads + ThreadsPerChild = 25 + 25) and is enough
|
||||
# to absorb typical bursts without respawning a new process.
|
||||
sed -i 's|MaxSpareThreads.*|MaxSpareThreads 50|' /usr/local/apache2/conf/extra/httpd-mpm.conf; \
|
||||
\
|
||||
rm -rf /usr/local/apache2/conf/original /var/www; \
|
||||
mkdir -p /var/www; \
|
||||
|
||||
@@ -9,6 +9,34 @@ Listen 8000
|
||||
ErrorLogFormat "[%t] [%l] [%E] [client: %{X-Forwarded-For}i] [%M] [%{User-Agent}i]"
|
||||
LogLevel warn
|
||||
|
||||
# KeepAlive On: allow the same TCP connection to carry multiple HTTP requests.
|
||||
# Without this each asset (JS, CSS, image) would require a full TCP handshake,
|
||||
# which is especially expensive on TLS connections and noticeably slows down
|
||||
# Nextcloud's login page and file manager that load dozens of resources at once.
|
||||
KeepAlive On
|
||||
# KeepAliveTimeout: close an idle keep-alive connection after 5 seconds.
|
||||
# A short timeout frees Apache worker threads quickly so they are available
|
||||
# for new requests; 5 s is long enough to cover the gap between requests
|
||||
# that a browser issues while rendering a page (typically < 1 s), yet short
|
||||
# enough to avoid holding threads open for idle or slow clients.
|
||||
KeepAliveTimeout 5
|
||||
# MaxKeepAliveRequests: allow at most 500 requests per persistent connection.
|
||||
# 100 (the Apache default) is too low for Nextcloud: the desktop and mobile
|
||||
# sync clients issue many small API calls (PROPFIND, GET, PUT, checksums …)
|
||||
# per sync cycle and routinely exceed 100 requests on a single connection.
|
||||
# Hitting the limit forces a new TCP/TLS handshake, adding latency and CPU
|
||||
# overhead. 500 gives sync clients enough headroom while still periodically
|
||||
# recycling threads to contain per-process memory growth.
|
||||
MaxKeepAliveRequests 500
|
||||
|
||||
# sendfile(2) is disabled because it bypasses Apache's output-filter chain: with
|
||||
# it enabled, mod_brotli is silently skipped for static files (JS, CSS, SVG),
|
||||
# negating the compression configured below. MMAP is also
|
||||
# disabled because files can be replaced by Nextcloud at any time and mmap'd
|
||||
# pages could serve stale data.
|
||||
EnableSendfile Off
|
||||
EnableMMAP Off
|
||||
|
||||
# PHP match
|
||||
<FilesMatch "\.php$">
|
||||
SetHandler "proxy:fcgi://${NEXTCLOUD_HOST}:9000"
|
||||
@@ -17,20 +45,25 @@ Listen 8000
|
||||
<Proxy "fcgi://${NEXTCLOUD_HOST}:9000" flushpackets=on>
|
||||
</Proxy>
|
||||
|
||||
# Enable Brotli compression for js, css and svg files - other plain files are compressed by Nextcloud by default
|
||||
# Compress JS, CSS and SVG responses with Brotli (quality 4 gives good
|
||||
# compression with reasonable CPU cost; the default of 0 barely compresses).
|
||||
# Other plain-text files are already compressed by Nextcloud itself.
|
||||
# No deflate fallback is needed: every browser that Nextcloud supports
|
||||
# (Chrome 49+, Firefox 44+, Safari 11+, Edge 15+ — all from 2016-2017)
|
||||
# supports Brotli. Internet Explorer, the only browser that never gained
|
||||
# Brotli support, was dropped by Nextcloud with NC15 (2019).
|
||||
# Desktop and mobile sync clients never request JS/CSS/SVG assets.
|
||||
<IfModule mod_brotli.c>
|
||||
AddOutputFilterByType BROTLI_COMPRESS text/javascript application/javascript application/x-javascript text/css image/svg+xml
|
||||
BrotliCompressionQuality 0
|
||||
BrotliCompressionQuality 4
|
||||
</IfModule>
|
||||
|
||||
# Nextcloud dir
|
||||
DocumentRoot /var/www/html/
|
||||
<Directory /var/www/html/>
|
||||
Options Indexes FollowSymLinks
|
||||
Options FollowSymLinks MultiViews
|
||||
Require all granted
|
||||
AllowOverride All
|
||||
Options FollowSymLinks MultiViews
|
||||
Satisfy Any
|
||||
<IfModule mod_dav.c>
|
||||
Dav off
|
||||
</IfModule>
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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; \
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
nc -z 127.0.0.1 9200 || exit 1
|
||||
curl -fs "http://127.0.0.1:9200/_cluster/health?filter_path=status" | grep -qE '"status":"(green|yellow)"' || exit 1
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Imaginary has started"
|
||||
if [ -z "$IMAGINARY_SECRET" ]; then
|
||||
imaginary -return-size -max-allowed-resolution 222.2 "$@"
|
||||
else
|
||||
imaginary -return-size -max-allowed-resolution 222.2 -key "$IMAGINARY_SECRET" "$@"
|
||||
|
||||
IMAGINARY_ARGS=(-return-size -max-allowed-resolution 222.2)
|
||||
|
||||
if [ -n "$IMAGINARY_SECRET" ]; then
|
||||
IMAGINARY_ARGS+=(-key "$IMAGINARY_SECRET")
|
||||
fi
|
||||
|
||||
exec imaginary "${IMAGINARY_ARGS[@]}" "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,6 +53,16 @@ RUN set -ex; \
|
||||
build-base; \
|
||||
pecl install APCu-5.1.28; \
|
||||
docker-php-ext-enable apcu; \
|
||||
{ \
|
||||
echo 'apc.shm_size=32M'; \
|
||||
} >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \
|
||||
{ \
|
||||
echo 'opcache.enable=1'; \
|
||||
echo 'opcache.memory_consumption=32'; \
|
||||
echo 'opcache.interned_strings_buffer=8'; \
|
||||
echo 'opcache.max_accelerated_files=4000'; \
|
||||
echo 'opcache.validate_timestamps=0'; \
|
||||
} > /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini; \
|
||||
rm -r /tmp/pear; \
|
||||
runDeps="$( \
|
||||
scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \
|
||||
|
||||
@@ -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=(), autoplay=(), battery=(), camera=(), 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=(), local-fonts=(), magnetometer=(), microphone=(), midi=(), otp-credentials=(), payment=(), picture-in-picture=(), publickey-credentials-create=(), publickey-credentials-get=(), screen-wake-lock=(), serial=(), speaker-selection=(), storage-access=(), 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
|
||||
|
||||
@@ -423,5 +423,11 @@ caddy fmt --overwrite /internal.Caddyfile
|
||||
# Fix caddy log
|
||||
chmod 777 /root
|
||||
|
||||
# Create Twig template cache directory (path must match TWIG_CACHE_PATH in php/public/index.php)
|
||||
mkdir -p /tmp/twig-cache
|
||||
rm -rf /tmp/twig-cache/*
|
||||
chown www-data:www-data /tmp/twig-cache
|
||||
chmod 770 /tmp/twig-cache
|
||||
|
||||
# Start supervisord
|
||||
exec /usr/bin/supervisord -c /supervisord.conf
|
||||
|
||||
@@ -114,18 +114,18 @@ RUN set -ex; \
|
||||
# set recommended PHP.ini settings
|
||||
# see https://docs.nextcloud.com/server/stable/admin_manual/installation/server_tuning.html#enable-php-opcache and below
|
||||
{ \
|
||||
echo 'opcache.max_accelerated_files=10000'; \
|
||||
echo 'opcache.max_accelerated_files=20000'; \
|
||||
echo 'opcache.memory_consumption=256'; \
|
||||
echo 'opcache.interned_strings_buffer=64'; \
|
||||
echo 'opcache.save_comments=1'; \
|
||||
echo 'opcache.revalidate_freq=60'; \
|
||||
echo 'opcache.jit=1255'; \
|
||||
echo 'opcache.jit_buffer_size=8M'; \
|
||||
echo 'opcache.jit_buffer_size=128M'; \
|
||||
} > /usr/local/etc/php/conf.d/opcache-recommended.ini; \
|
||||
\
|
||||
{ \
|
||||
echo 'apc.enable_cli=1'; \
|
||||
echo 'apc.shm_size=64M'; \
|
||||
echo 'apc.shm_size=128M'; \
|
||||
} >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \
|
||||
\
|
||||
{ \
|
||||
@@ -135,14 +135,20 @@ RUN set -ex; \
|
||||
echo 'max_execution_time=${PHP_MAX_TIME}'; \
|
||||
echo 'max_input_time=-1'; \
|
||||
echo 'default_socket_timeout=${PHP_MAX_TIME}'; \
|
||||
echo 'output_buffering=0'; \
|
||||
echo 'realpath_cache_size=8M'; \
|
||||
echo 'realpath_cache_ttl=600'; \
|
||||
} > /usr/local/etc/php/conf.d/nextcloud.ini; \
|
||||
\
|
||||
{ \
|
||||
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 'redis.session.lock_wait_time = 10000'; \
|
||||
echo '; 100ms in microseconds - prevents timeout on long requests such as large file uploads'; \
|
||||
echo 'redis.session.lock_wait_time = 100000'; \
|
||||
echo '; prevents stale locks from crashed workers (seconds)'; \
|
||||
echo 'redis.session.lock_expire = 60'; \
|
||||
echo 'session.gc_maxlifetime = 86400'; \
|
||||
} > /usr/local/etc/php/conf.d/redis-session.ini; \
|
||||
\
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ if (getenv('REDIS_MODE') !== 'rediscluster') {
|
||||
|
||||
if (getenv('REDIS_HOST')) {
|
||||
$CONFIG['redis']['host'] = (string) getenv('REDIS_HOST');
|
||||
$CONFIG['redis']['timeout'] = 3.0;
|
||||
$CONFIG['redis']['read_timeout'] = 10.0;
|
||||
}
|
||||
|
||||
if (getenv('REDIS_HOST_PASSWORD')) {
|
||||
@@ -21,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'));
|
||||
}
|
||||
@@ -58,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';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "$@"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
@@ -151,23 +152,65 @@ fi
|
||||
# Modify postgresql.conf
|
||||
if [ -f "/var/lib/postgresql/data/postgresql.conf" ]; then
|
||||
echo "Setting postgres values..."
|
||||
PGCONF="/var/lib/postgresql/data/postgresql.conf"
|
||||
|
||||
# Sync this with max pm.max_children and MaxRequestWorkers
|
||||
# 5000 connections is apparently the highest possible value with postgres so set it to that so that we don't run into a limit here.
|
||||
# We don't actually expect so many connections but don't want to limit it artificially because people will report issues otherwise
|
||||
# Also connections should usually be closed again after the process is done
|
||||
# If we should actually exceed this limit, it is definitely a bug in Nextcloud server or some of its apps that does not close connections correctly and not a bug in AIO
|
||||
sed -i "s|^max_connections =.*|max_connections = 5000|" "/var/lib/postgresql/data/postgresql.conf"
|
||||
sed -i "s|^max_connections =.*|max_connections = 5000|" "$PGCONF"
|
||||
|
||||
# Do not log checkpoints
|
||||
if grep -q "#log_checkpoints" /var/lib/postgresql/data/postgresql.conf; then
|
||||
sed -i 's|#log_checkpoints.*|log_checkpoints = off|' /var/lib/postgresql/data/postgresql.conf
|
||||
if grep -q "#log_checkpoints" "$PGCONF"; then
|
||||
sed -i 's|#log_checkpoints.*|log_checkpoints = off|' "$PGCONF"
|
||||
fi
|
||||
|
||||
# Closing idling connections automatically seems to break any logic so was reverted again to default where it is disabled
|
||||
if grep -q "^idle_session_timeout" /var/lib/postgresql/data/postgresql.conf; then
|
||||
sed -i 's|^idle_session_timeout.*|#idle_session_timeout|' /var/lib/postgresql/data/postgresql.conf
|
||||
if grep -q "^idle_session_timeout" "$PGCONF"; then
|
||||
sed -i 's|^idle_session_timeout.*|#idle_session_timeout|' "$PGCONF"
|
||||
fi
|
||||
|
||||
# Increase shared_buffers from the 128MB default for better data caching
|
||||
sed -i "s|^#shared_buffers = .*|shared_buffers = 256MB|" "$PGCONF"
|
||||
sed -i "s|^shared_buffers = .*|shared_buffers = 256MB|" "$PGCONF"
|
||||
|
||||
# Hint to the query planner about available OS page cache (does not allocate memory)
|
||||
sed -i "s|^#effective_cache_size = .*|effective_cache_size = 1GB|" "$PGCONF"
|
||||
sed -i "s|^effective_cache_size = .*|effective_cache_size = 1GB|" "$PGCONF"
|
||||
|
||||
# Increase per-operation sort/hash memory to reduce disk spills for file listing and share queries.
|
||||
# Note: this is allocated per sort/hash operation, not per connection, so the theoretical worst-case
|
||||
# (max_connections × work_mem) is rarely approached in practice.
|
||||
sed -i "s|^#work_mem = .*|work_mem = 16MB|" "$PGCONF"
|
||||
sed -i "s|^work_mem = .*|work_mem = 16MB|" "$PGCONF"
|
||||
|
||||
# Increase memory for VACUUM, CREATE INDEX, and other maintenance operations
|
||||
sed -i "s|^#maintenance_work_mem = .*|maintenance_work_mem = 256MB|" "$PGCONF"
|
||||
sed -i "s|^maintenance_work_mem = .*|maintenance_work_mem = 256MB|" "$PGCONF"
|
||||
|
||||
# Increase WAL buffers to reduce WAL write latency under concurrent write load
|
||||
sed -i "s|^#wal_buffers = .*|wal_buffers = 16MB|" "$PGCONF"
|
||||
sed -i "s|^wal_buffers = .*|wal_buffers = 16MB|" "$PGCONF"
|
||||
|
||||
# Spread checkpoint I/O over a longer window to reduce spikes
|
||||
sed -i "s|^#checkpoint_timeout = .*|checkpoint_timeout = 15min|" "$PGCONF"
|
||||
sed -i "s|^checkpoint_timeout = .*|checkpoint_timeout = 15min|" "$PGCONF"
|
||||
|
||||
# Tune for SSD storage: random reads are nearly as fast as sequential reads
|
||||
sed -i "s|^#random_page_cost = .*|random_page_cost = 1.1|" "$PGCONF"
|
||||
sed -i "s|^random_page_cost = .*|random_page_cost = 1.1|" "$PGCONF"
|
||||
|
||||
# Allow the kernel to issue more concurrent I/O prefetch requests (suitable for SSDs)
|
||||
sed -i "s|^#effective_io_concurrency = .*|effective_io_concurrency = 200|" "$PGCONF"
|
||||
sed -i "s|^effective_io_concurrency = .*|effective_io_concurrency = 200|" "$PGCONF"
|
||||
|
||||
# Trigger autovacuum earlier on large Nextcloud tables (e.g. oc_filecache, oc_activity)
|
||||
# to prevent table bloat accumulating before the default 20% threshold is reached
|
||||
sed -i "s|^#autovacuum_vacuum_scale_factor = .*|autovacuum_vacuum_scale_factor = 0.05|" "$PGCONF"
|
||||
sed -i "s|^autovacuum_vacuum_scale_factor = .*|autovacuum_vacuum_scale_factor = 0.05|" "$PGCONF"
|
||||
sed -i "s|^#autovacuum_analyze_scale_factor = .*|autovacuum_analyze_scale_factor = 0.02|" "$PGCONF"
|
||||
sed -i "s|^autovacuum_analyze_scale_factor = .*|autovacuum_analyze_scale_factor = 0.02|" "$PGCONF"
|
||||
fi
|
||||
|
||||
do_database_dump() {
|
||||
|
||||
@@ -6,12 +6,31 @@ if [ "$(sysctl -n vm.overcommit_memory)" != "1" ]; then
|
||||
echo "See https://github.com/nextcloud/all-in-one/discussions/1731 how to enable overcommit"
|
||||
fi
|
||||
|
||||
# Run redis with a password if provided
|
||||
echo "Redis has started"
|
||||
if [ -n "$REDIS_HOST_PASSWORD" ]; then
|
||||
exec redis-server --requirepass "$REDIS_HOST_PASSWORD" --loglevel warning
|
||||
else
|
||||
exec redis-server --loglevel warning
|
||||
# Warn if Transparent Huge Pages are enabled (causes latency spikes)
|
||||
if [ -f /sys/kernel/mm/transparent_hugepage/enabled ]; then
|
||||
if grep -q '\[always\]' /sys/kernel/mm/transparent_hugepage/enabled; then
|
||||
echo "WARNING: Transparent Huge Pages (THP) are enabled. This can cause latency and memory issues with Redis."
|
||||
echo "Consider disabling THP by running: echo never > /sys/kernel/mm/transparent_hugepage/enabled"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
# Build the redis-server argument list.
|
||||
REDIS_ARGS=(
|
||||
--loglevel warning
|
||||
--save "" # Disable RDB persistence (Redis is used as a pure cache/lock store)
|
||||
--maxmemory-policy allkeys-lru # Evict least-recently-used keys when memory is full
|
||||
--lazyfree-lazy-eviction yes # Perform evictions in a background thread
|
||||
--lazyfree-lazy-expire yes # Expire keys in a background thread
|
||||
--lazyfree-lazy-server-del yes # DEL/UNLINK in background thread
|
||||
--replica-lazy-flush yes # Flush replica dataset in background thread
|
||||
--activedefrag yes # Reclaim fragmented memory without restart
|
||||
--hz 15 # Run background tasks 15×/s (default 10) for faster key expiry
|
||||
)
|
||||
|
||||
if [ -n "$REDIS_HOST_PASSWORD" ]; then
|
||||
REDIS_ARGS+=(--requirepass "$REDIS_HOST_PASSWORD")
|
||||
fi
|
||||
|
||||
# Run redis with a password if provided
|
||||
echo "Redis has started"
|
||||
exec redis-server "${REDIS_ARGS[@]}"
|
||||
|
||||
@@ -4,7 +4,7 @@ FROM eturnal/eturnal:1.12.2-alpine AS eturnal
|
||||
FROM strukturag/nextcloud-spreed-signaling:2.1.1 AS signaling
|
||||
FROM alpine:3.23.4 AS janus
|
||||
|
||||
ARG JANUS_VERSION=v1.4.0
|
||||
ARG JANUS_VERSION=v1.4.1
|
||||
WORKDIR /src
|
||||
RUN set -ex; \
|
||||
apk upgrade --no-cache -a; \
|
||||
@@ -82,7 +82,9 @@ RUN set -ex; \
|
||||
touch \
|
||||
/etc/nats.conf \
|
||||
/etc/eturnal.yml; \
|
||||
echo "listen: 127.0.0.1:4222" | tee /etc/nats.conf; \
|
||||
# write_deadline: "10s" — without a write deadline, a lagging subscriber can stall the broker indefinitely, blocking all other signaling messages.
|
||||
# max_payload: 8MB — the default is 1 MB; signaling payloads in large meetings (many participants, ICE candidates) can exceed this, causing dropped messages.
|
||||
printf 'listen: 127.0.0.1:4222\nwrite_deadline: "10s"\nmax_payload: 8MB\n' | tee /etc/nats.conf; \
|
||||
mkdir -p \
|
||||
/var/tmp \
|
||||
/conf \
|
||||
|
||||
@@ -5,3 +5,6 @@ nc -z 127.0.0.1 8188 || exit 1
|
||||
nc -z 127.0.0.1 4222 || exit 1
|
||||
nc -z 127.0.0.1 "$TALK_PORT" || exit 1
|
||||
eturnalctl status || exit 1
|
||||
# Verify that the signaling server is actually serving requests, not just
|
||||
# listening on the TCP port (which nc -z above only tests for open port).
|
||||
wget -q -O /dev/null http://127.0.0.1:8081/api/v1/stats || exit 1
|
||||
|
||||
@@ -91,10 +91,12 @@ if [ -z "$TALK_MAX_SCREEN_BITRATE" ]; then
|
||||
TALK_MAX_SCREEN_BITRATE=2097152
|
||||
fi
|
||||
|
||||
# Signling
|
||||
# Signaling
|
||||
cat << SIGNALING_CONF > "/conf/signaling.conf"
|
||||
[http]
|
||||
listen = 0.0.0.0:8081
|
||||
readtimeout = 15
|
||||
writetimeout = 30
|
||||
|
||||
[app]
|
||||
debug = false
|
||||
@@ -110,7 +112,9 @@ internalsecret = ${INTERNAL_SECRET}
|
||||
backends = backend-1
|
||||
allowall = false
|
||||
timeout = 10
|
||||
connectionsperhost = 8
|
||||
# connectionsperhost: This is the HTTP keep-alive connection pool size from the signaling server to the Nextcloud backend.
|
||||
# Under load (many concurrent calls joining/leaving simultaneously) a pool of 8 creates a queue bottleneck for backend authentication and session lookups, thus increasing to 32.
|
||||
connectionsperhost = 32
|
||||
skipverify = ${SKIP_CERT_VERIFY}
|
||||
|
||||
[backend-1]
|
||||
|
||||
@@ -7,19 +7,23 @@ logfile_maxbytes=50MB
|
||||
logfile_backups=10
|
||||
loglevel=error
|
||||
|
||||
[program:eturnal]
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
command=eturnalctl foreground
|
||||
|
||||
[program:nats-server]
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
command=nats-server -c /etc/nats.conf
|
||||
# Start first: signaling depends on NATS being available
|
||||
priority=10
|
||||
|
||||
[program:eturnal]
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
command=eturnalctl foreground
|
||||
# Start alongside Janus; independent of signaling
|
||||
priority=20
|
||||
|
||||
[program:janus]
|
||||
stdout_logfile=/dev/stdout
|
||||
@@ -28,6 +32,8 @@ stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
# debug-level 3 means warning
|
||||
command=janus --config=/conf/janus.jcfg --disable-colors --log-stdout --full-trickle --debug-level 3
|
||||
# Start alongside eturnal; signaling connects to Janus via WebSocket
|
||||
priority=20
|
||||
|
||||
[program:signaling]
|
||||
stdout_logfile=/dev/stdout
|
||||
@@ -35,3 +41,5 @@ stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
command=nextcloud-spreed-signaling -config /conf/signaling.conf
|
||||
# Start last: depends on NATS (priority=10) and Janus (priority=20) being up
|
||||
priority=30
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
"enable_nvidia_gpu": true,
|
||||
"backup_volumes": [
|
||||
"nextcloud_aio_jellyfin"
|
||||
],
|
||||
"depends_on": [
|
||||
"nextcloud-aio-lldap"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@ services:
|
||||
- WHITEBOARD_ENABLED
|
||||
stop_grace_period: 600s
|
||||
restart: unless-stopped
|
||||
shm_size: 134217728
|
||||
cap_drop:
|
||||
- NET_RAW
|
||||
|
||||
@@ -444,6 +445,9 @@ services:
|
||||
- http.port=9200
|
||||
- xpack.license.self_generated.type=basic
|
||||
- xpack.security.enabled=false
|
||||
- indices.fielddata.cache.size=20%
|
||||
- indices.memory.index_buffer_size=20%
|
||||
- thread_pool.write.queue_size=1000
|
||||
- FULLTEXTSEARCH_PASSWORD
|
||||
volumes:
|
||||
- nextcloud_aio_elasticsearch:/usr/share/elasticsearch/data:rw
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
20
php/composer.lock
generated
20
php/composer.lock
generated
@@ -2520,16 +2520,16 @@
|
||||
},
|
||||
{
|
||||
"name": "amphp/socket",
|
||||
"version": "v2.3.1",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/amphp/socket.git",
|
||||
"reference": "58e0422221825b79681b72c50c47a930be7bf1e1"
|
||||
"reference": "dadb63c5d3179fd83803e29dfeac27350e619314"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1",
|
||||
"reference": "58e0422221825b79681b72c50c47a930be7bf1e1",
|
||||
"url": "https://api.github.com/repos/amphp/socket/zipball/dadb63c5d3179fd83803e29dfeac27350e619314",
|
||||
"reference": "dadb63c5d3179fd83803e29dfeac27350e619314",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2538,17 +2538,17 @@
|
||||
"amphp/dns": "^2",
|
||||
"ext-openssl": "*",
|
||||
"kelunik/certificate": "^1.1",
|
||||
"league/uri": "^6.5 | ^7",
|
||||
"league/uri-interfaces": "^2.3 | ^7",
|
||||
"league/uri": "^7",
|
||||
"league/uri-interfaces": "^7",
|
||||
"php": ">=8.1",
|
||||
"revolt/event-loop": "^1 || ^0.2"
|
||||
"revolt/event-loop": "^1"
|
||||
},
|
||||
"require-dev": {
|
||||
"amphp/php-cs-fixer-config": "^2",
|
||||
"amphp/phpunit-util": "^3",
|
||||
"amphp/process": "^2",
|
||||
"phpunit/phpunit": "^9",
|
||||
"psalm/phar": "5.20"
|
||||
"psalm/phar": "6.16.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -2592,7 +2592,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/amphp/socket/issues",
|
||||
"source": "https://github.com/amphp/socket/tree/v2.3.1"
|
||||
"source": "https://github.com/amphp/socket/tree/v2.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2600,7 +2600,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-04-21T14:33:03+00:00"
|
||||
"time": "2026-04-19T15:09:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "amphp/sync",
|
||||
|
||||
@@ -267,6 +267,7 @@
|
||||
],
|
||||
"stop_grace_period": 600,
|
||||
"restart": "unless-stopped",
|
||||
"shm_size": 134217728,
|
||||
"devices": [
|
||||
"/dev/dri"
|
||||
],
|
||||
@@ -813,6 +814,9 @@
|
||||
"http.port=9200",
|
||||
"xpack.license.self_generated.type=basic",
|
||||
"xpack.security.enabled=false",
|
||||
"indices.fielddata.cache.size=20%",
|
||||
"indices.memory.index_buffer_size=20%",
|
||||
"thread_pool.write.queue_size=1000",
|
||||
"FULLTEXTSEARCH_PASSWORD=%FULLTEXTSEARCH_PASSWORD%"
|
||||
],
|
||||
"volumes": [
|
||||
|
||||
@@ -9,8 +9,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
|
||||
document.querySelectorAll('input[data-input-show-password]').forEach((element) => {
|
||||
element.addEventListener('input', (element) => {
|
||||
let passwordField = element
|
||||
element.addEventListener('input', (event) => {
|
||||
let passwordField = event.target;
|
||||
if (passwordField.type === "password" && passwordField.value !== "") {
|
||||
passwordField.type = "text";
|
||||
} else if (passwordField.type === "text" && passwordField.value === "") {
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Clamav
|
||||
let clamav = document.getElementById("clamav");
|
||||
clamav.disabled = true;
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Collabora
|
||||
const collabora = document.getElementById("office-collabora");
|
||||
collabora.disabled = true;
|
||||
});
|
||||
44
php/public/disable-containers.js
Normal file
44
php/public/disable-containers.js
Normal file
@@ -0,0 +1,44 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Clamav
|
||||
let clamav = document.getElementById("clamav");
|
||||
clamav.disabled = true;
|
||||
|
||||
// Docker socket proxy
|
||||
let dockerSocketProxy = document.getElementById("docker-socket-proxy");
|
||||
if (dockerSocketProxy) {
|
||||
dockerSocketProxy.disabled = true;
|
||||
}
|
||||
|
||||
// HaRP
|
||||
let harp = document.getElementById("harp");
|
||||
if (harp) {
|
||||
harp.disabled = true;
|
||||
}
|
||||
|
||||
// Talk
|
||||
let talk = document.getElementById("talk");
|
||||
talk.disabled = true;
|
||||
|
||||
// Collabora
|
||||
const collabora = document.getElementById("office-collabora");
|
||||
collabora.disabled = true;
|
||||
|
||||
// OnlyOffice
|
||||
const onlyoffice = document.getElementById("office-onlyoffice");
|
||||
onlyoffice.disabled = true;
|
||||
|
||||
// Imaginary
|
||||
let imaginary = document.getElementById("imaginary");
|
||||
imaginary.disabled = true;
|
||||
|
||||
// Fulltextsearch
|
||||
let fulltextsearch = document.getElementById("fulltextsearch");
|
||||
fulltextsearch.disabled = true;
|
||||
|
||||
// Talk-recording
|
||||
document.getElementById("talk-recording").disabled = true;
|
||||
|
||||
// Whiteboard
|
||||
let whiteboard = document.getElementById("whiteboard");
|
||||
whiteboard.disabled = true;
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Docker socket proxy
|
||||
let dockerSocketProxy = document.getElementById("docker-socket-proxy");
|
||||
if (dockerSocketProxy) {
|
||||
dockerSocketProxy.disabled = true;
|
||||
}
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Fulltextsearch
|
||||
let fulltextsearch = document.getElementById("fulltextsearch");
|
||||
fulltextsearch.disabled = true;
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// HaRP
|
||||
let harp = document.getElementById("harp");
|
||||
if (harp) {
|
||||
harp.disabled = true;
|
||||
}
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Imaginary
|
||||
let imaginary = document.getElementById("imaginary");
|
||||
imaginary.disabled = true;
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// OnlyOffice
|
||||
const onlyoffice = document.getElementById("office-onlyoffice");
|
||||
onlyoffice.disabled = true;
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Talk-recording
|
||||
document.getElementById("talk-recording").disabled = true;
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Talk
|
||||
let talk = document.getElementById("talk");
|
||||
talk.disabled = true;
|
||||
});
|
||||
@@ -1,5 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
// Whiteboard
|
||||
let whiteboard = document.getElementById("whiteboard");
|
||||
whiteboard.disabled = true;
|
||||
});
|
||||
@@ -10,6 +10,9 @@ ini_set('max_execution_time', '7200');
|
||||
// Log whole log messages
|
||||
ini_set('log_errors_max_len', '0');
|
||||
|
||||
// Path for the Twig compiled-template cache (created at container startup by start.sh)
|
||||
const TWIG_CACHE_PATH = '/tmp/twig-cache';
|
||||
|
||||
use DI\Container;
|
||||
use DI\NotFoundException;
|
||||
use Slim\Csrf\Guard;
|
||||
@@ -37,22 +40,52 @@ $container->set(Guard::class, function () use ($responseFactory) {
|
||||
});
|
||||
|
||||
// Register Middleware To Be Executed On All Routes
|
||||
|
||||
// Migrate from the old PHPSESSID cookie to the new __Host-Http-PHPSESSID cookie.
|
||||
// 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;
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
||||
session_start([
|
||||
"name" => "__Host-Http-PHPSESSID", // Set cookie prefix to prevent other pages from overwriting this cookie. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#cookie_prefixes
|
||||
"save_path" => $dataConst->GetSessionDirectory(), // Where to save the session files
|
||||
"cookie_lifetime" => 0, // Delete the session cookie whenever the browser is closed. See https://www.php.net/manual/en/session.configuration.php#ini.session.cookie-lifetime
|
||||
"gc_maxlifetime" => 86400, // Delete sessions after 24 hours. See https://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime
|
||||
"gc_probability" => 1, // Probability that the session cleanup starts. See https://www.php.net/manual/en/session.configuration.php#ini.session.gc-probability
|
||||
"gc_divisor" => 1, // gc_probability/gc_divisor = 1/1 = 100%, meaning that *all* outdated sessions get deleted when the cleanup job runs. See https://www.php.net/manual/en/session.configuration.php#ini.session.gc-divisor
|
||||
"gc_probability" => 0, // Probability that the session cleanup starts. The sessions are cleaned up by a cron job instead, see /cron.sh. See https://www.php.net/manual/en/session.configuration.php#ini.session.gc-probability
|
||||
"gc_divisor" => 100, // gc_probability/gc_divisor = 0/100 = 0%, meaning that PHP will never run session GC itself (cron.sh handles cleanup instead). See https://www.php.net/manual/en/session.configuration.php#ini.session.gc-divisor
|
||||
"use_strict_mode" => true, // Only allow initialized session IDs. See https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode
|
||||
"cookie_secure" => true, // Only send cookies over https (not http). See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#secure
|
||||
"cookie_httponly" => true, // Block the cookie from being read with js in the browser, will still be send for fetch request triggered by js. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#httponly
|
||||
"cookie_samesite" => "Strict", // Only send the cookie with requests triggered by AIO itself. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie#samesitesamesite-value
|
||||
]);
|
||||
|
||||
if ($wasAuthenticated) {
|
||||
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);
|
||||
|
||||
// Create Twig
|
||||
$twig = Twig::create(__DIR__ . '/../templates/', ['cache' => false]);
|
||||
$twig = Twig::create(__DIR__ . '/../templates/', ['cache' => TWIG_CACHE_PATH]);
|
||||
$app->add(TwigMiddleware::create($app, $twig));
|
||||
$twig->addExtension(new \AIO\Twig\CsrfExtension($container->get(Guard::class)));
|
||||
|
||||
@@ -70,6 +103,7 @@ $app->post('/api/docker/backup-check-repair', AIO\Controller\DockerController::c
|
||||
$app->post('/api/docker/backup-test', AIO\Controller\DockerController::class . ':StartBackupContainerTest');
|
||||
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
|
||||
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
|
||||
$app->post('/api/docker/prune', AIO\Controller\DockerController::class . ':SystemPrune');
|
||||
$app->get('/api/docker/logs', AIO\Controller\DockerController::class . ':GetLogs');
|
||||
$app->post('/api/auth/login', AIO\Controller\LoginController::class . ':TryLogin');
|
||||
$app->get('/api/auth/getlogin', AIO\Controller\LoginController::class . ':GetTryLogin');
|
||||
|
||||
@@ -8,7 +8,7 @@ use AIO\Data\DataConst;
|
||||
use \DateTime;
|
||||
|
||||
readonly class AuthManager {
|
||||
private const string SESSION_KEY = 'aio_authenticated';
|
||||
public const string SESSION_KEY = 'aio_authenticated';
|
||||
|
||||
public function __construct(
|
||||
private ConfigurationManager $configurationManager
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,17 @@ readonly class ContainerDefinitionFetcher {
|
||||
*/
|
||||
private function GetDefinition(): array
|
||||
{
|
||||
$data = json_decode((string)file_get_contents(DataConst::GetContainersDefinitionPath()), true, 512, JSON_THROW_ON_ERROR);
|
||||
$containersDefinitionPath = DataConst::GetContainersDefinitionPath();
|
||||
$cacheKey = 'containers-json-' . $containersDefinitionPath;
|
||||
$cachedJson = apcu_fetch($cacheKey);
|
||||
if (!is_string($cachedJson)) {
|
||||
$cachedJson = (string)file_get_contents($containersDefinitionPath);
|
||||
apcu_add($cacheKey, $cachedJson);
|
||||
}
|
||||
$data = json_decode($cachedJson, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
// We store this information for later because we need to use it to distinct between community containers and default containers.
|
||||
$standardContainerNames = array_column($data['aio_services_v1'], 'container_name');
|
||||
|
||||
$additionalContainerNames = [];
|
||||
foreach ($this->configurationManager->aioCommunityContainers as $communityContainer) {
|
||||
@@ -212,6 +222,15 @@ readonly class ContainerDefinitionFetcher {
|
||||
if (!$this->configurationManager->isWhiteboardEnabled) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// Skip dependencies on community containers that are not currently enabled.
|
||||
// Only apply this when the current entry is itself a community container,
|
||||
// and the dependency is not an enabled community container or a standard built-in container.
|
||||
if (in_array($entry['container_name'], $additionalContainerNames, true)
|
||||
&& !in_array($value, $additionalContainerNames, true)
|
||||
&& !in_array($value, $standardContainerNames, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$dependsOn[] = $value;
|
||||
}
|
||||
|
||||
@@ -328,6 +328,22 @@ readonly class DockerController {
|
||||
return $nonbufResp;
|
||||
}
|
||||
|
||||
public function SystemPrune(Request $request, Response $response, array $args) : Response {
|
||||
// Get streaming response start and closure
|
||||
$nonbufResp = $this->startStreamingResponse($response);
|
||||
|
||||
$body = $nonbufResp->getBody();
|
||||
$addToStreamingResponseBody = function (string $message) use ($body) : void {
|
||||
$body->write("<div>$message</div>");
|
||||
};
|
||||
|
||||
$this->dockerActionManager->SystemPrune($addToStreamingResponseBody);
|
||||
|
||||
// End streaming response
|
||||
$this->finalizeStreamingResponse($nonbufResp);
|
||||
return $nonbufResp;
|
||||
}
|
||||
|
||||
public function stopTopContainer() : void {
|
||||
$id = self::TOP_CONTAINER;
|
||||
$this->PerformRecursiveContainerStop($id);
|
||||
|
||||
@@ -14,6 +14,10 @@ class ConfigurationManager
|
||||
|
||||
private bool $noWrite = false;
|
||||
|
||||
private string $dailyBackupFileCache = '';
|
||||
|
||||
private int $dailyBackupFileMtime = 0;
|
||||
|
||||
public string $aioToken {
|
||||
get => $this->get('AIO_TOKEN', '');
|
||||
set { $this->set('AIO_TOKEN', $value); }
|
||||
@@ -298,6 +302,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);
|
||||
}
|
||||
|
||||
@@ -356,7 +363,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.");
|
||||
@@ -556,7 +563,6 @@ class ConfigurationManager
|
||||
$this->set('domain', $domain);
|
||||
// Reset the borg restore password when setting the domain
|
||||
$this->borgRestorePassword = '';
|
||||
$this->startTransaction();
|
||||
$this->commitTransaction();
|
||||
}
|
||||
|
||||
@@ -698,7 +704,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 = [];
|
||||
}
|
||||
|
||||
@@ -760,23 +780,47 @@ class ConfigurationManager
|
||||
$time .= PHP_EOL;
|
||||
}
|
||||
file_put_contents(DataConst::GetDailyBackupTimeFile(), $time);
|
||||
$this->dailyBackupFileCache = '';
|
||||
$this->dailyBackupFileMtime = 0;
|
||||
}
|
||||
|
||||
private function getDailyBackupFileContent() : string {
|
||||
$file = DataConst::GetDailyBackupTimeFile();
|
||||
if (!file_exists($file)) {
|
||||
$this->dailyBackupFileCache = '';
|
||||
$this->dailyBackupFileMtime = 0;
|
||||
return '';
|
||||
}
|
||||
$mtime = filemtime($file);
|
||||
if ($mtime !== false && $this->dailyBackupFileMtime === $mtime && $this->dailyBackupFileCache !== '') {
|
||||
return $this->dailyBackupFileCache;
|
||||
}
|
||||
$content = file_get_contents($file);
|
||||
if ($content === false || $content === '') {
|
||||
return '';
|
||||
}
|
||||
if ($mtime !== false) {
|
||||
$this->dailyBackupFileCache = $content;
|
||||
$this->dailyBackupFileMtime = $mtime;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
public function getDailyBackupTime() : string {
|
||||
if (!file_exists(DataConst::GetDailyBackupTimeFile())) {
|
||||
$content = $this->getDailyBackupFileContent();
|
||||
if ($content === '') {
|
||||
return '';
|
||||
}
|
||||
$dailyBackupFile = (string)file_get_contents(DataConst::GetDailyBackupTimeFile());
|
||||
$dailyBackupFileArray = explode("\n", $dailyBackupFile);
|
||||
$dailyBackupFileArray = explode("\n", $content);
|
||||
return $dailyBackupFileArray[0];
|
||||
}
|
||||
|
||||
public function areAutomaticUpdatesEnabled() : bool {
|
||||
if (!file_exists(DataConst::GetDailyBackupTimeFile())) {
|
||||
$content = $this->getDailyBackupFileContent();
|
||||
if ($content === '') {
|
||||
return false;
|
||||
}
|
||||
$dailyBackupFile = (string)file_get_contents(DataConst::GetDailyBackupTimeFile());
|
||||
$dailyBackupFileArray = explode("\n", $dailyBackupFile);
|
||||
$dailyBackupFileArray = explode("\n", $content);
|
||||
if (isset($dailyBackupFileArray[1]) && $dailyBackupFileArray[1] === 'automaticUpdatesAreNotEnabled') {
|
||||
return false;
|
||||
} else {
|
||||
@@ -788,11 +832,10 @@ class ConfigurationManager
|
||||
if (file_exists(DataConst::GetDailyBackupTimeFile())) {
|
||||
unlink(DataConst::GetDailyBackupTimeFile());
|
||||
}
|
||||
$this->dailyBackupFileCache = '';
|
||||
$this->dailyBackupFileMtime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidSettingConfigurationException
|
||||
*/
|
||||
public function setAdditionalBackupDirectories(string $additionalBackupDirectories) : void {
|
||||
$additionalBackupDirectoriesArray = explode("\n", $additionalBackupDirectories);
|
||||
$validDirectories = '';
|
||||
|
||||
@@ -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);
|
||||
@@ -425,6 +426,13 @@ readonly class DockerActionManager {
|
||||
// $mounts[] = ["Type" => "bind", "Source" => $volume->name, "Target" => $volume->mountPoint, "ReadOnly" => !$volume->isWritable, "BindOptions" => [ "Propagation" => "rshared"]];
|
||||
// }
|
||||
|
||||
// Special things for the jellyfin community container
|
||||
} elseif ($container->identifier === 'nextcloud-aio-jellyfin') {
|
||||
$lldapIp = gethostbyname('nextcloud-aio-lldap');
|
||||
if ($lldapIp !== 'nextcloud-aio-lldap') {
|
||||
$requestBody['HostConfig']['ExtraHosts'] = ['nextcloud-aio-lldap:' . $lldapIp];
|
||||
}
|
||||
|
||||
// Special things for the caddy community container
|
||||
} elseif ($container->identifier === 'nextcloud-aio-caddy') {
|
||||
$requestBody['HostConfig']['ExtraHosts'] = ['host.docker.internal:host-gateway'];
|
||||
@@ -997,4 +1005,71 @@ readonly class DockerActionManager {
|
||||
return $this->dockerHubManager->GetLatestDigestOfTag($imageName, $tag);
|
||||
}
|
||||
}
|
||||
|
||||
public function SystemPrune(?\Closure $addToStreamingResponseBody = null): void {
|
||||
$endpoints = [
|
||||
// Remove stopped containers
|
||||
'containers/prune',
|
||||
// Remove unused images
|
||||
'images/prune',
|
||||
// Remove unused volumes
|
||||
'volumes/prune',
|
||||
// Remove unused networks
|
||||
'networks/prune',
|
||||
// Prune build cache
|
||||
'build/prune',
|
||||
];
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
// Special-case images prune to include the dangling filter as requested
|
||||
if ($endpoint === 'images/prune') {
|
||||
$filters = json_encode(['dangling' => ['false']]);
|
||||
$url = $this->BuildApiUrl($endpoint . '?filters=' . urlencode((string) $filters));
|
||||
} else {
|
||||
$url = $this->BuildApiUrl($endpoint);
|
||||
}
|
||||
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$addToStreamingResponseBody("Running $endpoint...");
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->guzzleClient->post($url);
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$data = json_decode((string)$response->getBody(), true);
|
||||
$deleted = 0;
|
||||
foreach (['ContainersDeleted', 'ImagesDeleted', 'VolumesDeleted', 'NetworksDeleted', 'CachesDeleted'] as $key) {
|
||||
if (isset($data[$key]) && is_array($data[$key])) {
|
||||
$deleted += count($data[$key]);
|
||||
}
|
||||
}
|
||||
$reclaimed = $data['SpaceReclaimed'] ?? 0;
|
||||
$parts = [];
|
||||
if ($deleted > 0) {
|
||||
$parts[] = "$deleted item(s) deleted";
|
||||
}
|
||||
if ($reclaimed > 0) {
|
||||
$i = (int)floor(log($reclaimed, 1024));
|
||||
$parts[] = 'Space reclaimed: ' . (string)round($reclaimed / (1024 ** $i), 2) . ' ' . ['B','KB','MB','GB'][$i];
|
||||
}
|
||||
$addToStreamingResponseBody(!empty($parts) ? implode('. ', $parts) . '.' : 'Nothing to prune.');
|
||||
}
|
||||
} catch (RequestException $e) {
|
||||
error_log(sprintf('Docker prune (%s) failed: %s', $endpoint, $e->getMessage()));
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$addToStreamingResponseBody('Error: ' . $e->getMessage());
|
||||
}
|
||||
// continue with next prune step
|
||||
}
|
||||
}
|
||||
|
||||
if ($addToStreamingResponseBody !== null) {
|
||||
$addToStreamingResponseBody("Docker system prune completed.");
|
||||
sleep(1);
|
||||
|
||||
// We automatically reload after 10s so that the output can be read or copied if necessary
|
||||
$addToStreamingResponseBody("Automatically reloading the page after 10s.");
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,13 +582,25 @@
|
||||
|
||||
{% if is_backup_container_running == false %}
|
||||
{% if isApacheStarting == false %}
|
||||
{% if isAnyRunning == true %}
|
||||
<h2>Docker System Prune</h2>
|
||||
<details>
|
||||
<summary>Click here to reveal a button to prune the docker system.</summary>
|
||||
<p>By clicking the button below you can run "docker system prune -a". This will remove unused images, containers, networks, volumes and build cache. It will not delete data of running containers.</p>
|
||||
<form method="POST" action="api/docker/prune" target="overlay-log">
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="submit" value="Prune docker system" data-confirm="Run docker system prune -a? This will remove unused images, containers, networks, volumes and build cache. It will not delete data of running containers. Continue?" />
|
||||
</form>
|
||||
</details>
|
||||
{% endif %}
|
||||
<h2>AIO passphrase change</h2>
|
||||
<details>
|
||||
<summary>Click here to change your AIO passphrase</summary>
|
||||
<p>You can change your AIO passphrase below:</p>
|
||||
<form method="POST" action="api/configuration" class="xhr">
|
||||
<input type="password" autocomplete="current-password" name="current-master-password" placeholder="Your current AIO passphrase" id="current-master-password" data-input-show-password="showPassword('current-master-password')">
|
||||
<input type="password" autocomplete="new-password" name="new-master-password" placeholder="Your new AIO passphrase" id="new-master-password" data-input-show-password="showPassword('new-master-password')">
|
||||
<input type="password" autocomplete="current-password" name="current-master-password" placeholder="Your current AIO passphrase" id="current-master-password" data-input-show-password>
|
||||
<input type="password" autocomplete="new-password" name="new-master-password" placeholder="Your new AIO passphrase" id="new-master-password" data-input-show-password>
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="submit" value="Submit passphrase change" />
|
||||
|
||||
@@ -224,16 +224,7 @@
|
||||
</form>
|
||||
<p><strong>Minimal system requirements:</strong> When any optional container is enabled, at least 2GB RAM, a dual-core CPU and 40GB system storage are required. When enabling ClamAV, Nextcloud Talk Recording-server or Fulltextsearch, at least 3GB RAM are required. For Talk Recording-server additional 2 vCPUs are required. When enabling everything, at least 5GB RAM and a quad-core CPU are required. Recommended are at least 1GB more RAM than the minimal requirement. For further advice and recommendations see <strong><a target="_blank" href="https://github.com/nextcloud/all-in-one/discussions/1335">this documentation</a></strong></p>
|
||||
{% if isAnyRunning == true %}
|
||||
<script type="text/javascript" src="disable-clamav.js"></script>
|
||||
<script type="text/javascript" src="disable-docker-socket-proxy.js"></script>
|
||||
<script type="text/javascript" src="disable-harp.js"></script>
|
||||
<script type="text/javascript" src="disable-talk.js"></script>
|
||||
<script type="text/javascript" src="disable-collabora.js?v2"></script>
|
||||
<script type="text/javascript" src="disable-onlyoffice.js?v2"></script>
|
||||
<script type="text/javascript" src="disable-imaginary.js"></script>
|
||||
<script type="text/javascript" src="disable-fulltextsearch.js"></script>
|
||||
<script type="text/javascript" src="disable-talk-recording.js"></script>
|
||||
<script type="text/javascript" src="disable-whiteboard.js"></script>
|
||||
<script type="text/javascript" src="disable-containers.js"></script>
|
||||
{% endif %}
|
||||
|
||||
{% if is_collabora_enabled == true and isAnyRunning == false and was_start_button_clicked == true %}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="icon" href="img/favicon.png">
|
||||
<script type="text/javascript" src="forms.js?v2"></script>
|
||||
<script type="text/javascript" src="toggle-dark-mode.js?v1"></script>
|
||||
<script type="text/javascript" src="click-handlers.js?v1"></script>
|
||||
<script type="text/javascript" src="click-handlers.js?v2"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% if is_login_allowed == true %}
|
||||
<p>Log in using your Nextcloud AIO passphrase:</p>
|
||||
<form method="POST" action="api/auth/login" class="xhr">
|
||||
<input type="password" autocomplete="current-password" name="password" placeholder="Password" id="master-password" data-input-show-password="showPassword('master-password')">
|
||||
<input type="password" autocomplete="current-password" name="password" placeholder="Password" id="master-password" data-input-show-password>
|
||||
<input type="hidden" name="{{csrf.keys.name}}" value="{{csrf.name}}">
|
||||
<input type="hidden" name="{{csrf.keys.value}}" value="{{csrf.value}}">
|
||||
<input type="submit" class="button" value="Log in" />
|
||||
|
||||
Reference in New Issue
Block a user