Initial import

This commit is contained in:
Nextcloud Team 2021-11-30 11:20:42 +01:00 committed by Lukas Reschke
commit 2295a33590
884 changed files with 93939 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.DS_Store
/php/data/containers.json
/php/data/configuration.json
/php/data/backupsecret.json

View file

@ -0,0 +1,53 @@
{
auto_https disable_redirects
storage file_system {
root /mnt/data/caddy
}
}
{$NC_DOMAIN}:443 {
# Notify Push
route /push/* {
uri strip_prefix /push
reverse_proxy {$NEXTCLOUD_HOST}:7867
}
# Talk
route /standalone-signaling/* {
uri strip_prefix /standalone-signaling
reverse_proxy {$TALK_HOST}:8081
}
# Collabora
route /browser/* {
reverse_proxy https://{$COLLABORA_HOST}:9980 {
transport http {
tls_insecure_skip_verify
}
}
}
route /hosting/* {
reverse_proxy https://{$COLLABORA_HOST}:9980 {
transport http {
tls_insecure_skip_verify
}
}
}
route /cool/* {
reverse_proxy https://{$COLLABORA_HOST}:9980 {
transport http {
tls_insecure_skip_verify
}
}
}
# Nextcloud
route {
rewrite /.well-known/carddav /remote.php/dav
rewrite /.well-known/caldav /remote.php/dav
header Strict-Transport-Security max-age=31536000;
reverse_proxy localhost:80
}
}

View file

@ -0,0 +1,68 @@
FROM debian:bullseye
EXPOSE 80
RUN mkdir -p /mnt/data; \
chown www-data:www-data /mnt/data;
VOLUME /mnt/data
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
apache2 \
supervisor \
wget \
ca-certificates \
openssl \
netcat \
; \
rm -rf /var/lib/apt/lists/*
RUN wget "https://caddyserver.com/api/download?os=linux&arch=amd64" -O "/usr/bin/caddy" \
&& chmod +x /usr/bin/caddy \
&& /usr/bin/caddy version
RUN a2enmod rewrite \
headers \
proxy \
proxy_fcgi \
setenvif \
env \
mime \
dir \
authz_core \
alias
COPY nextcloud.conf /etc/apache2/sites-available/
RUN a2dissite 000-default && \
a2dissite default-ssl && \
a2ensite nextcloud.conf && \
rm -rf /var/www/html/* && \
service apache2 restart; \
chown www-data:www-data -R /var/log/apache2; \
chown -R www-data:www-data /var/run/apache2; \
chown -R www-data:www-data /var/www;
RUN mkdir /var/log/supervisord; \
mkdir /var/run/supervisord; \
chown www-data:www-data /var/run/supervisord; \
chown www-data:www-data /var/log/supervisord;
COPY Caddyfile /
COPY start.sh /usr/bin/
COPY supervisord.conf /
RUN chmod +x /usr/bin/start.sh; \
chmod +r /supervisord.conf; \
chmod +r /Caddyfile;
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER www-data
ENTRYPOINT ["start.sh"]
CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"]

View file

@ -0,0 +1,22 @@
<VirtualHost *:80>
# PHP match
<FilesMatch "\.php$">
SetHandler "proxy:fcgi://nextcloud-aio-nextcloud:9000"
</FilesMatch>
# Nextcloud dir
DocumentRoot /var/www/html/
<Directory /var/www/html/>
Options Indexes FollowSymLinks
Require all granted
AllowOverride All
Options FollowSymLinks MultiViews
Satisfy Any
<IfModule mod_dav.c>
Dav off
</IfModule>
</Directory>
# Deny access to .ht files
<Files ".ht*">
Require all denied
</Files>
</VirtualHost>

View file

@ -0,0 +1,32 @@
#!/bin/bash
if [ -z "$NC_DOMAIN" ]; then
echo "NC_DOMAIN and NEXTCLOUD_HOST need to be provided. Exiting!"
exit 1
fi
# Need write access to /mnt/data
if ! [ -w /mnt/data ]; then
echo "Cannot write to /mnt/data"
exit 1
fi
# Only start container if nextcloud is accessible
while ! nc -z "$NEXTCLOUD_HOST" 9000; do
echo "Waiting for Nextcloud to start..."
sleep 5
done
# Only start container if collabora is started
while ! nc -z "$COLLABORA_HOST" 9980; do
echo "Waiting for Collabora to start..."
sleep 5
done
# Add caddy path
mkdir -p /mnt/data/caddy/
# Fix apache sturtup
rm -f /var/run/apache2/apache2.pid
exec "$@"

View file

@ -0,0 +1,23 @@
[supervisord]
nodaemon=true
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:apache]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=apachectl -DFOREGROUND
[program:caddy]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/usr/bin/caddy run -config /Caddyfile

View file

@ -0,0 +1,22 @@
FROM debian:bullseye
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
borgbackup \
rsync \
fuse \
python3-llfuse \
; \
rm -rf /var/lib/apt/lists/*
VOLUME /root
COPY start.sh /usr/bin/
COPY backupscript.sh /
RUN chmod +x /usr/bin/start.sh; \
chmod +x /backupscript.sh
USER root
ENTRYPOINT ["start.sh"]

View file

@ -0,0 +1,204 @@
#!/bin/bash
# Variables
BORG_BACKUP_DIRECTORY="/mnt/borgbackup/borg"
# Functions
get_start_time(){
START_TIME=$(date +%s)
CURRENT_DATE=$(date --date @"$START_TIME" +"%Y%m%d_%H%M%S")
CURRENT_DATE_READABLE=$(date --date @"$START_TIME" +"%d.%m.%Y - %H:%M:%S")
}
get_expiration_time() {
END_TIME=$(date +%s)
END_DATE_READABLE=$(date --date @"$END_TIME" +"%d.%m.%Y - %H:%M:%S")
DURATION=$((END_TIME-START_TIME))
DURATION_SEC=$((DURATION % 60))
DURATION_MIN=$(((DURATION / 60) % 60))
DURATION_HOUR=$((DURATION / 3600))
DURATION_READABLE=$(printf "%02d hours %02d minutes %02d seconds" $DURATION_HOUR $DURATION_MIN $DURATION_SEC)
}
# Export defaults
export BORG_PASSPHRASE="$BORG_PASSWORD"
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
export BORG_RELOCATED_REPO_ACCESS_IS_OK=yes
# Test if all volumes aren't empty
VOLUME_DIRS="$(find /nextcloud_aio_volumes -mindepth 1 -maxdepth 1 -type d)"
mapfile -t VOLUME_DIRS <<< "$VOLUME_DIRS"
for directory in "${VOLUME_DIRS[@]}"; do
if ! mountpoint -q "$directory"; then
echo "$directory is not a mountpoint which is not allowed."
exit 1
fi
done
# Check if target is mountpoint
if ! mountpoint -q /mnt/borgbackup; then
echo "/mnt/borgbackup is not a mountpoint which is not allowed"
exit 1
fi
# Check if target is empty
if [ "$BORG_MODE" != backup ] && ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then
echo "The repository is empty. cannot perform check or restore."
exit 1
fi
# Break the borg lock if it exists
if [ -f "$BORG_BACKUP_DIRECTORY/lock.roster" ]; then
echo "Breaking the borg lock..."
if ! borg break-lock "$BORG_BACKUP_DIRECTORY"; then
echo "Could not break the borg lock!"
exit 1
fi
fi
# Create lockfile
if [ "$BORG_MODE" = backup ] || [ "$BORG_MODE" = restore ]; then
touch "/nextcloud_aio_volumes/nextcloud_aio_database_dump/backup-is-running"
fi
# Do the backup
if [ "$BORG_MODE" = backup ]; then
# Test if important files are present
if ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/configuration.json" ]; then
echo "configuration.json not present. Cannot perform the backup!"
exit 1
elif ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_nextcloud/config/config.php" ]; then
echo "config.php is missing cannot perform backup"
exit 1
elif ! [ -f "/nextcloud_aio_volumes/nextcloud_aio_database_dump/database-dump.sql" ]; then
echo "database-dump is missing. cannot perform backup"
exit 1
fi
# Test that nothing is empty
for directory in "${VOLUME_DIRS[@]}"; do
if [ -z "$(ls -A "$directory")" ]; then
echo "$directory is empty which is not allowed."
exit 1
fi
done
# Create backup folder
mkdir -p "$BORG_BACKUP_DIRECTORY"
# Initialize the repository if the target is empty
if ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then
# Don't initialize if already initialized
if [ -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config" ]; then
echo "Cannot initialize a new repository as that was already done at least one time."
exit 1
fi
echo "initializing repository..."
if ! borg init --debug --encryption=repokey-blake2 "$BORG_BACKUP_DIRECTORY"; then
echo "Could not initialize borg repository."
rm -f "$BORG_BACKUP_DIRECTORY/config"
exit 1
fi
borg config "$BORG_BACKUP_DIRECTORY" additional_free_space 2G
# Fix too large Borg cache
# https://borgbackup.readthedocs.io/en/stable/faq.html#the-borg-cache-eats-way-too-much-disk-space-what-can-i-do
BORG_ID="$(borg config "$BORG_BACKUP_DIRECTORY" id)"
rm -r "/root/.cache/borg/$BORG_ID/chunks.archive.d"
touch "/root/.cache/borg/$BORG_ID/chunks.archive.d"
# Make a backup from the borg config file
if ! [ -f "$BORG_BACKUP_DIRECTORY/config" ]; then
echo "The borg config file wasn't created. Something is wrong."
exit 1
fi
rm -f "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config"
if ! cp "$BORG_BACKUP_DIRECTORY/config" "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/borg.config"; then
echo "Could not copy config file to second place. Cannot perform backup."
exit 1
fi
echo "Repository successfully initialized."
fi
# Perform backup
echo "Performing backup..."
# Borg options
# auto,zstd compression seems to has the best ratio based on:
# https://forum.level1techs.com/t/optimal-compression-for-borg-backups/145870/6
BORG_OPTS=(--stats --progress --compression "auto,zstd" --exclude-caches --checkpoint-interval 86400)
# Create the backup
echo "Starting the backup..."
get_start_time
if ! borg create "${BORG_OPTS[@]}" "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-nextcloud-aio" "/nextcloud_aio_volumes/"; then
echo "Deleting the failed backup archive..."
borg delete --stats --progress "$BORG_BACKUP_DIRECTORY::$CURRENT_DATE-nextcloud-aio"
echo "Backup failed!"
exit 1
fi
echo "$CURRENT_DATE,$CURRENT_DATE_READABLE" >> "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/backup_archives.list"
chmod +r "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer/data/backup_archives.list"
# Prune options
BORG_PRUNE_OPTS=(--stats --progress --keep-within=7d --keep-weekly=4 --keep-monthly=6 "$BORG_BACKUP_DIRECTORY")
# Prune archives
echo "Pruning the archives..."
if ! borg prune --prefix '*_*-nextcloud-aio' "${BORG_PRUNE_OPTS[@]}"; then
echo "Failed to prune archives!"
exit 1
fi
# Inform user
get_expiration_time
echo "Backup finished successfully on $END_DATE_READABLE ($DURATION_READABLE)"
exit 0
fi
# Do the restore
if [ "$BORG_MODE" = restore ]; then
get_start_time
echo "Restoring the last backup..."
# Perform the restore
FIRST_ARCHIVE="$(borg list "$BORG_BACKUP_DIRECTORY" | grep "nextcloud-aio" | awk -F " " '{print $1}' | sort -r | head -1)"
mkdir -p /tmp/borg
if ! borg mount "$BORG_BACKUP_DIRECTORY::$FIRST_ARCHIVE" /tmp/borg; then
echo "Could not mount the backup!"
exit 1
fi
if ! rsync --stats --archive --human-readable -vv --delete --exclude "nextcloud_aio_mastercontainer/data/backup_archives.list" /tmp/borg/nextcloud_aio_volumes/ /nextcloud_aio_volumes; then
echo "Something failed while restoring the boot partition."
umount /tmp/borg
exit 1
fi
umount /tmp/borg
# TODO: reset fetchtimes in configuration.json so that it doesn't get the latest directly...
# Inform user
get_expiration_time
echo "Restore finished successfully on $END_DATE_READABLE ($DURATION_READABLE)"
exit 0
fi
# Do the Backup check
if [ "$BORG_MODE" = check ]; then
get_start_time
echo "Checking the backup integity..."
# Perform the check
if ! borg check --verify-data --progress "$BORG_BACKUP_DIRECTORY"; then
echo "Some errors were found while checking the backup integrity!"
exit 1
fi
# Inform user
get_expiration_time
echo "Check finished successfully on $END_DATE_READABLE ($DURATION_READABLE)"
exit 0
fi

View file

@ -0,0 +1,31 @@
#!/bin/bash
# Validate BORG_PASSWORD
if [ -z "$BORG_PASSWORD" ]; then
echo "BORG_PASSWORD is not allowed to be empty."
exit 1
fi
export BORG_PASSWORD
# Validate BORG_MODE
if [ "$BORG_MODE" != backup ] && [ "$BORG_MODE" != restore ] && [ "$BORG_MODE" != check ]; then
echo "No correct BORG_MODE mode applied. Valid are 'backup' and 'restore'."
exit 1
fi
export BORG_MODE
# Run the backup script
if ! bash /backupscript.sh; then
FAILED=1
fi
# Remove lockfile
rm -f "/nextcloud_aio_volumes/nextcloud_aio_database_dump/backup-is-running"
if [ -n "$FAILED" ]; then
exit 1
fi
exec "$@"

View file

@ -0,0 +1,2 @@
# From a file located probably somewhere here: https://github.com/CollaboraOnline/online/tree/master/docker
FROM collabora/code:latest

View file

@ -0,0 +1,15 @@
FROM alpine
RUN apk add --update --no-cache lighttpd bash
RUN adduser -S www-data -G www-data
RUN rm -rf /etc/lighttpd/lighttpd.conf
COPY lighttpd.conf /etc/lighttpd/lighttpd.conf
RUN chmod +r -R /etc/lighttpd && \
chown www-data:www-data -R /var/www
COPY start.sh /
RUN chmod +x /start.sh
USER www-data
RUN mkdir -p /var/www/domaincheck/
ENTRYPOINT ["/start.sh"]

View file

@ -0,0 +1,16 @@
server.document-root = "/var/www/domaincheck/"
server.port = 443
server.username = "www-data"
server.groupname = "www-data"
mimetype.assign = (
".html" => "text/html",
".txt" => "text/plain",
".jpg" => "image/jpeg",
".png" => "image/png"
)
static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
index-file.names = ( "index.html" )

View file

@ -0,0 +1,16 @@
#!/bin/bash
if [ -z "$INSTANCE_ID" ]; then
echo "You need to provide an instance id."
exit 1
fi
echo "$INSTANCE_ID" > /var/www/domaincheck/index.html
# Check config file
lighttpd -tt -f /etc/lighttpd/lighttpd.conf
# Run server
lighttpd -D -f /etc/lighttpd/lighttpd.conf
exec "$@"

View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mastercontainer.iml" filepath="$PROJECT_DIR$/.idea/mastercontainer.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

View file

@ -0,0 +1,16 @@
{
auto_https disable_redirects
storage file_system {
root /mnt/docker-aio-config/caddy/
}
}
https://:8443 {
reverse_proxy localhost:8000
tls {
on_demand
}
}

View file

@ -0,0 +1,88 @@
# From https://github.com/docker-library/php/blob/master/8.0/buster/apache/Dockerfile
FROM php:8.0-apache-bullseye
EXPOSE 80
# EXPOSE 8080
EXPOSE 8443
RUN mkdir -p /mnt/docker-aio-config/; \
chown www-data:www-data /mnt/docker-aio-config;
VOLUME /mnt/docker-aio-config/
RUN mkdir -p /var/www/docker-aio; \
chown -R www-data:www-data /var/www;
WORKDIR /var/www/docker-aio
RUN apt-get update; \
apt-get install -y --no-install-recommends \
git \
supervisor \
openssl \
sudo \
; \
rm -rf /var/lib/apt/lists/*
RUN curl "https://caddyserver.com/api/download?os=linux&arch=amd64" -o "/usr/bin/caddy" \
&& chmod 0755 /usr/bin/caddy \
&& /usr/bin/caddy version
RUN cd /var/www/docker-aio; \
git clone git@github.com:nextcloud/all-in-one.git .; \
chown -R www-data:www-data ./; \
chmod 770 -R ./
RUN mkdir -p /etc/apache2/certs && \
cd /etc/apache2/certs && \
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "/C=DE/ST=BE/L=Local/O=Dev/CN=nextcloud.local" -keyout ./ssl.key -out ./ssl.crt; \
chown www-data:www-data -R /etc/apache2/certs;
COPY mastercontainer.conf /etc/apache2/sites-available/
RUN a2enmod rewrite \
headers \
env \
mime \
dir \
authz_core \
proxy \
proxy_http \
ssl
RUN rm /etc/apache2/ports.conf; \
sed -s -i -e "s/Include ports.conf//" /etc/apache2/apache2.conf; \
sed -i "/^Listen /d" /etc/apache2/apache2.conf
RUN a2dissite 000-default && \
a2dissite default-ssl && \
a2ensite mastercontainer.conf && \
service apache2 restart
RUN mkdir /var/log/supervisord; \
mkdir /var/run/supervisord; \
chown www-data:www-data /var/run/supervisord; \
chown www-data:www-data /var/log/supervisord;
RUN mkdir -p /usr/src/php/ext/apcu && curl -fsSL https://pecl.php.net/get/apcu | tar xvz -C "/usr/src/php/ext/apcu" --strip 1 && docker-php-ext-install apcu
COPY Caddyfile /
COPY start.sh /usr/bin/
COPY cron.sh /
COPY supervisord.conf /
RUN chmod +x /usr/bin/start.sh; \
chmod +r /supervisord.conf; \
chmod +r /Caddyfile; \
chmod +x /cron.sh
# add docker group
RUN groupadd -g 998 docker && \
usermod -aG docker www-data
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER www-data
ENTRYPOINT ["start.sh"]
CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"]

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
while true; do
php /var/www/docker-aio/php/src/Cron/cron.php
sleep 1d
done

View file

@ -0,0 +1,45 @@
Listen 8000
Listen 8080
CustomLog ${APACHE_LOG_DIR}/access.log combined
ErrorLog ${APACHE_LOG_DIR}/error.log
# Deny access to .ht files
<Files ".ht*">
Require all denied
</Files>
# Http host
<VirtualHost *:8000>
# PHP match
<FilesMatch "\.php$">
SetHandler application/x-httpd-php
</FilesMatch>
# Master dir
DocumentRoot /var/www/docker-aio/php/public/
<Directory /var/www/docker-aio/php/public/>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
Options Indexes FollowSymLinks
Require all granted
AllowOverride All
Options FollowSymLinks MultiViews
Satisfy Any
<IfModule mod_dav.c>
Dav off
</IfModule>
</Directory>
</VirtualHost>
# Https host
<VirtualHost *:8080>
# Proxy to https
ProxyPass / http://localhost:8000/
ProxyPassReverse / http://localhost:8000/
# SSL
SSLCertificateKeyFile /etc/apache2/certs/ssl.key
SSLCertificateFile /etc/apache2/certs/ssl.crt
SSLEngine on
SSLProtocol -all +TLSv1.2 +TLSv1.3
</VirtualHost>

View file

@ -0,0 +1,69 @@
#!/bin/bash
# Function to show text in green
print_green() {
local TEXT="$1"
printf "%b%s%b\n" "\e[0;92m" "$TEXT" "\e[0m"
}
# Check if socket is available and readable
if ! [ -a "/var/run/docker.sock" ]; then
echo "Docker socket is not available. Cannot continue."
exit 1
elif ! test -r /var/run/docker.sock; then
echo "Docker socket is not readable by the www-data user. Cannot continue."
exit 1
fi
# Check if volume is writeable
if ! [ -w /mnt/docker-aio-config ]; then
echo "/mnt/docker-aio-config is not writeable."
exit 1
fi
# Check if api version is supported
API_VERSION_FILE="$(find ./ -name DockerActionManager.php | head -1)"
API_VERSION="$(grep -oP 'const API_VERSION.*\;' "$API_VERSION_FILE" | grep -oP [0-9]+.[0-9]+ | head -1)"
API_VERSION_NUMB="$(echo "$API_VERSION" | sed 's/\.//')"
LOCAL_API_VERSION_NUMB="$(curl -s --unix-socket /var/run/docker.sock http://"$API_VERSION"/version | sed 's/,/\n/g' | grep ApiVersion | grep -oP [0-9]+.[0-9]+ | head -1 | sed 's/\.//')"
if [ -n "$LOCAL_API_VERSION_NUMB" ] && [ -n "$API_VERSION_NUMB" ]; then
if ! [ "$LOCAL_API_VERSION_NUMB" -ge "$API_VERSION_NUMB" ]; then
echo "Docker v$API_VERSION is not supported by your docker engine. Cannot proceed."
exit 1
fi
else
echo "LOCAL_API_VERSION_NUMB or API_VERSION_NUMB are not set correctly. Cannot check if the API version is supported."
sleep 10
fi
# Adjust data permissions
mkdir -p /mnt/docker-aio-config/data/
mkdir -p /mnt/docker-aio-config/session/
# Adjust caddy permissions
mkdir -p /mnt/docker-aio-config/caddy/
# Adjust certs
GENERATED_CERTS="/mnt/docker-aio-config/certs"
TMP_CERTS="/etc/apache2/certs"
mkdir -p "$GENERATED_CERTS"
cd "$GENERATED_CERTS"
if ! [ -f ./ssl.crt ] && ! [ -f ./ssl.key ]; then
openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 -subj "/C=DE/ST=BE/L=Local/O=Dev/CN=nextcloud.local" -keyout ./ssl.key -out ./ssl.crt
fi
if [ -f ./ssl.crt ] && [ -f ./ssl.key ]; then
cd "$TMP_CERTS"
rm ./ssl.crt
rm ./ssl.key
cp "$GENERATED_CERTS/ssl.crt" ./
cp "$GENERATED_CERTS/ssl.key" ./
fi
print_green "Initial startup of Nextcloud All In One complete!
You should be able to open the Nextcloud AIO Interface now on port 8080 of this server!
E.g. https://internal.ip.of.this.server:8080
If your server has port 80 and 8443 open and you point a domain to your server, you can get a valid certificate automatially by opening the Nextcloud AIO Interface via:
https://your-domain-that-points-to-this-server.tld:8443"
exec "$@"

View file

@ -0,0 +1,30 @@
[supervisord]
nodaemon=true
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:apache]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=apache2-foreground
[program:caddy]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/usr/bin/caddy run -config /Caddyfile
[program:cron]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/cron.sh

View file

@ -0,0 +1,244 @@
# From https://github.com/nextcloud/docker/blob/master/22/fpm/Dockerfile
FROM php:8.0-fpm-bullseye
# entrypoint.sh and cron.sh dependencies
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
rsync \
bzip2 \
; \
rm -rf /var/lib/apt/lists/*;
# install the PHP extensions we need
# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html
ENV PHP_MEMORY_LIMIT 512M
ENV PHP_UPLOAD_LIMIT 512M
RUN set -ex; \
\
savedAptMark="$(apt-mark showmanual)"; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
libcurl4-openssl-dev \
libevent-dev \
libfreetype6-dev \
libicu-dev \
libjpeg-dev \
libldap2-dev \
libmcrypt-dev \
libmemcached-dev \
libpng-dev \
libpq-dev \
libxml2-dev \
libmagickwand-dev \
libzip-dev \
libwebp-dev \
libgmp-dev \
; \
\
debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \
docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \
docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \
docker-php-ext-install -j "$(nproc)" \
bcmath \
exif \
gd \
intl \
ldap \
opcache \
pcntl \
pdo_mysql \
pdo_pgsql \
zip \
gmp \
; \
\
# pecl will claim success even if one install fails, so we need to perform each install separately
pecl install APCu-5.1.20; \
pecl install memcached-3.1.5; \
pecl install redis-5.3.4; \
pecl install imagick-3.5.1; \
\
docker-php-ext-enable \
apcu \
memcached \
redis \
imagick \
; \
rm -r /tmp/pear; \
\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark; \
ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \
| awk '/=>/ { print $3 }' \
| sort -u \
| xargs -r dpkg-query -S \
| cut -d: -f1 \
| sort -u \
| xargs -rt apt-mark manual; \
\
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*
# set recommended PHP.ini settings
# see https://docs.nextcloud.com/server/stable/admin_manual/configuration_server/server_tuning.html#enable-php-opcache
RUN { \
echo 'opcache.enable=1'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=10000'; \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.save_comments=1'; \
echo 'opcache.revalidate_freq=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini; \
\
echo 'apc.enable_cli=1' >> /usr/local/etc/php/conf.d/docker-php-ext-apcu.ini; \
\
{ \
echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \
echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \
echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \
} > /usr/local/etc/php/conf.d/nextcloud.ini; \
\
mkdir /var/www/data; \
chown -R www-data:root /var/www; \
chmod -R g=u /var/www
VOLUME /var/www/html
ENV NEXTCLOUD_VERSION 22.2.3
RUN set -ex; \
fetchDeps=" \
gnupg \
dirmngr \
"; \
apt-get update; \
apt-get install -y --no-install-recommends $fetchDeps; \
\
curl -fsSL -o nextcloud.tar.bz2 \
"https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2"; \
curl -fsSL -o nextcloud.tar.bz2.asc \
"https://download.nextcloud.com/server/releases/nextcloud-${NEXTCLOUD_VERSION}.tar.bz2.asc"; \
export GNUPGHOME="$(mktemp -d)"; \
# gpg key from https://nextcloud.com/nextcloud.asc
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 28806A878AE423A28372792ED75899B9A724937A; \
gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2; \
tar -xjf nextcloud.tar.bz2 -C /usr/src/; \
gpgconf --kill all; \
rm nextcloud.tar.bz2.asc nextcloud.tar.bz2; \
rm -rf "$GNUPGHOME" /usr/src/nextcloud/updater; \
mkdir -p /usr/src/nextcloud/data; \
mkdir -p /usr/src/nextcloud/custom_apps; \
chmod +x /usr/src/nextcloud/occ; \
\
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $fetchDeps; \
rm -rf /var/lib/apt/lists/*
COPY *.sh upgrade.exclude /
COPY config/* /usr/src/nextcloud/config/
ENTRYPOINT ["/entrypoint.sh"]
CMD ["php-fpm"]
# Template from https://github.com/nextcloud/docker/blob/master/.examples/dockerfiles/full/fpm/Dockerfile
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
ffmpeg \
libmagickcore-6.q16-6-extra \
procps \
smbclient \
supervisor \
# libreoffice \
; \
rm -rf /var/lib/apt/lists/*
RUN set -ex; \
\
savedAptMark="$(apt-mark showmanual)"; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
libbz2-dev \
libc-client-dev \
libkrb5-dev \
libsmbclient-dev \
; \
\
docker-php-ext-configure imap --with-kerberos --with-imap-ssl; \
docker-php-ext-install \
bz2 \
imap \
; \
pecl install smbclient; \
docker-php-ext-enable smbclient; \
\
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark; \
ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \
| awk '/=>/ { print $3 }' \
| sort -u \
| xargs -r dpkg-query -S \
| cut -d: -f1 \
| sort -u \
| xargs -rt apt-mark manual; \
\
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*
RUN mkdir -p \
/var/log/supervisord \
/var/run/supervisord \
;
COPY supervisord.conf /
ENV NEXTCLOUD_UPDATE=1
CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"]
# Custom:
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
netcat \
openssl \
gnupg \
dirmngr \
; \
rm -rf /var/lib/apt/lists/*
RUN chown www-data:root -R /usr/src && \
chown www-data:root -R /usr/local/etc/php/conf.d && \
chown www-data:root -R /var/log/supervisord/ && \
chown www-data:root -R /var/run/supervisord/ && \
mkdir -p /var/log/nextcloud/ && \
chown -R www-data:root /var/log/nextcloud/
COPY start.sh /
COPY notify.sh /
RUN chmod +x /start.sh && \
chmod +r /supervisord.conf && \
chmod +x /entrypoint.sh && \
chmod +r /upgrade.exclude && \
chmod +x /cron.sh && \
chmod +x /notify.sh
RUN mkdir /mnt/ncdata; \
chown www-data:www-data /mnt/ncdata;
VOLUME /mnt/ncdata
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER www-data
ENTRYPOINT ["/start.sh"]

View file

@ -0,0 +1,4 @@
<?php
$CONFIG = array (
'memcache.local' => '\OC\Memcache\APCu',
);

View file

@ -0,0 +1,15 @@
<?php
$CONFIG = array (
'apps_paths' => array (
0 => array (
'path' => OC::$SERVERROOT.'/apps',
'url' => '/apps',
'writable' => false,
),
1 => array (
'path' => OC::$SERVERROOT.'/custom_apps',
'url' => '/custom_apps',
'writable' => true,
),
),
);

View file

@ -0,0 +1,17 @@
<?php
if (getenv('REDIS_HOST')) {
$CONFIG = array(
'memcache.distributed' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' => array(
'host' => getenv('REDIS_HOST'),
'password' => (string) getenv('REDIS_HOST_PASSWORD'),
),
);
if (getenv('REDIS_HOST_PORT') !== false) {
$CONFIG['redis']['port'] = (int) getenv('REDIS_HOST_PORT');
} elseif (getenv('REDIS_HOST')[0] != '/') {
$CONFIG['redis']['port'] = 6379;
}
}

View file

@ -0,0 +1,25 @@
<?php
$overwriteHost = getenv('OVERWRITEHOST');
if ($overwriteHost) {
$CONFIG['overwritehost'] = $overwriteHost;
}
$overwriteProtocol = getenv('OVERWRITEPROTOCOL');
if ($overwriteProtocol) {
$CONFIG['overwriteprotocol'] = $overwriteProtocol;
}
$overwriteWebRoot = getenv('OVERWRITEWEBROOT');
if ($overwriteWebRoot) {
$CONFIG['overwritewebroot'] = $overwriteWebRoot;
}
$overwriteCondAddr = getenv('OVERWRITECONDADDR');
if ($overwriteCondAddr) {
$CONFIG['overwritecondaddr'] = $overwriteCondAddr;
}
$trustedProxies = getenv('TRUSTED_PROXIES');
if ($trustedProxies) {
$CONFIG['trusted_proxies'] = array_filter(array_map('trim', explode(' ', $trustedProxies)));
}

View file

@ -0,0 +1,7 @@
#!/bin/sh
set -eu
while true; do
php -f /var/www/html/cron.php &
sleep 5m
done

View file

@ -0,0 +1,254 @@
#!/bin/sh
# version_greater A B returns whether A > B
version_greater() {
[ "$(printf '%s\n' "$@" | sort -t '.' -n -k1,1 -k2,2 -k3,3 -k4,4 | head -n 1)" != "$1" ]
}
# return true if specified directory is empty
directory_empty() {
[ -z "$(ls -A "$1/")" ]
}
echo "Configuring Redis as session handler"
cat << REDIS_CONF > /usr/local/etc/php/conf.d/redis-session.ini
session.save_handler = redis
session.save_path = "tcp://${REDIS_HOST}:${REDIS_HOST_PORT:=6379}?auth=${REDIS_HOST_PASSWORD}"
redis.session.locking_enabled = 1
redis.session.lock_retries = -1
# redis.session.lock_wait_time is specified in microseconds.
# Wait 10ms before retrying the lock rather than the default 2ms.
redis.session.lock_wait_time = 10000
REDIS_CONF
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);')"
else
installed_version="0.0.0.0"
fi
if [ -f "/usr/src/nextcloud/version.php" ]; then
# shellcheck disable=SC2016
image_version="$(php -r 'require "/usr/src/nextcloud/version.php"; echo implode(".", $OC_Version);')"
else
image_version="$installed_version"
fi
# unset admin password
if [ "$installed_version" != "0.0.0.0" ]; then
unset ADMIN_PASSWORD
fi
if version_greater "$image_version" "$installed_version"; then
# Check if it skips a major version
INSTALLED_MAJOR="${installed_version%%.*}"
IMAGE_MAJOR="${image_version%%.*}"
if [ "$installed_version" != "0.0.0.0" ] && [ "$((IMAGE_MAJOR - INSTALLED_MAJOR))" -gt 1 ]; then
set -ex
NEXT_MAJOR="$((INSTALLED_MAJOR + 1))"
curl -fsSL -o nextcloud.tar.bz2 "https://download.nextcloud.com/server/releases/latest-${NEXT_MAJOR}.tar.bz2"
curl -fsSL -o nextcloud.tar.bz2.asc "https://download.nextcloud.com/server/releases/latest-${NEXT_MAJOR}.tar.bz2.asc"
export GNUPGHOME="$(mktemp -d)"
# gpg key from https://nextcloud.com/nextcloud.asc
gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 28806A878AE423A28372792ED75899B9A724937A
gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2
mkdir -p /usr/src/tmp
tar -xjf nextcloud.tar.bz2 -C /usr/src/tmp/
gpgconf --kill all
rm nextcloud.tar.bz2.asc nextcloud.tar.bz2
rm -rf "$GNUPGHOME" /usr/src/tmp/nextcloud/updater
mkdir -p /usr/src/tmp/nextcloud/data
mkdir -p /usr/src/tmp/nextcloud/custom_apps
chmod +x /usr/src/tmp/nextcloud/occ
cp /usr/src/nextcloud/config/* /usr/src/tmp/nextcloud/config/
mv /usr/src/nextcloud /usr/src/temp-nextcloud
mv /usr/src/tmp/nextcloud /usr/src/nextcloud
rm -r /usr/src/tmp
rm -r /usr/src/temp-nextcloud
# shellcheck disable=SC2016
image_version="$(php -r 'require "/usr/src/nextcloud/version.php"; echo implode(".", $OC_Version);')"
IMAGE_MAJOR="${image_version%%.*}"
set +ex
fi
if [ "$installed_version" != "0.0.0.0" ]; then
while true; do
echo -e "Checking connection to appstore"
CURL_STATUS="$(curl -LI "https://apps.nextcloud.com/" -o /dev/null -w '%{http_code}\n' -s)"
if [[ "$CURL_STATUS" = "200" ]]
then
echo "Appstore is reachable"
break
else
echo "Curl didn't produce a 200 status, is appstore reachable?"
fi
done
php /var/www/html/occ maintenance:mode --off
echo "Getting and backing up the status of apps for later, this might take a while..."
php /var/www/html/occ app:list | sed -n "/Enabled:/,/Disabled:/p" > /tmp/list_before
if [ "$((IMAGE_MAJOR - INSTALLED_MAJOR))" -eq 1 ]; then
php /var/www/html/occ config:system:delete app_install_overwrite
fi
php /var/www/html/occ app:update --all
fi
echo "Initializing nextcloud $image_version ..."
rsync -rlD --delete --exclude-from=/upgrade.exclude /usr/src/nextcloud/ /var/www/html/
for dir in config data custom_apps themes; do
if [ ! -d "/var/www/html/$dir" ] || directory_empty "/var/www/html/$dir"; then
rsync -rlD --include "/$dir/" --exclude '/*' /usr/src/nextcloud/ /var/www/html/
fi
done
rsync -rlD --include '/version.php' --exclude '/*' /usr/src/nextcloud/ /var/www/html/
echo "Initializing finished"
#install
if [ "$installed_version" = "0.0.0.0" ]; then
echo "New nextcloud instance"
INSTALL_OPTIONS=(-n --admin-user "$ADMIN_USER" --admin-pass "$ADMIN_PASSWORD")
if [ -n "${NEXTCLOUD_DATA_DIR}" ]; then
INSTALL_OPTIONS+=(--data-dir "$NEXTCLOUD_DATA_DIR")
fi
echo "Installing with PostgreSQL database"
INSTALL_OPTIONS+=(--database pgsql --database-name "$POSTGRES_DB" --database-user "$POSTGRES_USER" --database-pass "$POSTGRES_PASSWORD" --database-host "$POSTGRES_HOST")
echo "starting nextcloud installation"
max_retries=10
try=0
until php /var/www/html/occ maintenance:install "${INSTALL_OPTIONS[@]}" || [ "$try" -gt "$max_retries" ]
do
echo "retrying install..."
try=$((try+1))
sleep 10s
done
if [ "$try" -gt "$max_retries" ]; then
echo "installing of nextcloud failed!"
exit 1
fi
# unset admin password
unset ADMIN_PASSWORD
# Apply log settings
echo "Applying default settings..."
mkdir -p /var/www/html/data
php /var/www/html/occ config:system:set loglevel --value=2
php /var/www/html/occ config:system:set log_type --value=file
php /var/www/html/occ config:system:set logfile --value="/var/log/nextcloud/nextcloud.log"
php /var/www/html/occ config:system:set log_rotate_size --value="10485760"
php /var/www/html/occ app:enable admin_audit
php /var/www/html/occ config:app:set admin_audit logfile --value="/var/log/nextcloud/audit.log"
php /var/www/html/occ config:system:set log.condition apps 0 --value="admin_audit"
# Apply preview settings
echo "Applying preview settings..."
php /var/www/html/occ config:system:set preview_max_x --value="2048"
php /var/www/html/occ config:system:set preview_max_y --value="2048"
php /var/www/html/occ config:system:set jpeg_quality --value="60"
php /var/www/html/occ config:app:set preview jpeg_quality --value="60"
php /var/www/html/occ config:system:delete enabledPreviewProviders
php /var/www/html/occ config:system:set enabledPreviewProviders 1 --value="OC\\Preview\\Image"
php /var/www/html/occ config:system:set enabledPreviewProviders 2 --value="OC\\Preview\\MarkDown"
php /var/www/html/occ config:system:set enabledPreviewProviders 3 --value="OC\\Preview\\MP3"
php /var/www/html/occ config:system:set enabledPreviewProviders 4 --value="OC\\Preview\\TXT"
php /var/www/html/occ config:system:set enabledPreviewProviders 5 --value="OC\\Preview\\OpenDocument"
php /var/www/html/occ config:system:set enabledPreviewProviders 6 --value="OC\\Preview\\Movie"
php /var/www/html/occ config:system:set enable_previews --value=true --type=boolean
# Apply other settings
echo "Applying other settings..."
php /var/www/html/occ config:system:set upgrade.disable-web --type=bool --value=true
php /var/www/html/occ config:app:set updatenotification notify_groups --value="[]"
php /var/www/html/occ config:system:set mail_smtpmode --value="smtp"
php /var/www/html/occ config:system:set trashbin_retention_obligation --value="auto, 30"
php /var/www/html/occ config:system:set versions_retention_obligation --value="auto, 30"
php /var/www/html/occ config:system:set activity_expire_days --value="30"
php /var/www/html/occ config:system:set simpleSignUpLink.shown --type=bool --value=false
php /var/www/html/occ config:system:set share_folder --value="/Shared"
#upgrade
else
while [ -n "$(pgrep -f cron.php)" ]
do
echo "Waiting for Nextclouds cronjob to finish..."
sleep 5
done
echo "Upgrading nextcloud from $installed_version to $image_version..."
if ! php /var/www/html/occ upgrade || ! php /var/www/html/occ -V; then
echo "Upgrade failed. Please restore from backup."
exit 1
fi
php /var/www/html/occ app:list | sed -n "/Enabled:/,/Disabled:/p" > /tmp/list_after
echo "The following apps have been disabled:"
diff /tmp/list_before /tmp/list_after | grep '<' | cut -d- -f2 | cut -d: -f1
rm -f /tmp/list_before /tmp/list_after
# Apply optimization
echo "Doing some optimizations..."
php /var/www/html/occ maintenance:repair
php /var/www/html/occ db:add-missing-indices
php /var/www/html/occ db:add-missing-columns
php /var/www/html/occ db:add-missing-primary-keys
yes | php /var/www/html/occ db:convert-filecache-bigint
php /var/www/html/occ maintenance:mimetype:update-js
php /var/www/html/occ maintenance:mimetype:update-db
fi
fi
# Apply one-click-instance settings
echo "Applying one-click-instance settings..."
php /var/www/html/occ config:system:set one-click-instance --value=true --type=bool
php /var/www/html/occ config:system:set one-click-instance.user-limit --value=100 --type=int
# Apply network settings
echo "Applying network settings..."
php /var/www/html/occ config:system:set trusted_domains 1 --value="$NC_DOMAIN"
php /var/www/html/occ config:system:set overwrite.cli.url --value="https://$NC_DOMAIN/"
php /var/www/html/occ config:system:set htaccess.RewriteBase --value="/"
php /var/www/html/occ maintenance:update:htaccess
# Notify push
if ! [ -d "/var/www/html/custom_apps/notify_push" ]; then
php /var/www/html/occ app:install notify_push
elif [ "$(php /var/www/html/occ config:app:get notify_push enabled)" = "no" ]; then
php /var/www/html/occ app:enable notify_push
else
php /var/www/html/occ app:update notify_push
fi
php /var/www/html/occ config:app:set notify_push base_endpoint --value="https://$NC_DOMAIN/push"
# Collabora
if ! [ -d "/var/www/html/custom_apps/richdocuments" ]; then
php /var/www/html/occ app:install richdocuments
elif [ "$(php /var/www/html/occ config:app:get richdocuments enabled)" = "no" ]; then
php /var/www/html/occ app:enable richdocuments
else
php /var/www/html/occ app:update richdocuments
fi
php /var/www/html/occ config:app:set richdocuments wopi_url --value="https://$NC_DOMAIN/"
# php /var/www/html/occ richdocuments:activate-config
# Talk
if ! [ -d "/var/www/html/custom_apps/spreed" ]; then
php /var/www/html/occ app:install spreed
elif [ "$(php /var/www/html/occ config:app:get spreed enabled)" = "no" ]; then
php /var/www/html/occ app:enable spreed
else
php /var/www/html/occ app:update spreed
fi
STUN_SERVERS="[\"$NC_DOMAIN:3478\"]"
TURN_SERVERS="[{\"server\":\"$NC_DOMAIN:3478\",\"secret\":\"$TURN_SECRET\",\"protocols\":\"udp,tcp\"}]"
SIGNALING_SERVERS="{\"servers\":[{\"server\":\"https://$NC_DOMAIN/standalone-signaling/\",\"verify\":true}],\"secret\":\"$SIGNALING_SECRET\"}"
php /var/www/html/occ config:app:set spreed stun_servers --value="$STUN_SERVERS" --output json
php /var/www/html/occ config:app:set spreed turn_servers --value="$TURN_SERVERS" --output json
php /var/www/html/occ config:app:set spreed signaling_servers --value="$SIGNALING_SERVERS" --output json

View file

@ -0,0 +1,29 @@
#!/bin/bash
SUBJECT="$1"
MESSAGE="$2"
if [ "$(php /var/www/html/occ config:app:get notificaations enabled)" = "no" ]; then
echo "Cannot send notification as notification app is not enabled."
exit 1
fi
echo "Posting notifications to users that are admins..."
NC_USERS=$(php /var/www/html/occ user:list | sed 's|^ - ||g' | sed 's|:.*||')
mapfile -t NC_USERS <<< "$NC_USERS"
for user in "${NC_USERS[@]}"
do
if php /var/www/html/occ user:info "$user" | cut -d "-" -f2 | grep -x -q " admin"
then
NC_ADMIN_USER+=("$user")
fi
done
for admin in "${NC_ADMIN_USER[@]}"
do
echo "Posting '$SUBJECT' to: $admin"
php /var/www/html/occ notification:generate "$admin" "$NC_DOMAIN: $SUBJECT" -l "$MESSAGE"
done
echo "Done!"
exit 0

View file

@ -0,0 +1,14 @@
#!/bin/bash
# Only start container if database is accessible
while ! nc -z "$POSTGRES_HOST" 5432; do
echo "Waiting for database to start..."
sleep 5
done
# Run original entrypoint
if ! bash /entrypoint.sh; then
exit 1
fi
exec "$@"

View file

@ -0,0 +1,30 @@
# From https://github.com/nextcloud/docker/blob/master/.examples/dockerfiles/full/fpm/supervisord.conf
[supervisord]
nodaemon=true
logfile=/var/log/supervisord/supervisord.log
pidfile=/var/run/supervisord/supervisord.pid
childlogdir=/var/log/supervisord/
logfile_maxbytes=50MB ; maximum size of logfile before rotation
logfile_backups=10 ; number of backed up logfiles
loglevel=error
[program:php-fpm]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=php-fpm
[program:cron]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/cron.sh
[program:notify-push]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/var/www/html/custom_apps/notify_push/bin/x86_64/notify_push /var/www/html/config/config.php --port 7867 --redis-url redis://:%(ENV_REDIS_HOST_PASSWORD)s@%(ENV_REDIS_HOST)s

View file

@ -0,0 +1,5 @@
/config/
/data/
/custom_apps/
/themes/
/version.php

View file

@ -0,0 +1,24 @@
# From https://github.com/docker-library/postgres/blob/master/13/buster/Dockerfile
FROM postgres:13-buster
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
openssl \
; \
rm -rf /var/lib/apt/lists/*
COPY start.sh /usr/bin/
RUN chmod +x /usr/bin/start.sh
RUN mkdir /mnt/data; \
chown postgres:postgres /mnt/data;
VOLUME /mnt/data
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER postgres
ENTRYPOINT ["start.sh"]

View file

@ -0,0 +1,87 @@
#!/bin/bash
# Variables
DATADIR="/var/lib/postgresql/data"
DUMP_DIR="/mnt/data"
DUMP_FILE="$DUMP_DIR/database-dump.sql"
export PGPASSWORD="$POSTGRES_PASSWORD"
# Don't start database as long as backup is running
while [ -f "$DUMP_DIR/backup-is-running" ]; do
echo "Waiting for backup container to finish..."
sleep 10
done
# Check if dump dir is writeable
if ! [ -w "$DUMP_DIR" ]; then
echo "DUMP dir is not writeable by postgres user."
exit 1
fi
# Test if some things match
if ( [ -f "$DATADIR/PG_VERSION" ] && [ "$PG_MAJOR" != "$(cat "$DATADIR/PG_VERSION")" ] ) \
|| ( ! [ -f "$DATADIR/PG_VERSION" ] && [ -f "$DUMP_FILE" ] ); then
# The DUMP_file must be provided
if ! [ -f "$DUMP_FILE" ]; then
echo "Unable to restore the database because the database dump is missing."
exit 1
fi
# Inform
echo "Restoring from database dump."
# Exit if any command fails
set -e
# Remove old database files
rm -rf "$DATADIR/"*
# Change database port to a random port temporarily
export PGPORT=11000
# Create new database
exec docker-entrypoint.sh postgres &
# Wait 2s for creation
sleep 2s
# Restore database
echo "Restoring the database from database dump"
psql "$POSTGRES_DB" -U "$POSTGRES_USER" < "$DUMP_FILE"
# Shut down the database to be able to start it again
pg_ctl stop -m fast
# Change database port back to default
export PGPORT=5432
# Don't exit if command fails anymore
set +e
fi
# Cover the last case
if ! [ -f "$DATADIR/PG_VERSION" ] && ! [ -f "$DUMP_FILE" ]; then
# Remove old database files if somehow there should be some
rm -rf "$DATADIR/"*
fi
# Catch docker stop attempts
trap 'true' SIGINT SIGTERM
# Start the database
exec docker-entrypoint.sh postgres &
wait $!
# Continue with shutdown procedure: do database dump, etc.
rm -f "$DUMP_FILE.temp"
if pg_dump --username "$POSTGRES_USER" "$POSTGRES_DB" > "$DUMP_FILE.temp"; then
rm -f "$DUMP_FILE"
mv "$DUMP_FILE.temp" "$DUMP_FILE"
pg_ctl stop -m fast
echo 'Database dump successful!'
exit 0
else
pg_ctl stop -m fast
echo "Database dump unsucessful!"
exit 1
fi

View file

@ -0,0 +1,19 @@
# From https://github.com/docker-library/redis/blob/master/6.2/Dockerfile
FROM redis:6.2-buster
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
openssl \
; \
rm -rf /var/lib/apt/lists/*
COPY start.sh /usr/bin/
RUN chmod +x /usr/bin/start.sh
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER redis
ENTRYPOINT ["start.sh"]

10
Containers/redis/start.sh Normal file
View file

@ -0,0 +1,10 @@
#!/bin/bash
# Run redis with a password if provided
if [ -n "$REDIS_HOST_PASSWORD" ]; then
exec redis-server --requirepass "$REDIS_HOST_PASSWORD"
else
exec redis-server
fi
exec "$@"

View file

@ -0,0 +1,64 @@
FROM ubuntu:focal
EXPOSE 3478
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
openssl \
coturn \
supervisor \
curl \
ca-certificates \
; \
rm -rf /var/lib/apt/lists/*
RUN set -ex; \
curl -sL -o "/etc/apt/trusted.gpg.d/morph027-nats-server.asc" "https://packaging.gitlab.io/nats-server/gpg.key"; \
echo "deb https://packaging.gitlab.io/nats-server nats main" > /etc/apt/sources.list.d/morph027-nats-server.list; \
. /etc/lsb-release; \
curl -sL -o "/etc/apt/trusted.gpg.d/morph027-janus.asc" "https://packaging.gitlab.io/janus/gpg.key"; \
echo "deb https://packaging.gitlab.io/janus/$DISTRIB_CODENAME $DISTRIB_CODENAME main" > /etc/apt/sources.list.d/morph027-janus.list; \
curl -sL -o "/etc/apt/trusted.gpg.d/morph027-nextcloud-spreed-signaling.asc" "https://packaging.gitlab.io/nextcloud-spreed-signaling/gpg.key"; \
echo "deb https://packaging.gitlab.io/nextcloud-spreed-signaling signaling main" > /etc/apt/sources.list.d/morph027-nextcloud-spreed-signaling.list
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
nats-server \
janus \
nextcloud-spreed-signaling \
; \
rm -rf /var/lib/apt/lists/*
RUN adduser --system --group talk
RUN mkdir /var/log/supervisord; \
mkdir /var/run/supervisord; \
chown talk:talk /var/run/supervisord; \
chown talk:talk /var/log/supervisord;
COPY start.sh /usr/bin/
COPY supervisord.conf /
RUN chmod +x /usr/bin/start.sh; \
chmod +r /supervisord.conf; \
touch /etc/turnserver.conf; \
chown talk:talk /etc/turnserver.conf; \
sed -i '/TURNSERVER_ENABLED/c\TURNSERVER_ENABLED=1' /etc/default/coturn; \
mkdir -p /var/tmp;
RUN mkdir -p /etc/nats; \
echo "listen: 127.0.0.1:4222" > /etc/nats/nats.conf; \
chown talk:talk -R /etc/nats; \
chown talk:talk -R /etc/janus; \
chown talk:talk -R /etc/signaling; \
chown talk:talk -R /usr/share/janus
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
USER talk
ENTRYPOINT ["start.sh"]
CMD ["/usr/bin/supervisord", "-c", "/supervisord.conf"]

67
Containers/talk/start.sh Normal file
View file

@ -0,0 +1,67 @@
#!/bin/bash
# Variables
if [ -z "$NC_DOMAIN" ]; then
echo "You need to provide the NC_DOMAIN."
exit 1
elif [ -z "$TURN_SECRET" ]; then
echo "You need to provide the TURN_SECRET."
exit 1
elif [ -z "$JANUS_API_KEY" ]; then
echo "You need to provide the JANUS_API_KEY."
exit 1
elif [ -z "$SIGNALING_SECRET" ]; then
echo "You need to provide the JANUS_API_KEY."
exit 1
fi
# Turn
cat << TURN_CONF > "/etc/turnserver.conf"
listening-port=3478
fingerprint
use-auth-secret
static-auth-secret=$TURN_SECRET
realm=$NC_DOMAIN
total-quota=100
bps-capacity=0
stale-nonce
no-multicast-peers
simple-log
pidfile=/var/tmp/turnserver.pid
TURN_CONF
# Janus
sed -i "s|#turn_rest_api_key.*|turn_rest_api_key = $JANUS_API_KEY|" /etc/janus/janus.jcfg
sed -i "s|#full_trickle|full_trickle|g" /etc/janus/janus.jcfg
sed -i 's|#interface.*|interface = "lo"|g' /etc/janus/janus.transport.websockets.jcfg
sed -i 's|#ws_interface.*|ws_interface = "lo"|g' /etc/janus/janus.transport.websockets.jcfg
# Signling
cat << SIGNALING_CONF > "/etc/signaling/server.conf"
[http]
listen = 0.0.0.0:8081
[app]
debug = false
[sessions]
hashkey = $(openssl rand -hex 16)
blockkey = $(openssl rand -hex 16)
[clients]
internalsecret = $(openssl rand -hex 16)
[backend]
allowed = ${NC_DOMAIN}
allowall = false
secret = ${SIGNALING_SECRET}
timeout = 10
connectionsperhost = 8
[nats]
url = nats://127.0.0.1:4222
[mcu]
type = janus
url = ws://127.0.0.1:8188
[turn]
apikey = ${JANUS_API_KEY}
secret = ${TURN_SECRET}
servers = turn:$NC_DOMAIN:3478?transport=tcp,turn:$NC_DOMAIN:3478?transport=udp
SIGNALING_CONF
exec "$@"

View file

@ -0,0 +1,37 @@
[supervisord]
nodaemon=true
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:turnserver]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=turnserver
[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/nats.conf
[program:janus]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=janus
[program:signaling]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=signaling -config /etc/signaling/server.conf

View file

@ -0,0 +1,28 @@
# From https://github.com/containrrr/watchtower/blob/main/dockerfiles/Dockerfile.self-contained
FROM containrrr/watchtower:latest as watchtower
FROM debian:bullseye
RUN set -ex; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
ca-certificates \
openssl \
; \
rm -rf /var/lib/apt/lists/*
COPY --from=watchtower /watchtower /
COPY start.sh /
RUN chmod +x /start.sh
# Give root a random password
RUN echo "root:$(openssl rand -base64 12)" | chpasswd
# add docker group
RUN groupadd -g 998 docker && \
usermod -aG docker nobody
USER nobody
ENTRYPOINT ["/start.sh"]

View file

@ -0,0 +1,19 @@
#!/bin/bash
# Check if socket is available and readable
if ! [ -a "/var/run/docker.sock" ]; then
echo "Docker socket is not available. Cannot continue."
exit 1
elif ! [ -r "/var/run/docker.sock" ]; then
echo "Docker socket is not readable by the nobody user. Cannot continue."
exit 1
fi
if [ -n "$CONTAINER_TO_UPDATE" ]; then
exec /watchtower --cleanup --run-once "$CONTAINER_TO_UPDATE"
else
echo "'CONTAINER_TO_UPDATE' is not set. Cannot update anything."
fi
exec "$@"

19
app/.editorconfig Normal file
View file

@ -0,0 +1,19 @@
# https://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.feature]
indent_size = 2
indent_style = space
[*.yml]
indent_size = 2
indent_style = space

32
app/appinfo/info.xml Normal file
View file

@ -0,0 +1,32 @@
<?xml version="1.0"?>
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>nextcloud-aio</id>
<name>Nextcloud All In One</name>
<summary>Provides a login link for admins.</summary>
<description>Add a link to the admin settings that gives access to the Nextcloud All In One admin interface</description>
<version>0.1.0</version>
<licence>agpl</licence>
<author>Azul</author>
<namespace>AllInOne</namespace>
<default_enable/>
<category>monitoring</category>
<bugs>https://github.com/nextcloud/all-in-one/issues</bugs>
<dependencies>
<nextcloud min-version="22" max-version="23"/>
</dependencies>
<settings>
<admin>OCA\AllInOne\Settings\Admin</admin>
</settings>
<!-- not implemented yet - but might be useful:
<background-jobs>
<job>OCA\AllInOne\Notification\BackgroundJob</job>
</background-jobs>
<commands>
<command>OCA\UpdateNotification\Command\Check</command>
</commands>
-->
</info>

View file

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitAllInOne::getLoader();

View file

@ -0,0 +1,13 @@
{
"config" : {
"vendor-dir": ".",
"optimize-autoloader": true,
"classmap-authoritative": true,
"autoloader-suffix": "AllInOne"
},
"autoload" : {
"psr-4": {
"OCA\\AllInOne\\": "../lib/"
}
}
}

18
app/composer/composer.lock generated Normal file
View file

@ -0,0 +1,18 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d751713988987e9331980363e24189ce",
"packages": [],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": [],
"platform-dev": [],
"plugin-api-version": "2.1.0"
}

View file

@ -0,0 +1,481 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
private $vendorDir;
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}

View file

@ -0,0 +1,337 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require it's presence, you can require `composer-runtime-api ^2.0`
*/
class InstalledVersions
{
private static $installed;
private static $canGetVendors;
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = require __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
$installed[] = self::$installed;
return $installed;
}
}

