Initial Upload
This commit is contained in:
408
docker/entrypoint.sh
Normal file
408
docker/entrypoint.sh
Normal file
@@ -0,0 +1,408 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Copyright (C) 2025 Xibo Signage Ltd
|
||||
#
|
||||
# Xibo - Digital Signage - https://xibosignage.com
|
||||
#
|
||||
# This file is part of Xibo.
|
||||
#
|
||||
# Xibo is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# Xibo 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
|
||||
# along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
if [ "$CMS_DEV_MODE" == "true" ]
|
||||
then
|
||||
# Print MySQL connection details
|
||||
echo "MySQL Connection Details:"
|
||||
echo "Username: $MYSQL_USER"
|
||||
echo "Password: $MYSQL_PASSWORD"
|
||||
echo "DB: $MYSQL_DATABASE"
|
||||
echo "Host: $MYSQL_HOST"
|
||||
echo ""
|
||||
echo "XMR Connection Details:"
|
||||
echo "Host: $XMR_HOST"
|
||||
echo "Player Port: 9505"
|
||||
echo ""
|
||||
echo "Starting Webserver"
|
||||
fi
|
||||
|
||||
# Sleep for a few seconds to give MySQL time to initialise
|
||||
echo "Waiting for MySQL to start - max 300 seconds"
|
||||
/usr/local/bin/wait-for-command.sh -q -t 300 -c "nc -z $MYSQL_HOST $MYSQL_PORT"
|
||||
|
||||
if [ ! "$?" == 0 ]
|
||||
then
|
||||
echo "MySQL didn't start in the allocated time" > /var/www/backup/LOG
|
||||
fi
|
||||
|
||||
# Safety sleep to give MySQL a moment to settle after coming up
|
||||
echo "MySQL started"
|
||||
sleep 1
|
||||
|
||||
# Write a /root/.my.cnf file
|
||||
echo "Configuring MySQL cnf file"
|
||||
echo "[client]" > /root/.my.cnf
|
||||
echo "host = $MYSQL_HOST" >> /root/.my.cnf
|
||||
echo "port = $MYSQL_PORT" >> /root/.my.cnf
|
||||
echo "user = $MYSQL_USER" >> /root/.my.cnf
|
||||
echo "password = $MYSQL_PASSWORD" >> /root/.my.cnf
|
||||
|
||||
if [ ! "$MYSQL_ATTR_SSL_CA" == "none" ]
|
||||
then
|
||||
echo "ssl_ca = $MYSQL_ATTR_SSL_CA" >> /root/.my.cnf
|
||||
|
||||
if [ "$MYSQL_ATTR_SSL_VERIFY_SERVER_CERT" == "true" ]
|
||||
then
|
||||
echo "ssl_mode = VERIFY_IDENTITY" >> /root/.my.cnf
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set permissions on the new cnf file.
|
||||
chmod 0600 /root/.my.cnf
|
||||
|
||||
# Check to see if we have a settings.php file in this container
|
||||
# if we don't, then we will need to create one here (it only contains the $_SERVER environment
|
||||
# variables we've already set
|
||||
if [ ! -f "/var/www/cms/web/settings.php" ]
|
||||
then
|
||||
# Write settings.php
|
||||
echo "Updating settings.php"
|
||||
|
||||
# We won't have a settings.php in place, so we'll need to copy one in
|
||||
cp /tmp/settings.php-template /var/www/cms/web/settings.php
|
||||
chown www-data.www-data /var/www/cms/web/settings.php
|
||||
|
||||
SECRET_KEY=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)
|
||||
/bin/sed -i "s/define('SECRET_KEY','');/define('SECRET_KEY','$SECRET_KEY');/" /var/www/cms/web/settings.php
|
||||
fi
|
||||
|
||||
# Check to see if we have a public/private key pair and encryption key
|
||||
if [ ! -f "/var/www/cms/library/certs/private.key" ]
|
||||
then
|
||||
# Make the dir
|
||||
mkdir -p /var/www/cms/library/certs
|
||||
|
||||
# Create the Keys
|
||||
openssl genrsa -out /var/www/cms/library/certs/private.key 2048
|
||||
openssl rsa -in /var/www/cms/library/certs/private.key -pubout -out /var/www/cms/library/certs/public.key
|
||||
|
||||
php -r 'echo base64_encode(random_bytes(32)), PHP_EOL;' >> /var/www/cms/library/certs/encryption.key
|
||||
fi
|
||||
|
||||
# Set the correct permissions on the public/private key
|
||||
chmod 600 /var/www/cms/library/certs/private.key
|
||||
chmod 660 /var/www/cms/library/certs/public.key
|
||||
chown -R www-data.www-data /var/www/cms/library/certs
|
||||
|
||||
# Check if there's a database file to import
|
||||
if [ -f "/var/www/backup/import.sql" ] && [ "$CMS_DEV_MODE" == "false" ]
|
||||
then
|
||||
echo "Attempting to import database"
|
||||
|
||||
echo "Importing Database"
|
||||
mysql -D $MYSQL_DATABASE -e "SOURCE /var/www/backup/import.sql"
|
||||
|
||||
echo "Configuring Database Settings"
|
||||
# Set LIBRARY_LOCATION
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='/var/www/cms/library/', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='LIBRARY_LOCATION' LIMIT 1"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='Apache', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='SENDFILE_MODE' LIMIT 1"
|
||||
|
||||
# Set XMR public/private address
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='http://$XMR_HOST:8081', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='XMR_ADDRESS' LIMIT 1"
|
||||
|
||||
# Configure Maintenance
|
||||
echo "Setting up Maintenance"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='Protected' WHERE \`setting\`='MAINTENANCE_ENABLED' LIMIT 1"
|
||||
|
||||
MAINTENANCE_KEY=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='$MAINTENANCE_KEY' WHERE \`setting\`='MAINTENANCE_KEY' LIMIT 1"
|
||||
|
||||
# Configure Quick Chart
|
||||
echo "Setting up Quickchart"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='$CMS_QUICK_CHART_URL', userSee=0 WHERE \`setting\`='QUICK_CHART_URL' LIMIT 1"
|
||||
|
||||
mv /var/www/backup/import.sql /var/www/backup/import.sql.done
|
||||
fi
|
||||
|
||||
DB_EXISTS=0
|
||||
# Check if the database exists already
|
||||
if mysql -D $MYSQL_DATABASE -e "SELECT settingId FROM \`setting\` LIMIT 1"
|
||||
then
|
||||
# Database exists.
|
||||
DB_EXISTS=1
|
||||
fi
|
||||
|
||||
# Check if we need to run an upgrade
|
||||
# if DB_EXISTS then see if the version installed matches
|
||||
# only upgrade for production containers
|
||||
if [ "$DB_EXISTS" == "1" ] && [ "$CMS_DEV_MODE" == "false" ]
|
||||
then
|
||||
echo "Existing Database, checking if we need to upgrade it"
|
||||
# Determine if there are any migrations to be run
|
||||
/var/www/cms/vendor/bin/phinx status -c "/var/www/cms/phinx.php"
|
||||
|
||||
if [ ! "$?" == 0 ]
|
||||
then
|
||||
echo "We will upgrade it, take a backup"
|
||||
|
||||
# We're going to run an upgrade. Make a database backup
|
||||
mysqldump -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER -p$MYSQL_PASSWORD \
|
||||
--hex-blob --no-tablespaces $MYSQL_DATABASE | gzip > /var/www/backup/db-$(date +"%Y-%m-%d_%H-%M-%S").sql.gz
|
||||
|
||||
# Drop app cache on upgrade
|
||||
rm -rf /var/www/cms/cache/*
|
||||
|
||||
# Upgrade
|
||||
echo 'Running database migrations'
|
||||
/var/www/cms/vendor/bin/phinx migrate -c /var/www/cms/phinx.php
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DB_EXISTS" == "0" ]
|
||||
then
|
||||
# This is a fresh install so bootstrap the whole
|
||||
# system
|
||||
echo "New install"
|
||||
|
||||
echo "Provisioning Database"
|
||||
|
||||
# Create the database if it doesn't exist
|
||||
mysql -e "CREATE DATABASE IF NOT EXISTS $MYSQL_DATABASE;"
|
||||
|
||||
# Populate the database
|
||||
php /var/www/cms/vendor/bin/phinx migrate -c "/var/www/cms/phinx.php"
|
||||
|
||||
CMS_KEY=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)
|
||||
|
||||
echo "Configuring Database Settings"
|
||||
# Set LIBRARY_LOCATION
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='/var/www/cms/library/', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='LIBRARY_LOCATION' LIMIT 1"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='Apache', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='SENDFILE_MODE' LIMIT 1"
|
||||
|
||||
# Set admin username/password
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`user\` SET \`UserName\`='xibo_admin', \`UserPassword\`='5f4dcc3b5aa765d61d8327deb882cf99' WHERE \`UserID\` = 1 LIMIT 1"
|
||||
|
||||
# Set XMR public address
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='tcp://cms.example.org:9505' WHERE \`setting\`='XMR_PUB_ADDRESS' LIMIT 1"
|
||||
|
||||
# Set CMS Key
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='$CMS_KEY' WHERE \`setting\`='SERVER_KEY' LIMIT 1"
|
||||
|
||||
# Configure Maintenance
|
||||
echo "Setting up Maintenance"
|
||||
|
||||
if [ "$CMS_DEV_MODE" == "false" ]
|
||||
then
|
||||
echo "Protected Maintenance"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='Protected' WHERE \`setting\`='MAINTENANCE_ENABLED' LIMIT 1"
|
||||
fi
|
||||
|
||||
MAINTENANCE_KEY=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='$MAINTENANCE_KEY' WHERE \`setting\`='MAINTENANCE_KEY' LIMIT 1"
|
||||
fi
|
||||
|
||||
if [ "$CMS_DEV_MODE" == "false" ]
|
||||
then
|
||||
# Import any ca-certificate files that might be needed to use a proxy etc
|
||||
echo "Importing ca-certs"
|
||||
cp -v /var/www/cms/ca-certs/*.pem /usr/local/share/ca-certificates
|
||||
cp -v /var/www/cms/ca-certs/*.crt /usr/local/share/ca-certificates
|
||||
/usr/sbin/update-ca-certificates
|
||||
|
||||
# Configure XMR private API
|
||||
echo "Setting up XMR private API"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='http://$XMR_HOST:8081', \`userChange\`=0, \`userSee\`=0 WHERE \`setting\`='XMR_ADDRESS' LIMIT 1"
|
||||
|
||||
# Configure Quick Chart
|
||||
echo "Setting up Quickchart"
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='$CMS_QUICK_CHART_URL', userSee=0 WHERE \`setting\`='QUICK_CHART_URL' LIMIT 1"
|
||||
|
||||
# Set the daily maintenance task to run
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`task\` SET \`runNow\`=1 WHERE \`taskId\`='1' LIMIT 1"
|
||||
|
||||
# Update /etc/periodic/15min/cms-db-backup with current environment (for cron)
|
||||
/bin/sed -i "s/^MYSQL_BACKUP_ENABLED=.*$/MYSQL_BACKUP_ENABLED=$MYSQL_BACKUP_ENABLED/" /etc/periodic/15min/cms-db-backup
|
||||
/bin/sed -i "s/^MYSQL_DATABASE=.*$/MYSQL_DATABASE=$MYSQL_DATABASE/" /etc/periodic/15min/cms-db-backup
|
||||
|
||||
echo "*/15 * * * * root /etc/periodic/15min/cms-db-backup > /dev/null 2>&1" > /etc/cron.d/cms_backup_cron
|
||||
echo "" >> /etc/cron.d/cms_backup_cron
|
||||
|
||||
# Update /var/www/maintenance with current environment (for cron)
|
||||
if [ "$XTR_ENABLED" == "true" ]
|
||||
then
|
||||
echo "Configuring Maintenance"
|
||||
echo "#!/bin/bash" > /var/www/maintenance.sh
|
||||
echo "" >> /var/www/maintenance.sh
|
||||
/usr/bin/env | sed 's/^\(.*\)$/export \1/g' | grep -E "^export MYSQL" >> /var/www/maintenance.sh
|
||||
/usr/bin/env | sed 's/^\(.*\)$/export \1/g' | grep -E "^export MEMCACHED" >> /var/www/maintenance.sh
|
||||
echo "export CMS_USE_MEMCACHED=$CMS_USE_MEMCACHED" >> /var/www/maintenance.sh
|
||||
echo "export INSTALL_TYPE=$INSTALL_TYPE" >> /var/www/maintenance.sh
|
||||
echo "cd /var/www/cms && /usr/bin/php bin/xtr.php" >> /var/www/maintenance.sh
|
||||
chmod 755 /var/www/maintenance.sh
|
||||
|
||||
echo "* * * * * www-data /var/www/maintenance.sh > /dev/null 2>&1 " > /etc/cron.d/cms_maintenance_cron
|
||||
echo "" >> /etc/cron.d/cms_maintenance_cron
|
||||
fi
|
||||
|
||||
# Configure MSMTP to send emails if required
|
||||
# Config lives in /etc/msmtprc
|
||||
|
||||
# Split CMS_SMTP_SERVER in to CMS_SMTP_SEVER_HOST : PORT
|
||||
host_port=($(echo $CMS_SMTP_SERVER | tr ":" "\n"))
|
||||
|
||||
/bin/sed -i "s/host .*$/host ${host_port[0]}/" /etc/msmtprc
|
||||
/bin/sed -i "s/port .*$/port ${host_port[1]}/" /etc/msmtprc
|
||||
|
||||
if [ -z "$CMS_SMTP_USERNAME" ] || [ "$CMS_SMTP_USERNAME" == "none" ]
|
||||
then
|
||||
# Use no authentication
|
||||
/bin/sed -i "s/^auth .*$/auth off/" /etc/msmtprc
|
||||
else
|
||||
if [ -z "$CMS_SMTP_OAUTH_CLIENT_ID" ] || [ "$CMS_SMTP_OAUTH_CLIENT_ID" == "none" ]
|
||||
then
|
||||
# Use Username/Password
|
||||
/bin/sed -i "s/^auth .*$/auth on/" /etc/msmtprc
|
||||
/bin/sed -i "s/^user .*$/user $CMS_SMTP_USERNAME/" /etc/msmtprc
|
||||
/bin/sed -i "s/^password .*$/password $CMS_SMTP_PASSWORD/" /etc/msmtprc
|
||||
else
|
||||
# Use OAUTH credentials
|
||||
/bin/sed -i "s/^auth .*$/auth oauthbearer/" /etc/msmtprc
|
||||
/bin/sed -i "s/^user .*$/#user/" /etc/msmtprc
|
||||
/bin/sed -i "s/^password .*$/passwordeval \"/usr/bin/oauth2.py --quiet --user=$CMS_SMTP_USERNAME --client_id=$CMS_SMTP_OAUTH_CLIENT_ID --client_secret=$CMS_SMTP_OAUTH_CLIENT_SECRET --refresh_token=$CMS_SMTP_OAUTH_CLIENT_REFRESH\"/" /etc/msmtprc
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$CMS_SMTP_USE_TLS" == "YES" ]
|
||||
then
|
||||
/bin/sed -i "s/tls .*$/tls on/" /etc/msmtprc
|
||||
else
|
||||
/bin/sed -i "s/tls .*$/tls off/" /etc/msmtprc
|
||||
fi
|
||||
|
||||
if [ "$CMS_SMTP_USE_STARTTLS" == "YES" ]
|
||||
then
|
||||
/bin/sed -i "s/tls_starttls .*$/tls_starttls on/" /etc/msmtprc
|
||||
else
|
||||
/bin/sed -i "s/tls_starttls .*$/tls_starttls off/" /etc/msmtprc
|
||||
fi
|
||||
|
||||
/bin/sed -i "s/maildomain .*$/maildomain $CMS_SMTP_REWRITE_DOMAIN/" /etc/msmtprc
|
||||
/bin/sed -i "s/domain .*$/domain $CMS_SMTP_HOSTNAME/" /etc/msmtprc
|
||||
|
||||
if [ "$CMS_SMTP_FROM" == "none" ]
|
||||
then
|
||||
/bin/sed -i "s/from .*$/from cms@$CMS_SMTP_REWRITE_DOMAIN/" /etc/msmtprc
|
||||
else
|
||||
/bin/sed -i "s/from .*$/from $CMS_SMTP_FROM/" /etc/msmtprc
|
||||
fi
|
||||
|
||||
mkdir -p /var/www/cms/library/temp
|
||||
chown www-data:www-data -R /var/www/cms/library
|
||||
chown www-data:www-data -R /var/www/cms/custom
|
||||
chown www-data:www-data -R /var/www/cms/web/theme/custom
|
||||
chown www-data:www-data -R /var/www/cms/web/userscripts
|
||||
chown www-data:www-data -R /var/www/cms/ca-certs
|
||||
|
||||
# If we have a CMS ALIAS environment variable, then configure that in our Apache conf.
|
||||
# this must not be done in DEV mode, as it modifies the .htaccess file, which might then be committed by accident
|
||||
if [ ! "$CMS_ALIAS" == "none" ]
|
||||
then
|
||||
echo "Setting up CMS alias"
|
||||
/bin/sed -i "s|.*Alias.*$|Alias $CMS_ALIAS /var/www/cms/web|" /etc/apache2/sites-enabled/000-default.conf
|
||||
|
||||
echo "Settings up htaccess"
|
||||
/bin/cp /tmp/.htaccess /var/www/cms/web/.htaccess
|
||||
/bin/sed -i "s|REPLACE_ME|$CMS_ALIAS|" /var/www/cms/web/.htaccess
|
||||
fi
|
||||
|
||||
if [ ! -e /var/www/cms/custom/settings-custom.php ]
|
||||
then
|
||||
/bin/cp /tmp/settings-custom.php /var/www/cms/custom
|
||||
fi
|
||||
|
||||
# Remove install.php if it exists
|
||||
if [ -e /var/www/cms/web/install/index.php ]
|
||||
then
|
||||
echo "Removing web/install/index.php from production container"
|
||||
rm /var/www/cms/web/install/index.php
|
||||
fi
|
||||
fi
|
||||
|
||||
# Configure Anonymous usage reporting
|
||||
if [ "$CMS_USAGE_REPORT" == "true" ]
|
||||
then
|
||||
# Turn on
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='1', userChange=0 WHERE \`setting\`='PHONE_HOME' LIMIT 1"
|
||||
fi
|
||||
|
||||
if [ "$CMS_USAGE_REPORT" == "false" ]
|
||||
then
|
||||
# Turn off
|
||||
mysql -D $MYSQL_DATABASE -e "UPDATE \`setting\` SET \`value\`='0', userChange=0 WHERE \`setting\`='PHONE_HOME' LIMIT 1"
|
||||
fi
|
||||
|
||||
echo "Configure PHP"
|
||||
|
||||
# Configure PHP
|
||||
sed -i "s/session.gc_maxlifetime = .*$/session.gc_maxlifetime = $CMS_PHP_SESSION_GC_MAXLIFETIME/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/post_max_size = .*$/post_max_size = $CMS_PHP_POST_MAX_SIZE/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/upload_max_filesize = .*$/upload_max_filesize = $CMS_PHP_UPLOAD_MAX_FILESIZE/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/max_execution_time = .*$/max_execution_time = $CMS_PHP_MAX_EXECUTION_TIME/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/memory_limit = .*$/memory_limit = $CMS_PHP_MEMORY_LIMIT/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/session.cookie_httponly =.*$/session.cookie_httponly = $CMS_PHP_COOKIE_HTTP_ONLY/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/session.cookie_samesite =.*$/session.cookie_samesite = $CMS_PHP_COOKIE_SAMESITE/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/;session.cookie_secure =.*$/session.cookie_secure = $CMS_PHP_COOKIE_SECURE/" /etc/php/8.4/apache2/php.ini
|
||||
sed -i "s/session.gc_maxlifetime = .*$/session.gc_maxlifetime = $CMS_PHP_SESSION_GC_MAXLIFETIME/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/post_max_size = .*$/post_max_size = $CMS_PHP_POST_MAX_SIZE/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/upload_max_filesize = .*$/upload_max_filesize = $CMS_PHP_UPLOAD_MAX_FILESIZE/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/max_execution_time = .*$/max_execution_time = $CMS_PHP_CLI_MAX_EXECUTION_TIME/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/memory_limit = .*$/memory_limit = $CMS_PHP_CLI_MEMORY_LIMIT/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/session.cookie_httponly =.*$/session.cookie_httponly = $CMS_PHP_COOKIE_HTTP_ONLY/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/session.cookie_samesite =.*$/session.cookie_samesite = $CMS_PHP_COOKIE_SAMESITE/" /etc/php/8.4/cli/php.ini
|
||||
sed -i "s/;session.cookie_secure =.*$/session.cookie_secure = $CMS_PHP_COOKIE_SECURE/" /etc/php/8.4/cli/php.ini
|
||||
|
||||
echo "Configure Apache"
|
||||
|
||||
# Configure Apache TimeOut
|
||||
sed -i "s/\bTimeout\b .*$/Timeout $CMS_APACHE_TIMEOUT/" /etc/apache2/apache2.conf
|
||||
|
||||
# Configure Indexes
|
||||
if [ "$CMS_APACHE_OPTIONS_INDEXES" == "true" ]
|
||||
then
|
||||
sed -i "s/\-Indexes/\+Indexes/" /etc/apache2/sites-enabled/000-default.conf
|
||||
fi
|
||||
|
||||
# Configure Apache ServerTokens
|
||||
if [ "$CMS_APACHE_SERVER_TOKENS" == "Prod" ]
|
||||
then
|
||||
sed -i "s/ServerTokens.*$/ServerTokens Prod/" /etc/apache2/sites-enabled/000-default.conf
|
||||
fi
|
||||
|
||||
# Configure Apache logging
|
||||
if [ "$CMS_APACHE_LOG_REQUEST_TIME" == "true" ]
|
||||
then
|
||||
sed -i '/combined/s/^/#/' /etc/apache2/sites-enabled/000-default.conf
|
||||
else
|
||||
sed -i '/requesttime/s/^/#/' /etc/apache2/sites-enabled/000-default.conf
|
||||
fi
|
||||
|
||||
# Run CRON in Production mode
|
||||
if [ "$CMS_DEV_MODE" == "false" ]
|
||||
then
|
||||
echo "Starting cron"
|
||||
/usr/sbin/cron
|
||||
fi
|
||||
|
||||
echo "Starting webserver"
|
||||
exec /usr/local/bin/httpd-foreground
|
||||
14
docker/etc/apache2/mods-enabled/mpm_prefork.conf
Normal file
14
docker/etc/apache2/mods-enabled/mpm_prefork.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# prefork MPM
|
||||
# StartServers: number of server processes to start
|
||||
# MinSpareServers: minimum number of server processes which are kept spare
|
||||
# MaxSpareServers: maximum number of server processes which are kept spare
|
||||
# MaxRequestWorkers: maximum number of server processes allowed to start
|
||||
# MaxConnectionsPerChild: maximum number of requests a server process serves
|
||||
|
||||
<IfModule mpm_prefork_module>
|
||||
StartServers ${CMS_APACHE_START_SERVERS}
|
||||
MinSpareServers ${CMS_APACHE_MIN_SPARE_SERVERS}
|
||||
MaxSpareServers ${CMS_APACHE_MAX_SPARE_SERVERS}
|
||||
MaxRequestWorkers ${CMS_APACHE_MAX_REQUEST_WORKERS}
|
||||
MaxConnectionsPerChild ${CMS_APACHE_MAX_CONNECTIONS_PER_CHILD}
|
||||
</IfModule>
|
||||
66
docker/etc/apache2/sites-available/000-default.conf
Normal file
66
docker/etc/apache2/sites-available/000-default.conf
Normal file
@@ -0,0 +1,66 @@
|
||||
TraceEnable Off
|
||||
ServerSignature Off
|
||||
ServerTokens OS
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin me@example.org
|
||||
DocumentRoot /var/www/cms/web/
|
||||
|
||||
PassEnv MYSQL_DATABASE
|
||||
PassEnv MYSQL_HOST
|
||||
PassEnv MYSQL_USER
|
||||
PassEnv MYSQL_PORT
|
||||
PassEnv MYSQL_PASSWORD
|
||||
PassEnv MYSQL_ATTR_SSL_CA
|
||||
PassEnv MYSQL_ATTR_SSL_VERIFY_SERVER_CERT
|
||||
PassEnv CMS_SERVER_NAME
|
||||
PassEnv CMS_DEV_MODE
|
||||
PassEnv INSTALL_TYPE
|
||||
PassEnv GIT_COMMIT
|
||||
PassEnv CMS_USE_MEMCACHED
|
||||
PassEnv MEMCACHED_HOST
|
||||
PassEnv MEMCACHED_PORT
|
||||
PassEnv XMR_HOST
|
||||
|
||||
ServerName ${CMS_SERVER_NAME}
|
||||
|
||||
KeepAlive Off
|
||||
LimitRequestBody 0
|
||||
|
||||
XSendFile on
|
||||
XSendFilePath /var/www/cms/library
|
||||
|
||||
<Directory /var/www/cms/web/>
|
||||
DirectoryIndex index.php index.html
|
||||
Options -Indexes +FollowSymLinks -MultiViews
|
||||
AllowOverride All
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Alias /chromeos /var/www/cms/library/playersoftware/chromeos/latest
|
||||
<Directory /var/www/cms/library/playersoftware/chromeos/latest>
|
||||
php_admin_value engine Off
|
||||
DirectoryIndex index.html
|
||||
Options -Indexes -FollowSymLinks -MultiViews
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Location /xmr>
|
||||
ProxyPass ws://${XMR_HOST}:8080
|
||||
</Location>
|
||||
|
||||
ErrorLog /dev/stderr
|
||||
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" *%Ts* *%Dus*" requesttime
|
||||
|
||||
CustomLog /dev/stdout combined
|
||||
CustomLog /dev/stdout requesttime
|
||||
|
||||
# Hardening
|
||||
Header always append X-Frame-Options SAMEORIGIN
|
||||
Header always append X-Content-Type-Options nosniff
|
||||
|
||||
# Alias /xibo /var/www/cms/web
|
||||
|
||||
</VirtualHost>
|
||||
22
docker/etc/msmtprc
Normal file
22
docker/etc/msmtprc
Normal file
@@ -0,0 +1,22 @@
|
||||
account cms
|
||||
|
||||
# CMS_SMTP_SERVER
|
||||
host smtp.gmail.com
|
||||
# CMS_SMTP_SERVER
|
||||
port 587
|
||||
auth off
|
||||
# CMS_SMTP_USERNAME
|
||||
user example
|
||||
# CMS_SMTP_PASSWORD
|
||||
password password
|
||||
# CMS_SMTP_USE_TLS
|
||||
tls on
|
||||
# CMS_SMTP_USE_STARTTLS
|
||||
tls_starttls on
|
||||
# CMS_SMTP_REWRITE_DOMAIN
|
||||
maildomain gmail.com
|
||||
# CMS_SMTP_HOSTNAME
|
||||
domain gmail.com
|
||||
from cms@example.org
|
||||
|
||||
account default : cms
|
||||
89
docker/etc/periodic/15min/cms-db-backup
Normal file
89
docker/etc/periodic/15min/cms-db-backup
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Copyright (c) 2022 Xibo Signage Ltd
|
||||
#
|
||||
# Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
#
|
||||
# This file is part of Xibo.
|
||||
#
|
||||
# Xibo is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# Xibo 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
|
||||
# along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
/bin/mkdir -p /var/www/backup/db
|
||||
age=86401
|
||||
|
||||
MYSQL_BACKUP_ENABLED=
|
||||
MYSQL_DATABASE=
|
||||
|
||||
# Is this enabled?
|
||||
if [ "$MYSQL_BACKUP_ENABLED" == "false" ]
|
||||
then
|
||||
echo "Backup not enabled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# See if there is an existing backup, and if so, get its age
|
||||
if [ -e /var/www/backup/db/latest.sql.gz ]
|
||||
then
|
||||
age=$((`date +%s` - `date -r /var/www/backup/db/latest.sql.gz +%s`))
|
||||
|
||||
# Age can potentially be negative. If it is, make it 0 so that
|
||||
# we run a backup now
|
||||
if [ $age -lt 0 ]
|
||||
then
|
||||
echo "Last backup was in the future. Resetting"
|
||||
age=86401
|
||||
fi
|
||||
|
||||
echo "Existing backup is $age seconds old"
|
||||
fi
|
||||
|
||||
# Check if mysqldump is running already
|
||||
# pgrep exits with 0 if a process is found and 1 otherwise
|
||||
pgrep mysqldump > /dev/null 2>&1
|
||||
mysqldump_running=$?
|
||||
|
||||
# If the backup is older than 1 day, and mysqldump isn't running,
|
||||
# then take a new one
|
||||
if [ $age -gt 86400 ] && [ $mysqldump_running -ne 0 ]
|
||||
then
|
||||
echo "Creating new backup"
|
||||
|
||||
# Tell bash to consider all exit values when evaluating the
|
||||
# exit code of a pipe rather than just the right-most one
|
||||
# That way we can detect if mysqldump errors or is killed etc
|
||||
set -o pipefail
|
||||
|
||||
/usr/bin/mysqldump --single-transaction --hex-blob --no-tablespaces $MYSQL_DATABASE \
|
||||
| gzip > /var/www/backup/db/backup.sql.gz
|
||||
|
||||
RESULT=$?
|
||||
|
||||
if [ $RESULT -eq 0 ] && [ -e /var/www/backup/db/backup.sql.gz ]
|
||||
then
|
||||
echo "Rotating backups"
|
||||
mv /var/www/backup/db/latest.sql.gz /var/www/backup/db/previous.sql.gz
|
||||
mv /var/www/backup/db/backup.sql.gz /var/www/backup/db/latest.sql.gz
|
||||
exit 0
|
||||
else
|
||||
echo "BACKUP FAILED"
|
||||
echo "Not rotating backups"
|
||||
exit 1
|
||||
fi
|
||||
elif [ $mysqldump_running -eq 0 ]
|
||||
then
|
||||
echo "Backup already in progress. Exiting"
|
||||
exit 0
|
||||
fi
|
||||
608
docker/mod_xsendfile.c
Normal file
608
docker/mod_xsendfile.c
Normal file
@@ -0,0 +1,608 @@
|
||||
/* Copyright 2006-2010 by Nils Maier
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* mod_xsendfile.c: Process X-SENDFILE header cgi/scripts may set
|
||||
* Written by Nils Maier, testnutzer123 at google mail, March 2006
|
||||
*
|
||||
* Whenever an X-SENDFILE header occures in the response headers drop
|
||||
* the body and send the replacement file idenfitied by this header instead.
|
||||
*
|
||||
* Method inspired by lighttpd <http://lighttpd.net/>
|
||||
* Code inspired by mod_headers, mod_rewrite and such
|
||||
*
|
||||
* Installation:
|
||||
* apxs2 -cia mod_xsendfile.c
|
||||
*/
|
||||
|
||||
/*
|
||||
* v0.12 (peer-review still required)
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#include "apr.h"
|
||||
#include "apr_lib.h"
|
||||
#include "apr_strings.h"
|
||||
#include "apr_buckets.h"
|
||||
#include "apr_file_io.h"
|
||||
|
||||
#include "apr_hash.h"
|
||||
#define APR_WANT_IOVEC
|
||||
#define APR_WANT_STRFUNC
|
||||
#include "apr_want.h"
|
||||
|
||||
#include "httpd.h"
|
||||
#include "http_log.h"
|
||||
#include "http_config.h"
|
||||
#include "http_log.h"
|
||||
#define CORE_PRIVATE
|
||||
#include "http_request.h"
|
||||
#include "http_core.h" /* needed for per-directory core-config */
|
||||
#include "util_filter.h"
|
||||
#include "http_protocol.h" /* ap_hook_insert_error_filter */
|
||||
|
||||
#define AP_XSENDFILE_HEADER "X-SENDFILE"
|
||||
|
||||
module AP_MODULE_DECLARE_DATA xsendfile_module;
|
||||
|
||||
typedef enum {
|
||||
XSENDFILE_UNSET = 0,
|
||||
XSENDFILE_ENABLED = 1<<0,
|
||||
XSENDFILE_DISABLED = 1<<1
|
||||
} xsendfile_conf_active_t;
|
||||
|
||||
typedef struct xsendfile_conf_t {
|
||||
xsendfile_conf_active_t enabled;
|
||||
xsendfile_conf_active_t ignoreETag;
|
||||
xsendfile_conf_active_t ignoreLM;
|
||||
apr_array_header_t *paths;
|
||||
} xsendfile_conf_t;
|
||||
|
||||
static xsendfile_conf_t *xsendfile_config_create(apr_pool_t *p) {
|
||||
xsendfile_conf_t *conf;
|
||||
|
||||
conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
|
||||
conf->ignoreETag =
|
||||
conf->ignoreLM =
|
||||
conf->enabled =
|
||||
XSENDFILE_UNSET;
|
||||
|
||||
conf->paths = apr_array_make(p, 1, sizeof(char*));
|
||||
|
||||
return conf;
|
||||
}
|
||||
|
||||
static void *xsendfile_config_server_create(apr_pool_t *p, server_rec *s) {
|
||||
return (void*)xsendfile_config_create(p);
|
||||
}
|
||||
|
||||
#define XSENDFILE_CFLAG(x) conf->x = overrides->x != XSENDFILE_UNSET ? overrides->x : base->x
|
||||
|
||||
static void *xsendfile_config_merge(apr_pool_t *p, void *basev, void *overridesv) {
|
||||
xsendfile_conf_t *base = (xsendfile_conf_t *)basev;
|
||||
xsendfile_conf_t *overrides = (xsendfile_conf_t *)overridesv;
|
||||
xsendfile_conf_t *conf;
|
||||
|
||||
conf = (xsendfile_conf_t *) apr_pcalloc(p, sizeof(xsendfile_conf_t));
|
||||
|
||||
XSENDFILE_CFLAG(enabled);
|
||||
XSENDFILE_CFLAG(ignoreETag);
|
||||
XSENDFILE_CFLAG(ignoreLM);
|
||||
|
||||
conf->paths = apr_array_append(p, overrides->paths, base->paths);
|
||||
|
||||
return (void*)conf;
|
||||
}
|
||||
|
||||
static void *xsendfile_config_perdir_create(apr_pool_t *p, char *path) {
|
||||
return (void*)xsendfile_config_create(p);
|
||||
}
|
||||
#undef XSENDFILE_CFLAG
|
||||
|
||||
static const char *xsendfile_cmd_flag(cmd_parms *cmd, void *perdir_confv, int flag) {
|
||||
xsendfile_conf_t *conf = (xsendfile_conf_t *)perdir_confv;
|
||||
if (cmd->path == NULL) {
|
||||
conf = (xsendfile_conf_t*)ap_get_module_config(
|
||||
cmd->server->module_config,
|
||||
&xsendfile_module
|
||||
);
|
||||
}
|
||||
if (!conf) {
|
||||
return "Cannot get configuration object";
|
||||
}
|
||||
if (!strcasecmp(cmd->cmd->name, "xsendfile")) {
|
||||
conf->enabled = flag ? XSENDFILE_ENABLED : XSENDFILE_DISABLED;
|
||||
}
|
||||
else if (!strcasecmp(cmd->cmd->name, "xsendfileignoreetag")) {
|
||||
conf->ignoreETag = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
|
||||
}
|
||||
else if (!strcasecmp(cmd->cmd->name, "xsendfileignorelastmodified")) {
|
||||
conf->ignoreLM = flag ? XSENDFILE_ENABLED: XSENDFILE_DISABLED;
|
||||
}
|
||||
else {
|
||||
return apr_psprintf(cmd->pool, "Not a valid command in this context: %s %s", cmd->cmd->name, flag ? "On": "Off");
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *xsendfile_cmd_path(cmd_parms *cmd, void *pdc, const char *arg) {
|
||||
xsendfile_conf_t *conf = (xsendfile_conf_t*)ap_get_module_config(
|
||||
cmd->server->module_config,
|
||||
&xsendfile_module
|
||||
);
|
||||
char **newpath = (char**)apr_array_push(conf->paths);
|
||||
*newpath = apr_pstrdup(cmd->pool, arg);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
little helper function to get the original request path
|
||||
code borrowed from request.c and util_script.c
|
||||
*/
|
||||
static const char *ap_xsendfile_get_orginal_path(request_rec *rec) {
|
||||
const char
|
||||
*rv = rec->the_request,
|
||||
*last;
|
||||
|
||||
int dir = 0;
|
||||
size_t uri_len;
|
||||
|
||||
/* skip method && spaces */
|
||||
while (*rv && !apr_isspace(*rv)) {
|
||||
++rv;
|
||||
}
|
||||
while (apr_isspace(*rv)) {
|
||||
++rv;
|
||||
}
|
||||
/* first space is the request end */
|
||||
last = rv;
|
||||
while (*last && !apr_isspace(*last)) {
|
||||
++last;
|
||||
}
|
||||
uri_len = last - rv;
|
||||
if (!uri_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* alright, lets see if the request_uri changed! */
|
||||
if (strncmp(rv, rec->uri, uri_len) == 0) {
|
||||
rv = apr_pstrdup(rec->pool, rec->filename);
|
||||
dir = rec->finfo.filetype == APR_DIR;
|
||||
}
|
||||
else {
|
||||
/* need to lookup the url again as it changed */
|
||||
request_rec *sr = ap_sub_req_lookup_uri(
|
||||
apr_pstrmemdup(rec->pool, rv, uri_len),
|
||||
rec,
|
||||
NULL
|
||||
);
|
||||
if (!sr) {
|
||||
return NULL;
|
||||
}
|
||||
rv = apr_pstrdup(rec->pool, sr->filename);
|
||||
dir = rec->finfo.filetype == APR_DIR;
|
||||
ap_destroy_sub_req(sr);
|
||||
}
|
||||
|
||||
/* now we need to truncate so we only have the directory */
|
||||
if (!dir && (last = ap_strrchr(rv, '/')) != NULL) {
|
||||
*((char*)last + 1) = '\0';
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
little helper function to build the file path if available
|
||||
*/
|
||||
static apr_status_t ap_xsendfile_get_filepath(request_rec *r, xsendfile_conf_t *conf, const char *file, /* out */ char **path) {
|
||||
|
||||
const char *root = ap_xsendfile_get_orginal_path(r);
|
||||
apr_status_t rv;
|
||||
|
||||
apr_array_header_t *patharr;
|
||||
const char **paths;
|
||||
int i;
|
||||
|
||||
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: path is %s", root);
|
||||
#endif
|
||||
|
||||
/* merge the array */
|
||||
if (root) {
|
||||
patharr = apr_array_make(r->pool, conf->paths->nelts + 1, sizeof(char*));
|
||||
*(const char**)(apr_array_push(patharr)) = root;
|
||||
apr_array_cat(patharr, conf->paths);
|
||||
} else {
|
||||
patharr = conf->paths;
|
||||
}
|
||||
if (patharr->nelts == 0) {
|
||||
return APR_EBADPATH;
|
||||
}
|
||||
paths = (const char**)patharr->elts;
|
||||
|
||||
for (i = 0; i < patharr->nelts; ++i) {
|
||||
if ((rv = apr_filepath_merge(
|
||||
path,
|
||||
paths[i],
|
||||
file,
|
||||
APR_FILEPATH_TRUENAME | APR_FILEPATH_NOTABOVEROOT,
|
||||
r->pool
|
||||
)) == OK) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rv != OK) {
|
||||
*path = NULL;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static apr_status_t ap_xsendfile_output_filter(ap_filter_t *f, apr_bucket_brigade *in) {
|
||||
request_rec *r = f->r, *sr = NULL;
|
||||
|
||||
xsendfile_conf_t
|
||||
*dconf = (xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module),
|
||||
*sconf = (xsendfile_conf_t *)ap_get_module_config(r->server->module_config, &xsendfile_module),
|
||||
*conf = xsendfile_config_merge(r->pool, sconf, dconf);
|
||||
|
||||
core_dir_config *coreconf = (core_dir_config *)ap_get_module_config(r->per_dir_config, &core_module);
|
||||
|
||||
apr_status_t rv;
|
||||
apr_bucket *e;
|
||||
|
||||
apr_file_t *fd = NULL;
|
||||
apr_finfo_t finfo;
|
||||
|
||||
const char *file = NULL;
|
||||
char *translated = NULL;
|
||||
|
||||
int errcode;
|
||||
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: output_filter for %s", r->the_request);
|
||||
#endif
|
||||
/*
|
||||
should we proceed with this request?
|
||||
|
||||
* sub-requests suck
|
||||
* furthermore default-handled requests suck, as they actually shouldn't be able to set headers
|
||||
*/
|
||||
if (
|
||||
r->status != HTTP_OK
|
||||
|| r->main
|
||||
|| (r->handler && strcmp(r->handler, "default-handler") == 0) /* those table-keys are lower-case, right? */
|
||||
) {
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: not met [%d]", r->status);
|
||||
#endif
|
||||
ap_remove_output_filter(f);
|
||||
return ap_pass_brigade(f->next, in);
|
||||
}
|
||||
|
||||
/*
|
||||
alright, look for x-sendfile
|
||||
*/
|
||||
file = apr_table_get(r->headers_out, AP_XSENDFILE_HEADER);
|
||||
apr_table_unset(r->headers_out, AP_XSENDFILE_HEADER);
|
||||
|
||||
/* cgi/fastcgi will put the stuff into err_headers_out */
|
||||
if (!file || !*file) {
|
||||
file = apr_table_get(r->err_headers_out, AP_XSENDFILE_HEADER);
|
||||
apr_table_unset(r->err_headers_out, AP_XSENDFILE_HEADER);
|
||||
}
|
||||
|
||||
/* nothing there :p */
|
||||
if (!file || !*file) {
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: nothing found");
|
||||
#endif
|
||||
ap_remove_output_filter(f);
|
||||
return ap_pass_brigade(f->next, in);
|
||||
}
|
||||
|
||||
/*
|
||||
drop *everything*
|
||||
might be pretty expensive to generate content first that goes straight to the bitbucket,
|
||||
but actually the scripts that might set this flag won't output too much anyway
|
||||
*/
|
||||
while (!APR_BRIGADE_EMPTY(in)) {
|
||||
e = APR_BRIGADE_FIRST(in);
|
||||
apr_bucket_delete(e);
|
||||
}
|
||||
r->eos_sent = 0;
|
||||
|
||||
/* as we dropped all the content this field is not valid anymore! */
|
||||
apr_table_unset(r->headers_out, "Content-Length");
|
||||
apr_table_unset(r->err_headers_out, "Content-Length");
|
||||
apr_table_unset(r->headers_out, "Content-Encoding");
|
||||
apr_table_unset(r->err_headers_out, "Content-Encoding");
|
||||
|
||||
rv = ap_xsendfile_get_filepath(r, conf, file, &translated);
|
||||
if (rv != OK) {
|
||||
ap_log_rerror(
|
||||
APLOG_MARK,
|
||||
APLOG_ERR,
|
||||
rv,
|
||||
r,
|
||||
"xsendfile: unable to find file: %s",
|
||||
file
|
||||
);
|
||||
ap_remove_output_filter(f);
|
||||
ap_die(HTTP_NOT_FOUND, r);
|
||||
return HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: found %s", translated);
|
||||
#endif
|
||||
|
||||
/*
|
||||
try open the file
|
||||
*/
|
||||
if ((rv = apr_file_open(
|
||||
&fd,
|
||||
translated,
|
||||
APR_READ | APR_BINARY
|
||||
#if APR_HAS_SENDFILE
|
||||
| (coreconf->enable_sendfile != ENABLE_SENDFILE_OFF ? APR_SENDFILE_ENABLED : 0)
|
||||
#endif
|
||||
,
|
||||
0,
|
||||
r->pool
|
||||
)) != APR_SUCCESS) {
|
||||
ap_log_rerror(
|
||||
APLOG_MARK,
|
||||
APLOG_ERR,
|
||||
rv,
|
||||
r,
|
||||
"xsendfile: cannot open file: %s",
|
||||
translated
|
||||
);
|
||||
ap_remove_output_filter(f);
|
||||
ap_die(HTTP_NOT_FOUND, r);
|
||||
return HTTP_NOT_FOUND;
|
||||
}
|
||||
#if APR_HAS_SENDFILE && defined(_DEBUG)
|
||||
if (coreconf->enable_sendfile == ENABLE_SENDFILE_OFF) {
|
||||
ap_log_error(
|
||||
APLOG_MARK,
|
||||
APLOG_WARNING,
|
||||
0,
|
||||
r->server,
|
||||
"xsendfile: sendfile configured, but not active %d",
|
||||
coreconf->enable_sendfile
|
||||
);
|
||||
}
|
||||
#endif
|
||||
/* stat (for etag/cache/content-length stuff) */
|
||||
if ((rv = apr_file_info_get(&finfo, APR_FINFO_NORM, fd)) != APR_SUCCESS) {
|
||||
ap_log_rerror(
|
||||
APLOG_MARK,
|
||||
APLOG_ERR,
|
||||
rv,
|
||||
r,
|
||||
"xsendfile: unable to stat file: %s",
|
||||
translated
|
||||
);
|
||||
apr_file_close(fd);
|
||||
ap_remove_output_filter(f);
|
||||
ap_die(HTTP_FORBIDDEN, r);
|
||||
return HTTP_FORBIDDEN;
|
||||
}
|
||||
/* no inclusion of directories! we're serving files! */
|
||||
if (finfo.filetype != APR_REG) {
|
||||
ap_log_rerror(
|
||||
APLOG_MARK,
|
||||
APLOG_ERR,
|
||||
APR_EBADPATH,
|
||||
r,
|
||||
"xsendfile: not a file %s",
|
||||
translated
|
||||
);
|
||||
apr_file_close(fd);
|
||||
ap_remove_output_filter(f);
|
||||
ap_die(HTTP_NOT_FOUND, r);
|
||||
return HTTP_NOT_FOUND;
|
||||
}
|
||||
|
||||
/*
|
||||
need to cheat here a bit
|
||||
as etag generator will use those ;)
|
||||
and we want local_copy and cache
|
||||
*/
|
||||
r->finfo.inode = finfo.inode;
|
||||
r->finfo.size = finfo.size;
|
||||
|
||||
/*
|
||||
caching? why not :p
|
||||
*/
|
||||
r->no_cache = r->no_local_copy = 0;
|
||||
|
||||
/* some script (f?cgi) place stuff in err_headers_out */
|
||||
if (
|
||||
conf->ignoreLM == XSENDFILE_ENABLED
|
||||
|| (
|
||||
!apr_table_get(r->headers_out, "last-modified")
|
||||
&& !apr_table_get(r->headers_out, "last-modified")
|
||||
)
|
||||
) {
|
||||
apr_table_unset(r->err_headers_out, "last-modified");
|
||||
ap_update_mtime(r, finfo.mtime);
|
||||
ap_set_last_modified(r);
|
||||
}
|
||||
if (
|
||||
conf->ignoreETag == XSENDFILE_ENABLED
|
||||
|| (
|
||||
!apr_table_get(r->headers_out, "etag")
|
||||
&& !apr_table_get(r->err_headers_out, "etag")
|
||||
)
|
||||
) {
|
||||
apr_table_unset(r->err_headers_out, "etag");
|
||||
ap_set_etag(r);
|
||||
}
|
||||
|
||||
ap_set_content_length(r, finfo.size);
|
||||
|
||||
/* cache or something? */
|
||||
if ((errcode = ap_meets_conditions(r)) != OK) {
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(
|
||||
APLOG_MARK,
|
||||
APLOG_DEBUG,
|
||||
0,
|
||||
r->server,
|
||||
"xsendfile: met condition %d for %s",
|
||||
errcode,
|
||||
file
|
||||
);
|
||||
#endif
|
||||
apr_file_close(fd);
|
||||
r->status = errcode;
|
||||
}
|
||||
else {
|
||||
/* For platforms where the size of the file may be larger than
|
||||
* that which can be stored in a single bucket (where the
|
||||
* length field is an apr_size_t), split it into several
|
||||
* buckets: */
|
||||
if (sizeof(apr_off_t) > sizeof(apr_size_t)
|
||||
&& finfo.size > AP_MAX_SENDFILE) {
|
||||
apr_off_t fsize = finfo.size;
|
||||
e = apr_bucket_file_create(fd, 0, AP_MAX_SENDFILE, r->pool,
|
||||
in->bucket_alloc);
|
||||
while (fsize > AP_MAX_SENDFILE) {
|
||||
apr_bucket *ce;
|
||||
apr_bucket_copy(e, &ce);
|
||||
APR_BRIGADE_INSERT_TAIL(in, ce);
|
||||
e->start += AP_MAX_SENDFILE;
|
||||
fsize -= AP_MAX_SENDFILE;
|
||||
}
|
||||
e->length = (apr_size_t)fsize; /* Resize just the last bucket */
|
||||
}
|
||||
else {
|
||||
e = apr_bucket_file_create(fd, 0, (apr_size_t)finfo.size,
|
||||
r->pool, in->bucket_alloc);
|
||||
}
|
||||
|
||||
|
||||
#if APR_HAS_MMAP
|
||||
if (coreconf->enable_mmap == ENABLE_MMAP_ON) {
|
||||
apr_bucket_file_enable_mmap(e, 0);
|
||||
}
|
||||
#if defined(_DEBUG)
|
||||
else {
|
||||
ap_log_error(
|
||||
APLOG_MARK,
|
||||
APLOG_WARNING,
|
||||
0,
|
||||
r->server,
|
||||
"xsendfile: mmap configured, but not active %d",
|
||||
coreconf->enable_mmap
|
||||
);
|
||||
}
|
||||
#endif /* _DEBUG */
|
||||
#endif /* APR_HAS_MMAP */
|
||||
APR_BRIGADE_INSERT_TAIL(in, e);
|
||||
}
|
||||
|
||||
e = apr_bucket_eos_create(in->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(in, e);
|
||||
|
||||
/* remove ourselves from the filter chain */
|
||||
ap_remove_output_filter(f);
|
||||
|
||||
#ifdef _DEBUG
|
||||
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "xsendfile: sending %d bytes", (int)finfo.size);
|
||||
#endif
|
||||
|
||||
/* send the data up the stack */
|
||||
return ap_pass_brigade(f->next, in);
|
||||
}
|
||||
|
||||
static void ap_xsendfile_insert_output_filter(request_rec *r) {
|
||||
xsendfile_conf_active_t enabled = ((xsendfile_conf_t *)ap_get_module_config(r->per_dir_config, &xsendfile_module))->enabled;
|
||||
if (XSENDFILE_UNSET == enabled) {
|
||||
enabled = ((xsendfile_conf_t*)ap_get_module_config(r->server->module_config, &xsendfile_module))->enabled;
|
||||
}
|
||||
|
||||
if (XSENDFILE_ENABLED != enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ap_add_output_filter(
|
||||
"XSENDFILE",
|
||||
NULL,
|
||||
r,
|
||||
r->connection
|
||||
);
|
||||
}
|
||||
static const command_rec xsendfile_command_table[] = {
|
||||
AP_INIT_FLAG(
|
||||
"XSendFile",
|
||||
xsendfile_cmd_flag,
|
||||
NULL,
|
||||
OR_FILEINFO,
|
||||
"On|Off - Enable/disable(default) processing"
|
||||
),
|
||||
AP_INIT_FLAG(
|
||||
"XSendFileIgnoreEtag",
|
||||
xsendfile_cmd_flag,
|
||||
NULL,
|
||||
OR_FILEINFO,
|
||||
"On|Off - Ignore script provided Etag headers (default: Off)"
|
||||
),
|
||||
AP_INIT_FLAG(
|
||||
"XSendFileIgnoreLastModified",
|
||||
xsendfile_cmd_flag,
|
||||
NULL,
|
||||
OR_FILEINFO,
|
||||
"On|Off - Ignore script provided Last-Modified headers (default: Off)"
|
||||
),
|
||||
AP_INIT_TAKE1(
|
||||
"XSendFilePath",
|
||||
xsendfile_cmd_path,
|
||||
NULL,
|
||||
RSRC_CONF|ACCESS_CONF,
|
||||
"Allow to serve files from that Path. Must be absolute"
|
||||
),
|
||||
{ NULL }
|
||||
};
|
||||
static void xsendfile_register_hooks(apr_pool_t *p) {
|
||||
ap_register_output_filter(
|
||||
"XSENDFILE",
|
||||
ap_xsendfile_output_filter,
|
||||
NULL,
|
||||
AP_FTYPE_CONTENT_SET
|
||||
);
|
||||
|
||||
ap_hook_insert_filter(
|
||||
ap_xsendfile_insert_output_filter,
|
||||
NULL,
|
||||
NULL,
|
||||
APR_HOOK_LAST + 1
|
||||
);
|
||||
}
|
||||
module AP_MODULE_DECLARE_DATA xsendfile_module = {
|
||||
STANDARD20_MODULE_STUFF,
|
||||
xsendfile_config_perdir_create,
|
||||
xsendfile_config_merge,
|
||||
xsendfile_config_server_create,
|
||||
xsendfile_config_merge,
|
||||
xsendfile_command_table,
|
||||
xsendfile_register_hooks
|
||||
};
|
||||
32
docker/tmp/.htaccess
Normal file
32
docker/tmp/.htaccess
Normal file
@@ -0,0 +1,32 @@
|
||||
# htaccess file for CMS instances using an Alias.
|
||||
# REPLACE_ME gets replaced on container start by entrypoint.sh
|
||||
RewriteEngine On
|
||||
|
||||
RewriteBase REPLACE_ME/
|
||||
|
||||
# fix authorization header
|
||||
RewriteCond %{HTTP:Authorization} .+
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# requests for api authorize
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_URI} ^REPLACE_ME/api/authorize/.*$
|
||||
RewriteRule ^ api/authorize/index.php [QSA,L]
|
||||
|
||||
# requests that start with api go down to api/index.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_URI} ^REPLACE_ME/api/.*$
|
||||
RewriteRule ^ api/index.php [QSA,L]
|
||||
|
||||
# install
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_URI} ^REPLACE_ME/install/.*$
|
||||
RewriteRule ^ install/index.php [QSA,L]
|
||||
|
||||
# all others - i.e. web
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !\.(css|js|png|jpg)$
|
||||
RewriteCond %{REQUEST_URI} !^REPLACE_ME/dist/.*$
|
||||
RewriteCond %{REQUEST_URI} !^REPLACE_ME/theme/.*$
|
||||
RewriteRule ^ index.php [QSA,L]
|
||||
97
docker/tmp/settings-custom.php
Normal file
97
docker/tmp/settings-custom.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/*
|
||||
* Copyright (C) 2024 Xibo Signage Ltd
|
||||
*
|
||||
* Xibo - Digital Signage - https://xibosignage.com
|
||||
*
|
||||
* This file is part of Xibo.
|
||||
*
|
||||
* Xibo is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* any later version.
|
||||
*
|
||||
* Xibo 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
|
||||
* along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// If you need to add custom configuration settings to the CMS settings.php file,
|
||||
// this is the place to do it.
|
||||
|
||||
// For example, if you want to configure SAML authentication, you can add the
|
||||
// required configuration here
|
||||
|
||||
/*
|
||||
$authentication = new \Xibo\Middleware\SAMLAuthentication();
|
||||
$samlSettings = [
|
||||
'workflow' => [
|
||||
// Enable/Disable Just-In-Time provisioning
|
||||
'jit' => true,
|
||||
// Attribute to identify the user
|
||||
'field_to_identify' => 'UserName', // Alternatives: UserID, UserName or email
|
||||
// Default libraryQuota assigned to the created user by JIT
|
||||
'libraryQuota' => 1000,
|
||||
// Home Page
|
||||
'homePage' => 'icondashboard.view',
|
||||
// Enable/Disable Single Logout
|
||||
'slo' => true,
|
||||
// Attribute mapping between XIBO-CMS and the IdP
|
||||
'mapping' => [
|
||||
'UserID' => '',
|
||||
'usertypeid' => '',
|
||||
'UserName' => 'uid',
|
||||
'email' => 'mail',
|
||||
],
|
||||
// Initial User Group
|
||||
'group' => 'Users',
|
||||
// Group Assignments
|
||||
'matchGroups' => [
|
||||
'enabled' => false,
|
||||
'attribute' => null,
|
||||
'extractionRegEx' => null,
|
||||
],
|
||||
],
|
||||
// Settings for the PHP-SAML toolkit.
|
||||
// See documentation: https://github.com/onelogin/php-saml#settings
|
||||
'strict' => false,
|
||||
'debug' => true,
|
||||
'idp' => [
|
||||
'entityId' => 'https://idp.example.com/simplesaml/saml2/idp/metadata.php',
|
||||
'singleSignOnService' => [
|
||||
'url' => 'http://idp.example.com/simplesaml/saml2/idp/SSOService.php',
|
||||
],
|
||||
'singleLogoutService' => [
|
||||
'url' => 'http://idp.example.com/simplesaml/saml2/idp/SingleLogoutService.php',
|
||||
],
|
||||
'x509cert' => '',
|
||||
],
|
||||
'sp' => [
|
||||
'entityId' => 'http://xibo-cms.example.com/saml/metadata',
|
||||
'assertionConsumerService' => [
|
||||
'url' => 'http://xibo-cms.example.com/saml/acs',
|
||||
],
|
||||
'singleLogoutService' => [
|
||||
'url' => 'http://xibo-cms.example.com/saml/sls',
|
||||
],
|
||||
'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress',
|
||||
'x509cert' => '',
|
||||
'privateKey' > '',
|
||||
,
|
||||
'security' => [
|
||||
'nameIdEncrypted' => false,
|
||||
'authnRequestsSigned' => false,
|
||||
'logoutRequestSigned' => false,
|
||||
'logoutResponseSigned' => false,
|
||||
'signMetadata' => false,
|
||||
'wantMessagesSigned' => false,
|
||||
'wantAssertionsSigned' => false,
|
||||
'wantAssertionsEncrypted' => false,
|
||||
'wantNameIdEncrypted' => false,
|
||||
],
|
||||
];
|
||||
*/
|
||||
47
docker/tmp/settings.php-template
Normal file
47
docker/tmp/settings.php-template
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Xibo - Digital Signage - http://www.xibo.org.uk
|
||||
*
|
||||
* This file is part of Xibo - and is automatically generated by the installer
|
||||
*
|
||||
* You should not need to edit this file, unless your SQL connection details have changed.
|
||||
*/
|
||||
|
||||
defined('XIBO') or die(__("Sorry, you are not allowed to directly access this page.") . "<br />" . __("Please press the back button in your browser."));
|
||||
|
||||
global $dbhost;
|
||||
global $dbuser;
|
||||
global $dbpass;
|
||||
global $dbname;
|
||||
global $dbssl;
|
||||
global $dbsslverify;
|
||||
|
||||
$dbhost = $_SERVER['MYSQL_HOST'] . ':' . $_SERVER['MYSQL_PORT'];
|
||||
$dbuser = $_SERVER['MYSQL_USER'];
|
||||
$dbpass = $_SERVER['MYSQL_PASSWORD'];
|
||||
$dbname = $_SERVER['MYSQL_DATABASE'];
|
||||
$dbssl = $_SERVER['MYSQL_ATTR_SSL_CA'];
|
||||
$dbsslverify = $_SERVER['MYSQL_ATTR_SSL_VERIFY_SERVER_CERT'];
|
||||
|
||||
if (!defined('SECRET_KEY')) {
|
||||
define('SECRET_KEY','');
|
||||
}
|
||||
|
||||
if (array_key_exists('CMS_USE_MEMCACHED', $_SERVER)
|
||||
&& ($_SERVER['CMS_USE_MEMCACHED'] === true || $_SERVER['CMS_USE_MEMCACHED'] === 'true')
|
||||
) {
|
||||
global $cacheDrivers;
|
||||
$cacheDrivers = [
|
||||
new Stash\Driver\Memcache([
|
||||
'servers' => [$_SERVER['MEMCACHED_HOST'], $_SERVER['MEMCACHED_PORT']],
|
||||
'CONNECT_TIMEOUT' => 10,
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
if (file_exists('/var/www/cms/custom/settings-custom.php')) {
|
||||
include('/var/www/cms/custom/settings-custom.php');
|
||||
}
|
||||
|
||||
?>
|
||||
347
docker/usr/bin/oauth2.py
Executable file
347
docker/usr/bin/oauth2.py
Executable file
@@ -0,0 +1,347 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright 2012 Google Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Performs client tasks for testing IMAP OAuth2 authentication.
|
||||
|
||||
To use this script, you'll need to have registered with Google as an OAuth
|
||||
application and obtained an OAuth client ID and client secret.
|
||||
See https://developers.google.com/identity/protocols/OAuth2 for instructions on
|
||||
registering and for documentation of the APIs invoked by this code.
|
||||
|
||||
This script has 3 modes of operation.
|
||||
|
||||
1. The first mode is used to generate and authorize an OAuth2 token, the
|
||||
first step in logging in via OAuth2.
|
||||
|
||||
oauth2 --user=xxx@gmail.com \
|
||||
--client_id=1038[...].apps.googleusercontent.com \
|
||||
--client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||
--generate_oauth2_token
|
||||
|
||||
The script will converse with Google and generate an oauth request
|
||||
token, then present you with a URL you should visit in your browser to
|
||||
authorize the token. Once you get the verification code from the Google
|
||||
website, enter it into the script to get your OAuth access token. The output
|
||||
from this command will contain the access token, a refresh token, and some
|
||||
metadata about the tokens. The access token can be used until it expires, and
|
||||
the refresh token lasts indefinitely, so you should record these values for
|
||||
reuse.
|
||||
|
||||
2. The script will generate new access tokens using a refresh token.
|
||||
|
||||
oauth2 --user=xxx@gmail.com \
|
||||
--client_id=1038[...].apps.googleusercontent.com \
|
||||
--client_secret=VWFn8LIKAMC-MsjBMhJeOplZ \
|
||||
--refresh_token=1/Yzm6MRy4q1xi7Dx2DuWXNgT6s37OrP_DW_IoyTum4YA
|
||||
|
||||
3. The script will generate an OAuth2 string that can be fed
|
||||
directly to IMAP or SMTP. This is triggered with the --generate_oauth2_string
|
||||
option.
|
||||
|
||||
oauth2 --generate_oauth2_string --user=xxx@gmail.com \
|
||||
--access_token=ya29.AGy[...]ezLg
|
||||
|
||||
The output of this mode will be a base64-encoded string. To use it, connect to a
|
||||
IMAPFE and pass it as the second argument to the AUTHENTICATE command.
|
||||
|
||||
a AUTHENTICATE XOAUTH2 a9sha9sfs[...]9dfja929dk==
|
||||
"""
|
||||
|
||||
import base64
|
||||
import imaplib
|
||||
import json
|
||||
from optparse import OptionParser
|
||||
import smtplib
|
||||
import sys
|
||||
import urllib
|
||||
|
||||
|
||||
def SetupOptionParser():
|
||||
# Usage message is the module's docstring.
|
||||
parser = OptionParser(usage=__doc__)
|
||||
parser.add_option('--generate_oauth2_token',
|
||||
action='store_true',
|
||||
dest='generate_oauth2_token',
|
||||
help='generates an OAuth2 token for testing')
|
||||
parser.add_option('--generate_oauth2_string',
|
||||
action='store_true',
|
||||
dest='generate_oauth2_string',
|
||||
help='generates an initial client response string for '
|
||||
'OAuth2')
|
||||
parser.add_option('--client_id',
|
||||
default=None,
|
||||
help='Client ID of the application that is authenticating. '
|
||||
'See OAuth2 documentation for details.')
|
||||
parser.add_option('--client_secret',
|
||||
default=None,
|
||||
help='Client secret of the application that is '
|
||||
'authenticating. See OAuth2 documentation for '
|
||||
'details.')
|
||||
parser.add_option('--access_token',
|
||||
default=None,
|
||||
help='OAuth2 access token')
|
||||
parser.add_option('--refresh_token',
|
||||
default=None,
|
||||
help='OAuth2 refresh token')
|
||||
parser.add_option('--scope',
|
||||
default='https://mail.google.com/',
|
||||
help='scope for the access token. Multiple scopes can be '
|
||||
'listed separated by spaces with the whole argument '
|
||||
'quoted.')
|
||||
parser.add_option('--test_imap_authentication',
|
||||
action='store_true',
|
||||
dest='test_imap_authentication',
|
||||
help='attempts to authenticate to IMAP')
|
||||
parser.add_option('--test_smtp_authentication',
|
||||
action='store_true',
|
||||
dest='test_smtp_authentication',
|
||||
help='attempts to authenticate to SMTP')
|
||||
parser.add_option('--user',
|
||||
default=None,
|
||||
help='email address of user whose account is being '
|
||||
'accessed')
|
||||
parser.add_option('--quiet',
|
||||
action='store_true',
|
||||
default=False,
|
||||
dest='quiet',
|
||||
help='Omit verbose descriptions and only print '
|
||||
'machine-readable outputs.')
|
||||
return parser
|
||||
|
||||
|
||||
# The URL root for accessing Google Accounts.
|
||||
GOOGLE_ACCOUNTS_BASE_URL = 'https://accounts.google.com'
|
||||
|
||||
|
||||
# Hardcoded dummy redirect URI for non-web apps.
|
||||
REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
|
||||
|
||||
def AccountsUrl(command):
|
||||
"""Generates the Google Accounts URL.
|
||||
|
||||
Args:
|
||||
command: The command to execute.
|
||||
|
||||
Returns:
|
||||
A URL for the given command.
|
||||
"""
|
||||
return '%s/%s' % (GOOGLE_ACCOUNTS_BASE_URL, command)
|
||||
|
||||
|
||||
def UrlEscape(text):
|
||||
# See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||
return urllib.quote(text, safe='~-._')
|
||||
|
||||
|
||||
def UrlUnescape(text):
|
||||
# See OAUTH 5.1 for a definition of which characters need to be escaped.
|
||||
return urllib.unquote(text)
|
||||
|
||||
|
||||
def FormatUrlParams(params):
|
||||
"""Formats parameters into a URL query string.
|
||||
|
||||
Args:
|
||||
params: A key-value map.
|
||||
|
||||
Returns:
|
||||
A URL query string version of the given parameters.
|
||||
"""
|
||||
param_fragments = []
|
||||
for param in sorted(params.iteritems(), key=lambda x: x[0]):
|
||||
param_fragments.append('%s=%s' % (param[0], UrlEscape(param[1])))
|
||||
return '&'.join(param_fragments)
|
||||
|
||||
|
||||
def GeneratePermissionUrl(client_id, scope='https://mail.google.com/'):
|
||||
"""Generates the URL for authorizing access.
|
||||
|
||||
This uses the "OAuth2 for Installed Applications" flow described at
|
||||
https://developers.google.com/accounts/docs/OAuth2InstalledApp
|
||||
|
||||
Args:
|
||||
client_id: Client ID obtained by registering your app.
|
||||
scope: scope for access token, e.g. 'https://mail.google.com'
|
||||
Returns:
|
||||
A URL that the user should visit in their browser.
|
||||
"""
|
||||
params = {}
|
||||
params['client_id'] = client_id
|
||||
params['redirect_uri'] = REDIRECT_URI
|
||||
params['scope'] = scope
|
||||
params['response_type'] = 'code'
|
||||
return '%s?%s' % (AccountsUrl('o/oauth2/auth'),
|
||||
FormatUrlParams(params))
|
||||
|
||||
|
||||
def AuthorizeTokens(client_id, client_secret, authorization_code):
|
||||
"""Obtains OAuth access token and refresh token.
|
||||
|
||||
This uses the application portion of the "OAuth2 for Installed Applications"
|
||||
flow at https://developers.google.com/accounts/docs/OAuth2InstalledApp#handlingtheresponse
|
||||
|
||||
Args:
|
||||
client_id: Client ID obtained by registering your app.
|
||||
client_secret: Client secret obtained by registering your app.
|
||||
authorization_code: code generated by Google Accounts after user grants
|
||||
permission.
|
||||
Returns:
|
||||
The decoded response from the Google Accounts server, as a dict. Expected
|
||||
fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||
"""
|
||||
params = {}
|
||||
params['client_id'] = client_id
|
||||
params['client_secret'] = client_secret
|
||||
params['code'] = authorization_code
|
||||
params['redirect_uri'] = REDIRECT_URI
|
||||
params['grant_type'] = 'authorization_code'
|
||||
request_url = AccountsUrl('o/oauth2/token')
|
||||
|
||||
response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
|
||||
return json.loads(response)
|
||||
|
||||
|
||||
def RefreshToken(client_id, client_secret, refresh_token):
|
||||
"""Obtains a new token given a refresh token.
|
||||
|
||||
See https://developers.google.com/accounts/docs/OAuth2InstalledApp#refresh
|
||||
|
||||
Args:
|
||||
client_id: Client ID obtained by registering your app.
|
||||
client_secret: Client secret obtained by registering your app.
|
||||
refresh_token: A previously-obtained refresh token.
|
||||
Returns:
|
||||
The decoded response from the Google Accounts server, as a dict. Expected
|
||||
fields include 'access_token', 'expires_in', and 'refresh_token'.
|
||||
"""
|
||||
params = {}
|
||||
params['client_id'] = client_id
|
||||
params['client_secret'] = client_secret
|
||||
params['refresh_token'] = refresh_token
|
||||
params['grant_type'] = 'refresh_token'
|
||||
request_url = AccountsUrl('o/oauth2/token')
|
||||
|
||||
response = urllib.urlopen(request_url, urllib.urlencode(params)).read()
|
||||
return json.loads(response)
|
||||
|
||||
|
||||
def GenerateOAuth2String(username, access_token, base64_encode=True):
|
||||
"""Generates an IMAP OAuth2 authentication string.
|
||||
|
||||
See https://developers.google.com/google-apps/gmail/oauth2_overview
|
||||
|
||||
Args:
|
||||
username: the username (email address) of the account to authenticate
|
||||
access_token: An OAuth2 access token.
|
||||
base64_encode: Whether to base64-encode the output.
|
||||
|
||||
Returns:
|
||||
The SASL argument for the OAuth2 mechanism.
|
||||
"""
|
||||
auth_string = 'user=%s\1auth=Bearer %s\1\1' % (username, access_token)
|
||||
if base64_encode:
|
||||
auth_string = base64.b64encode(auth_string)
|
||||
return auth_string
|
||||
|
||||
|
||||
def TestImapAuthentication(user, auth_string):
|
||||
"""Authenticates to IMAP with the given auth_string.
|
||||
|
||||
Prints a debug trace of the attempted IMAP connection.
|
||||
|
||||
Args:
|
||||
user: The Gmail username (full email address)
|
||||
auth_string: A valid OAuth2 string, as returned by GenerateOAuth2String.
|
||||
Must not be base64-encoded, since imaplib does its own base64-encoding.
|
||||
"""
|
||||
print
|
||||
imap_conn = imaplib.IMAP4_SSL('imap.gmail.com')
|
||||
imap_conn.debug = 4
|
||||
imap_conn.authenticate('XOAUTH2', lambda x: auth_string)
|
||||
imap_conn.select('INBOX')
|
||||
|
||||
|
||||
def TestSmtpAuthentication(user, auth_string):
|
||||
"""Authenticates to SMTP with the given auth_string.
|
||||
|
||||
Args:
|
||||
user: The Gmail username (full email address)
|
||||
auth_string: A valid OAuth2 string, not base64-encoded, as returned by
|
||||
GenerateOAuth2String.
|
||||
"""
|
||||
print
|
||||
smtp_conn = smtplib.SMTP('smtp.gmail.com', 587)
|
||||
smtp_conn.set_debuglevel(True)
|
||||
smtp_conn.ehlo('test')
|
||||
smtp_conn.starttls()
|
||||
smtp_conn.docmd('AUTH', 'XOAUTH2 ' + base64.b64encode(auth_string))
|
||||
|
||||
|
||||
def RequireOptions(options, *args):
|
||||
missing = [arg for arg in args if getattr(options, arg) is None]
|
||||
if missing:
|
||||
print 'Missing options: %s' % ' '.join(missing)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def main(argv):
|
||||
options_parser = SetupOptionParser()
|
||||
(options, args) = options_parser.parse_args()
|
||||
if options.refresh_token:
|
||||
RequireOptions(options, 'client_id', 'client_secret')
|
||||
response = RefreshToken(options.client_id, options.client_secret,
|
||||
options.refresh_token)
|
||||
if options.quiet:
|
||||
print response['access_token']
|
||||
else:
|
||||
print 'Access Token: %s' % response['access_token']
|
||||
print 'Access Token Expiration Seconds: %s' % response['expires_in']
|
||||
elif options.generate_oauth2_string:
|
||||
RequireOptions(options, 'user', 'access_token')
|
||||
oauth2_string = GenerateOAuth2String(options.user, options.access_token)
|
||||
if options.quiet:
|
||||
print oauth2_string
|
||||
else:
|
||||
print 'OAuth2 argument:\n' + oauth2_string
|
||||
elif options.generate_oauth2_token:
|
||||
RequireOptions(options, 'client_id', 'client_secret')
|
||||
print 'To authorize token, visit this url and follow the directions:'
|
||||
print ' %s' % GeneratePermissionUrl(options.client_id, options.scope)
|
||||
authorization_code = raw_input('Enter verification code: ')
|
||||
response = AuthorizeTokens(options.client_id, options.client_secret,
|
||||
authorization_code)
|
||||
print 'Refresh Token: %s' % response['refresh_token']
|
||||
print 'Access Token: %s' % response['access_token']
|
||||
print 'Access Token Expiration Seconds: %s' % response['expires_in']
|
||||
elif options.test_imap_authentication:
|
||||
RequireOptions(options, 'user', 'access_token')
|
||||
TestImapAuthentication(options.user,
|
||||
GenerateOAuth2String(options.user, options.access_token,
|
||||
base64_encode=False))
|
||||
elif options.test_smtp_authentication:
|
||||
RequireOptions(options, 'user', 'access_token')
|
||||
TestSmtpAuthentication(options.user,
|
||||
GenerateOAuth2String(options.user, options.access_token,
|
||||
base64_encode=False))
|
||||
else:
|
||||
options_parser.print_help()
|
||||
print 'Nothing to do, exiting.'
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
30
docker/usr/local/bin/httpd-foreground
Normal file
30
docker/usr/local/bin/httpd-foreground
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2023 Xibo Signage Ltd
|
||||
#
|
||||
# Xibo - Digital Signage - https://xibosignage.com
|
||||
#
|
||||
# This file is part of Xibo.
|
||||
#
|
||||
# Xibo is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# any later version.
|
||||
#
|
||||
# Xibo 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
|
||||
# along with Xibo. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Apache gets grumpy about PID files pre-existing
|
||||
rm -rf /run/apache2/*
|
||||
|
||||
source /etc/apache2/envvars
|
||||
|
||||
/usr/sbin/apache2 -DFOREGROUND
|
||||
151
docker/usr/local/bin/wait-for-command.sh
Normal file
151
docker/usr/local/bin/wait-for-command.sh
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env sh
|
||||
#
|
||||
# Compare a command exit status to some given number(s) for a period of time.
|
||||
|
||||
CMD_NAME="${0##*/}"
|
||||
CMD=""
|
||||
STATUS=0
|
||||
TIME=10
|
||||
TIME_START=0
|
||||
QUIET=0
|
||||
EXIT_STATUS=1
|
||||
|
||||
usage() {
|
||||
cat << EOF >&2
|
||||
Usage: ${CMD_NAME} [-c 'COMMAND']
|
||||
${CMD_NAME} [OPTION]... [-c 'COMMAND']
|
||||
${CMD_NAME} [-c 'COMMAND'] [OPTION]...
|
||||
|
||||
${CMD_NAME} compares a command exit status to some given number(s)
|
||||
for a period of time. If comparison is successfully
|
||||
${CMD_NAME} returns 0, otherwise 1.
|
||||
|
||||
Example: ${CMD_NAME} -c 'echo > /dev/tcp/127.0.0.1/5432'
|
||||
${CMD_NAME} -s 0 57 -c 'curl 127.0.0.1:5432'
|
||||
${CMD_NAME} -c 'nc -z 127.0.0.1 5432' -s 0 -t 20 -q
|
||||
|
||||
Options:
|
||||
-c, --command ['COMMAND'] execute a COMMAND.
|
||||
-s, --status [NUMBER]... target exit status of COMMAND, default 0.
|
||||
-t, --time [NUMBER] max time to wait in seconds, default 10.
|
||||
-q, --quiet do not make any output, default false.
|
||||
--help display this help.
|
||||
|
||||
Notice that quotes are needed after -c/--command for multi-argument
|
||||
COMMANDs.
|
||||
|
||||
Specifying a same OPTION more than once overrides the previews.
|
||||
So "${CMD_NAME} -c 'nothing' -c 'curl 127.0.0.1:5432'" will be
|
||||
the same as "${CMD_NAME} -c 'curl 127.0.0.1:5432'".
|
||||
It does not apply to option -q/--quiet.
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
output() {
|
||||
if [ "${QUIET}" -ne 1 ]; then
|
||||
printf "%s\n" "$*" 1>&2;
|
||||
fi
|
||||
}
|
||||
|
||||
process_command() {
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-c | --command)
|
||||
# allow one shift when no arguments
|
||||
if [ -n "$2" ]; then
|
||||
CMD="$2"
|
||||
shift 1
|
||||
fi
|
||||
shift 1
|
||||
;;
|
||||
-s | --status)
|
||||
# ensure that a number is provided
|
||||
if ([ "$2" -eq "$2" ]) >/dev/null 2>&1; then
|
||||
unset STATUS
|
||||
# ensure that a number is provided
|
||||
while ([ "$2" -eq "$2" ]) >/dev/null 2>&1; do
|
||||
if [ -z "${STATUS}" ]; then
|
||||
STATUS="$2"
|
||||
shift 1
|
||||
else
|
||||
STATUS="${STATUS} $2"
|
||||
shift 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
shift 1
|
||||
;;
|
||||
-t | --time)
|
||||
# ensure that a number is provided
|
||||
if ([ "$2" -eq "$2" ]) >/dev/null 2>&1; then
|
||||
TIME="$2"
|
||||
shift 1
|
||||
fi
|
||||
shift 1
|
||||
;;
|
||||
-q | --quiet)
|
||||
QUIET=1
|
||||
shift 1
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
output "Unknown argument: $1"
|
||||
output "Try '${CMD_NAME} --help' for more information."
|
||||
exit "${EXIT_STATUS}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "${CMD}" ]; then
|
||||
output "Missing command: -c, --command ['COMMAND']"
|
||||
output "Try '${CMD_NAME} --help' for more information."
|
||||
exit "${EXIT_STATUS}"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
message="failed"
|
||||
|
||||
process_command "$@"
|
||||
|
||||
TIME_START=$(date +%s)
|
||||
|
||||
while [ $(($(date +%s)-TIME_START)) -lt "${TIME}" ]; do
|
||||
|
||||
($CMD) >/dev/null 2>&1 &
|
||||
pid="$!"
|
||||
|
||||
# while both ps and time are running sleep 1s
|
||||
while kill -0 "${pid}" >/dev/null 2>&1 &&
|
||||
[ $(($(date +%s)-TIME_START)) -lt "${TIME}" ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# gets CMD status
|
||||
kill "${pid}" >/dev/null 2>&1
|
||||
wait "${pid}" >/dev/null 2>&1
|
||||
cmd_exit_status="$?"
|
||||
|
||||
# looks for equlity in CMD exit status and one of the given status
|
||||
for i in $STATUS; do
|
||||
if ([ "${cmd_exit_status}" -eq "${i}" ]) >/dev/null 2>&1; then
|
||||
message="finished successfully"
|
||||
EXIT_STATUS=0
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
|
||||
done
|
||||
|
||||
output "${CMD_NAME} ${message} after $(($(date +%s)-TIME_START)) second(s)."
|
||||
output "cmd: ${CMD}"
|
||||
output "target exit status: ${STATUS}"
|
||||
output "exited status: ${cmd_exit_status}"
|
||||
|
||||
exit "${EXIT_STATUS}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
0
docker/var/www/.gnupg/private-keys-v1.d/.gitkeep
Normal file
0
docker/var/www/.gnupg/private-keys-v1.d/.gitkeep
Normal file
BIN
docker/var/www/.gnupg/pubring.kbx
Normal file
BIN
docker/var/www/.gnupg/pubring.kbx
Normal file
Binary file not shown.
BIN
docker/var/www/.gnupg/trustdb.gpg
Normal file
BIN
docker/var/www/.gnupg/trustdb.gpg
Normal file
Binary file not shown.
Reference in New Issue
Block a user