mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-05-22 11:20:13 +00:00
Compare commits
45 Commits
copilot/in
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01046015a9 | ||
|
|
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 |
@@ -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/
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
nc -z 127.0.0.1 "$PORT" || exit 1
|
||||
wget -q -O /dev/null "http://127.0.0.1:${PORT}/health" || 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
|
||||
|
||||
imaginary "${IMAGINARY_ARGS[@]}" "$@"
|
||||
|
||||
@@ -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=(), 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=()"
|
||||
|
||||
-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,6 +135,9 @@ 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; \
|
||||
\
|
||||
{ \
|
||||
@@ -142,7 +145,10 @@ RUN set -ex; \
|
||||
echo 'session.save_path = "tcp://${REDIS_HOST}:${REDIS_PORT}?database=${REDIS_DB_INDEX}${REDIS_USER_AUTH}&auth[]=${REDIS_HOST_PASSWORD}"'; \
|
||||
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; \
|
||||
\
|
||||
@@ -238,12 +244,27 @@ RUN set -ex; \
|
||||
imagemagick-tiff \
|
||||
coreutils; \
|
||||
\
|
||||
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
|
||||
# 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.
|
||||
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; \
|
||||
|
||||
@@ -7,6 +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;
|
||||
}
|
||||
|
||||
if (getenv('REDIS_HOST_PASSWORD')) {
|
||||
|
||||
@@ -151,23 +151,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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -444,6 +444,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
|
||||
|
||||
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,40 @@ $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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
$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 +91,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
|
||||
|
||||
@@ -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); }
|
||||
@@ -760,23 +764,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 +816,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 = '';
|
||||
|
||||
@@ -425,6 +425,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 +1004,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