View file

@ -0,0 +1,21 @@
Copyright (c) Nils Adermann, Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,11 @@
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = $vendorDir;
return array(
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'OCA\\AllInOne\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
);

View file

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = $vendorDir;
return array(
);

View file

@ -0,0 +1,10 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = $vendorDir;
return array(
'OCA\\AllInOne\\' => array($baseDir . '/../lib'),
);

View file

@ -0,0 +1,46 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitAllInOne
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInitAllInOne', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitAllInOne', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitAllInOne::getInitializer($loader));
} else {
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->setClassMapAuthoritative(true);
$loader->register(true);
return $loader;
}
}

View file

@ -0,0 +1,37 @@
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitAllInOne
{
public static $prefixLengthsPsr4 = array (
'O' =>
array (
'OCA\\AllInOne\\' => 13,
),
);
public static $prefixDirsPsr4 = array (
'OCA\\AllInOne\\' =>
array (
0 => __DIR__ . '/..' . '/../lib',
),
);
public static $classMap = array (
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'OCA\\AllInOne\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitAllInOne::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitAllInOne::$prefixDirsPsr4;
$loader->classMap = ComposerStaticInitAllInOne::$classMap;
}, null, ClassLoader::class);
}
}

View file

@ -0,0 +1,5 @@
{
"packages": [],
"dev": true,
"dev-package-names": []
}

