mirror of
https://github.com/nextcloud/all-in-one.git
synced 2026-06-10 08:37:02 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e1b13cda63 | |||
| 8925baf12f | |||
| b3f66b4c50 | |||
| e1e85c5ca9 | |||
| 1ee3db8195 | |||
| 56afde115d | |||
| b205e46976 | |||
| e077aea86d | |||
| c60861d79c | |||
| 1823336f89 | |||
| d63f0afc63 | |||
| 9f90afbc5e | |||
| 51124784c2 | |||
| f08103ca15 | |||
| 312acddf27 | |||
| 5cbdb00ff4 | |||
| 4b27d6954f |
@@ -63,6 +63,12 @@ http://{$APACHE_HOST}.nextcloud-aio:23973, # For Collabora callback and WOPI req
|
|||||||
reverse_proxy {$WHITEBOARD_HOST}:3002
|
reverse_proxy {$WHITEBOARD_HOST}:3002
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Windmill
|
||||||
|
route /windmill/* {
|
||||||
|
uri strip_prefix /windmill
|
||||||
|
reverse_proxy {$WINDMILL_HOST}:8000
|
||||||
|
}
|
||||||
|
|
||||||
# HaRP (ExApps)
|
# HaRP (ExApps)
|
||||||
route /exapps/* {
|
route /exapps/* {
|
||||||
reverse_proxy {$HARP_HOST}:8780
|
reverse_proxy {$HARP_HOST}:8780
|
||||||
|
|||||||
@@ -263,6 +263,8 @@ RUN set -ex; \
|
|||||||
mkdir -p /nc-updater; \
|
mkdir -p /nc-updater; \
|
||||||
chmod -R 777 /nc-updater
|
chmod -R 777 /nc-updater
|
||||||
|
|
||||||
|
COPY --chmod=775 Containers/nextcloud/create-oauth2-client.php /create-oauth2-client.php
|
||||||
|
|
||||||
# hadolint ignore=DL3002
|
# hadolint ignore=DL3002
|
||||||
USER root
|
USER root
|
||||||
ENTRYPOINT ["/start.sh"]
|
ENTRYPOINT ["/start.sh"]
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Nextcloud OAuth2 client and prints its credentials to stdout.
|
||||||
|
*
|
||||||
|
* Usage: php create-oauth2-client.php <name> <redirect-uri>
|
||||||
|
*
|
||||||
|
* Output (two lines):
|
||||||
|
* <client_id>
|
||||||
|
* <client_secret>
|
||||||
|
*
|
||||||
|
* Any existing client with the same name is deleted first so that the
|
||||||
|
* operation is idempotent (stale clients whose secret has been lost are
|
||||||
|
* replaced by a fresh one).
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ($argc !== 3) {
|
||||||
|
fwrite(STDERR, "Usage: php create-oauth2-client.php <name> <redirect-uri>\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $argv[1];
|
||||||
|
$redirectUri = $argv[2];
|
||||||
|
|
||||||
|
define('OC_CONSOLE', 1);
|
||||||
|
require_once '/var/www/html/lib/base.php';
|
||||||
|
|
||||||
|
\OC_App::loadApp('oauth2');
|
||||||
|
|
||||||
|
$container = \OC::getContainer();
|
||||||
|
/** @var \OCA\OAuth2\Db\ClientMapper $mapper */
|
||||||
|
$mapper = $container->get(\OCA\OAuth2\Db\ClientMapper::class);
|
||||||
|
/** @var \OCP\Security\ICrypto $crypto */
|
||||||
|
$crypto = $container->get(\OCP\Security\ICrypto::class);
|
||||||
|
/** @var \OCP\Security\ISecureRandom $random */
|
||||||
|
$random = $container->get(\OCP\Security\ISecureRandom::class);
|
||||||
|
|
||||||
|
// Delete any stale client with the same name that might have lost its secret.
|
||||||
|
foreach ($mapper->getClients() as $existing) {
|
||||||
|
if ($existing->getName() === $name) {
|
||||||
|
$mapper->delete($existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$client = new \OCA\OAuth2\Db\Client();
|
||||||
|
$client->setName($name);
|
||||||
|
$client->setRedirectUri($redirectUri);
|
||||||
|
|
||||||
|
$secret = $random->generate(64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');
|
||||||
|
$client->setSecret(bin2hex($crypto->calculateHMAC($secret)));
|
||||||
|
|
||||||
|
$clientId = $random->generate(64, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789');
|
||||||
|
$client->setClientIdentifier($clientId);
|
||||||
|
|
||||||
|
$mapper->insert($client);
|
||||||
|
|
||||||
|
echo $clientId . "\n";
|
||||||
|
echo $secret . "\n";
|
||||||
@@ -1090,5 +1090,41 @@ else
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Windmill app
|
||||||
|
if [ "$WINDMILL_ENABLED" = 'yes' ]; then
|
||||||
|
if ! [ -d "/var/www/html/custom_apps/windmill" ]; then
|
||||||
|
php /var/www/html/occ app:install windmill
|
||||||
|
elif [ "$(php /var/www/html/occ config:app:get windmill enabled)" != "yes" ]; then
|
||||||
|
php /var/www/html/occ app:enable windmill
|
||||||
|
elif [ "$SKIP_UPDATE" != 1 ]; then
|
||||||
|
php /var/www/html/occ app:update windmill
|
||||||
|
fi
|
||||||
|
php /var/www/html/occ config:app:set windmill windmill_url --value="https://$NC_DOMAIN/windmill"
|
||||||
|
php /var/www/html/occ config:app:set windmill windmill_instance_url --value="http://$WINDMILL_HOST:8000"
|
||||||
|
# Create an NC OAuth2 client for Windmill (idempotent: reuse stored credentials if already created)
|
||||||
|
WINDMILL_NC_OAUTH_CLIENT_ID="$(php /var/www/html/occ config:app:get windmill nc_oauth_client_id 2>/dev/null)"
|
||||||
|
WINDMILL_NC_OAUTH_CLIENT_SECRET="$(php /var/www/html/occ config:app:get windmill nc_oauth_client_secret 2>/dev/null)"
|
||||||
|
if [ -z "$WINDMILL_NC_OAUTH_CLIENT_ID" ] || [ -z "$WINDMILL_NC_OAUTH_CLIENT_SECRET" ]; then
|
||||||
|
WINDMILL_NC_REDIRECT_URI="https://$NC_DOMAIN/windmill/user/login_callback/nextcloud"
|
||||||
|
WINDMILL_OAUTH_OUTPUT="$(php /create-oauth2-client.php "Windmill" "$WINDMILL_NC_REDIRECT_URI" 2>/dev/null)"
|
||||||
|
WINDMILL_NC_OAUTH_CLIENT_ID="$(printf '%s' "$WINDMILL_OAUTH_OUTPUT" | head -1)"
|
||||||
|
WINDMILL_NC_OAUTH_CLIENT_SECRET="$(printf '%s' "$WINDMILL_OAUTH_OUTPUT" | tail -1)"
|
||||||
|
php /var/www/html/occ config:app:set windmill nc_oauth_client_id --value="$WINDMILL_NC_OAUTH_CLIENT_ID"
|
||||||
|
php /var/www/html/occ config:app:set windmill nc_oauth_client_secret --value="$WINDMILL_NC_OAUTH_CLIENT_SECRET"
|
||||||
|
fi
|
||||||
|
# Configure Windmill to use NC as OAuth SSO provider
|
||||||
|
WINDMILL_OAUTH_BODY="{\"value\":{\"nextcloud\":{\"id\":\"$WINDMILL_NC_OAUTH_CLIENT_ID\",\"secret\":\"$WINDMILL_NC_OAUTH_CLIENT_SECRET\",\"login_config\":{\"auth_url\":\"https://$NC_DOMAIN/apps/oauth2/authorize\",\"token_url\":\"https://$NC_DOMAIN/apps/oauth2/api/v1/token\",\"userinfo_url\":\"https://$NC_DOMAIN/ocs/v2.php/cloud/user?format=json\",\"scopes\":[]}}}}"
|
||||||
|
WINDMILL_OAUTH_RESPONSE="$(curl -s -w '\n%{http_code}' -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $WINDMILL_SECRET" -d "$WINDMILL_OAUTH_BODY" "http://$WINDMILL_HOST:8000/api/settings/global/oauths")"
|
||||||
|
WINDMILL_OAUTH_STATUS="$(echo "$WINDMILL_OAUTH_RESPONSE" | tail -1)"
|
||||||
|
if [ "$WINDMILL_OAUTH_STATUS" != "200" ]; then
|
||||||
|
echo "Failed to configure Windmill OAuth against http://$WINDMILL_HOST:8000 (HTTP $WINDMILL_OAUTH_STATUS). Response: $(echo "$WINDMILL_OAUTH_RESPONSE" | head -1). Exiting!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$REMOVE_DISABLED_APPS" = yes ] && [ -d "/var/www/html/custom_apps/windmill" ]; then
|
||||||
|
php /var/www/html/occ app:remove windmill
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Remove the update skip file always
|
# Remove the update skip file always
|
||||||
rm -f "$NEXTCLOUD_DATA_DIR"/skip.update
|
rm -f "$NEXTCLOUD_DATA_DIR"/skip.update
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
# syntax=docker/dockerfile:latest
|
||||||
|
# Stage 1: Current PostgreSQL server – version is pinned to a full patch
|
||||||
|
# release so builds are reproducible and the exact version is auditable.
|
||||||
|
# (matches the Debian bookworm base used by windmill-labs/windmill)
|
||||||
|
FROM postgres:17.9-bookworm AS postgres-base
|
||||||
|
|
||||||
|
# Final stage: derive from the official Windmill image and bundle PostgreSQL
|
||||||
|
FROM ghcr.io/windmill-labs/windmill:1.691.1
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
# Copy PostgreSQL server binaries, libraries, and utilities from the postgres stage
|
||||||
|
COPY --from=postgres-base /usr/lib/postgresql /usr/lib/postgresql
|
||||||
|
COPY --from=postgres-base /usr/share/postgresql /usr/share/postgresql
|
||||||
|
COPY --from=postgres-base /usr/bin/pg_dump \
|
||||||
|
/usr/bin/pg_dumpall \
|
||||||
|
/usr/bin/pg_restore \
|
||||||
|
/usr/bin/
|
||||||
|
|
||||||
|
# Install supervisor from standard Debian repos (remove broken external sources first)
|
||||||
|
# hadolint ignore=DL3008
|
||||||
|
RUN set -ex; \
|
||||||
|
rm -f \
|
||||||
|
/etc/apt/sources.list.d/nodesource.sources \
|
||||||
|
/etc/apt/sources.list.d/pgdg.list; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get upgrade -y; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
ca-certificates \
|
||||||
|
supervisor \
|
||||||
|
tzdata \
|
||||||
|
netcat-openbsd; \
|
||||||
|
rm -rf /var/lib/apt/lists/*; \
|
||||||
|
update-ca-certificates; \
|
||||||
|
# Copy the CA bundle to a world-readable path so uid=1000 can access it.
|
||||||
|
# /etc/ssl/certs/ is persistently mode 700 in the base image's layer and
|
||||||
|
# cannot be chmod'd in a derived image, so we copy the regenerated bundle.
|
||||||
|
cp /etc/ssl/certs/ca-certificates.crt /etc/ssl/ca-bundle.crt; \
|
||||||
|
chmod 644 /etc/ssl/ca-bundle.crt; \
|
||||||
|
\
|
||||||
|
# The base image already ships a 'windmill' user/group at uid/gid 1000.
|
||||||
|
# Create the directories our services need and hand them to that user.
|
||||||
|
# /tmp/windmill/cache is intentionally excluded: the base image pre-creates
|
||||||
|
# it as 777 so any UID can write to it; a recursive chown on ~340 MB of
|
||||||
|
# pre-installed runtimes would create a huge and unnecessary image layer.
|
||||||
|
mkdir -p \
|
||||||
|
/var/lib/postgresql/data \
|
||||||
|
/var/lib/windmill-dump \
|
||||||
|
/var/run/postgresql \
|
||||||
|
/var/log/supervisord \
|
||||||
|
/var/run/supervisord; \
|
||||||
|
chown -R windmill:windmill \
|
||||||
|
/var/lib/postgresql \
|
||||||
|
/var/lib/windmill-dump \
|
||||||
|
/var/run/postgresql \
|
||||||
|
/var/log/supervisord \
|
||||||
|
/var/run/supervisord; \
|
||||||
|
chmod 1777 \
|
||||||
|
/var/run/postgresql \
|
||||||
|
/var/log/supervisord \
|
||||||
|
/var/run/supervisord; \
|
||||||
|
\
|
||||||
|
# Create symlinks so postgres tools are on PATH
|
||||||
|
ln -sf /usr/lib/postgresql/17/bin/postgres /usr/local/bin/postgres; \
|
||||||
|
ln -sf /usr/lib/postgresql/17/bin/initdb /usr/local/bin/initdb; \
|
||||||
|
ln -sf /usr/lib/postgresql/17/bin/pg_ctl /usr/local/bin/pg_ctl; \
|
||||||
|
ln -sf /usr/lib/postgresql/17/bin/pg_isready /usr/local/bin/pg_isready; \
|
||||||
|
\
|
||||||
|
# Record the current PostgreSQL major version so start.sh can detect when
|
||||||
|
# the bundled major version has changed and trigger an automatic dump/restore upgrade.
|
||||||
|
echo "17" > /etc/postgres-major-version; \
|
||||||
|
\
|
||||||
|
# Write a build-time marker so start.sh can detect image updates and
|
||||||
|
# clear the cache volume when a new image version is deployed.
|
||||||
|
date -u +%s > /etc/windmill-image-build-epoch
|
||||||
|
|
||||||
|
COPY --chmod=775 start.sh /start.sh
|
||||||
|
COPY --chmod=775 healthcheck.sh /healthcheck.sh
|
||||||
|
COPY --chmod=775 windmill-start.sh /windmill-start.sh
|
||||||
|
COPY --chmod=664 supervisord.conf /supervisord.conf
|
||||||
|
|
||||||
|
# Redirect writable tool dirs to the persistent cache volume so the
|
||||||
|
# container can run with a read-only root filesystem.
|
||||||
|
# HOME is already correct (/home/windmill) for the pre-existing uid=1000 user.
|
||||||
|
# WINDMILL_DIR is set to the cache volume so Windmill's logs/worker dirs
|
||||||
|
# are inside the volume and writable (Docker creates the volume mount-point
|
||||||
|
# parent /tmp/windmill as root:root, making /tmp/windmill/logs inaccessible
|
||||||
|
# for uid=1000 when /tmp is a tmpfs).
|
||||||
|
ENV UV_TOOL_BIN_DIR=/tmp/windmill/cache/uv/bin \
|
||||||
|
UV_TOOL_DIR=/tmp/windmill/cache/uv/tools \
|
||||||
|
WINDMILL_DIR=/tmp/windmill/cache \
|
||||||
|
SSL_CERT_FILE=/etc/ssl/ca-bundle.crt \
|
||||||
|
CURL_CA_BUNDLE=/etc/ssl/ca-bundle.crt \
|
||||||
|
REQUESTS_CA_BUNDLE=/etc/ssl/ca-bundle.crt \
|
||||||
|
NODE_EXTRA_CA_CERTS=/etc/ssl/ca-bundle.crt
|
||||||
|
|
||||||
|
VOLUME ["/var/lib/postgresql/data", "/var/lib/windmill-dump", "/tmp/windmill/cache"]
|
||||||
|
|
||||||
|
# Use the pre-existing windmill user (uid=1000) from the base image
|
||||||
|
USER 1000
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENTRYPOINT ["/start.sh"]
|
||||||
|
|
||||||
|
HEALTHCHECK CMD /healthcheck.sh
|
||||||
|
LABEL com.centurylinklabs.watchtower.enable="false" \
|
||||||
|
wud.watch="false" \
|
||||||
|
org.opencontainers.image.title="Windmill for Nextcloud AIO" \
|
||||||
|
org.opencontainers.image.description="Windmill workflow engine with bundled PostgreSQL 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"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if PostgreSQL is accepting connections on the Unix socket
|
||||||
|
if ! pg_isready -h /var/run/postgresql -q 2>/dev/null; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if Windmill is accepting connections on port 8000
|
||||||
|
if ! nc -z localhost 8000; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Validate required environment variables
|
||||||
|
if [ -z "$BASE_URL" ]; then
|
||||||
|
echo "BASE_URL must be provided. Exiting!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export TZ="${TZ:-Etc/UTC}"
|
||||||
|
|
||||||
|
# Clear the cache volume when the image has been updated.
|
||||||
|
# /etc/windmill-image-build-epoch is written at image build time.
|
||||||
|
# A copy is stored in the cache volume after first start.
|
||||||
|
# If the two differ the image was updated and any stale cached artefacts
|
||||||
|
# (uv tools, worker dirs) should be removed so Windmill starts clean.
|
||||||
|
IMAGE_EPOCH_FILE="/etc/windmill-image-build-epoch"
|
||||||
|
CACHE_EPOCH_FILE="/tmp/windmill/cache/.image-build-epoch"
|
||||||
|
if [ -f "$IMAGE_EPOCH_FILE" ]; then
|
||||||
|
IMAGE_EPOCH="$(cat "$IMAGE_EPOCH_FILE")"
|
||||||
|
if [ -f "$CACHE_EPOCH_FILE" ]; then
|
||||||
|
CACHE_EPOCH="$(cat "$CACHE_EPOCH_FILE")"
|
||||||
|
if [ "$IMAGE_EPOCH" != "$CACHE_EPOCH" ]; then
|
||||||
|
echo "Windmill image updated (was $CACHE_EPOCH, now $IMAGE_EPOCH). Clearing cache..."
|
||||||
|
find /tmp/windmill/cache -mindepth 1 -maxdepth 1 ! -name '.image-build-epoch' -exec rm -rf {} +
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$IMAGE_EPOCH" > "$CACHE_EPOCH_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
PGDATA="/var/lib/postgresql/data"
|
||||||
|
|
||||||
|
# The dump and its sentinel/log files live on a dedicated Docker volume that is
|
||||||
|
# separate from the postgres data directory. This means the dump survives a
|
||||||
|
# complete PGDATA wipe (which happens during a major-version upgrade) and there
|
||||||
|
# is no need for a staging subdirectory or complex file exclusion logic.
|
||||||
|
DUMP_DIR="/var/lib/windmill-dump"
|
||||||
|
DUMP_FILE="$DUMP_DIR/windmill-db-dump.sql"
|
||||||
|
|
||||||
|
# Current PG major version as shipped in this image
|
||||||
|
CURRENT_PG_MAJOR=$(cat /etc/postgres-major-version 2>/dev/null)
|
||||||
|
|
||||||
|
# ── Don't start if previous import failed ────────────────────────────────────
|
||||||
|
if [ -f "$DUMP_DIR/import.failed" ]; then
|
||||||
|
echo "The database import failed the last time. Please restore a backup and try again."
|
||||||
|
echo "For further clues on what went wrong, look at the logs above."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Don't start if previous export failed ────────────────────────────────────
|
||||||
|
if [ -f "$DUMP_DIR/export.failed" ]; then
|
||||||
|
echo "Database export failed the last time. Most likely was the export time not high enough."
|
||||||
|
echo "Please report this to https://github.com/nextcloud/all-in-one/issues. Thanks!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write the standard pg_hba.conf and listen_addresses settings into a data directory.
|
||||||
|
configure_pg() {
|
||||||
|
local datadir="$1"
|
||||||
|
cat > "$datadir/pg_hba.conf" << 'HBAEOF'
|
||||||
|
# TYPE DATABASE USER ADDRESS METHOD
|
||||||
|
local all all trust
|
||||||
|
HBAEOF
|
||||||
|
# Disable TCP entirely; all communication uses the Unix socket.
|
||||||
|
echo "listen_addresses = ''" >> "$datadir/postgresql.conf"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── PostgreSQL major-version upgrade via dump/restore ────────────────────────
|
||||||
|
if [ -f "$PGDATA/PG_VERSION" ]; then
|
||||||
|
DATA_PG_MAJOR=$(cat "$PGDATA/PG_VERSION")
|
||||||
|
|
||||||
|
if [ "$DATA_PG_MAJOR" -gt "$CURRENT_PG_MAJOR" ]; then
|
||||||
|
echo "ERROR: Data directory was created by PostgreSQL $DATA_PG_MAJOR but this image ships $CURRENT_PG_MAJOR."
|
||||||
|
echo "Downgrade is not supported. Please use a newer image version."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$DATA_PG_MAJOR" -lt "$CURRENT_PG_MAJOR" ]; then
|
||||||
|
echo "PostgreSQL major-version upgrade required: $DATA_PG_MAJOR → $CURRENT_PG_MAJOR"
|
||||||
|
|
||||||
|
if ! [ -f "$DUMP_FILE" ]; then
|
||||||
|
echo "Unable to upgrade because the database dump is missing."
|
||||||
|
echo "Please restore a backup and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write output to logfile so the import can be inspected later
|
||||||
|
exec > >(tee -i "$DUMP_DIR/database-import.log")
|
||||||
|
exec 2>&1
|
||||||
|
|
||||||
|
echo "Restoring database from dump into new PostgreSQL $CURRENT_PG_MAJOR cluster."
|
||||||
|
|
||||||
|
# Set the sentinel BEFORE any destructive operation so that a crash at
|
||||||
|
# any point leaves the guard in place and blocks the next start.
|
||||||
|
# The sentinel lives on the dump volume and therefore survives the PGDATA wipe.
|
||||||
|
touch "$DUMP_DIR/import.failed"
|
||||||
|
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# Wipe the old cluster and initialise a fresh one.
|
||||||
|
# The dump file is on a separate volume and is not affected.
|
||||||
|
rm -rf "${PGDATA:?}/"*
|
||||||
|
|
||||||
|
initdb -D "$PGDATA" \
|
||||||
|
--username=windmill \
|
||||||
|
--auth-local=trust \
|
||||||
|
--auth-host=trust \
|
||||||
|
--no-instructions
|
||||||
|
|
||||||
|
configure_pg "$PGDATA"
|
||||||
|
|
||||||
|
# Start postgres temporarily on a socket in /tmp so we can import.
|
||||||
|
# No TCP port is needed since we connect via the socket.
|
||||||
|
postgres -D "$PGDATA" -k /tmp -h "" &
|
||||||
|
TEMP_PG_PID=$!
|
||||||
|
|
||||||
|
# Wait until postgres accepts connections
|
||||||
|
while ! psql -h /tmp -U windmill -d postgres -c "select now()" > /dev/null 2>&1; do
|
||||||
|
echo "Waiting for the temporary database to start..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create the windmill database
|
||||||
|
psql -h /tmp -U windmill -d postgres \
|
||||||
|
-c "CREATE DATABASE windmill OWNER windmill;"
|
||||||
|
|
||||||
|
# Restore from dump
|
||||||
|
echo "Restoring the database from dump..."
|
||||||
|
psql -h /tmp -U windmill -d windmill < "$DUMP_FILE"
|
||||||
|
|
||||||
|
# Stop the temporary postgres cleanly
|
||||||
|
pg_ctl -D "$PGDATA" stop -m smart -t 1800
|
||||||
|
wait "$TEMP_PG_PID" 2>/dev/null || true
|
||||||
|
|
||||||
|
set +ex
|
||||||
|
|
||||||
|
# Remove the sentinel only after the restore has fully completed
|
||||||
|
rm "$DUMP_DIR/import.failed"
|
||||||
|
echo "PostgreSQL upgrade to $CURRENT_PG_MAJOR complete."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# ── End of major-version upgrade section ─────────────────────────────────────
|
||||||
|
|
||||||
|
# ── Initialize PostgreSQL data directory on first run ────────────────────────
|
||||||
|
if [ -z "$(ls -A "$PGDATA" 2>/dev/null)" ]; then
|
||||||
|
echo "Initializing PostgreSQL database for Windmill..."
|
||||||
|
|
||||||
|
initdb -D "$PGDATA" \
|
||||||
|
--username=windmill \
|
||||||
|
--auth-local=trust \
|
||||||
|
--auth-host=trust \
|
||||||
|
--no-instructions
|
||||||
|
|
||||||
|
configure_pg "$PGDATA"
|
||||||
|
|
||||||
|
# Start PostgreSQL temporarily to create the windmill database, then stop it.
|
||||||
|
# supervisord will restart it properly afterward.
|
||||||
|
pg_ctl -D "$PGDATA" start -w -o "-k /var/run/postgresql"
|
||||||
|
psql -h /var/run/postgresql -U windmill postgres \
|
||||||
|
-c "CREATE DATABASE windmill OWNER windmill;"
|
||||||
|
pg_ctl -D "$PGDATA" stop -w
|
||||||
|
|
||||||
|
echo "PostgreSQL initialization complete."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Dump database and shut down on container stop ────────────────────────────
|
||||||
|
do_database_dump() {
|
||||||
|
# pg_dump uses a consistent transaction snapshot, so it is safe to run
|
||||||
|
# while Windmill is still connected — no need to stop it beforehand.
|
||||||
|
|
||||||
|
# Verify postgres is still accepting connections before attempting the dump.
|
||||||
|
if ! pg_isready -h /var/run/postgresql -U windmill -q; then
|
||||||
|
echo "WARNING: postgres is not ready; skipping dump."
|
||||||
|
kill "$SUPERVISORD_PID" 2>/dev/null || true
|
||||||
|
wait "$SUPERVISORD_PID" 2>/dev/null || true
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -x
|
||||||
|
touch "$DUMP_DIR/export.failed"
|
||||||
|
rm -f "$DUMP_FILE.temp"
|
||||||
|
if pg_dump -h /var/run/postgresql -U windmill windmill > "$DUMP_FILE.temp"; then
|
||||||
|
mv "$DUMP_FILE.temp" "$DUMP_FILE"
|
||||||
|
rm "$DUMP_DIR/export.failed"
|
||||||
|
echo "Database dump successful!"
|
||||||
|
else
|
||||||
|
rm -f "$DUMP_FILE.temp"
|
||||||
|
echo "Database dump unsuccessful!"
|
||||||
|
fi
|
||||||
|
set +x
|
||||||
|
|
||||||
|
# Stop supervisord (which stops postgres and windmill)
|
||||||
|
kill "$SUPERVISORD_PID" 2>/dev/null || true
|
||||||
|
wait "$SUPERVISORD_PID" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
trap do_database_dump SIGTERM SIGINT
|
||||||
|
|
||||||
|
# ── Start services ────────────────────────────────────────────────────────────
|
||||||
|
supervisord -c /supervisord.conf &
|
||||||
|
SUPERVISORD_PID=$!
|
||||||
|
wait "$SUPERVISORD_PID"
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
logfile=/var/log/supervisord/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord/supervisord.pid
|
||||||
|
childlogdir=/var/log/supervisord/
|
||||||
|
logfile_maxbytes=50MB
|
||||||
|
logfile_backups=10
|
||||||
|
loglevel=error
|
||||||
|
|
||||||
|
[program:postgresql]
|
||||||
|
command=postgres -D /var/lib/postgresql/data -k /var/run/postgresql
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
# Start first: Windmill depends on PostgreSQL being ready
|
||||||
|
priority=10
|
||||||
|
autorestart=true
|
||||||
|
startsecs=5
|
||||||
|
|
||||||
|
[program:windmill]
|
||||||
|
command=/windmill-start.sh
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
priority=20
|
||||||
|
autorestart=true
|
||||||
|
startsecs=10
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Wait for PostgreSQL to accept connections
|
||||||
|
until pg_isready -h /var/run/postgresql -q 2>/dev/null; do
|
||||||
|
echo "Waiting for PostgreSQL to be ready..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Start Windmill
|
||||||
|
exec windmill
|
||||||
+79
-4
@@ -11,6 +11,7 @@
|
|||||||
"nextcloud-aio-notify-push",
|
"nextcloud-aio-notify-push",
|
||||||
"nextcloud-aio-whiteboard",
|
"nextcloud-aio-whiteboard",
|
||||||
"nextcloud-aio-harp",
|
"nextcloud-aio-harp",
|
||||||
|
"nextcloud-aio-windmill",
|
||||||
"nextcloud-aio-nextcloud"
|
"nextcloud-aio-nextcloud"
|
||||||
],
|
],
|
||||||
"display_name": "Apache & Caddy",
|
"display_name": "Apache & Caddy",
|
||||||
@@ -51,7 +52,8 @@
|
|||||||
"APACHE_MAX_TIME=%NEXTCLOUD_MAX_TIME%",
|
"APACHE_MAX_TIME=%NEXTCLOUD_MAX_TIME%",
|
||||||
"NOTIFY_PUSH_HOST=nextcloud-aio-notify-push",
|
"NOTIFY_PUSH_HOST=nextcloud-aio-notify-push",
|
||||||
"WHITEBOARD_HOST=nextcloud-aio-whiteboard",
|
"WHITEBOARD_HOST=nextcloud-aio-whiteboard",
|
||||||
"HARP_HOST=nextcloud-aio-harp"
|
"HARP_HOST=nextcloud-aio-harp",
|
||||||
|
"WINDMILL_HOST=nextcloud-aio-windmill"
|
||||||
],
|
],
|
||||||
"volumes": [
|
"volumes": [
|
||||||
{
|
{
|
||||||
@@ -148,7 +150,8 @@
|
|||||||
"nextcloud-aio-fulltextsearch",
|
"nextcloud-aio-fulltextsearch",
|
||||||
"nextcloud-aio-talk-recording",
|
"nextcloud-aio-talk-recording",
|
||||||
"nextcloud-aio-imaginary",
|
"nextcloud-aio-imaginary",
|
||||||
"nextcloud-aio-docker-socket-proxy"
|
"nextcloud-aio-docker-socket-proxy",
|
||||||
|
"nextcloud-aio-windmill"
|
||||||
],
|
],
|
||||||
"display_name": "Nextcloud",
|
"display_name": "Nextcloud",
|
||||||
"image": "ghcr.io/nextcloud-releases/aio-nextcloud",
|
"image": "ghcr.io/nextcloud-releases/aio-nextcloud",
|
||||||
@@ -175,7 +178,8 @@
|
|||||||
"FULLTEXTSEARCH_PASSWORD",
|
"FULLTEXTSEARCH_PASSWORD",
|
||||||
"IMAGINARY_SECRET",
|
"IMAGINARY_SECRET",
|
||||||
"WHITEBOARD_SECRET",
|
"WHITEBOARD_SECRET",
|
||||||
"HP_SHARED_KEY"
|
"HP_SHARED_KEY",
|
||||||
|
"WINDMILL_SECRET"
|
||||||
],
|
],
|
||||||
"volumes": [
|
"volumes": [
|
||||||
{
|
{
|
||||||
@@ -263,7 +267,10 @@
|
|||||||
"WHITEBOARD_SECRET=%WHITEBOARD_SECRET%",
|
"WHITEBOARD_SECRET=%WHITEBOARD_SECRET%",
|
||||||
"WHITEBOARD_ENABLED=%WHITEBOARD_ENABLED%",
|
"WHITEBOARD_ENABLED=%WHITEBOARD_ENABLED%",
|
||||||
"HARP_ENABLED=%HARP_ENABLED%",
|
"HARP_ENABLED=%HARP_ENABLED%",
|
||||||
"HP_SHARED_KEY=%HP_SHARED_KEY%"
|
"HP_SHARED_KEY=%HP_SHARED_KEY%",
|
||||||
|
"WINDMILL_ENABLED=%WINDMILL_ENABLED%",
|
||||||
|
"WINDMILL_HOST=nextcloud-aio-windmill",
|
||||||
|
"WINDMILL_SECRET=%WINDMILL_SECRET%"
|
||||||
],
|
],
|
||||||
"stop_grace_period": 600,
|
"stop_grace_period": 600,
|
||||||
"restart": "unless-stopped",
|
"restart": "unless-stopped",
|
||||||
@@ -949,6 +956,74 @@
|
|||||||
"cap_drop": [
|
"cap_drop": [
|
||||||
"NET_RAW"
|
"NET_RAW"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"container_name": "nextcloud-aio-windmill",
|
||||||
|
"image_tag": "%AIO_CHANNEL%",
|
||||||
|
"display_name": "Windmill",
|
||||||
|
"image": "ghcr.io/nextcloud-releases/aio-windmill",
|
||||||
|
"user": "1000",
|
||||||
|
"init": true,
|
||||||
|
"internal_port": "8000",
|
||||||
|
"expose": [
|
||||||
|
"8000"
|
||||||
|
],
|
||||||
|
"healthcheck": {
|
||||||
|
"start_period": "0s",
|
||||||
|
"test": "/healthcheck.sh",
|
||||||
|
"interval": "30s",
|
||||||
|
"timeout": "30s",
|
||||||
|
"start_interval": "5s",
|
||||||
|
"retries": 3
|
||||||
|
},
|
||||||
|
"environment": [
|
||||||
|
"BASE_URL=https://%NC_DOMAIN%/windmill",
|
||||||
|
"TZ=%TIMEZONE%",
|
||||||
|
"NUM_WORKERS=1",
|
||||||
|
"MODE=standalone",
|
||||||
|
"DISABLE_NSJAIL=true",
|
||||||
|
"DATABASE_URL=postgresql://windmill@localhost/windmill?host=/var/run/postgresql",
|
||||||
|
"SUPERADMIN_SECRET=%WINDMILL_SECRET%"
|
||||||
|
],
|
||||||
|
"secrets": [
|
||||||
|
"WINDMILL_SECRET"
|
||||||
|
],
|
||||||
|
"volumes": [
|
||||||
|
{
|
||||||
|
"source": "nextcloud_aio_windmill_db",
|
||||||
|
"destination": "/var/lib/postgresql/data",
|
||||||
|
"writeable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "nextcloud_aio_windmill_dump",
|
||||||
|
"destination": "/var/lib/windmill-dump",
|
||||||
|
"writeable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "nextcloud_aio_windmill_cache",
|
||||||
|
"destination": "/tmp/windmill/cache",
|
||||||
|
"writeable": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"backup_volumes": [
|
||||||
|
"nextcloud_aio_windmill_db",
|
||||||
|
"nextcloud_aio_windmill_dump"
|
||||||
|
],
|
||||||
|
"restart": "unless-stopped",
|
||||||
|
"stop_grace_period": 1800,
|
||||||
|
"read_only": true,
|
||||||
|
"tmpfs": [
|
||||||
|
"/tmp",
|
||||||
|
"/var/run/postgresql",
|
||||||
|
"/var/log/supervisord",
|
||||||
|
"/var/run/supervisord"
|
||||||
|
],
|
||||||
|
"profiles": [
|
||||||
|
"windmill"
|
||||||
|
],
|
||||||
|
"cap_drop": [
|
||||||
|
"NET_RAW"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,4 +41,10 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
|||||||
// Whiteboard
|
// Whiteboard
|
||||||
let whiteboard = document.getElementById("whiteboard");
|
let whiteboard = document.getElementById("whiteboard");
|
||||||
whiteboard.disabled = true;
|
whiteboard.disabled = true;
|
||||||
|
|
||||||
|
// Windmill
|
||||||
|
let windmill = document.getElementById("windmill");
|
||||||
|
if (windmill) {
|
||||||
|
windmill.disabled = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ $app->get('/containers', function (Request $request, Response $response, array $
|
|||||||
'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled,
|
'is_docker_socket_proxy_enabled' => $configurationManager->isDockerSocketProxyEnabled,
|
||||||
'is_harp_enabled' => $configurationManager->isHarpEnabled,
|
'is_harp_enabled' => $configurationManager->isHarpEnabled,
|
||||||
'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled,
|
'is_whiteboard_enabled' => $configurationManager->isWhiteboardEnabled,
|
||||||
|
'is_windmill_enabled' => $configurationManager->isWindmillEnabled,
|
||||||
'community_containers' => $configurationManager->listAvailableCommunityContainers(),
|
'community_containers' => $configurationManager->listAvailableCommunityContainers(),
|
||||||
'community_containers_enabled' => $configurationManager->aioCommunityContainers,
|
'community_containers_enabled' => $configurationManager->aioCommunityContainers,
|
||||||
'bypass_container_update' => $bypass_container_update,
|
'bypass_container_update' => $bypass_container_update,
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ readonly class ContainerDefinitionFetcher {
|
|||||||
if (!$this->configurationManager->isWhiteboardEnabled) {
|
if (!$this->configurationManager->isWhiteboardEnabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} elseif ($entry['container_name'] === 'nextcloud-aio-windmill') {
|
||||||
|
if (!$this->configurationManager->isWindmillEnabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$ports = new ContainerPorts();
|
$ports = new ContainerPorts();
|
||||||
@@ -222,6 +226,10 @@ readonly class ContainerDefinitionFetcher {
|
|||||||
if (!$this->configurationManager->isWhiteboardEnabled) {
|
if (!$this->configurationManager->isWhiteboardEnabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} elseif ($value === 'nextcloud-aio-windmill') {
|
||||||
|
if (!$this->configurationManager->isWindmillEnabled) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Skip dependencies on community containers that are not currently enabled.
|
// Skip dependencies on community containers that are not currently enabled.
|
||||||
// Only apply this when the current entry is itself a community container,
|
// Only apply this when the current entry is itself a community container,
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ readonly class ConfigurationController {
|
|||||||
$this->configurationManager->isDockerSocketProxyEnabled = isset($request->getParsedBody()['docker-socket-proxy']);
|
$this->configurationManager->isDockerSocketProxyEnabled = isset($request->getParsedBody()['docker-socket-proxy']);
|
||||||
$this->configurationManager->isHarpEnabled = isset($request->getParsedBody()['harp']);
|
$this->configurationManager->isHarpEnabled = isset($request->getParsedBody()['harp']);
|
||||||
$this->configurationManager->isWhiteboardEnabled = isset($request->getParsedBody()['whiteboard']);
|
$this->configurationManager->isWhiteboardEnabled = isset($request->getParsedBody()['whiteboard']);
|
||||||
|
$this->configurationManager->isWindmillEnabled = isset($request->getParsedBody()['windmill']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($request->getParsedBody()['community-form'])) {
|
if (isset($request->getParsedBody()['community-form'])) {
|
||||||
|
|||||||
@@ -45,6 +45,12 @@ class ConfigurationManager
|
|||||||
set { $this->set('isWhiteboardEnabled', $value); }
|
set { $this->set('isWhiteboardEnabled', $value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool $isWindmillEnabled {
|
||||||
|
// Type-cast because old configs could have 1/0 for this key.
|
||||||
|
get => (bool) $this->get('isWindmillEnabled', false);
|
||||||
|
set { $this->set('isWindmillEnabled', $value); }
|
||||||
|
}
|
||||||
|
|
||||||
public bool $restoreExcludePreviews {
|
public bool $restoreExcludePreviews {
|
||||||
// Type-cast because old configs could have '1'/'' for this key.
|
// Type-cast because old configs could have '1'/'' for this key.
|
||||||
get => (bool) $this->get('restore-exclude-previews', false);
|
get => (bool) $this->get('restore-exclude-previews', false);
|
||||||
@@ -1108,6 +1114,7 @@ class ConfigurationManager
|
|||||||
// Allow to get local ip-address of caddy container and add it to trusted proxies automatically
|
// Allow to get local ip-address of caddy container and add it to trusted proxies automatically
|
||||||
'CADDY_IP_ADDRESS' => in_array('caddy', $this->aioCommunityContainers, true) ? gethostbyname('nextcloud-aio-caddy') : '',
|
'CADDY_IP_ADDRESS' => in_array('caddy', $this->aioCommunityContainers, true) ? gethostbyname('nextcloud-aio-caddy') : '',
|
||||||
'WHITEBOARD_ENABLED' => $this->isWhiteboardEnabled ? 'yes' : '',
|
'WHITEBOARD_ENABLED' => $this->isWhiteboardEnabled ? 'yes' : '',
|
||||||
|
'WINDMILL_ENABLED' => $this->isWindmillEnabled ? 'yes' : '',
|
||||||
'AIO_VERSION' => $this->getAioVersion(),
|
'AIO_VERSION' => $this->getAioVersion(),
|
||||||
default => $this->getRegisteredSecret($placeholder),
|
default => $this->getRegisteredSecret($placeholder),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -220,6 +220,20 @@
|
|||||||
>
|
>
|
||||||
<label for="whiteboard">Whiteboard</label>
|
<label for="whiteboard">Whiteboard</label>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="windmill"
|
||||||
|
name="windmill"
|
||||||
|
{% if is_windmill_enabled == true %}
|
||||||
|
checked="checked"
|
||||||
|
data-initial-state="true"
|
||||||
|
{% else %}
|
||||||
|
data-initial-state="false"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
|
<label for="windmill">Windmill (workflow automation. Access the Windmill UI at <code>https://{{ domain }}/windmill</code>)</label>
|
||||||
|
</p>
|
||||||
<input class="options-form-submit" type="submit" value="Save changes" />
|
<input class="options-form-submit" type="submit" value="Save changes" />
|
||||||
</form>
|
</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>
|
<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>
|
||||||
|
|||||||
Reference in New Issue
Block a user