View file

@ -0,0 +1,23 @@
<?php return array(
'root' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
'reference' => '1b16a136ebd8f63e09df061d383f34170e2cef35',
'name' => '__root__',
'dev' => true,
),
'versions' => array(
'__root__' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'type' => 'library',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
'reference' => '1b16a136ebd8f63e09df061d383f34170e2cef35',
'dev_requirement' => false,
),
),
);

View file

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2021, Azul <azul@riseup.net>
*
* @author Azul <azul@riseup.net>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OCA\AllInOne\Settings;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
use OCP\L10N\IFactory;
use OCP\Settings\ISettings;
class Admin implements ISettings {
/** @var IConfig */
private $config;
/** @var IDateTimeFormatter */
private $dateTimeFormatter;
/** @var IFactory */
private $l10nFactory;
public function __construct(
IConfig $config,
IDateTimeFormatter $dateTimeFormatter,
IFactory $l10nFactory
) {
$this->config = $config;
$this->dateTimeFormatter = $dateTimeFormatter;
$this->l10nFactory = $l10nFactory;
}
/**
* @return TemplateResponse
*/
public function getForm(): TemplateResponse {
$lastUpdateCheckTimestamp = $this->config->getAppValue('core', 'lastupdatedat');
$lastUpdateCheck = $this->dateTimeFormatter->formatDateTime($lastUpdateCheckTimestamp);
$token = urlencode(getenv('AIO_TOKEN'));
$params = [
'AIOLoginUrl' => 'https://' . getenv('AIO_URL') . '/api/auth/getlogin' . '?token=' . $token,
];
return new TemplateResponse('nextcloud-aio', 'admin', $params, '');
}
/**
* @return string the section ID, e.g. 'sharing'
*/
public function getSection(): string {
return 'overview';
}
/**
* @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100.
*
* E.g.: 70
*/
public function getPriority(): int {
return 5;
}
}

7
app/readme.md Normal file
View file

@ -0,0 +1,7 @@
## How to develope the app?
Please note that in order to check if an app is already downloaded
Nextcloud will look for a folder with the same name as the app.
Therefore you need to add the app to one of the app directories
naming the directory `docker-aio`.

15
app/templates/admin.php Normal file
View file

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* @copyright (c) 2021 Azul <azul@riseup.net>
*
* @author Azul <azul@riseup.net>
*
* This file is licensed under the Affero General Public License version 3 or
* later. See the COPYING file.
*/
/** @var array $_ */ ?>
<div id="allinone" class="section">
<h2><?php p($l->t('Nextcloud All In One'));?></h2>
<a href="<?php p($_['AIOLoginUrl']);?>" class="button" target="_blank" rel="noopener">Open Nextcloud AIO Interface </a>
</div>

40
develop.md Normal file
View file

@ -0,0 +1,40 @@
## Developer channel
If you want to switch to the develop channel, you simply stop and delete the mastercontainer and create a new one with a changed tag to develop:
```shell
sudo docker run -it \
--name nextcloud-aio-mastercontainer \
--restart always \
-p 80:80 \
-p 8080:8080 \
-p 8443:8443 \
--volume nextcloud_aio_mastercontainer:/mnt/docker-aio-config \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
nextcloud/all-in-one:develop
```
And you are done :)
It will now also select the developer channel for all other containers automatically.
## How to promote builds from develop to latest
You can use the Docker CLI to promote builds from develop to latest. Make sure to adjust:
- $name
- $digest
```shell
export AIO_NAME=$name
export AIO_DIGEST=$digest
docker pull nextcloud/$AIO_NAME@sha256:$AIO_DIGEST
docker tag nextcloud/$AIO_NAME@sha256:$AIO_DIGEST nextcloud/$AIO_NAME\:latest
docker push nextcloud/$AIO_NAME\:latest
```
To automatically promoted the latest develop version you can use the following script:
**WARNING:** Make sure to verify that the latest develop tag is what you really want to deploy since someone could have pushed to main and created a new container in between.
```shell
export AIO_NAME=$name
docker pull nextcloud/$AIO_NAME\:develop
docker tag nextcloud/$AIO_NAME\:develop nextcloud/$AIO_NAME\:latest
docker push nextcloud/$AIO_NAME\:latest
```

3
php/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/php/data/configuration.json
/php/data/containers.json

8
php/.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

34
php/.idea/aio.iml Normal file
View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="AIO\" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-middleware" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/log" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-message" />
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-factory" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/container" />
<excludeFolder url="file://$MODULE_DIR$/vendor/http-interop/http-factory-guzzle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-server-handler" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/slim" />
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
<excludeFolder url="file://$MODULE_DIR$/vendor/nikic/fast-route" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/opis/closure" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/slim-bridge" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/invoker" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/php-di" />
<excludeFolder url="file://$MODULE_DIR$/vendor/php-di/phpdoc-reader" />
<excludeFolder url="file://$MODULE_DIR$/vendor/slim/twig-view" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-mbstring" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-ctype" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
php/.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/aio.iml" filepath="$PROJECT_DIR$/.idea/aio.iml" />
</modules>
</component>
</project>

34
php/.idea/php.xml Normal file
View file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/psr/http-server-middleware" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
<path value="$PROJECT_DIR$/vendor/psr/log" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
<path value="$PROJECT_DIR$/vendor/psr/http-message" />
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
<path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/psr/container" />
<path value="$PROJECT_DIR$/vendor/http-interop/http-factory-guzzle" />
<path value="$PROJECT_DIR$/vendor/psr/http-server-handler" />
<path value="$PROJECT_DIR$/vendor/slim/slim" />
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
<path value="$PROJECT_DIR$/vendor/nikic/fast-route" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/opis/closure" />
<path value="$PROJECT_DIR$/vendor/php-di/slim-bridge" />
<path value="$PROJECT_DIR$/vendor/php-di/invoker" />
<path value="$PROJECT_DIR$/vendor/php-di/php-di" />
<path value="$PROJECT_DIR$/vendor/php-di/phpdoc-reader" />
<path value="$PROJECT_DIR$/vendor/slim/twig-view" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
</project>

6
php/.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

15
php/README.md Normal file
View file

@ -0,0 +1,15 @@
# PHP Docker Controller
This is the code for the PHP Docker controller.
## How to run
Running this locally requires Docker Engine on the same machine.
If this is the case, just execute the following command:
```
cd public/
php -S 0.0.0.0:8080
```
You can then access the web interface at `localhost:8080`.

20
php/composer.json Normal file
View file

@ -0,0 +1,20 @@
{
"autoload": {
"psr-4": {
"AIO\\": ["src/"]
}
},
"require": {
"ext-json": "*",
"ext-sodium": "*",
"ext-curl": "*",
"slim/slim": "4.*",
"php-di/slim-bridge": "^3.1",
"guzzlehttp/guzzle": "^7.3",
"guzzlehttp/psr7": "^1.8",
"http-interop/http-factory-guzzle": "^1.2",
"slim/twig-view": "^3.2",
"slim/csrf": "^1.2",
"ext-apcu": "*"
}
}

1529
php/composer.lock generated Normal file

File diff suppressed because it is too large Load diff

288
php/containers.json Normal file
View file

@ -0,0 +1,288 @@
{
"production": [
{
"dependsOn": [
"nextcloud-aio-nextcloud",
"nextcloud-aio-collabora",
"nextcloud-aio-talk"
],
"identifier": "nextcloud-aio-apache",
"displayName": "Apache",
"containerName": "nextcloud/aio-apache",
"ports": [
"443/tcp"
],
"internalPorts": [
"443"
],
"secrets": [],
"environmentVariables": [
"NC_DOMAIN=%NC_DOMAIN%",
"NEXTCLOUD_HOST=nextcloud-aio-nextcloud",
"COLLABORA_HOST=nextcloud-aio-collabora",
"TALK_HOST=nextcloud-aio-talk"
],
"volumes": [
{
"name": "nextcloud_aio_nextcloud",
"location": "/var/www/html",
"writeable": false
},
{
"name": "nextcloud_aio_apache",
"location": "/mnt/data",
"writeable": true
}
],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-database",
"displayName": "Database",
"containerName": "nextcloud/aio-postgresql",
"ports": [],
"internalPorts": [
"5432"
],
"secrets": [
"DATABASE_PASSWORD"
],
"volumes": [
{
"name": "nextcloud_aio_database",
"location": "/var/lib/postgresql/data",
"writeable": true
},
{
"name": "nextcloud_aio_database_dump",
"location": "/mnt/data",
"writeable": true
}
],
"environmentVariables": [
"POSTGRES_PASSWORD=%DATABASE_PASSWORD%",
"POSTGRES_DB=nextcloud_database",
"POSTGRES_USER=nextcloud"
],
"maxShutdownTime": 1800,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [
"nextcloud-aio-database",
"nextcloud-aio-redis"
],
"identifier": "nextcloud-aio-nextcloud",
"displayName": "Nextcloud",
"containerName": "nextcloud/aio-nextcloud",
"ports": [],
"internalPorts": [
"9000"
],
"secrets": [
"DATABASE_PASSWORD",
"REDIS_PASSWORD",
"NEXTCLOUD_PASSWORD",
"TURN_SECRET",
"SIGNALING_SECRET"
],
"volumes": [
{
"name": "nextcloud_aio_nextcloud",
"location": "/var/www/html",
"writeable": true
},
{
"name": "nextcloud_aio_nextcloud_data",
"location": "/mnt/ncdata",
"writeable": true
}
],
"environmentVariables": [
"POSTGRES_HOST=nextcloud-aio-database",
"POSTGRES_PASSWORD=%DATABASE_PASSWORD%",
"POSTGRES_DB=nextcloud_database",
"POSTGRES_USER=nextcloud",
"REDIS_HOST=nextcloud-aio-redis",
"REDIS_HOST_PASSWORD=%REDIS_PASSWORD%",
"AIO_TOKEN=%AIO_TOKEN%",
"NC_DOMAIN=%NC_DOMAIN%",
"ADMIN_USER=admin",
"ADMIN_PASSWORD=%NEXTCLOUD_PASSWORD%",
"NEXTCLOUD_DATA_DIR=/mnt/ncdata",
"OVERWRITEHOST=%NC_DOMAIN%",
"OVERWRITEPROTOCOL=https",
"TRUSTED_PROXIES=127.0.0.1",
"TURN_SECRET=%TURN_SECRET%",
"SIGNALING_SECRET=%SIGNALING_SECRET%",
"AIO_URL=%AIO_URL%"
],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-redis",
"displayName": "Redis",
"containerName": "nextcloud/aio-redis",
"ports": [],
"internalPorts": [
"6379"
],
"environmentVariables": [
"REDIS_HOST_PASSWORD=%REDIS_PASSWORD%"
],
"volumes": [],
"secrets": [
"REDIS_PASSWORD"
],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-collabora",
"displayName": "Collabora",
"containerName": "nextcloud/aio-collabora",
"ports": [],
"internalPorts": [
"9980"
],
"environmentVariables": [
"domain=%NC_DOMAIN%"
],
"volumes": [],
"secrets": [],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-talk",
"displayName": "Talk",
"containerName": "nextcloud/aio-talk",
"ports": [
"3478/tcp",
"3478/udp"
],
"internalPorts": [
"3478"
],
"environmentVariables": [
"NC_DOMAIN=%NC_DOMAIN%",
"TURN_SECRET=%TURN_SECRET%",
"SIGNALING_SECRET=%SIGNALING_SECRET%",
"JANUS_API_KEY=%JANUS_API_KEY%"
],
"volumes": [],
"secrets": [
"TURN_SECRET",
"SIGNALING_SECRET",
"JANUS_API_KEY"
],
"maxShutdownTime": 10,
"restartPolicy": "unless-stopped"
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-borgbackup",
"displayName": "Borgbackup",
"containerName": "nextcloud/aio-borgbackup",
"ports": [],
"internalPorts": [],
"environmentVariables": [
"BORG_PASSWORD=%BORGBACKUP_PASSWORD%",
"BORG_MODE=%BORGBACKUP_MODE%"
],
"volumes": [
{
"name": "nextcloud_aio_backup_cache",
"location": "/root",
"writeable": true
},
{
"name": "nextcloud_aio_nextcloud",
"location": "/nextcloud_aio_volumes/nextcloud_aio_nextcloud",
"writeable": true
},
{
"name": "nextcloud_aio_nextcloud_data",
"location": "/nextcloud_aio_volumes/nextcloud_aio_nextcloud_data",
"writeable": true
},
{
"name": "nextcloud_aio_database",
"location": "/nextcloud_aio_volumes/nextcloud_aio_database",
"writeable": true
},
{
"name": "nextcloud_aio_database_dump",
"location": "/nextcloud_aio_volumes/nextcloud_aio_database_dump",
"writeable": true
},
{
"name": "nextcloud_aio_apache",
"location": "/nextcloud_aio_volumes/nextcloud_aio_apache",
"writeable": true
},
{
"name": "nextcloud_aio_mastercontainer",
"location": "/nextcloud_aio_volumes/nextcloud_aio_mastercontainer",
"writeable": true
},
{
"name": "%BORGBACKUP_HOST_LOCATION%",
"location": "/mnt/borgbackup",
"writeable": true
}
],
"secrets": [
"BORGBACKUP_PASSWORD"
],
"maxShutdownTime": 10,
"restartPolicy": ""
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-watchtower",
"displayName": "Watchtower",
"containerName": "nextcloud/aio-watchtower",
"ports": [],
"internalPorts": [],
"environmentVariables": [
"CONTAINER_TO_UPDATE=nextcloud-aio-mastercontainer"
],
"volumes": [
{
"name": "/var/run/docker.sock",
"location": "/var/run/docker.sock",
"writeable": false
}
],
"secrets": [],
"maxShutdownTime": 10,
"restartPolicy": ""
},
{
"dependsOn": [],
"identifier": "nextcloud-aio-domaincheck",
"displayName": "Domaincheck",
"containerName": "nextcloud/aio-domaincheck",
"ports": [
"443/tcp"
],
"internalPorts": [],
"environmentVariables": [
"INSTANCE_ID=%INSTANCE_ID%"
],
"volumes": [],
"secrets": [
"INSTANCE_ID"
],
"maxShutdownTime": 1,
"restartPolicy": ""
}
]
}

0
php/data/.gitkeep Normal file
View file

View file

@ -0,0 +1 @@
I_AM_HERE

79
php/public/forms.js Normal file
View file

@ -0,0 +1,79 @@
"use strict";
(function (){
var lastError;
function showError(message) {
const body = document.getElementsByTagName('body')[0]
const toast = document.createElement("div")
toast.className = "toast error"
toast.prepend(message)
if (lastError) {
lastError.remove()
}
lastError = toast
body.prepend(toast)
setTimeout(toast.remove.bind(toast), 3000)
}
function handleEvent(e) {
const xhr = e.target;
if (xhr.status === 201) {
window.location.replace(xhr.getResponseHeader('Location'));
}
if (xhr.status === 422) {
showError(xhr.response);
}
if (xhr.status === 500) {
showError("Server error. Please see the logs for details.");
}
}
function disable(element) {
document.getElementById('overlay').classList.add('loading');
element.classList.add('loading');
element.disabled = true;
}
function enable(element) {
document.getElementById('overlay').classList.remove('loading');
element.classList.remove('loading');
element.disabled = false;
}
function initForm(form) {
function submit(event)
{
if (lastError) {
lastError.remove()
}
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', handleEvent);
xhr.addEventListener('error', () => showError("Failed to talk to server."));
xhr.addEventListener('load', () => enable(event.submitter));
xhr.addEventListener('error', () => enable(event.submitter));
xhr.open(form.method, form.getAttribute("action"));
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
disable(event.submitter);
xhr.send(new URLSearchParams(new FormData(form)));
event.preventDefault();
}
form.onsubmit = submit;
console.info(form);
}
function initForms() {
const forms = document.querySelectorAll('form.xhr')
console.info("Making " + forms.length + " form(s) use XHR.");
for (const form of forms) {
initForm(form);
}
}
if (document.readyState === 'loading') {
// Loading hasn't finished yet
document.addEventListener('DOMContentLoaded', initForms);
} else { // `DOMContentLoaded` has already fired
initForms();
}
})()

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
php/public/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

1
php/public/img/logo.svg Normal file
View file

@ -0,0 +1 @@
<svg width="256" height="128" version="1.1" viewBox="0 0 256 128" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke-width="22"><circle cx="40" cy="64" r="26" stroke="#ffffff" fill="none"/><circle cx="216" cy="64" r="26" stroke="#ffffff" fill="none"/><circle cx="128" cy="64" r="46" stroke="#ffffff" fill="none"/></g></svg>

After

Width:  |  Height:  |  Size: 330 B

140
php/public/index.php Normal file
View file

@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
// increase memory limit to 2GB
ini_set('memory_limit', '2048M');
// set max execution time to 2h just in case of a very slow internet connection
ini_set('max_execution_time', '7200');
use DI\Container;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
require __DIR__ . '/../vendor/autoload.php';
$container = \AIO\DependencyInjection::GetContainer();
$dataConst = $container->get(\AIO\Data\DataConst::class);
ini_set('session.save_path', $dataConst->GetSessionDirectory());
// Auto logout on browser close
ini_set('session.cookie_lifetime', '0');
// Make sure to delete all stale sessions after at least one day
ini_set('session.gc_maxlifetime', '86400');
ini_set('session.gc_probability', '1');
ini_set('session.gc_divisor', '1');
// Create app
AppFactory::setContainer($container);
$app = AppFactory::create();
$responseFactory = $app->getResponseFactory();
// Register Middleware On Container
$container->set(Guard::class, function () use ($responseFactory) {
$guard = new Guard($responseFactory);
$guard->setPersistentTokenMode(true);
return $guard;
});
// Register Middleware To Be Executed On All Routes
session_start();
$app->add(Guard::class);
// Create Twig
$twig = Twig::create(__DIR__ . '/../templates/', ['cache' => false]);
$app->add(TwigMiddleware::create($app, $twig));
$twig->addExtension(new \AIO\Twig\CsrfExtension($container->get(Guard::class)));
// Auth Middleware
$app->add(new \AIO\Middleware\AuthMiddleware($container->get(\AIO\Auth\AuthManager::class)));
// API
$app->post('/api/docker/watchtower', AIO\Controller\DockerController::class . ':StartWatchtowerContainer');
$app->post('/api/docker/start', AIO\Controller\DockerController::class . ':StartContainer');
$app->post('/api/docker/backup', AIO\Controller\DockerController::class . ':StartBackupContainerBackup');
$app->post('/api/docker/backup-check', AIO\Controller\DockerController::class . ':StartBackupContainerCheck');
$app->post('/api/docker/restore', AIO\Controller\DockerController::class . ':StartBackupContainerRestore');
$app->post('/api/docker/stop', AIO\Controller\DockerController::class . ':StopContainer');
$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');
$app->post('/api/auth/logout', AIO\Controller\LoginController::class . ':Logout');
$app->post('/api/configuration', \AIO\Controller\ConfigurationController::class . ':SetConfig');
// Views
$app->get('/containers', function ($request, $response, $args) use ($container) {
$view = Twig::fromRequest($request);
/** @var \AIO\Data\ConfigurationManager $configurationManager */
$configurationManager = $container->get(\AIO\Data\ConfigurationManager::class);
$dockerActionManger = $container->get(\AIO\Docker\DockerActionManager::class);
$dockerActionManger->ConnectMasterContainerToNetwork();
$dockerController = $container->get(\AIO\Controller\DockerController::class);
$dockerController->StartDomaincheckContainer();
$view->addExtension(new \AIO\Twig\ClassExtension());
return $view->render($response, 'containers.twig', [
'domain' => $configurationManager->GetDomain(),
'borg_backup_host_location' => $configurationManager->GetBorgBackupHostLocation(),
'borg_backup_mode' => $configurationManager->GetBorgBackupMode(),
'nextcloud_password' => $configurationManager->GetSecret('NEXTCLOUD_PASSWORD'),
'containers' => (new \AIO\ContainerDefinitionFetcher($container->get(\AIO\Data\ConfigurationManager::class), $container))->FetchDefinition(),
'borgbackup_password' => $configurationManager->GetSecret('BORGBACKUP_PASSWORD'),
'is_mastercontainer_update_available' => $dockerActionManger->IsMastercontainerUpdateAvailable(),
'has_backup_run_once' => $configurationManager->hasBackupRunOnce(),
'backup_exit_code' => $dockerActionManger->GetBackupcontainerExitCode(),
'was_start_button_clicked' => $configurationManager->wasStartButtonClicked(),
'has_update_available' => $dockerActionManger->isAnyUpdateAvailable(),
'last_backup_time' => $configurationManager->GetLastBackupTime(),
]);
})->setName('profile');
$app->get('/login', function ($request, $response, $args) {
$view = Twig::fromRequest($request);
return $view->render($response, 'login.twig');
});
$app->get('/setup', function ($request, $response, $args) use ($container) {
$view = Twig::fromRequest($request);
/** @var \AIO\Data\Setup $setup */
$setup = $container->get(\AIO\Data\Setup::class);
if(!$setup->CanBeInstalled()) {
return $view->render(
$response,
'already-installed.twig'
);
}
return $view->render(
$response,
'setup.twig',
[
'password' => $setup->Setup(),
]
);
});
// Auth Redirector
$app->get('/', function (\Psr\Http\Message\RequestInterface $request, \Psr\Http\Message\ResponseInterface $response, $args) use ($container) {
$authManager = $container->get(\AIO\Auth\AuthManager::class);
/** @var \AIO\Data\Setup $setup */
$setup = $container->get(\AIO\Data\Setup::class);
if($setup->CanBeInstalled()) {
return $response
->withHeader('Location', '/setup')
->withStatus(302);
}
if($authManager->IsAuthenticated()) {
return $response
->withHeader('Location', '/containers')
->withStatus(302);
} else {
return $response
->withHeader('Location', '/login')
->withStatus(302);
}
});
$app->run();

206
php/public/style.css Normal file
View file

@ -0,0 +1,206 @@
html, body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Cantarell, Ubuntu, Helvetica Neue, Arial, Noto Color Emoji, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;;
}
a {
text-decoration: none;
}
.button {
padding: 6px 16px;
width: auto;
min-height: 34px;
cursor: pointer;
background-color:#0082c9;
font-weight: bold;
border-radius: 100px;
margin: 3px 3px 3px 0;
font-size: 13px;
color: white;
border: 1px solid black;
outline: none;
}
ul {
list-style: none;
padding: 0;
}
li {
padding-bottom: 5px;
}
span.error {
background-color: #e9322d;
}
div.toast.error {
border-left-color: #e9322d;
}
.status {
display: inline-block;
height: 16px;
width: 16px;
vertical-align: text-bottom
}
.status {
border-radius: 50%
}
span.success {
background-color: #46ba61;
}
span.running {
background-color: rgb(255, 208, 0);
}
div.toast.success {
border-left-color: #46ba61;
}
div.toast {
border-left: 3px solid;
right: 10px;
min-width: 200px;
box-shadow: 0 0 6px 0 rgba(77, 77, 77, 0.3);
padding: 12px;
margin-top: 45px;
position: fixed;
z-index: 1;
border-radius: 3px;
background: none;
background-color: white;
}
.login {
padding: 50px;
background-color: white;
width: 500px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 16px;
}
.login > .monospace {
font-family: monospace;
font-size: 17px;
}
input {
padding: 10px;
margin-bottom: 15px;
}
.login > form > input {
width: 100%;
}
.login > img {
margin-left: auto;
margin-right: auto;
display: block;
}
.login > .button {
margin-left: auto;
margin-right: auto;
display: block;
text-align: center;
line-height: 33px;
margin-top: 20px;
}
.login-wrapper {
height: 100%;
width: 100%;
background-color: #0082c9;
background-image: linear-gradient(
40deg
, #0082c9 0%, #30b6ff 100%);
background-size: contain;
background-image: url('/img/background.png'), linear-gradient(
40deg
, #0082c9 0%, #30b6ff 100%);
position: relative;
}
.content {
padding: 20px;
max-width: 100%;
word-break: break-word;
max-width: 450px;
margin: 0 auto;
}
.logo {
background-image: url('/img/logo.svg');
height: 50px;
background-repeat: no-repeat;
display: inline-flex;
background-size: contain;
background-position: center center;
width: 62px;
position: absolute;
left: 12px;
top: 1px;
bottom: 1px;
}
header {
background-color: #0082c9;
background-image: linear-gradient(40deg, #0082c9 0%, #30b6ff 100%);
height: 50px;
justify-content: space-between;
display: flex;
}
.loading {
color: grey;
}
#overlay {
position: fixed; /* Sit on top of the page content */
display: none; /* Hidden by default */
width: 100%; /* Full width (cover the whole page) */
height: 100%; /* Full height (cover the whole page) */
top: 0;
left: 0;
background-color: rgba(0,0,0,0.5); /* Black background with opacity */
z-index: 2;
}
#overlay.loading {
display: block;
}
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 120px;
height: 120px;
-webkit-animation: spin 2s linear infinite; /* Safari */
animation: spin 2s linear infinite;
position: absolute;
top: calc(50% - 60px);
left: calc(50% - 60px);
}
/* Safari */
@-webkit-keyframes spin {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

0
php/session/.gitkeep Normal file
View file

View file

@ -0,0 +1 @@
csrf|a:1:{s:17:"csrf61a4cf452d1d1";s:32:"aca558848ae342fdf00f078576fbf5cb";}aio_authenticated|b:1;

View file

@ -0,0 +1,34 @@
<?php
namespace AIO\Auth;
use AIO\Data\ConfigurationManager;
class AuthManager {
private const SESSION_KEY = 'aio_authenticated';
private ConfigurationManager $configurationManager;
public function __construct(ConfigurationManager $configurationManager) {
$this->configurationManager = $configurationManager;
}
public function CheckCredentials(string $username, string $password) : bool {
if($username === $this->configurationManager->GetUserName()) {
return hash_equals($this->configurationManager->GetPassword(), $password);
}
return false;
}
public function CheckToken(string $token) : bool {
return hash_equals($this->configurationManager->GetToken(), $token);
}
public function SetAuthState(bool $isLoggedIn) : void {
$_SESSION[self::SESSION_KEY] = $isLoggedIn;
}
public function IsAuthenticated() : bool {
return isset($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY] === true;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,112 @@
<?php
namespace AIO\Container;
use AIO\Container\State\IContainerState;
use AIO\Data\ConfigurationManager;
use AIO\Docker\DockerActionManager;
use AIO\ContainerDefinitionFetcher;
class Container {
private string $identifier;
private string $displayName;
private string $containerName;
private string $restartPolicy;
private int $maxShutdownTime;
private ContainerPorts $ports;
private ContainerInternalPorts $internalPorts;
private ContainerVolumes $volumes;
private ContainerEnvironmentVariables $containerEnvironmentVariables;
/** @var string[] */
private array $dependsOn;
/** @var string[] */
private array $secrets;
private DockerActionManager $dockerActionManager;
public function __construct(
string $identifier,
string $displayName,
string $containerName,
string $restartPolicy,
int $maxShutdownTime,
ContainerPorts $ports,
ContainerInternalPorts $internalPorts,
ContainerVolumes $volumes,
ContainerEnvironmentVariables $containerEnvironmentVariables,
array $dependsOn,
array $secrets,
DockerActionManager $dockerActionManager
) {
$this->identifier = $identifier;
$this->displayName = $displayName;
$this->containerName = $containerName;
$this->restartPolicy = $restartPolicy;
$this->maxShutdownTime = $maxShutdownTime;
$this->ports = $ports;
$this->internalPorts = $internalPorts;
$this->volumes = $volumes;
$this->containerEnvironmentVariables = $containerEnvironmentVariables;
$this->dependsOn = $dependsOn;
$this->secrets = $secrets;
$this->dockerActionManager = $dockerActionManager;
}
public function GetIdentifier() : string {
return $this->identifier;
}
public function GetDisplayName() : string {
return $this->displayName;
}
public function GetContainerName() : string {
return $this->containerName;
}
public function GetRestartPolicy() : string {
return $this->restartPolicy;
}
public function GetMaxShutdownTime() : int {
return $this->maxShutdownTime;
}
public function GetSecrets() : array {
return $this->secrets;
}
public function GetPorts() : ContainerPorts {
return $this->ports;
}
public function GetInternalPorts() : ContainerInternalPorts {
return $this->internalPorts;
}
public function GetVolumes() : ContainerVolumes {
return $this->volumes;
}
public function GetRunningState() : IContainerState {
return $this->dockerActionManager->GetContainerRunningState($this);
}
public function GetUpdateState() : IContainerState {
return $this->dockerActionManager->GetContainerUpdateState($this);
}
public function GetStartingState() : IContainerState {
return $this->dockerActionManager->GetContainerStartingState($this);
}
/**
* @return string[]
*/
public function GetDependsOn() : array {
return $this->dependsOn;
}
public function GetEnvironmentVariables() : ContainerEnvironmentVariables {
return $this->containerEnvironmentVariables;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace AIO\Container;
class ContainerEnvironmentVariables {
/** @var string[] */
private array $variables = [];
public function AddVariable(string $variable) : void {
$this->variables[] = $variable;
}
/**
* @return string[]
*/
public function GetVariables() : array {
return $this->variables;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace AIO\Container;
class ContainerInternalPorts {
/** @var string[] */
private array $internalPorts = [];
public function AddInternalPort(string $internalPort) : void {
$this->internalPorts[] = $internalPort;
}
/**
* @return string[]
*/
public function GetInternalPorts() : array {
return $this->internalPorts;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace AIO\Container;
class ContainerPorts {
/** @var string[] */
private array $ports = [];
public function AddPort(string $port) : void {
$this->ports[] = $port;
}
/**
* @return string[]
*/
public function GetPorts() : array {
return $this->ports;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace AIO\Container;
class ContainerVolume {
public string $name;
public string $mountPoint;
public bool $isWritable;
public function __construct(
string $name,
string $mountPoint,
bool $isWritable
) {
$this->name = $name;
$this->mountPoint = $mountPoint;
$this->isWritable = $isWritable;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace AIO\Container;
class ContainerVolumes {
/** @var ContainerVolume[] */
private array $volumes = [];
public function AddVolume(ContainerVolume $volume) {
$this->volumes[] = $volume;
}
/**
* @return ContainerVolume[]
*/
public function GetVolumes() : array {
return $this->volumes;
}
}

View file

@ -0,0 +1,5 @@
<?php
namespace AIO\Container\State;
interface IContainerState {}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class ImageDoesNotExistState implements IContainerState
{}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class RunningState implements IContainerState
{}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class StartingState implements IContainerState
{}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class StoppedState implements IContainerState
{}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class VersionDifferentState implements IContainerState
{}

View file

@ -0,0 +1,6 @@
<?php
namespace AIO\Container\State;
class VersionEqualState implements IContainerState
{}

View file

@ -0,0 +1,136 @@
<?php
namespace AIO;
use AIO\Container\Container;
use AIO\Container\ContainerEnvironmentVariables;
use AIO\Container\ContainerPorts;
use AIO\Container\ContainerInternalPorts;
use AIO\Container\ContainerVolume;
use AIO\Container\ContainerVolumes;
use AIO\Container\State\RunningState;
use AIO\Data\ConfigurationManager;
use AIO\Data\DataConst;
use AIO\Docker\DockerActionManager;
class ContainerDefinitionFetcher
{
private ConfigurationManager $configurationManager;
private \DI\Container $container;
public function __construct(
ConfigurationManager $configurationManager,
\DI\Container $container
)
{
$this->configurationManager = $configurationManager;
$this->container = $container;
}
public function GetContainerById(string $id): ?Container
{
$containers = $this->FetchDefinition();
foreach ($containers as $container) {
if ($container->GetIdentifier() === $id) {
return $container;
}
}
return null;
}
/**
* @return array
*/
private function GetDefinition(bool $latest): array
{
$data = json_decode(file_get_contents(__DIR__ . '/../containers.json'), true);
$containers = [];
foreach ($data['production'] as $entry) {
$ports = new ContainerPorts();
foreach ($entry['ports'] as $port) {
$ports->AddPort($port);
}
$internalPorts = new ContainerInternalPorts();
foreach ($entry['internalPorts'] as $internalPort) {
$internalPorts->AddInternalPort($internalPort);
}
$volumes = new ContainerVolumes();
foreach ($entry['volumes'] as $value) {
if($value['name'] === '%BORGBACKUP_HOST_LOCATION%') {
$value['name'] = $this->configurationManager->GetBorgBackupHostLocation();
if($value['name'] === '') {
continue;
}
}
$volumes->AddVolume(
new ContainerVolume(
$value['name'],
$value['location'],
$value['writeable']
)
);
}
$variables = new ContainerEnvironmentVariables();
foreach ($entry['environmentVariables'] as $value) {
$variables->AddVariable($value);
}
$containers[] = new Container(
$entry['identifier'],
$entry['displayName'],
$entry['containerName'],
$entry['restartPolicy'],
$entry['maxShutdownTime'],
$ports,
$internalPorts,
$volumes,
$variables,
$entry['dependsOn'],
$entry['secrets'],
$this->container->get(DockerActionManager::class)
);
}
return $containers;
}
public function FetchDefinition(): array
{
if (!file_exists(DataConst::GetDataDirectory() . '/containers.json')) {
$containers = $this->GetDefinition(true);
} else {
$containers = $this->GetDefinition(false);
}
$borgBackupMode = $this->configurationManager->GetBorgBackupMode();
$fetchLatest = false;
foreach ($containers as $container) {
if ($container->GetIdentifier() === 'nextcloud-aio-borgbackup') {
if ($container->GetRunningState() === RunningState::class) {
if ($borgBackupMode !== 'backup' && $borgBackupMode !== 'restore') {
$fetchLatest = true;
}
} else {
$fetchLatest = true;
}
} elseif ($container->GetIdentifier() === 'nextcloud-aio-watchtower' && $container->GetRunningState() === RunningState::class) {
return $containers;
}
}
if ($fetchLatest === true) {
$containers = $this->GetDefinition(true);
}
return $containers;
}
}

Some files were not shown because too many files have changed in this diff Show more