remaining docker

This commit is contained in:
root
2025-12-07 10:55:22 -08:00
parent 0f08fcb176
commit ff9ca35e91
20 changed files with 4025 additions and 0 deletions

24
export-certs.sh Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
# Exit immediately if any command fails
set -e
# --- Configuration ---
REDIS_HOST="caddy-redis"
DOMAIN="mail.poppyglen.cc"
OUTPUT_DIR="/export"
# Define the exact keys for the certificate and private key files
CERT_REDIS_KEY="caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}.crt"
PKEY_REDIS_KEY="caddy/certificates/acme-v02.api.letsencrypt.org-directory/${DOMAIN}/${DOMAIN}.key"
echo "Fetching, parsing, and decoding certificate from Redis..."
# The new pipeline: Get from Redis -> Extract the .value field -> Decode from Base64 -> Save to file
redis-cli -h "${REDIS_HOST}" --raw GET "${CERT_REDIS_KEY}" | jq -r '.value' | base64 -d > "${OUTPUT_DIR}/${DOMAIN}.crt"
echo "Fetching, parsing, and decoding private key from Redis..."
redis-cli -h "${REDIS_HOST}" --raw GET "${PKEY_REDIS_KEY}" | jq -r '.value' | base64 -d > "${OUTPUT_DIR}/${DOMAIN}.key"
# Set correct permissions
chmod 644 "${OUTPUT_DIR}/${DOMAIN}.crt" "${OUTPUT_DIR}/${DOMAIN}.key"
echo "✅ Successfully exported and decoded certificate and key for ${DOMAIN}."

View File

@@ -0,0 +1,68 @@
#!/bin/bash
echo "[HOOK] Post-install: creating users and group"
# Source environment variables
if [ -f /var/www/html/.env ]; then
source /var/www/html/.env
fi
# Ensure USERS_JSON is set
if [ -z "$USERS_JSON" ]; then
echo "Error: USERS_JSON environment variable is not set."
exit 1
fi
# Create Family group if it doesn't exist
if ! php /var/www/html/occ group:list | grep -q 'Family'; then
php /var/www/html/occ group:add Family || { echo "Failed to create group Family"; exit 1; }
fi
# Iterate over each user using PHP to parse JSON
php -r '
$users = json_decode(getenv("USERS_JSON"), true);
if ($users === null) {
fwrite(STDERR, "Invalid USERS_JSON\n");
exit(1);
}
foreach ($users as $user) {
$username = $user["username"] ?? "";
$password = $user["password"] ?? "";
$display_name = $user["name"] ?? "";
$email = $user["email"] ?? "";
if (!$username || !$password) {
fwrite(STDERR, "Skipping user with missing username or password\n");
continue;
}
echo "$username|$password|$display_name|$email\n";
}
' | while IFS='|' read -r username password display_name email; do
# Skip if password missing
if [ -z "$password" ] || [ "$password" == "null" ]; then
echo "Error: Password is not set for $username."
continue
fi
# Check if user exists
if ! php /var/www/html/occ user:info "$username" > /dev/null 2>&1; then
echo "Creating user $username..."
env OC_PASS="$password" php /var/www/html/occ user:add \
--password-from-env "$username" \
--display-name="$display_name" \
--email="$email" \
--group='Family' || echo "Failed to create user $username"
# Setup user files
mkdir -p /var/www/html/data/"$username"/files
cp /var/www/html/core/skeleton/* /var/www/html/data/"$username"/files/ 2>/dev/null || true
chown -R www-data:www-data /var/www/html/data/"$username"
php /var/www/html/occ files:scan "$username" || echo "Failed to scan files for $username"
# Set the user's personal email in Nextcloud
if [ -n "$email" ]; then
php /var/www/html/occ user:setting "$username" settings email "$email" || echo "Failed to set email for $username"
fi
fi
done

View File

@@ -0,0 +1,507 @@
#!/bin/bash
# --- BEGIN: S3 Object Storage Configuration ---
echo "[HOOK] Configuring S3 Object Storage..."
CONFIG_FILE="/var/www/html/config/config.php"
# Check if objectstore is already configured to make this script idempotent
if ! grep -q "'objectstore'" "$CONFIG_FILE"; then
echo " -> S3 objectstore not found in config. Adding it now..."
# Define the single-line configuration block with \n for newlines
S3_CONFIG=" 'objectstore' => [\n 'class' => '\\\\\\\\OC\\\\\\\\Files\\\\\\\\ObjectStore\\\\\\\\S3',\n 'arguments' => [\n 'bucket' => 'nextcloud-data',\n 'hostname' => 'minio',\n 'port' => 9000,\n 'key' => getenv('MINIO_ACCESS_KEY'),\n 'secret' => getenv('MINIO_SECRET_KEY'),\n 'use_ssl' => false,\n 'use_path_style' => true,\n ],\n ],"
# Use sed to replace the closing ");" with ",\n S3_CONFIG \n);"
sed -i "s/^);/\n${S3_CONFIG}\n);/" "$CONFIG_FILE"
echo " -> S3 objectstore configuration has been added to config.php."
else
echo " -> S3 objectstore already configured. Skipping."
fi
# --- END: S3 Object Storage Configuration ---
echo "[HOOK] Post-install: enabling default apps"
# --- App Configuration ---
# Set the specific version for Calendar
#CALENDAR_VERSION="5.3.10"
#CALENDAR_DOWNLOAD_URL="https://github.com/nextcloud-releases/calendar/releases/download/v${CALENDAR_VERSION}/calendar-v${CALENDAR_VERSION}.tar.gz"
# --- Paths ---
#NEXTCLOUD_APPS_PATH="/var/www/html/apps"
#CALENDAR_APP_DIR="${NEXTCLOUD_APPS_PATH}/calendar"
#TEMP_ARCHIVE_PATH="/tmp/calendar.tar.gz"
#if [ ! -d "${CALENDAR_APP_DIR}" ]; then
# echo "Installing Calendar version ${CALENDAR_VERSION} using PHP..."
# php -r "
# \$url = '${CALENDAR_DOWNLOAD_URL}';
# \$dest = '${TEMP_ARCHIVE_PATH}';
# echo 'Downloading from ' . \$url . '...\\n';
# \$fileData = file_get_contents(\$url);
# if (\$fileData === false) {
# echo 'Error: Failed to download file.\\n';
# exit(1);
# }
# file_put_contents(\$dest, \$fileData);
# echo 'Download complete.\\n';
# "
# if [ -f "${TEMP_ARCHIVE_PATH}" ]; then
# tar -xzf "${TEMP_ARCHIVE_PATH}" -C "${NEXTCLOUD_APPS_PATH}"
# rm "${TEMP_ARCHIVE_PATH}"
# echo "Calendar version ${CALENDAR_VERSION} installed."
# else
# echo "Failed to install Calendar. The archive was not downloaded."
# fi
#fi
if ! php /var/www/html/occ app:list | grep -q 'calendar'; then
php /var/www/html/occ app:install calendar || echo "Failed to install calendar"
php /var/www/html/occ app:enable calendar || echo "Failed to enable calendar"
fi
echo "Calendar app script finished."
if ! php /var/www/html/occ app:list | grep -wq 'contacts:'; then
php /var/www/html/occ app:install contacts || echo "Failed to install contacts"
php /var/www/html/occ app:enable contacts || echo "Failed to enable contacts"
fi
if ! php /var/www/html/occ app:list | grep -q 'deck'; then
php /var/www/html/occ app:install deck || echo "Failed to install deck"
php /var/www/html/occ app:enable deck || echo "Failed to enable deck"
fi
if ! php /var/www/html/occ app:list | grep -q 'whiteboard'; then
php /var/www/html/occ app:install whiteboard || echo "Failed to install whiteboard"
php /var/www/html/occ app:enable whiteboard || echo "Failed to enable whiteboard"
fi
echo "Configuring Whiteboard app for real-time collaboration..."
php /var/www/html/occ config:app:set whiteboard collabBackendUrl --value="https://whiteboard.poppyglen.cc"
php /var/www/html/occ config:app:set whiteboard jwt_secret_key --value="${WHITEBOARD_JWT}"
echo "--- Installing and Configuring Antivirus for files ---"
if ! php /var/www/html/occ app:list | grep -q 'files_antivirus'; then
php /var/www/html/occ app:install files_antivirus || echo "Failed to install files_antivirus"
php /var/www/html/occ app:enable files_antivirus || echo "Failed to enable files_antivirus"
fi
# Set the mode to "daemon"
php /var/www/html/occ config:app:set files_antivirus av_mode --value="daemon"
# Set the host to the Docker service name of the ClamAV container
php /var/www/html/occ config:app:set files_antivirus av_host --value="clamav"
# Set the port for the ClamAV daemon
php /var/www/html/occ config:app:set files_antivirus av_port --value="3310"
# Set the stream length for scanning large files (Nextcloud's recommended value)
php /var/www/html/occ config:app:set files_antivirus av_stream_max_length --value="10485760"
# Set the action to take when an infected file is found
php /var/www/html/occ config:app:set files_antivirus av_infected_action --value="delete"
echo "Antivirus configuration complete."
if ! php /var/www/html/occ app:list | grep -q 'collectives'; then
php /var/www/html/occ app:install collectives || echo "Failed to install collectives"
php /var/www/html/occ app:enable collectives || echo "Failed to enable collectives"
fi
if ! php /var/www/html/occ app:list | grep -q 'tables'; then
php /var/www/html/occ app:install tables || echo "Failed to install tables"
php /var/www/html/occ app:enable tables || echo "Failed to enable tables"
fi
if ! php /var/www/html/occ app:list | grep -q 'cookbook'; then
php /var/www/html/occ app:install cookbook || echo "Failed to install cookbook"
php /var/www/html/occ app:enable cookbook || echo "Failed to enable cookbook"
fi
# Install and enable the Giphy app
#if ! php /var/www/html/occ app:list | grep -q 'giphy:.*enabled'; then
# php /var/www/html/occ app:install giphy || echo "Failed to install giphy"
# php /var/www/html/occ app:enable giphy || echo "Failed to enable giphy"
#fi
# IMPORTANT: Configure the Giphy API Key
# Replace 'YOUR_GIPHY_API_KEY' with the key you generate.
#php /var/www/html/occ config:app:set giphy giphy_api_key --value=${GIPHY_API_KEY}
if ! php /var/www/html/occ app:list | grep -q 'notes'; then
php /var/www/html/occ app:install notes || echo "Failed to install notes"
php /var/www/html/occ app:enable notes || echo "Failed to enable notes"
fi
if ! php /var/www/html/occ app:list | grep -q 'tasks'; then
php /var/www/html/occ app:install tasks || echo "Failed to install tasks"
php /var/www/html/occ app:enable tasks || echo "Failed to enable tasks"
fi
if ! php /var/www/html/occ app:list | grep -q 'forms'; then
php /var/www/html/occ app:install forms || echo "Failed to install forms"
php /var/www/html/occ app:enable forms || echo "Failed to enable forms"
fi
if ! php /var/www/html/occ app:list | grep -q 'recognize'; then
php /var/www/html/occ app:install recognize || echo "Failed to install recognize"
php /var/www/html/occ app:enable recognize || echo "Failed to enable recognize"
fi
php /var/www/html/occ config:app:set recognize faces.enabled --value "true"
php /var/www/html/occ recognize:download-models
if ! php /var/www/html/occ app:list | grep -wq 'groupfolders:'; then
php /var/www/html/occ app:install groupfolders || echo "Failed to install groupfolders"
php /var/www/html/occ app:enable groupfolders || echo "Failed to enable groupfolders"
fi
if ! php /var/www/html/occ app:list | grep -q 'user_usage_report'; then
php /var/www/html/occ app:install user_usage_report || echo "Failed to install user_usage_report"
php /var/www/html/occ app:enable user_usage_report || echo "Failed to enable user_usage_report"
fi
if ! php /var/www/html/occ app:list | grep -q 'maps'; then
php /var/www/html/occ app:install maps || echo "Failed to install maps"
php /var/www/html/occ app:enable maps || echo "Failed to enable maps"
fi
if ! php /var/www/html/occ app:list | grep -q 'music'; then
php /var/www/html/occ app:install music || echo "Failed to install music"
php /var/www/html/occ app:enable music || echo "Failed to enable music"
fi
if ! php /var/www/html/occ app:list | grep -q 'assistant'; then
php /var/www/html/occ app:install assistant || echo "Failed to install assistant"
php /var/www/html/occ app:enable assistant || echo "Failed to enable assistant"
fi
if ! php /var/www/html/occ app:list | grep -q 'context_chat'; then
php /var/www/html/occ app:install context_chat || echo "Failed to install context_chat"
php /var/www/html/occ app:enable context_chat || echo "Failed to enable context_chat"
fi
if ! php /var/www/html/occ app:list | grep -q 'integration_openai'; then
php /var/www/html/occ app:install integration_openai || echo "Failed to install integration_openai"
php /var/www/html/occ app:enable integration_openai || echo "Failed to enable integration_openai"
fi
php /var/www/html/occ config:app:set integration_openai url --value="http://ollama:11434/v1"
#php /var/www/html/occ config:app:set integration_openai translation_provider_enabled --value="true" --type=boolean
#php /var/www/html/occ config:app:set integration_openai t2i_provider_enabled --value="false" --type=boolean
php /var/www/html/occ config:app:set integration_openai default_image_model_id --value=""
#php /var/www/html/occ config:app:set integration_openai stt_provider_enabled --value="false" --type=boolean
php /var/www/html/occ config:app:set integration_openai default_stt_model_id --value=""
#php /var/www/html/occ config:app:set integration_openai tts_provider_enabled --value="false" --type=boolean
php /var/www/html/occ config:app:set integration_openai default_speech_model_id --value=""
#php /var/www/html/occ config:app:set integration_openai llm_provider_enabled --value="true" --type=boolean
php /var/www/html/occ config:app:set integration_openai default_completion_model_id --value="qwen3:4b"
if ! php /var/www/html/occ app:list | grep -q 'previewgenerator'; then
php /var/www/html/occ app:install previewgenerator || echo "Failed to install previewgenerator"
php /var/www/html/occ app:enable previewgenerator || echo "Failed to enable previewgenerator"
fi
#if ! php /var/www/html/occ app:list | grep -q 'facerecognition'; then
# php /var/www/html/occ app:install facerecognition || echo "Failed to install facerecognition"
# php /var/www/html/occ app:enable facerecognition || echo "Failed to enable facerecognition"
#fi
#apt-get update && apt-get install -y ffmpeg
# --- Installing and Configuring Memories ---
echo "Checking Memories app installation..."
if ! php /var/www/html/occ app:list | grep -q 'memories'; then
php /var/www/html/occ app:install memories || echo "Failed to install memories"
php /var/www/html/occ app:enable memories || echo "Failed to enable memories"
fi
# Configure Memories App
# Set the welcome screen to 'seen' for all users
php /var/www/html/occ config:app:set memories welcome_seen --value="1"
# OPTIONAL: Enable Geotagging to see photos on a map
php /var/www/html/occ config:app:set memories geotagging --value="1"
php /var/www/html/occ memories:places-setup
php /var/www/html/occ config:system:set memories.vod.disable --value="false" --type=boolean
php /var/www/html/occ config:system:set memories.vod.external --value="true" --type=boolean
php /var/www/html/occ config:system:set memories.vod.bind --value="go-vod:47788"
php /var/www/html/occ config:system:set memories.vod.connect --value="go-vod:47788"
php /var/www/html/occ config:system:set memories.vod.vaapi --value="true" --type=boolean
php /var/www/html/occ config:system:set memories.vod.ffmpeg --value="/usr/bin/ffmpeg"
php /var/www/html/occ config:system:set memories.vod.ffprobe --value="/usr/bin/ffprobe"
echo "Starting initial file scan for Memories. This may take a long time..."
# Run the initial indexing scan. This command finds and processes all media files.
# The Nextcloud cron job will handle subsequent scans automatically.
php /var/www/html/occ memories:index
echo "✅ Memories installation and initial scan complete."
if ! php /var/www/html/occ app:list | grep -q 'spreed'; then
php /var/www/html/occ app:install spreed || echo "Failed to install spreed"
php /var/www/html/occ app:enable spreed || echo "Failed to enable spreed"
fi
php /var/www/html/occ talk:turn:add "turn" "cloud.poppyglen.cc:3478" "udp,tcp" --secret "$TURN_SECRET"
php /var/www/html/occ talk:stun:add "cloud.poppyglen.cc:3478"
php /var/www/html/occ talk:signaling:add "https://signal.poppyglen.cc" "${SIGNALING_SECRET}" --verify
php /var/www/html/occ config:app:set spreed recording_servers --value={"servers":[{"server":"http://nextcloud-recording-server","verify":true}],"secret":"${RECORDING_SECRET}"}
# --- ADDED: Install and enable the Mail app ---
echo "Checking Mail app installation..."
if ! php /var/www/html/occ app:list | grep -wq 'mail:'; then
php /var/www/html/occ app:install mail || echo "Failed to install mail"
php /var/www/html/occ app:enable mail || echo "Failed to enable mail"
fi
# --- ADDED: Configure a default Mail account for the 'admin' user ---
echo "Configuring default mail account for user '${NEXTCLOUD_ADMIN_USER}'..."
# This command uses environment variables for security.
php /var/www/html/occ mail:account:create \
${NEXTCLOUD_ADMIN_USER} "Admin Account" ${MAIL_ADMIN_EMAIL} \
${EMAIL_IMAP_HOST} 993 ssl \
${MAIL_ADMIN_EMAIL} ${MAIL_ADMIN_PASSWORD} \
${EMAIL_SMTP_HOST} 465 ssl \
${MAIL_ADMIN_EMAIL} ${MAIL_ADMIN_PASSWORD}
echo "✅ Mail account configuration for '${NEXTCLOUD_ADMIN_USER}' finished."
# --- Configure Mail accounts from USERS_JSON environment variable ---
if [ -n "${USERS_JSON:-}" ]; then
echo "Configuring mail accounts from USERS_JSON..."
# Use PHP to parse JSON and output each user line
php -r '
$users = json_decode(getenv("USERS_JSON"), true);
if ($users === null) {
fwrite(STDERR, "Invalid USERS_JSON\n");
exit(1);
}
foreach ($users as $user) {
$username = $user["username"] ?? "";
$name = $user["name"] ?? "";
$password = $user["password"] ?? "";
echo "$username|$name|$password\n";
}
' | while IFS='|' read -r username name password; do
# Construct email
email="${username}@${MAIL_DOMAIN}"
# Skip if password missing
if [ -z "$password" ] || [ "$password" == "null" ]; then
echo "Error: Password is not set for $username."
continue
fi
echo "Adding mail account for user '$username' ($email)..."
php /var/www/html/occ mail:account:create \
"$username" "$name" "$email" \
"${EMAIL_IMAP_HOST}" 993 ssl \
"$email" "$password" \
"${EMAIL_SMTP_HOST}" 465 ssl \
"$email" "$password"
done
echo "✅ Mail accounts from USERS_JSON configured successfully."
else
echo "No USERS_JSON variable found; skipping additional mail accounts."
fi
# --- End of Mail account configuration ---
echo "--- Installing Full Text Search ---"
# Install the main app and the files extractor app
if ! php /var/www/html/occ app:list | grep -q 'fulltextsearch'; then
php /var/www/html/occ app:install fulltextsearch || echo "Failed to install fulltextsearch"
php /var/www/html/occ app:enable fulltextsearch || echo "Failed to enable fulltextsearch"
fi
if ! php /var/www/html/occ app:list | grep -q 'fulltextsearch_elasticsearch'; then
php /var/www/html/occ app:install fulltextsearch_elasticsearch || echo "Failed to install fulltextsearch_elasticsearch"
php /var/www/html/occ app:enable fulltextsearch_elasticsearch || echo "Failed to enable fulltextsearch_elasticsearch"
fi
if ! php /var/www/html/occ app:list | grep -q 'files_fulltextsearch'; then
php /var/www/html/occ app:install files_fulltextsearch || echo "Failed to install files_fulltextsearch"
php /var/www/html/occ app:enable files_fulltextsearch || echo "Failed to enable files_fulltextsearch"
fi
# Configure the search platform to use Elasticsearch
php /var/www/html/occ config:app:set fulltextsearch enabled --value="yes"
php /var/www/html/occ config:app:set fulltextsearch search_platform --value="OCA\FullTextSearch_Elasticsearch\Platform\ElasticSearchPlatform"
php /var/www/html/occ config:app:set fulltextsearch types --value=""
php /var/www/html/occ config:app:set fulltextsearch app_navigation --value="1"
#php /var/www/html/occ config:app:set fulltextsearch_elasticsearch analyzer_tokenizer --value=""
#php /var/www/html/occ config:app:set fulltextsearch_elasticsearch analyzer_tokenizer --value="standard"
php /var/www/html/occ config:app:set fulltextsearch_elasticsearch elastic_host --value="http://elasticsearch:9200"
php /var/www/html/occ config:app:set fulltextsearch_elasticsearch elastic_index --value="nextcloud"
php /var/www/html/occ config:app:set fulltextsearch_elasticsearch enabled --value="yes"
php /var/www/html/occ config:app:set fulltextsearch_elasticsearch types --value=""
php /var/www/html/occ fulltextsearch:index
echo "Full text search apps installed and configured."
if ! php /var/www/html/occ app:list | grep -q 'richdocuments'; then
php /var/www/html/occ app:install richdocuments || echo "Failed to install richdocuments"
php /var/www/html/occ app:enable richdocuments || echo "Failed to enable richdocuments"
php /var/www/html/occ config:app:set richdocuments wopi_url --value="https://office.poppyglen.cc" || echo "Failed to configure richdocuments"
fi
echo "Checking for and adding any missing database indices..."
php /var/www/html/occ db:add-missing-indices
# Set a maintenance window for resource-intensive background jobs
echo "Setting maintenance window start time to 1 AM..."
php /var/www/html/occ config:system:set maintenance_window_start --value="1"
# --- Run Expensive Maintenance Tasks ---
# Note: This can significantly increase the container's first-start time.
echo "Running expensive maintenance tasks (including mimetype migration)..."
php /var/www/html/occ maintenance:repair --include-expensive
# Set the default phone region for validating numbers without a country code
echo "Setting default phone region to US..."
php /var/www/html/occ config:system:set default_phone_region --value="US"
echo "--- Configuring System Mail Server Settings ---"
# Use the same environment variables passed to the container
php /var/www/html/occ config:system:set mail_from_address --value="${MAIL_ADMIN_EMAIL}"
php /var/www/html/occ config:system:set mail_smtpmode --value="smtp"
php /var/www/html/occ config:system:set mail_sendmailmode --value="smtp"
php /var/www/html/occ config:system:set mail_domain --value="${MAIL_DOMAIN}"
php /var/www/html/occ config:system:set mail_smtpauthtype --value="LOGIN"
php /var/www/html/occ config:system:set mail_smtpauth --value="${MAIL_SMTPAUTH}"
php /var/www/html/occ config:system:set mail_smtphost --value="${MAIL_SMTPHOST}"
php /var/www/html/occ config:system:set mail_smtpport --value="${MAIL_SMTPPORT}"
php /var/www/html/occ config:system:set mail_smtpsecure --value="${MAIL_SMTPSECURE}"
php /var/www/html/occ config:system:set mail_smtpname --value="${MAIL_SMTPNAME}"
php /var/www/html/occ config:system:set mail_smtppassword --value="${MAIL_SMTPPASSWORD}"
echo "✅ System mail server configured."
echo "System files_external enabling..."
php /var/www/html/occ app:enable files_external || echo "Failed to enable files_external"
# --- BEGIN: Configure External Storage for Media ---
echo "--- Configuring External Storage for Media ---"
# --- Configuration ---
# Define the external folders you want to create.
# Format: ["Name inside Nextcloud"]="Path inside the container"
declare -A EXTERNAL_MOUNTS=(
["Family Movies"]="/movies/"
["Family Music"]="/music/"
["Family Photos"]="/photos/"
["Family TV Shows"]="/tvshows/"
["Family Games"]="/games/"
)
# The Nextcloud group that should have access to ALL folders defined above.
# This group MUST already exist.
TARGET_GROUP="Family"
# --- End of Configuration ---
for MOUNT_NAME in "${!EXTERNAL_MOUNTS[@]}"; do
CONTAINER_PATH="${EXTERNAL_MOUNTS[$MOUNT_NAME]}"
echo "Processing external mount: '${MOUNT_NAME}'"
if ! php /var/www/html/occ files_external:list | grep -qw "${MOUNT_NAME}"; then
echo " -> Mount point '${MOUNT_NAME}' does not exist. Creating..."
# Capture the storage ID
STORAGE_ID=$(php /var/www/html/occ files_external:create "${MOUNT_NAME}" local "null::null" -c datadir="${CONTAINER_PATH}" \
| awk '/Storage created with id/ {print $NF}')
if [[ -z "$STORAGE_ID" ]]; then
echo "❌ ERROR: Failed to get storage ID for '${MOUNT_NAME}'."
continue
fi
# Assign the mount to the specified group (must pass the ID, not the name!)
php /var/www/html/occ files_external:applicable --add-group="${TARGET_GROUP}" "${STORAGE_ID}" \
|| { echo "❌ ERROR: Failed to assign storage ID ${STORAGE_ID} to group '${TARGET_GROUP}'. Does the group exist?"; }
echo " -> Successfully created '${MOUNT_NAME}' (ID ${STORAGE_ID}) and assigned to group '${TARGET_GROUP}'."
else
echo " -> Mount point '${MOUNT_NAME}' already exists. Skipping creation."
fi
done
# Run a single scan for all users after all folders have been configured.
# This is more efficient than running a scan inside the loop.
echo " -> Scanning all files for new external mounts..."
php /var/www/html/occ files:scan --all || echo "⚠️ WARNING: File scan command failed, but setup is likely complete."
echo "✅ External storage configuration finished."
# --- END: Configure External Storage for Media ---
create_group_folder() {
local folder_name="$1"
local group_name="$2"
local folder_id=""
echo "--- Processing Group Folder: '$folder_name' ---"
# Check if the folder already exists by grepping the list output.
# The grep command looks for the folder name at the beginning of a line.
if php /var/www/html/occ groupfolders:list | grep -q "^${folder_name}:"; then
echo " -> Folder '$folder_name' already exists. Re-fetching ID to ensure configuration is correct."
# If it exists, we must parse the list to get the ID.
folder_id=$(php /var/www/html/occ groupfolders:list | grep "^${folder_name}:" | awk '{print $2}')
else
echo " -> Creating folder..."
# Create the folder and capture the output, which should be the new ID.
folder_id=$(php /var/www/html/occ groupfolders:create "$folder_name")
fi
# Check if we successfully got a numeric ID
if ! [[ "$folder_id" =~ ^[0-9]+$ ]]; then
echo "❌ Error: Could not determine a valid folder ID for '$folder_name'. Output was: '$folder_id'. Skipping."
return 1
fi
echo " -> Folder ID is: $folder_id"
echo " -> Assigning group '$group_name'..."
php /var/www/html/occ groupfolders:group "$folder_id" "$group_name"
echo " -> Setting full permissions for '$group_name'..."
php /var/www/html/occ groupfolders:permissions "$folder_id" --enable
php /var/www/html/occ groupfolders:group "$folder_id" "$group_name" write delete share
echo " -> Successfully configured '$folder_name'."
}
# --- Main Script ---
# Ensure the 'groupfolders' app is enabled first
if ! php /var/www/html/occ app:list | grep -wq 'groupfolders:'; then
echo "❌ Error: The 'groupfolders' app is not enabled."
exit 1
fi
# Define the list of folders to create
FOLDERS_TO_CREATE=(
"Family Documents"
)
# Loop through the list and create each folder
for folder in "${FOLDERS_TO_CREATE[@]}"; do
create_group_folder "$folder" "Family"
done
echo "✅ All group folders have been created and configured successfully."
php /var/www/html/occ background:cron
echo "Post-installation script finished."

57
immich/hwaccel.ml.yml Normal file
View File

@@ -0,0 +1,57 @@
# Configurations for hardware-accelerated machine learning
# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# into the immich-machine-learning service in the docker-compose.yml file.
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
services:
armnn:
devices:
- /dev/mali0:/dev/mali0
volumes:
- /lib/firmware/mali_csffw.bin:/lib/firmware/mali_csffw.bin:ro # Mali firmware for your chipset (not always required depending on the driver)
- /usr/lib/libmali.so:/usr/lib/libmali.so:ro # Mali driver for your chipset (always required)
rknn:
security_opt:
- systempaths=unconfined
- apparmor=unconfined
devices:
- /dev/dri:/dev/dri
cpu: {}
cuda:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities:
- gpu
rocm:
group_add:
- video
devices:
- /dev/dri:/dev/dri
- /dev/kfd:/dev/kfd
openvino:
device_cgroup_rules:
- 'c 189:* rmw'
devices:
- /dev/dri:/dev/dri
volumes:
- /dev/bus/usb:/dev/bus/usb
openvino-wsl:
devices:
- /dev/dri:/dev/dri
- /dev/dxg:/dev/dxg
volumes:
- /dev/bus/usb:/dev/bus/usb
- /usr/lib/wsl:/usr/lib/wsl

View File

@@ -0,0 +1,55 @@
# Configurations for hardware-accelerated transcoding
# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# into the immich-microservices service in the docker-compose.yml file.
# See https://immich.app/docs/features/hardware-transcoding for more info on using hardware transcoding.
services:
cpu: {}
nvenc:
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities:
- gpu
- compute
- video
quicksync:
devices:
- /dev/dri:/dev/dri
rkmpp:
security_opt: # enables full access to /sys and /proc, still far better than privileged: true
- systempaths=unconfined
- apparmor=unconfined
group_add:
- video
devices:
- /dev/rga:/dev/rga
- /dev/dri:/dev/dri
- /dev/dma_heap:/dev/dma_heap
- /dev/mpp_service:/dev/mpp_service
#- /dev/mali0:/dev/mali0 # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
volumes:
#- /etc/OpenCL:/etc/OpenCL:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
#- /usr/lib/aarch64-linux-gnu/libmali.so.1:/usr/lib/aarch64-linux-gnu/libmali.so.1:ro # only required to enable OpenCL-accelerated HDR -> SDR tonemapping
vaapi:
devices:
- /dev/dri:/dev/dri
vaapi-wsl: # use this for VAAPI if you're running Immich in WSL2
devices:
- /dev/dri:/dev/dri
- /dev/dxg:/dev/dxg
volumes:
- /usr/lib/wsl:/usr/lib/wsl
environment:
- LIBVA_DRIVER_NAME=d3d12

154
immich/init-immich.sh Normal file
View File

@@ -0,0 +1,154 @@
#!/bin/sh
set -euo pipefail
# --- Library Configuration ---
# The name for your shared library as it will appear in Immich.
EXTERNAL_LIBRARY_NAME="Shared Photos"
# The path to the photos INSIDE the container (must match your volume mount).
EXTERNAL_LIBRARY_PATH="/import"
# --- End Configuration ---
BASE="${IMMICH_BASE_URL:-http://immich-server:2283/api}"
apk add --no-cache curl jq >/dev/null
echo "⏳ Waiting for Immich API at $BASE ..."
for i in $(seq 1 120); do
if curl -fsS "$BASE/server/ping" | grep -q '"res":"pong"'; then
echo "✅ Immich is responding."
break
fi
sleep 2
done
echo "👤 Ensuring admin exists..."
curl -s -X POST "$BASE/auth/admin-sign-up" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${IMMICH_INIT_ADMIN_EMAIL}\",\"password\":\"${IMMICH_INIT_ADMIN_PASSWORD}\",\"name\":\"${IMMICH_INIT_ADMIN_NAME:-Admin}\"}" \
|| true
echo "🔑 Logging in as admin..."
TOKEN=$(curl -fsS -X POST "$BASE/auth/login" \
-H "Content-Type: application/json" \
-d "{\"email\":\"${IMMICH_INIT_ADMIN_EMAIL}\",\"password\":\"${IMMICH_INIT_ADMIN_PASSWORD}\"}" \
| jq -r '.accessToken')
[ -z "$TOKEN" ] && { echo "❌ Could not obtain access token"; exit 1; }
if [ -n "${USERS_JSON:-}" ]; then
echo "${USERS_JSON}" | jq -c '.[]' | while read -r u; do
email=$(echo "$u" | jq -r '.email')
pass=$(echo "$u" | jq -r '.password')
name=$(echo "$u" | jq -r '.name')
[ -z "$name" ] || [ "$name" = "null" ] && name="$email"
echo "👥 Creating user $email ..."
curl -s -X POST "$BASE/admin/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"email\":\"$email\",\"password\":\"$pass\",\"name\":\"$name\"}" \
|| echo "⚠️ Failed to create $email"
done
fi
# --- NEW: Create and Share External Library ---
echo "--- 📚 Configuring External Library ---"
echo "🆔 Getting admin user ID..."
ADMIN_ID=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/users/me" | jq -r '.id')
[ -z "$ADMIN_ID" ] && { echo "❌ Could not obtain admin user ID"; exit 1; }
echo "🔎 Checking for existing library named '${EXTERNAL_LIBRARY_NAME}'..."
LIBRARY_ID=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/libraries" | jq -r ".[] | select(.name==\"${EXTERNAL_LIBRARY_NAME}\") | .id")
if [ -z "$LIBRARY_ID" ]; then
echo "✨ Library does not exist. Creating..."
LIBRARY_ID=$(curl -fsS -X POST "$BASE/libraries" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"${EXTERNAL_LIBRARY_NAME}\",\"ownerId\":\"${ADMIN_ID}\",\"importPaths\":[\"${EXTERNAL_LIBRARY_PATH}\"]}" \
| jq -r '.id')
[ -z "$LIBRARY_ID" ] && { echo "❌ Failed to create library"; exit 1; }
echo "✅ Library created with ID: $LIBRARY_ID"
else
echo "👍 Library already exists with ID: $LIBRARY_ID"
fi
echo "📋 Fetching all user IDs for sharing..."
USER_IDS=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/users" | jq -r '.[].id')
SHARE_PAYLOAD=$(echo "$USER_IDS" | jq -R . | jq -s '{"sharedWithUserIds": .}')
echo "🤝 Sharing library with all users..."
curl -fsS -X PUT "$BASE/libraries/${LIBRARY_ID}/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$SHARE_PAYLOAD"
echo "✅ Library sharing configured."
# --- ADDED: Create Album for Library ---
echo "--- 🖼️ Configuring Album for Library ---"
echo "🔎 Checking for existing album named '${EXTERNAL_LIBRARY_NAME}'..."
ALBUM_ID=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/albums" | jq -r ".[] | select(.albumName==\"${EXTERNAL_LIBRARY_NAME}\") | .id")
if [ -z "$ALBUM_ID" ]; then
echo "✨ Album does not exist. Creating..."
curl -fsS -X POST "$BASE/albums" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"albumName\":\"${EXTERNAL_LIBRARY_NAME}\"}" > /dev/null
echo "✅ Album '${EXTERNAL_LIBRARY_NAME}' created."
else
echo "👍 Album '${EXTERNAL_LIBRARY_NAME}' already exists."
fi
# --- END: Create Album Section ---
# --- ADDED: Configure SMTP (Email) Settings ---
if [ -n "${MAIL_SMTPHOST:-}" ]; then
echo "--- 📧 Configuring Email (SMTP) Settings ---"
echo "🔎 Fetching current system configuration..."
CURRENT_CONFIG=$(curl -fsS -H "Authorization: Bearer $TOKEN" "$BASE/system-config")
echo "⚙️ Updating SMTP settings..."
# Note: For --argjson, "true" or "false" must not be quoted in the .env file.
UPDATED_CONFIG=$(echo "$CURRENT_CONFIG" | jq \
--arg host "${MAIL_SMTPHOST}" \
--arg port "${SMTP_PORT:-587}" \
--arg user "${MAIL_ADMIN_EMAIL:-}" \
--arg pass "${MAIL_ADMIN_PASSWORD:-}" \
--arg from "${MAIL_SMTPHOST}" \
--argjson secure "${SMTP_SECURE:-true}" \
'.smtp.enabled = true |
.smtp.host = $host |
.smtp.port = ($port | tonumber) |
.smtp.username = $user |
.smtp.password = $pass |
.smtp.from = $from |
.smtp.secure = $secure'
)
curl -fsS -X PUT "$BASE/system-config" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$UPDATED_CONFIG" > /dev/null
echo "✅ Email settings configured."
else
echo " Skipping email configuration. Set MAIL_SMTPHOST to enable."
fi
# --- END: Configure SMTP Section ---
echo "🚀 Triggering library scan for '${EXTERNAL_LIBRARY_NAME}'..."
curl -fsS -X POST "$BASE/libraries/${LIBRARY_ID}/scan" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{}"
echo "✅ Scan job started."
# --- END: New Section ---
echo "✅ Seeding complete."

15
init-minio.sh Normal file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
set -e
# Wait for MinIO to be available
until mc alias set myminio http://minio:9000 "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY"; do
echo "Waiting for MinIO..."
sleep 1
done
echo "MinIO is ready."
# Create the 'nextcloud-data' bucket if it doesn't already exist.
mc mb myminio/nextcloud-data --ignore-existing
echo "Bucket 'nextcloud-data' is configured."

130
jellyfin/init-jellyfin.sh Normal file
View File

@@ -0,0 +1,130 @@
#!/bin/sh
set -euo pipefail
# --- Configuration ---
BASE="${JELLYFIN_BASE_URL:-http://jellyfin:8096}"
ADMIN_USER="${JELLYFIN_INIT_ADMIN_USER:-jellyfin}"
ADMIN_PASS="${JELLYFIN_INIT_ADMIN_PASSWORD:-jellyfin}"
CLIENT_HEADER='X-Emby-Authorization: MediaBrowser Client="cURL", Device="CLI", DeviceId="12345", Version="10.9.0"'
DEBUG="${JELLYFIN_INIT_DEBUG:-false}"
SERVERNAME="${JELLYFIN_INIT_SERVERNAME:-MyJellyfin}"
# --- Dependencies ---
apk update && apk add --no-cache curl jq coreutils
log() {
[ "$DEBUG" = "true" ] && echo "$@"
}
echo "⏳ Waiting for Jellyfin API at $BASE..."
for i in $(seq 1 120); do
STATUS=$(curl -s -H "$CLIENT_HEADER" "$BASE/Users/Public" || true)
if [ -n "$STATUS" ] && [ "$STATUS" != *"Server is loading"* ]; then
echo "✅ Jellyfin API responsive."
break
fi
sleep 2
done
echo "👤 Sending startup configuration..."
curl -fsS -X POST "$BASE/Startup/Configuration" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"ServerName": "$SERVERNAME",
"UICulture":"en-US",
"MetadataCountryCode":"US",
"PreferredMetadataLanguage":"en"
}
EOF
echo "👤 Creating admin user '$ADMIN_USER'..."
curl -fsS -X GET "$BASE/Startup/User" -H "Content-Type: application/json"
curl -fsS -X POST "$BASE/Startup/User" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"Name": "$ADMIN_USER",
"Password": "$ADMIN_PASS"
}
EOF
echo "⚙ Completing startup wizard..."
curl -fsS -X POST "$BASE/Startup/Complete" \
-H "Content-Type: application/json" \
-d '{}' || true
echo "✅ Admin setup complete."
# --- Authenticate as admin ---
echo "🔑 Logging in as admin..."
RAW_TOKEN=$(curl -s -X POST "$BASE/Users/AuthenticateByName" \
-H "Content-Type: application/json" \
-H "$CLIENT_HEADER" \
-d "{\"Username\":\"$ADMIN_USER\",\"Pw\":\"$ADMIN_PASS\"}")
# Print raw token response if debug
[ "$DEBUG" = "true" ] && echo "RAW TOKEN RESPONSE: $RAW_TOKEN"
TOKEN=$(echo "$RAW_TOKEN" | jq -r '.AccessToken' 2>/dev/null || echo "")
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "❌ Failed to obtain admin token."
echo "⚠️ Raw response from server: $RAW_TOKEN"
exit 1
fi
echo "✅ Obtained access token."
# --- Function to create libraries ---
create_library() {
NAME=$1
TYPE=$2
LIB_PATH=$3
if [ ! -d "$LIB_PATH" ]; then
mkdir -p "$LIB_PATH"
echo "📂 Created missing library path: $LIB_PATH"
fi
echo "📁 Creating library: $NAME"
curl -v -X POST "$BASE/Library/VirtualFolders?collectionType=$TYPE&name=$NAME" \
-H "Authorization: MediaBrowser Token=$TOKEN" \
-H "Content-Type: application/json" \
-d @- << EOF
{
"LibraryOptions": {
"PathInfos": [{"Path": "$LIB_PATH"}]
}
}
EOF
}
# --- Create standard libraries ---
create_library "Music" music "/media/music"
create_library "Movies" movies "/media/movies"
create_library "TVShows" tvshows "/media/tvshows"
create_library "Photos" homevideos "/media/photos"
# --- Refresh all libraries ---
echo "🔄 Refreshing all libraries..."
curl -fsS -X POST "$BASE/Library/Refresh" \
-H "Authorization: MediaBrowser Token=$TOKEN" \
-H "Content-Type: application/json" || echo "⚠️ Failed to refresh libraries"
# --- Create additional users ---
if [ -n "$USERS_JSON" ]; then
echo "$USERS_JSON" | jq -c '.[]' | while read -r u; do
NAME=$(echo "$u" | jq -r '.username')
PASS=$(echo "$u" | jq -r '.password')
echo "👥 Creating user $NAME..."
curl -fsS -X POST "$BASE/Users/New" \
-H "Authorization: MediaBrowser Token=$TOKEN" \
-H "Content-Type: application/json" \
-d "{\"Name\":\"$NAME\",\"Password\":\"$PASS\"}" || echo "⚠️ Failed to create $NAME"
done
fi
echo "✅ Jellyfin initialization complete."

24
nextcloud/Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
# /nextcloud/Dockerfile
# Use the official full-featured Nextcloud image (Debian + Apache)
FROM nextcloud:latest
# Switch to the root user to install packages
USER root
# Update package lists and install FFmpeg and other preview dependencies
# -y accepts prompts, --no-install-recommends keeps the image smaller
# Clean up apt cache to reduce final image size
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ffmpeg \
imagemagick \
ghostscript \
&& rm -rf /var/lib/apt/lists/*
# --- ADD THIS LINE ---
# Change ownership of the PHP config directory to the www-data user
RUN chown www-data:www-data /usr/local/etc/php/conf.d/
# Switch back to the default, non-root user for security
USER www-data

View File

@@ -0,0 +1,135 @@
{
# This tells Caddy to get certificates from ZeroSSL instead of Let's Encrypt
#acme_ca https://acme.zerossl.com/v2/DV90
email charles.a.monroe@gmail.com
}
# (auth) is a reusable snippet for Authelia's forward authentication.
(auth) {
forward_auth authelia:9091 {
uri /api/verify?rd=https{http.request.host.value}
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
}
# This is the authentication portal itself. It has no auth applied.
auth.poppyglen.cc {
reverse_proxy authelia:9091
}
# Public WAN access for Nextcloud
cloud.poppyglen.cc {
import auth
request_body {
max_size 10G
}
# Your custom security and performance headers (these are good to keep)
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains;"
Referrer-Policy no-referrer
X-Content-Type-Options nosniff
X-Download-Options noopen
X-Frame-Options SAMEORIGIN
X-Permitted-Cross-Domain-Policies none
X-Robots-Tag "noindex,nofollow"
X-XSS-Protection "1; mode=block"
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
}
# Your logging configuration
log {
output file /data/caddy_access.log
format json
}
# Whiteboard WebSocket reverse proxy
handle_path /whiteboard/* {
reverse_proxy http://nextcloud-whiteboard-server:3002
}
reverse_proxy nextcloud-app:80 {
# Pass essential headers so Apache knows about the original client request
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
}
# LAN access (plain HTTP) for Nextcloud
http://local.poppyglen.cc {
# CORRECTED: Proxy all requests to the Nextcloud Apache container on port 80
reverse_proxy nextcloud-app:80 {
# Pass essential headers so Apache knows about the original client request
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
log {
output file /data/caddy_access.log
}
}
# Collabora Online - This block is already correct
office.poppyglen.cc {
reverse_proxy collabora:9980 {
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
}
whiteboard.poppyglen.cc {
reverse_proxy nextcloud-whiteboard-server:3002 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
signal.poppyglen.cc {
# Log errors for this site
log {
output file /var/log/caddy/signaling.log
}
# Proxy all requests to the internal signaling container
reverse_proxy nc-talk:8081 {
# Required headers to correctly handle WebSocket connections for Talk
header_up Host {host}
header_up X-Real-IP {remote_ip}
header_up X-Forwarded-For {remote_ip}
header_up X-Forwarded-Proto {scheme}
}
}
# Mail server autodiscovery and webmail
mail.poppyglen.cc {
# You can add a webmail client here later, like Roundcube or SnappyMail
# For now, this handles autodiscovery for mail clients
respond "Mail server for poppyglen.cc"
}
immich.poppyglen.cc {
import auth
reverse_proxy immich-server:2283 {
header_up X-Real-IP {remote_ip}
}
}
jellyfin.poppyglen.cc {
import auth
reverse_proxy jellyfin:8096
}
sunshine.poppyglen.cc {
# Proxy to Sunshine, but use HTTPS for the internal connection
import auth
reverse_proxy https://sunshine:47990 {
# This block tells Caddy how to handle the connection
transport http {
# This is required to make Caddy trust Sunshine's self-signed cert
tls_insecure_skip_verify
}
}
}

View File

@@ -0,0 +1,118 @@
{
# This tells Caddy to get certificates from ZeroSSL instead of Let's Encrypt
#acme_ca https://acme.zerossl.com/v2/DV90
email charles.a.monroe@gmail.com
}
# Public WAN access for Nextcloud
cloud.poppyglen.cc {
# Increase max request body size for large file uploads
request_body {
max_size 10G
}
# Your custom security and performance headers (these are good to keep)
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains;"
Referrer-Policy no-referrer
X-Content-Type-Options nosniff
X-Download-Options noopen
X-Frame-Options SAMEORIGIN
X-Permitted-Cross-Domain-Policies none
X-Robots-Tag "noindex,nofollow"
X-XSS-Protection "1; mode=block"
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
}
# Your logging configuration
log {
output file /data/caddy_access.log
format json
}
# Whiteboard WebSocket reverse proxy
handle_path /whiteboard/* {
reverse_proxy http://nextcloud-whiteboard-server:3002
}
reverse_proxy nextcloud-app:80 {
# Pass essential headers so Apache knows about the original client request
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
}
# LAN access (plain HTTP) for Nextcloud
http://local.poppyglen.cc {
# CORRECTED: Proxy all requests to the Nextcloud Apache container on port 80
reverse_proxy nextcloud-app:80 {
# Pass essential headers so Apache knows about the original client request
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
log {
output file /data/caddy_access.log
}
}
# Collabora Online - This block is already correct
office.poppyglen.cc {
reverse_proxy collabora:9980 {
header_up Host {host}
header_up X-Forwarded-For {remote_ip}
}
}
whiteboard.poppyglen.cc {
reverse_proxy nextcloud-whiteboard-server:3002 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
}
signal.poppyglen.cc {
# Log errors for this site
log {
output file /var/log/caddy/signaling.log
}
# Proxy all requests to the internal signaling container
reverse_proxy nc-talk:8081 {
# Required headers to correctly handle WebSocket connections for Talk
header_up Host {host}
header_up X-Real-IP {remote_ip}
header_up X-Forwarded-For {remote_ip}
header_up X-Forwarded-Proto {scheme}
}
}
# Mail server autodiscovery and webmail
mail.poppyglen.cc {
# You can add a webmail client here later, like Roundcube or SnappyMail
# For now, this handles autodiscovery for mail clients
respond "Mail server for poppyglen.cc"
}
immich.poppyglen.cc {
reverse_proxy immich-server:2283 {
header_up X-Real-IP {remote_ip}
}
}
jellyfin.poppyglen.cc {
reverse_proxy jellyfin:8096
}
sunshine.poppyglen.cc {
# Proxy to Sunshine, but use HTTPS for the internal connection
reverse_proxy https://sunshine:47990 {
# This block tells Caddy how to handle the connection
transport http {
# This is required to make Caddy trust Sunshine's self-signed cert
tls_insecure_skip_verify
}
}
}

126
old_examples/Caddyfile_fpm Normal file
View File

@@ -0,0 +1,126 @@
# Public WAN access
cloud.poppyglen.cc {
request_body {
max_size 10G
}
# Enable gzip but do not remove ETag headers
encode {
zstd
gzip 4
minimum_length 256
match {
header Content-Type application/atom+xml
header Content-Type application/javascript
header Content-Type application/json
header Content-Type application/ld+json
header Content-Type application/manifest+json
header Content-Type application/rss+xml
header Content-Type application/vnd.geo+json
header Content-Type application/vnd.ms-fontobject
header Content-Type application/wasm
header Content-Type application/x-font-ttf
header Content-Type application/x-web-app-manifest+json
header Content-Type application/xhtml+xml
header Content-Type application/xml
header Content-Type font/opentype
header Content-Type image/bmp
header Content-Type image/svg+xml
header Content-Type image/x-icon
header Content-Type text/cache-manifest
header Content-Type text/css
header Content-Type text/plain
header Content-Type text/vcard
header Content-Type text/vnd.rim.location.xloc
header Content-Type text/vtt
header Content-Type text/x-component
header Content-Type text/x-cross-domain-policy
}
}
header {
# Based on following source:
# https://raw.githubusercontent.com/nextcloud/docker/refs/heads/master/.examples/docker-compose/insecure/mariadb/fpm/web/nginx.conf
#
# HSTS settings
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
# Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;"
Strict-Transport-Security: "max-age=31536000; includeSubDomains;"
# HTTP response headers borrowed from Nextcloud `.htaccess`
Referrer-Policy no-referrer
X-Content-Type-Options nosniff
X-Download-Options noopen
X-Frame-Options SAMEORIGIN
X-Permitted-Cross-Domain-Policies none
X-Robots-Tag "noindex,nofollow"
X-XSS-Protection "1; mode=block"
Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
}
root * /var/www/html
php_fastcgi nextcloud-app:9000 {
env modHeadersAvailable true # Avoid sending the security headers twice
env front_controller_active true
}
file_server
log {
output file /data/caddy_access.log
format json
}
redir /.well-known/carddav /remote.php/dav 301
redir /.well-known/caldav /remote.php/dav 301
@forbidden {
path /.htaccess
path /.xml
path /3rdparty/*
path /README
path /config/*
path /console.php
path /data/*
path /db_structure
path /lib/*
path /occ
path /templates/*
path /tests/*
}
respond @forbidden 404
}
# LAN access (plain HTTP or internal TLS)
http://local.poppyglen.cc {
root * /var/www/html
php_fastcgi nextcloud-app:9000 {
env PATH /usr/local/bin:/usr/bin:/bin
env front_controller_active true
}
file_server
log {
output file /data/caddy_access.log
}
}
# Collabora Online - optional reverse proxy
office.poppyglen.cc {
reverse_proxy collabora:9980 {
header_up Host {http.reverse_proxy.host.name}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {http.request.remote.ip}
}
}

View File

@@ -0,0 +1,236 @@
#!/bin/bash
set -eo pipefail # Exit on error and handle pipe failures
COMPOSE_FILE="docker-compose.yml"
PROJECT_NAME=$(basename "$PWD")
# --- SCRIPT SETUP AND VALIDATION ---
if [[ "$(id -u)" -ne 0 ]]; then
echo "❌ Error: This script must be run as root or with sudo." >&2
exit 1
fi
if [[ ! -f "$COMPOSE_FILE" ]]; then
echo "❌ Error: $COMPOSE_FILE not found in the current directory." >&2
exit 1
fi
# --- MAIN CLEANUP (NEXTCLOUD CORE) ---
echo "This script will reset the '$PROJECT_NAME' stack for a fresh installation."
read -p "Proceed with deleting all Nextcloud-related data (excluding Mail, Caddy, Jellyfin, and Immich)? (Y/n) " -r response
if [[ -z "$response" || "$response" =~ ^[Yy]$ ]]; then
# Stop and remove containers and networks
echo "Bringing down Docker containers and networks..."
docker compose down --remove-orphans
# Clean specific host-mapped directories for Nextcloud
HOST_VOLUMES_TO_CLEAN=(
"/mnt/Nextcloud/Nextcloud"
"/mnt/Nextcloud/nextcloud_db"
"/mnt/Nextcloud/redis"
"/mnt/Nextcloud/clamav_data"
"/mnt/Nextcloud/es_data"
)
echo "Cleaning Nextcloud host-mapped directories..."
for vol in "${HOST_VOLUMES_TO_CLEAN[@]}"; do
echo "Cleaning host directory: $vol"
if [[ -d "$vol" ]]; then
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
# Set correct ownership for persistent directories to prevent permission errors on restart
echo "Setting correct ownership for directories..."
echo " -> Nextcloud (www-data)..."
chown -R 33:33 "/mnt/Nextcloud/Nextcloud"
# --- BEGIN: Set Ownership for External Media Directories ---
echo " -> External Media Directories (for Nextcloud)..."
EXTERNAL_MEDIA_DIRS=(
"/mnt/Nextcloud/movies"
"/mnt/Nextcloud/tvshows"
"/mnt/Nextcloud/photos"
"/mnt/Nextcloud/music"
"/mnt/Nextcloud/games"
)
for dir in "${EXTERNAL_MEDIA_DIRS[@]}"; do
if [[ -d "$dir" ]]; then
echo " - Processing: $dir"
# Set ownership to www-data (UID 33)
chown -R 33:33 "$dir"
# Set permissions to allow owner/group to read/write
chmod -R 775 "$dir"
else
echo " - Directory not found, skipping: $dir"
fi
done
# --- END: Set Ownership for External Media Directories ---
echo " -> Nextcloud PostgreSQL (postgres)..."
chown -R 999:999 "/mnt/Nextcloud/nextcloud_db"
echo " -> Immich PostgreSQL (postgres)..."
chown -R 999:999 "/mnt/Nextcloud/immich/immich_db"
echo " -> Redis (redis)..."
chown -R 999:999 "/mnt/Nextcloud/redis"
echo " -> Elasticsearch (elasticsearch)..."
chown -R 1000:1000 "/mnt/Nextcloud/es_data"
echo " -> Immich App Data (node)..."
chown -R 1000:1000 "/mnt/Nextcloud/immich/immich_upload"
chown -R 1000:1000 "/mnt/Nextcloud/immich/immich_models"
echo " -> ClamAV (clamav)..."
chown -R 101:102 "/mnt/Nextcloud/clamav_data"
echo " -> Jellyfin (user 1000)..."
chown -R 1000:1000 "/mnt/Nextcloud/jellyfin"
else
echo "Operation cancelled."
exit 0
fi
# --- OPTIONAL IMMICH CLEANUP ---
read -p "Do you also want to delete ALL Immich data (database, photos, models)? (y/N) " -r immich_response
if [[ "$immich_response" =~ ^[Yy]$ ]]; then
IMMICH_HOST_VOLUMES=(
"/mnt/Nextcloud/immich/immich_db"
"/mnt/Nextcloud/immich/immich_upload"
"/mnt/Nextcloud/immich/immich_models"
)
echo "Cleaning Immich host-mapped directories..."
for vol in "${IMMICH_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
echo "Immich data has been cleared."
else
echo "Immich data has been retained."
fi
# --- OPTIONAL JELLYFIN CLEANUP ---
read -p "Do you also want to delete Jellyfin data (config, cache)? (y/N) " -r jellyfin_response
if [[ "$jellyfin_response" =~ ^[Yy]$ ]]; then
JELLYFIN_HOST_VOLUMES=(
"/mnt/Nextcloud/jellyfin/config"
"/mnt/Nextcloud/jellyfin/cache"
)
echo "Cleaning Jellyfin host-mapped directories..."
for vol in "${JELLYFIN_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
echo "Jellyfin data has been cleared."
else
echo "Jellyfin data has been retained."
fi
# --- OPTIONAL MAIL CLEANUP ---
read -p "Do you also want to delete the Mail Server data (mailboxes, config, logs)? (y/N) " -r mail_response
if [[ "$mail_response" =~ ^[Yy]$ ]]; then
MAIL_HOST_VOLUMES=(
"/mnt/Nextcloud/mail/maildata"
"/mnt/Nextcloud/mail/mailstate"
"/mnt/Nextcloud/mail/mail-logs"
"/mnt/Nextcloud/mail/config"
)
echo "Cleaning Mail Server host-mapped directories..."
for vol in "${MAIL_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
fi
done
echo "Mail Server data has been cleared."
else
echo "Mail Server data has been retained."
fi
# --- OPTIONAL SUNSHINE CLEANUP ---
read -p "Do you also want to delete Sunshine local files and configuration? (y/N) " -r sunshine_response
if [[ "$sunshine_response" =~ ^[Yy]$ ]]; then
SUNSHINE_HOST_VOLUMES=(
"/mnt/Nextcloud/sunshine"
)
echo "Cleaning Sunshine host-mapped directories..."
for vol in "${SUNSHINE_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
echo "Sunshine configuration and local files have been cleared."
else
echo "Sunshine local files and configuration have been retained."
fi
# --- OPTIONAL TECHNITIUM DNS CLEANUP ---
read -p "Do you also want to delete Technitium DNS configuration and data? (y/N) " -r technitium_response
if [[ "$technitium_response" =~ ^[Yy]$ ]]; then
TECHNITIUM_HOST_VOLUMES=(
"/mnt/Nextcloud/technitium/config"
)
echo "Cleaning Technitium DNS host-mapped directories..."
for vol in "${TECHNITIUM_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
echo "Technitium DNS data has been cleared."
else
echo "Technitium DNS data has been retained."
fi
# --- OPTIONAL OLLAMA CLEANUP ---
read -p "Do you also want to delete Ollama local files (models, cache, configs)? (y/N) " -r ollama_response
if [[ "$ollama_response" =~ ^[Yy]$ ]]; then
OLLAMA_HOST_VOLUMES=(
"/mnt/Nextcloud/ollama"
)
echo "Cleaning Ollama host-mapped directories..."
for vol in "${OLLAMA_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
else
echo " -> Directory not found, skipping."
fi
done
echo "Ollama local files have been cleared."
else
echo "Ollama local files have been retained."
fi
# --- OPTIONAL CADDY CLEANUP ---
read -p "Do you also want to delete the Caddy data and config? (This removes SSL certs) (y/N) " -r caddy_response
if [[ "$caddy_response" =~ ^[Yy]$ ]]; then
CADDY_HOST_VOLUMES=(
"/mnt/Nextcloud/caddy/data"
"/mnt/Nextcloud/caddy/config"
"/mnt/Nextcloud/mail/caddy"
)
echo "Cleaning Caddy host-mapped directories..."
for vol in "${CADDY_HOST_VOLUMES[@]}"; do
if [[ -d "$vol" ]]; then
echo "Cleaning host directory: $vol"
find "$vol" -mindepth 1 -delete
fi
done
echo "Caddy data has been cleared."
else
echo "Caddy data and certificates have been retained."
fi
echo "✅ Cleanup complete."
echo "You can now restart your stack with: docker compose up -d"

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
USERS_JSON='[
{"username":"camonroe","password":"s8KKumVhgp@","name":"Charles Monroe","email":"charles.a.monroe@gmail.com"},
{"username":"smmonroe","password":"TM6HWnaYH6n","name":"Sarah Monroe","email":"sarah.m.monroe@gmail.com"},
{"username":"esmonroe","password":"g86@tPsV3b68R","name":"Ezra Monroe","email":"wolfesm9@gmail.com"},
{"username":"vjmonroe","password":"!44sw%vL]hnE~^","name":"Violet Monroe","email":"foxvjm8@gmail.com"},
{"username":"jsmonroe","password":"A3R,pSWYRR2%","name":"Jane Monroe","email":"kittyjsm5@gmail.com"}
]'
echo "Original USERS_JSON:"
echo "$USERS_JSON"
echo
echo "---------------------------"
echo "Parsing USERS_JSON..."
# Replace "},{" with newline to safely split JSON objects
echo "$USERS_JSON" \
| sed 's/^\[//;s/]$//;s/},{/}\
{/g' \
| while read -r user; do
# Extract fields using pure Bash + sed
username=$(echo "$user" | sed -E 's/.*"username":"([^"]*)".*/\1/')
password=$(echo "$user" | sed -E 's/.*"password":"([^"]*)".*/\1/')
display_name=$(echo "$user" | sed -E 's/.*"name":"([^"]*)".*/\1/')
email=$(echo "$user" | sed -E 's/.*"email":"([^"]*)".*/\1/')
echo "---------------------------"
echo "Would create user:"
echo " Username: $username"
echo " Display Name: $display_name"
echo " Email: $email"
echo " Password: $password"
done
echo "---------------------------"
echo "✅ Finished parsing USERS_JSON."

View File

@@ -0,0 +1,652 @@
networks:
nextcloud-net:
driver: bridge
enable_ipv6: true # <-- Enable IPv6 for this network
ipam:
driver: default
config:
- subnet: 172.16.0.0/16 # <-- Define your desired IPv4 subnet
- subnet: "fd00:22::/64" # <-- Define your desired IPv6 subnet
#sunshine_net:
# driver: macvlan
# driver_opts:
# parent: enp102s0f3u1u4 # Your physical network card
# ipam:
# config:
# - subnet: 192.168.0.0/24 # Your network's subnet
# gateway: 192.168.0.1 # Your network's gateway
services:
authelia:
image: authelia/authelia:latest
container_name: authelia
restart: unless-stopped
volumes:
- ./authelia:/config
networks:
- nextcloud-net
expose:
- 9091
environment:
- TZ=America/Los_Angeles
depends_on:
- authelia-redis
authelia-redis:
image: redis:alpine
container_name: authelia-redis
restart: unless-stopped
volumes:
- /mnt/Nextcloud/authelia-redis:/data
networks:
- nextcloud-net
cloudflare-ddns:
image: docker.io/oznu/cloudflare-ddns:latest
container_name: cloudflare_ddns
restart: unless-stopped
environment:
- API_KEY=${CF_API_KEY}
- ZONE=${CF_ZONE}
- SUBDOMAIN=${CF_SUBDOMAIN}
- PROXIED=${CF_PROXIED}
networks:
- nextcloud-net
db:
image: postgres:16
container_name: nextcloud-db
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
environment:
- TZ=America/Los_Angeles
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_STORAGE_TYPE='HDD'
volumes:
- /mnt/Nextcloud/nextcloud_db:/var/lib/postgresql/data
networks:
- nextcloud-net
#apt-get update && apt-get install -y ffmpeg
app:
image: nextcloud:latest
container_name: nextcloud-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy # This tells the app to wait for the healthcheck
redis:
condition: service_started
collabora:
condition: service_started
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
REDIS_HOST: redis
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.poppyglen.cc local.poppyglen.cc
OVERWRITEHOST: cloud.poppyglen.cc
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.poppyglen.cc
OVERWRITECONDADDR: 172.16.0.0\/12
TRUSTED_PROXIES: 172.16.0.0/12
USERS_JSON: ${USERS_JSON}
HOME: /var/www # Set HOME for www-data
GIPHY_API_KEY: ${GIPHY_API_KEY}
WHITEBOARD_JWT: ${WHITEBOARD_JWT}
TURN_SECRET: ${TURN_SECRET}
SIGNALING_SECRET: ${SIGNALING_SECRET}
MAIL_FROM_ADDRESS: admin
MAIL_DOMAIN: ${MAIL_DOMAIN}
MAIL_SMTPAUTH: 1
MAIL_SMTPSECURE: ssl
MAIL_SMTPHOST: ${MAIL_SMTPHOST}
MAIL_SMTPPORT: 465
MAIL_SMTPNAME: ${MAIL_ADMIN_EMAIL}
MAIL_SMTPPASSWORD: ${MAIL_ADMIN_PASSWORD}
MAIL_ADMIN_EMAIL: ${MAIL_ADMIN_EMAIL}
MAIL_ADMIN_PASSWORD: ${MAIL_ADMIN_PASSWORD}
EMAIL_IMAP_HOST: ${MAIL_SMTPHOST}
EMAIL_SMTP_HOST: ${MAIL_SMTPHOST}
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
- ./hooks:/docker-entrypoint-hooks.d
- /mnt/Nextcloud/music:/music
- /mnt/Nextcloud/tvshows:/tvshows
- /mnt/Nextcloud/games:/games
- /mnt/Nextcloud/movies:/movies
- /mnt/Nextcloud/photos:/photos
networks:
- nextcloud-net
cron:
image: nextcloud:latest
container_name: nextcloud-cron
restart: unless-stopped
depends_on:
- app
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
entrypoint: /cron.sh
networks:
- nextcloud-net
#create 32 character password fpr each service
#openssl rand -hex 32
nc-talk:
image: ghcr.io/nextcloud-releases/aio-talk:latest
container_name: nc-talk
init: true
restart: unless-stopped
ports:
- 3478:3478/tcp
- 3478:3478/udp
- 8181:8081/tcp
environment:
- NC_DOMAIN=cloud.poppyglen.cc
- TALK_HOST=signal.poppyglen.cc
- TURN_SECRET=${TURN_SECRET}
- SIGNALING_SECRET=${SIGNALING_SECRET}
- TZ=America/Los_Angeles
- TALK_PORT=3478
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
depends_on:
- app
networks:
- nextcloud-net
collabora:
image: collabora/code
container_name: collabora
restart: unless-stopped
networks:
- nextcloud-net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9980/hosting/capabilities || exit 1"]
interval: 30s
timeout: 10s
retries: 3
environment:
#- PUID=1000
#- PGID=1000
- TZ=America/Los_Angeles
- password=${COLLABORA_PASSWORD}
- username=${COLLABORA_USERNAME}
- server_name=office.poppyglen.cc
- aliasgroup1=https://cloud.poppyglen.cc:443
- aliasgroup2=http://local.poppyglen.cc
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- DONT_GEN_PASSWD=1
#ports:
# - 9980:9980
#volumes:
# - /mnt/Nextcloud/Nextcloud:/var/www/html:ro
cap_add:
- SYS_ADMIN
redis:
image: docker.io/library/redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
mem_limit: 2048m
mem_reservation: 512m
volumes:
- /mnt/Nextcloud/redis:/data
networks:
- nextcloud-net
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
- "443:443/udp"
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
- ./Caddyfile:/etc/caddy/Caddyfile
- /mnt/Nextcloud/caddy/data:/data
- /mnt/Nextcloud/caddy/config:/config
#- /mnt/Nextcloud/caddy/waf:/etc/caddy/waf
- /mnt/Nextcloud/caddy/logs:/var/log/caddy
networks:
- nextcloud-net
clamav:
image: clamav/clamav:1.3
container_name: clamav
restart: unless-stopped
networks:
- nextcloud-net
volumes:
- /mnt/Nextcloud/clamav_data:/var/lib/clamav
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
container_name: elasticsearch
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health?wait_for_status=yellow || exit 1"]
interval: 30s
timeout: 10s
retries: 5
environment:
- discovery.type=single-node
- xpack.security.enabled=false # Easiest for internal Docker communication
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # Recommended to limit memory usage
volumes:
- /mnt/Nextcloud/es_data:/usr/share/elasticsearch/data
networks:
- nextcloud-net
go-vod:
image: radialapps/go-vod
container_name: go-vod
restart: always
init: true
depends_on:
- app
environment:
- NEXTCLOUD_HOST=https://cloud.poppyglen.cc
# - NEXTCLOUD_ALLOW_INSECURE=1 # (self-signed certs or no HTTPS)
- NVIDIA_VISIBLE_DEVICES=all
devices:
- /dev/dri:/dev/dri # VA-API (omit for NVENC)
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
# runtime: nvidia # (NVENC)
networks:
- nextcloud-net
nextcloud-whiteboard-server:
image: ghcr.io/nextcloud-releases/whiteboard:stable
container_name: nextcloud-whiteboard-server
ports:
- 3002:3002
environment:
NEXTCLOUD_URL: https://cloud.poppyglen.cc
JWT_SECRET_KEY: ${WHITEBOARD_JWT}
networks:
- nextcloud-net
#mkdir -p /mnt/Nextcloud/mail/{maildata,mailstate,mail-logs,config,caddy}
mail:
image: docker.io/mailserver/docker-mailserver:latest
container_name: mail
hostname: mail.poppyglen.cc
restart: unless-stopped
environment:
- SSL_TYPE=manual
- SSL_CERT_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.crt
- SSL_KEY_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.key
- TZ=America/Los_Angeles
- PERMIT_DOCKER=network
- DMS_DEBUG=0
- ONE_DIR=1
- ENABLE_POSTGREY=0
- ENABLE_FAIL2BAN=1
- ENABLE_CLAMAV=1
- CLAMAV_HOST=clamav
- ENABLE_SPAMASSASSIN=1
- ENABLE_OPENDKIM=1
volumes:
- /mnt/Nextcloud/mail/maildata:/var/mail
- /mnt/Nextcloud/mail/mailstate:/var/mail-state
- /mnt/Nextcloud/mail/mail-logs:/var/log/mail
- /mnt/Nextcloud/mail/config:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/caddy/data:/caddy-data:ro
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
cap_add:
- NET_ADMIN # Required for Fail2Ban
- SYS_PTRACE
networks:
- nextcloud-net
depends_on:
- clamav
#docker exec -it mail setup config dkim
#docker exec -it mail cat /tmp/docker-mailserver/opendkim/keys/poppyglen.cc/mail.txt | awk -F'"' '/"/{print $2}' | tr -d '\n'
ollama:
image: ollama/ollama:rocm
container_name: ollama
restart: unless-stopped
volumes:
- /mnt/Nextcloud/ollama:/root/.ollama
- /lib/modules:/lib/modules:ro
- /sys/module:/sys/module:ro
networks:
- nextcloud-net
environment:
HSA_OVERRIDE_GFX_VERSION: ${HSA_OVERRIDE_GFX_VERSION}
HIP_VISIBLE_DEVICES: "0"
devices:
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
security_opt:
- seccomp:unconfined
cap_add:
- SYS_MODULE
- SYS_RAWIO
#docker exec -it ollama ollama pull qwen3:4b
recording-server:
image: nextcloud/aio-talk-recording
container_name: nextcloud-recording-server
restart: unless-stopped
environment:
- NEXTCLOUD_URL=https://cloud.poppyglen.cc
- NC_DOMAIN=cloud.poppyglen.cc
- NEXTCLOUD_RECORDING_USERNAME=${NEXTCLOUD_RECORDING_USERNAME}
- NEXTCLOUD_RECORDING_PASSWORD=${NEXTCLOUD_RECORDING_PASSWORD}
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
networks:
- nextcloud-net # Must be on the same network as other services
immich-postgres:
container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg16-v0.2.0 # Required for vector search
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${IMMICH_DB_PASSWORD}
POSTGRES_USER: ${IMMICH_DB_USER}
POSTGRES_DB: ${IMMICH_DB_NAME}
DB_STORAGE_TYPE: 'HDD'
volumes:
- /mnt/Nextcloud/immich/immich_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
networks:
- nextcloud-net
immich-redis:
container_name: immich_redis
image: redis:6.2-alpine
restart: unless-stopped
networks:
- nextcloud-net
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:release
#command: [ "start.sh", "immich" ]
extends:
file: hwaccel.transcoding.yml
service: vaapi
restart: unless-stopped
depends_on:
immich-postgres:
condition: service_healthy
immich-redis:
condition: service_started
environment:
# This is an anchor that other services will reference
&immich-common-env
DB_HOSTNAME: immich-postgres
DB_USERNAME: ${IMMICH_DB_USER}
DB_PASSWORD: ${IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: ${IMMICH_DB_NAME}
REDIS_HOSTNAME: immich-redis
JWT_SECRET: ${IMMICH_JWT_SECRET}
# You can add other Immich specific env vars here if needed
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2283/api/server/ping"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:release
# This command is required to run microservices
#command: [ "start.sh", "microservices" ]
extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
file: hwaccel.ml.yml
service: rocm # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
restart: unless-stopped
depends_on:
- immich-server
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release
restart: unless-stopped
volumes:
- /mnt/Nextcloud/immich/immich_models:/cache
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
networks:
- nextcloud-net
immich-init:
image: alpine:3.20
container_name: immich-init
env_file: .env
environment:
IMMICH_BASE_URL: http://immich-server:2283/api
depends_on:
immich-server:
condition: service_healthy
volumes:
- ./init-immich.sh:/init-immich.sh:ro
entrypoint: ["/bin/sh", "/init-immich.sh"]
restart: "no"
networks:
- nextcloud-net
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: 1000:1000
environment:
- TZ=America/Los_Angeles
- PUID=1000 # Replace with your user ID
- PGID=1000 # Replace with your group ID
volumes:
- /mnt/Nextcloud/jellyfin/config:/config
- /mnt/Nextcloud/jellyfin/cache:/cache
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
ports:
- "8096:8096" # Web UI
- "7359:7359/udp" # Client discovery
- "1900:1900/udp" # DLNA
devices:
- /dev/dri:/dev/dri # For Intel Quick Sync or AMD VA-API hardware acceleration
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/Users/Public"]
interval: 15s
timeout: 5s
retries: 40
start_period: 30s
jellyfin-init:
image: alpine:3.20
container_name: jellyfin-init
env_file: .env
environment:
JELLYFIN_BASE_URL: ${JELLYFIN_BASE_URL}
JELLYFIN_INIT_ADMIN_USER: ${JELLYFIN_INIT_ADMIN_USER}
JELLYFIN_INIT_ADMIN_PASSWORD: ${JELLYFIN_INIT_ADMIN_PASSWORD}
JELLYFIN_INIT_SERVERNAME: ${JELLYFIN_INIT_SERVERNAME}
depends_on:
jellyfin:
condition: service_healthy
volumes:
- ./init-jellyfin.sh:/init-jellyfin.sh:ro
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
entrypoint: ["/bin/sh", "/init-jellyfin.sh"]
restart: "no"
networks:
- nextcloud-net
#sunshine:
# image: ghcr.io/lizardbyte/sunshine:v2025.628.4510-archlinux
# container_name: sunshine
# restart: unless-stopped
# privileged: true
# shm_size: "2gb"
# user: "root"
# group_add:
# - "render"
# - "video"
# - "input"
# environment:
# - TZ=America/Los_Angeles
# - PUID=1000
# - PGID=1000
# - LIBVA_DRIVER_NAME=radeonsi
# - WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-wayland-0}
# - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/1000} # replace 1000 with your UID
# - SUNSHINE_USERNAME=${SUNSHINE_USERNAME}
# - SUNSHINE_PASSWORD=${SUNSHINE_PASSWORD}
# - DISPLAY=:1
# volumes:
# - /mnt/Nextcloud/sunshine/config:/config
# - /run/user/1000:/run/user/1000
# - /tmp/.X11-unix:/tmp/.X11-unix # optional for X11
# - /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
# - /run/dbus:/run/dbus:ro
# - /run/udev/data:/run/udev/data:ro
# ports:
# - "47984:47984/udp"
# - "47990:47990/tcp"
# - "47989:47989/tcp"
# - "48010:48010/udp"
# - "48010:48010/tcp"
# - "47998-48000:47998-48000/udp"
# devices:
# - /dev/dri:/dev/dri
# - /dev/input:/dev/input
# - /dev/uinput:/dev/uinput
# #networks:
# # - nextcloud-net
# networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
wolf:
image: ghcr.io/games-on-whales/wolf:stable
container_name: wolf
#user: "1000:1000"
privileged: true
environment:
- XDG_RUNTIME_DIR=/tmp/sockets
- HOST_APPS_STATE_FOLDER=/mnt/Nextcloud/wolf
- WAYLAND_DISPLAY=wayland-1
- MESA_LOADER_DRIVER_OVERRIDE=amd
- VDPAU_DRIVER=va_gl
- LIBVA_DRIVER_NAME=radeonsi
- RADV_DEBUG=llvm
- WOLF_LOG_LEVEL:DEBUG
volumes:
- /mnt/Nextcloud/wolf:/mnt/Nextcloud/wolf:rw
- /mnt/Nextcloud/wolf/cfg:/etc/wolf/cfg:rw
- /run/user/1000:/run/user/1000
- /run/udev/data:/run/udev/data:ro
- /tmp/sockets:/tmp/sockets:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
- /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
- /usr/share/vulkan/icd.d:/usr/share/vulkan/icd.d:ro
- /usr/lib/libvulkan.so:/usr/lib/libvulkan.so:ro
- /usr/lib/libvulkan.so.1:/usr/lib/libvulkan.so.1:ro
- /usr/lib/libvulkan.so.1.4.321:/usr/lib/libvulkan.so.1.4.321:ro
- /usr/lib/libvulkan_radeon.so:/usr/lib/libvulkan_radeon.so:ro
- /usr/lib/libEGL_mesa.so:/usr/lib/libEGL_mesa.so:ro
- /usr/lib/libEGL_mesa.so.0:/usr/lib/libEGL_mesa.so.0:ro
- /usr/lib/libEGL_mesa.so.0.0.0:/usr/lib/libEGL_mesa.so.0.0.0:ro
- /usr/lib/libGLX_mesa.so:/usr/lib/libGLX_mesa.so:ro
- /usr/lib/libGLX_mesa.so.0:/usr/lib/libGLX_mesa.so.0:ro
- /usr/lib/libGLX_mesa.so.0.0.0:/usr/lib/libGLX_mesa.so.0.0.0:ro
- /usr/lib/libva.so:/usr/lib/libva.so:ro
- /usr/lib/libva.so.2:/usr/lib/libva.so.2:ro
- /usr/lib/libva.so.2.0.0:/usr/lib/libva.so.2.0.0:ro
- /usr/lib/dri/radeonsi_drv_video.so:/usr/lib/dri/radeonsi_drv_video.so:ro
- /usr/lib/x86_64-linux-gnu/dri:/usr/lib/x86_64-linux-gnu/dri:ro
- /run/dbus:/run/dbus:ro
- /run/udev:/run/udev:rw
device_cgroup_rules:
- 'c 13:* rmw'
devices:
- /dev/dri:/dev/dri:rw
- /dev/dri/renderD128:/dev/dri/renderD128:rw
- /dev/uinput:/dev/uinput
- /dev/uhid:/dev/uhid
group_add:
- "989"
- "video"
- "994"
network_mode: host
restart: unless-stopped
#networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
technitium-dns:
image: technitium/dns-server:latest
container_name: technitium-dns
hostname: technitium-dns
restart: unless-stopped
volumes:
- /mnt/Nextcloud/technitium/config:/etc/dns
environment:
- DNS_SERVER_DOMAIN=home.lan
- DNS_SERVER_ADMIN_PASSWORD=${TECHNITIUM_ADMIN_PASSWORD}
- DNS_SERVER_FORWARDERS=1.1.1.1,8.8.8.8,2606:4700:4700::1111,2001:4860:4860::8888
- DNS_SERVER_FORWARDER_PROTOCOL=Udp
- DNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
- DNS_SERVER_RECURSION_ALLOWED_NETWORKS=192.168.0.0/24,fd00::/8
ports:
- "5380:5380/tcp" # Web UI
- "53:53/tcp" # DNS
- "53:53/udp" # DNS
networks:
- nextcloud-net
#echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
#echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

View File

@@ -0,0 +1,628 @@
networks:
nextcloud-net:
driver: bridge
enable_ipv6: true # <-- Enable IPv6 for this network
ipam:
driver: default
config:
- subnet: 172.16.0.0/16 # <-- Define your desired IPv4 subnet
- subnet: "fd00:22::/64" # <-- Define your desired IPv6 subnet
#sunshine_net:
# driver: macvlan
# driver_opts:
# parent: enp102s0f3u1u4 # Your physical network card
# ipam:
# config:
# - subnet: 192.168.0.0/24 # Your network's subnet
# gateway: 192.168.0.1 # Your network's gateway
services:
cloudflare-ddns:
image: docker.io/oznu/cloudflare-ddns:latest
container_name: cloudflare_ddns
restart: unless-stopped
environment:
- API_KEY=${CF_API_KEY}
- ZONE=${CF_ZONE}
- SUBDOMAIN=${CF_SUBDOMAIN}
- PROXIED=${CF_PROXIED}
networks:
- nextcloud-net
db:
image: postgres:16
container_name: nextcloud-db
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
environment:
- TZ=America/Los_Angeles
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_STORAGE_TYPE='HDD'
volumes:
- /mnt/Nextcloud/nextcloud_db:/var/lib/postgresql/data
networks:
- nextcloud-net
#apt-get update && apt-get install -y ffmpeg
app:
image: nextcloud:latest
container_name: nextcloud-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy # This tells the app to wait for the healthcheck
redis:
condition: service_started
collabora:
condition: service_started
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
REDIS_HOST: redis
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.poppyglen.cc local.poppyglen.cc
OVERWRITEHOST: cloud.poppyglen.cc
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.poppyglen.cc
OVERWRITECONDADDR: 172.16.0.0\/12
TRUSTED_PROXIES: 172.16.0.0/12
USERS_JSON: ${USERS_JSON}
HOME: /var/www # Set HOME for www-data
GIPHY_API_KEY: ${GIPHY_API_KEY}
WHITEBOARD_JWT: ${WHITEBOARD_JWT}
TURN_SECRET: ${TURN_SECRET}
SIGNALING_SECRET: ${SIGNALING_SECRET}
MAIL_FROM_ADDRESS: admin
MAIL_DOMAIN: ${MAIL_DOMAIN}
MAIL_SMTPAUTH: 1
MAIL_SMTPSECURE: ssl
MAIL_SMTPHOST: ${MAIL_SMTPHOST}
MAIL_SMTPPORT: 465
MAIL_SMTPNAME: ${MAIL_ADMIN_EMAIL}
MAIL_SMTPPASSWORD: ${MAIL_ADMIN_PASSWORD}
MAIL_ADMIN_EMAIL: ${MAIL_ADMIN_EMAIL}
MAIL_ADMIN_PASSWORD: ${MAIL_ADMIN_PASSWORD}
EMAIL_IMAP_HOST: ${MAIL_SMTPHOST}
EMAIL_SMTP_HOST: ${MAIL_SMTPHOST}
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
- ./hooks:/docker-entrypoint-hooks.d
- /mnt/Nextcloud/music:/music
- /mnt/Nextcloud/tvshows:/tvshows
- /mnt/Nextcloud/games:/games
- /mnt/Nextcloud/movies:/movies
- /mnt/Nextcloud/photos:/photos
networks:
- nextcloud-net
cron:
image: nextcloud:latest
container_name: nextcloud-cron
restart: unless-stopped
depends_on:
- app
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
entrypoint: /cron.sh
networks:
- nextcloud-net
#create 32 character password fpr each service
#openssl rand -hex 32
nc-talk:
image: ghcr.io/nextcloud-releases/aio-talk:latest
container_name: nc-talk
init: true
restart: unless-stopped
ports:
- 3478:3478/tcp
- 3478:3478/udp
- 8181:8081/tcp
environment:
- NC_DOMAIN=cloud.poppyglen.cc
- TALK_HOST=signal.poppyglen.cc
- TURN_SECRET=${TURN_SECRET}
- SIGNALING_SECRET=${SIGNALING_SECRET}
- TZ=America/Los_Angeles
- TALK_PORT=3478
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
depends_on:
- app
networks:
- nextcloud-net
collabora:
image: collabora/code
container_name: collabora
restart: unless-stopped
networks:
- nextcloud-net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9980/hosting/capabilities || exit 1"]
interval: 30s
timeout: 10s
retries: 3
environment:
#- PUID=1000
#- PGID=1000
- TZ=America/Los_Angeles
- password=${COLLABORA_PASSWORD}
- username=${COLLABORA_USERNAME}
- server_name=office.poppyglen.cc
- aliasgroup1=https://cloud.poppyglen.cc:443
- aliasgroup2=http://local.poppyglen.cc
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- DONT_GEN_PASSWD=1
#ports:
# - 9980:9980
#volumes:
# - /mnt/Nextcloud/Nextcloud:/var/www/html:ro
cap_add:
- SYS_ADMIN
redis:
image: docker.io/library/redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
mem_limit: 2048m
mem_reservation: 512m
volumes:
- /mnt/Nextcloud/redis:/data
networks:
- nextcloud-net
caddy:
image: caddy:2-alpine
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
- "443:443/udp"
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
- ./Caddyfile:/etc/caddy/Caddyfile
- /mnt/Nextcloud/caddy/data:/data
- /mnt/Nextcloud/caddy/config:/config
#- /mnt/Nextcloud/caddy/waf:/etc/caddy/waf
- /mnt/Nextcloud/caddy/logs:/var/log/caddy
networks:
- nextcloud-net
clamav:
image: clamav/clamav:1.3
container_name: clamav
restart: unless-stopped
networks:
- nextcloud-net
volumes:
- /mnt/Nextcloud/clamav_data:/var/lib/clamav
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
container_name: elasticsearch
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health?wait_for_status=yellow || exit 1"]
interval: 30s
timeout: 10s
retries: 5
environment:
- discovery.type=single-node
- xpack.security.enabled=false # Easiest for internal Docker communication
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # Recommended to limit memory usage
volumes:
- /mnt/Nextcloud/es_data:/usr/share/elasticsearch/data
networks:
- nextcloud-net
go-vod:
image: radialapps/go-vod
container_name: go-vod
restart: always
init: true
depends_on:
- app
environment:
- NEXTCLOUD_HOST=https://cloud.poppyglen.cc
# - NEXTCLOUD_ALLOW_INSECURE=1 # (self-signed certs or no HTTPS)
- NVIDIA_VISIBLE_DEVICES=all
devices:
- /dev/dri:/dev/dri # VA-API (omit for NVENC)
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
# runtime: nvidia # (NVENC)
networks:
- nextcloud-net
nextcloud-whiteboard-server:
image: ghcr.io/nextcloud-releases/whiteboard:stable
container_name: nextcloud-whiteboard-server
ports:
- 3002:3002
environment:
NEXTCLOUD_URL: https://cloud.poppyglen.cc
JWT_SECRET_KEY: ${WHITEBOARD_JWT}
networks:
- nextcloud-net
#mkdir -p /mnt/Nextcloud/mail/{maildata,mailstate,mail-logs,config,caddy}
mail:
image: docker.io/mailserver/docker-mailserver:latest
container_name: mail
hostname: mail.poppyglen.cc
restart: unless-stopped
environment:
- SSL_TYPE=manual
- SSL_CERT_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.crt
- SSL_KEY_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.key
- TZ=America/Los_Angeles
- PERMIT_DOCKER=network
- DMS_DEBUG=0
- ONE_DIR=1
- ENABLE_POSTGREY=0
- ENABLE_FAIL2BAN=1
- ENABLE_CLAMAV=1
- CLAMAV_HOST=clamav
- ENABLE_SPAMASSASSIN=1
- ENABLE_OPENDKIM=1
volumes:
- /mnt/Nextcloud/mail/maildata:/var/mail
- /mnt/Nextcloud/mail/mailstate:/var/mail-state
- /mnt/Nextcloud/mail/mail-logs:/var/log/mail
- /mnt/Nextcloud/mail/config:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/caddy/data:/caddy-data:ro
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
cap_add:
- NET_ADMIN # Required for Fail2Ban
- SYS_PTRACE
networks:
- nextcloud-net
depends_on:
- clamav
#docker exec -it mail setup config dkim
#docker exec -it mail cat /tmp/docker-mailserver/opendkim/keys/poppyglen.cc/mail.txt | awk -F'"' '/"/{print $2}' | tr -d '\n'
ollama:
image: ollama/ollama:rocm
container_name: ollama
restart: unless-stopped
volumes:
- /mnt/Nextcloud/ollama:/root/.ollama
- /lib/modules:/lib/modules:ro
- /sys/module:/sys/module:ro
networks:
- nextcloud-net
environment:
HSA_OVERRIDE_GFX_VERSION: ${HSA_OVERRIDE_GFX_VERSION}
HIP_VISIBLE_DEVICES: "0"
devices:
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
security_opt:
- seccomp:unconfined
cap_add:
- SYS_MODULE
- SYS_RAWIO
#docker exec -it ollama ollama pull qwen3:4b
recording-server:
image: nextcloud/aio-talk-recording
container_name: nextcloud-recording-server
restart: unless-stopped
environment:
- NEXTCLOUD_URL=https://cloud.poppyglen.cc
- NC_DOMAIN=cloud.poppyglen.cc
- NEXTCLOUD_RECORDING_USERNAME=${NEXTCLOUD_RECORDING_USERNAME}
- NEXTCLOUD_RECORDING_PASSWORD=${NEXTCLOUD_RECORDING_PASSWORD}
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
networks:
- nextcloud-net # Must be on the same network as other services
immich-postgres:
container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg16-v0.2.0 # Required for vector search
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${IMMICH_DB_PASSWORD}
POSTGRES_USER: ${IMMICH_DB_USER}
POSTGRES_DB: ${IMMICH_DB_NAME}
DB_STORAGE_TYPE: 'HDD'
volumes:
- /mnt/Nextcloud/immich/immich_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
networks:
- nextcloud-net
immich-redis:
container_name: immich_redis
image: redis:6.2-alpine
restart: unless-stopped
networks:
- nextcloud-net
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:release
#command: [ "start.sh", "immich" ]
extends:
file: hwaccel.transcoding.yml
service: vaapi
restart: unless-stopped
depends_on:
immich-postgres:
condition: service_healthy
immich-redis:
condition: service_started
environment:
# This is an anchor that other services will reference
&immich-common-env
DB_HOSTNAME: immich-postgres
DB_USERNAME: ${IMMICH_DB_USER}
DB_PASSWORD: ${IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: ${IMMICH_DB_NAME}
REDIS_HOSTNAME: immich-redis
JWT_SECRET: ${IMMICH_JWT_SECRET}
# You can add other Immich specific env vars here if needed
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2283/api/server/ping"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:release
# This command is required to run microservices
#command: [ "start.sh", "microservices" ]
extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
file: hwaccel.ml.yml
service: rocm # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
restart: unless-stopped
depends_on:
- immich-server
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release
restart: unless-stopped
volumes:
- /mnt/Nextcloud/immich/immich_models:/cache
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
networks:
- nextcloud-net
immich-init:
image: alpine:3.20
container_name: immich-init
env_file: .env
environment:
IMMICH_BASE_URL: http://immich-server:2283/api
depends_on:
immich-server:
condition: service_healthy
volumes:
- ./init-immich.sh:/init-immich.sh:ro
entrypoint: ["/bin/sh", "/init-immich.sh"]
restart: "no"
networks:
- nextcloud-net
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: 1000:1000
environment:
- TZ=America/Los_Angeles
- PUID=1000 # Replace with your user ID
- PGID=1000 # Replace with your group ID
volumes:
- /mnt/Nextcloud/jellyfin/config:/config
- /mnt/Nextcloud/jellyfin/cache:/cache
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
ports:
- "8096:8096" # Web UI
- "7359:7359/udp" # Client discovery
- "1900:1900/udp" # DLNA
devices:
- /dev/dri:/dev/dri # For Intel Quick Sync or AMD VA-API hardware acceleration
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/Users/Public"]
interval: 15s
timeout: 5s
retries: 40
start_period: 30s
jellyfin-init:
image: alpine:3.20
container_name: jellyfin-init
env_file: .env
environment:
JELLYFIN_BASE_URL: ${JELLYFIN_BASE_URL}
JELLYFIN_INIT_ADMIN_USER: ${JELLYFIN_INIT_ADMIN_USER}
JELLYFIN_INIT_ADMIN_PASSWORD: ${JELLYFIN_INIT_ADMIN_PASSWORD}
JELLYFIN_INIT_SERVERNAME: ${JELLYFIN_INIT_SERVERNAME}
depends_on:
jellyfin:
condition: service_healthy
volumes:
- ./init-jellyfin.sh:/init-jellyfin.sh:ro
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
entrypoint: ["/bin/sh", "/init-jellyfin.sh"]
restart: "no"
networks:
- nextcloud-net
#sunshine:
# image: ghcr.io/lizardbyte/sunshine:v2025.628.4510-archlinux
# container_name: sunshine
# restart: unless-stopped
# privileged: true
# shm_size: "2gb"
# user: "root"
# group_add:
# - "render"
# - "video"
# - "input"
# environment:
# - TZ=America/Los_Angeles
# - PUID=1000
# - PGID=1000
# - LIBVA_DRIVER_NAME=radeonsi
# - WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-wayland-0}
# - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/1000} # replace 1000 with your UID
# - SUNSHINE_USERNAME=${SUNSHINE_USERNAME}
# - SUNSHINE_PASSWORD=${SUNSHINE_PASSWORD}
# - DISPLAY=:1
# volumes:
# - /mnt/Nextcloud/sunshine/config:/config
# - /run/user/1000:/run/user/1000
# - /tmp/.X11-unix:/tmp/.X11-unix # optional for X11
# - /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
# - /run/dbus:/run/dbus:ro
# - /run/udev/data:/run/udev/data:ro
# ports:
# - "47984:47984/udp"
# - "47990:47990/tcp"
# - "47989:47989/tcp"
# - "48010:48010/udp"
# - "48010:48010/tcp"
# - "47998-48000:47998-48000/udp"
# devices:
# - /dev/dri:/dev/dri
# - /dev/input:/dev/input
# - /dev/uinput:/dev/uinput
# #networks:
# # - nextcloud-net
# networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
wolf:
image: ghcr.io/games-on-whales/wolf:stable
container_name: wolf
#user: "1000:1000"
privileged: true
environment:
- XDG_RUNTIME_DIR=/tmp/sockets
- HOST_APPS_STATE_FOLDER=/mnt/Nextcloud/wolf
- WAYLAND_DISPLAY=wayland-1
- MESA_LOADER_DRIVER_OVERRIDE=amd
- VDPAU_DRIVER=va_gl
- LIBVA_DRIVER_NAME=radeonsi
- RADV_DEBUG=llvm
- WOLF_LOG_LEVEL:DEBUG
volumes:
- /mnt/Nextcloud/wolf:/mnt/Nextcloud/wolf:rw
- /mnt/Nextcloud/wolf/cfg:/etc/wolf/cfg:rw
- /run/user/1000:/run/user/1000
- /run/udev/data:/run/udev/data:ro
- /tmp/sockets:/tmp/sockets:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
- /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
- /usr/share/vulkan/icd.d:/usr/share/vulkan/icd.d:ro
- /usr/lib/libvulkan.so:/usr/lib/libvulkan.so:ro
- /usr/lib/libvulkan.so.1:/usr/lib/libvulkan.so.1:ro
- /usr/lib/libvulkan.so.1.4.321:/usr/lib/libvulkan.so.1.4.321:ro
- /usr/lib/libvulkan_radeon.so:/usr/lib/libvulkan_radeon.so:ro
- /usr/lib/libEGL_mesa.so:/usr/lib/libEGL_mesa.so:ro
- /usr/lib/libEGL_mesa.so.0:/usr/lib/libEGL_mesa.so.0:ro
- /usr/lib/libEGL_mesa.so.0.0.0:/usr/lib/libEGL_mesa.so.0.0.0:ro
- /usr/lib/libGLX_mesa.so:/usr/lib/libGLX_mesa.so:ro
- /usr/lib/libGLX_mesa.so.0:/usr/lib/libGLX_mesa.so.0:ro
- /usr/lib/libGLX_mesa.so.0.0.0:/usr/lib/libGLX_mesa.so.0.0.0:ro
- /usr/lib/libva.so:/usr/lib/libva.so:ro
- /usr/lib/libva.so.2:/usr/lib/libva.so.2:ro
- /usr/lib/libva.so.2.0.0:/usr/lib/libva.so.2.0.0:ro
- /usr/lib/dri/radeonsi_drv_video.so:/usr/lib/dri/radeonsi_drv_video.so:ro
- /usr/lib/x86_64-linux-gnu/dri:/usr/lib/x86_64-linux-gnu/dri:ro
- /run/dbus:/run/dbus:ro
- /run/udev:/run/udev:rw
device_cgroup_rules:
- 'c 13:* rmw'
devices:
- /dev/dri:/dev/dri:rw
- /dev/dri/renderD128:/dev/dri/renderD128:rw
- /dev/uinput:/dev/uinput
- /dev/uhid:/dev/uhid
group_add:
- "989"
- "video"
- "994"
network_mode: host
restart: unless-stopped
#networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
technitium-dns:
image: technitium/dns-server:latest
container_name: technitium-dns
hostname: technitium-dns
restart: unless-stopped
volumes:
- /mnt/Nextcloud/technitium/config:/etc/dns
environment:
- DNS_SERVER_DOMAIN=home.lan
- DNS_SERVER_ADMIN_PASSWORD=${TECHNITIUM_ADMIN_PASSWORD}
- DNS_SERVER_FORWARDERS=1.1.1.1,8.8.8.8,2606:4700:4700::1111,2001:4860:4860::8888
- DNS_SERVER_FORWARDER_PROTOCOL=Udp
- DNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
- DNS_SERVER_RECURSION_ALLOWED_NETWORKS=192.168.0.0/24,fd00::/8
ports:
- "5380:5380/tcp" # Web UI
- "53:53/tcp" # DNS
- "53:53/udp" # DNS
networks:
- nextcloud-net
#echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
#echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf

View File

@@ -0,0 +1,695 @@
networks:
nextcloud-net:
driver: bridge
enable_ipv6: true # <-- Enable IPv6 for this network
ipam:
driver: default
config:
- subnet: 172.16.0.0/16 # <-- Define your desired IPv4 subnet
- subnet: "fd00:22::/64" # <-- Define your desired IPv6 subnet
#sunshine_net:
# driver: macvlan
# driver_opts:
# parent: enp102s0f3u1u4 # Your physical network card
# ipam:
# config:
# - subnet: 192.168.0.0/24 # Your network's subnet
# gateway: 192.168.0.1 # Your network's gateway
services:
cloudflare-ddns:
image: docker.io/oznu/cloudflare-ddns:latest
container_name: cloudflare_ddns
restart: unless-stopped
environment:
- API_KEY=${CF_API_KEY}
- ZONE=${CF_ZONE}
- SUBDOMAIN=${CF_SUBDOMAIN}
- PROXIED=${CF_PROXIED}
networks:
- nextcloud-net
db:
image: postgres:16
container_name: nextcloud-db
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
environment:
- TZ=America/Los_Angeles
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- DB_STORAGE_TYPE='HDD'
volumes:
- /mnt/Nextcloud/nextcloud_db:/var/lib/postgresql/data
networks:
- nextcloud-net
app:
build: ./nextcloud
container_name: nextcloud-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
collabora:
condition: service_started
environment:
POSTGRES_HOST: db
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
REDIS_HOST: redis
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.poppyglen.cc local.poppyglen.cc
OVERWRITEHOST: cloud.poppyglen.cc
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.poppyglen.cc
OVERWRITECONDADDR: 172.16.0.0\/12
TRUSTED_PROXIES: 172.16.0.0/12
USERS_JSON: ${USERS_JSON}
HOME: /var/www # Set HOME for www-data
GIPHY_API_KEY: ${GIPHY_API_KEY}
WHITEBOARD_JWT: ${WHITEBOARD_JWT}
TURN_SECRET: ${TURN_SECRET}
SIGNALING_SECRET: ${SIGNALING_SECRET}
MAIL_FROM_ADDRESS: admin
MAIL_DOMAIN: ${MAIL_DOMAIN}
MAIL_SMTPAUTH: 1
MAIL_SMTPSECURE: ssl
MAIL_SMTPHOST: ${MAIL_SMTPHOST}
MAIL_SMTPPORT: 465
MAIL_SMTPNAME: ${MAIL_ADMIN_EMAIL}
MAIL_SMTPPASSWORD: ${MAIL_ADMIN_PASSWORD}
MAIL_ADMIN_EMAIL: ${MAIL_ADMIN_EMAIL}
MAIL_ADMIN_PASSWORD: ${MAIL_ADMIN_PASSWORD}
EMAIL_IMAP_HOST: ${MAIL_SMTPHOST}
EMAIL_SMTP_HOST: ${MAIL_SMTPHOST}
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
- ./hooks:/docker-entrypoint-hooks.d
- /mnt/Nextcloud/music:/music
- /mnt/Nextcloud/tvshows:/tvshows
- /mnt/Nextcloud/games:/games
- /mnt/Nextcloud/movies:/movies
- /mnt/Nextcloud/photos:/photos
networks:
- nextcloud-net
cron:
image: nextcloud:latest
container_name: nextcloud-cron
restart: unless-stopped
depends_on:
- app
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
entrypoint: /cron.sh
networks:
- nextcloud-net
#create 32 character password fpr each service
#openssl rand -hex 32
nc-talk:
image: ghcr.io/nextcloud-releases/aio-talk:latest
container_name: nc-talk
init: true
restart: unless-stopped
ports:
- 3478:3478/tcp
- 3478:3478/udp
- 8181:8081/tcp
environment:
- NC_DOMAIN=cloud.poppyglen.cc
- TALK_HOST=signal.poppyglen.cc
- TURN_SECRET=${TURN_SECRET}
- SIGNALING_SECRET=${SIGNALING_SECRET}
- TZ=America/Los_Angeles
- TALK_PORT=3478
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
depends_on:
- app
networks:
- nextcloud-net
collabora:
image: collabora/code
container_name: collabora
restart: unless-stopped
networks:
- nextcloud-net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9980/hosting/capabilities || exit 1"]
interval: 30s
timeout: 10s
retries: 3
environment:
#- PUID=1000
#- PGID=1000
- TZ=America/Los_Angeles
- password=${COLLABORA_PASSWORD}
- username=${COLLABORA_USERNAME}
- server_name=office.poppyglen.cc
- aliasgroup1=https://cloud.poppyglen.cc:443
- aliasgroup2=http://local.poppyglen.cc
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- DONT_GEN_PASSWD=1
#ports:
# - 9980:9980
#volumes:
# - /mnt/Nextcloud/Nextcloud:/var/www/html:ro
cap_add:
- SYS_ADMIN
redis:
image: docker.io/library/redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
mem_limit: 2048m
mem_reservation: 512m
volumes:
- /mnt/Nextcloud/redis:/data
networks:
- nextcloud-net
caddy:
build: ./caddy
container_name: caddy
restart: unless-stopped
env_file: .env
environment:
- CF_API_KEY=${CF_API_KEY}
ports:
- 80:80
- 443:443
- "443:443/udp"
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
- ./caddy/Caddyfile:/etc/caddy/Caddyfile
#- /mnt/Nextcloud/caddy/data:/data
#- /mnt/Nextcloud/caddy/config:/config
- ./caddy/waf:/etc/caddy/waf
#- /mnt/Nextcloud/caddy/logs:/var/log/caddy
networks:
- nextcloud-net
depends_on:
- caddy-redis
caddy-redis:
image: redis:alpine
container_name: caddy-redis
restart: unless-stopped
volumes:
- /mnt/Nextcloud/caddy_redis:/data
networks:
- nextcloud-net
clamav:
image: clamav/clamav:1.3
container_name: clamav
restart: unless-stopped
networks:
- nextcloud-net
volumes:
- /mnt/Nextcloud/clamav_data:/var/lib/clamav
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
container_name: elasticsearch
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health?wait_for_status=yellow || exit 1"]
interval: 30s
timeout: 10s
retries: 5
environment:
- discovery.type=single-node
- xpack.security.enabled=false # Easiest for internal Docker communication
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # Recommended to limit memory usage
volumes:
- /mnt/Nextcloud/es_data:/usr/share/elasticsearch/data
networks:
- nextcloud-net
go-vod:
image: radialapps/go-vod
container_name: go-vod
restart: always
init: true
depends_on:
- app
environment:
- NEXTCLOUD_HOST=https://cloud.poppyglen.cc
# - NEXTCLOUD_ALLOW_INSECURE=1 # (self-signed certs or no HTTPS)
- NVIDIA_VISIBLE_DEVICES=all
devices:
- /dev/dri:/dev/dri # VA-API (omit for NVENC)
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
# runtime: nvidia # (NVENC)
networks:
- nextcloud-net
nextcloud-whiteboard-server:
image: ghcr.io/nextcloud-releases/whiteboard:stable
container_name: nextcloud-whiteboard-server
ports:
- 3002:3002
environment:
NEXTCLOUD_URL: https://cloud.poppyglen.cc
JWT_SECRET_KEY: ${WHITEBOARD_JWT}
networks:
- nextcloud-net
#mkdir -p /mnt/Nextcloud/mail/{maildata,mailstate,mail-logs,config,caddy}
mail:
image: docker.io/mailserver/docker-mailserver:latest
container_name: mail
hostname: mail.poppyglen.cc
restart: unless-stopped
environment:
- SSL_TYPE=manual
- SSL_CERT_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.crt
- SSL_KEY_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.key
- TZ=America/Los_Angeles
- PERMIT_DOCKER=network
- DMS_DEBUG=0
- ONE_DIR=1
- ENABLE_POSTGREY=0
- ENABLE_FAIL2BAN=1
- ENABLE_CLAMAV=1
- CLAMAV_HOST=clamav
- ENABLE_SPAMASSASSIN=1
- ENABLE_OPENDKIM=1
volumes:
- /mnt/Nextcloud/mail/maildata:/var/mail
- /mnt/Nextcloud/mail/mailstate:/var/mail-state
- /mnt/Nextcloud/mail/mail-logs:/var/log/mail
- /mnt/Nextcloud/mail/config:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/caddy/data:/caddy-data:ro
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
cap_add:
- NET_ADMIN # Required for Fail2Ban
- SYS_PTRACE
networks:
- nextcloud-net
depends_on:
- clamav
#docker exec -it mail setup config dkim
#docker exec -it mail cat /tmp/docker-mailserver/opendkim/keys/poppyglen.cc/mail.txt | awk -F'"' '/"/{print $2}' | tr -d '\n'
ollama:
image: ollama/ollama:rocm
container_name: ollama
restart: unless-stopped
volumes:
- /mnt/Nextcloud/ollama:/root/.ollama
- /lib/modules:/lib/modules:ro
- /sys/module:/sys/module:ro
networks:
- nextcloud-net
environment:
HSA_OVERRIDE_GFX_VERSION: ${HSA_OVERRIDE_GFX_VERSION}
HIP_VISIBLE_DEVICES: "0"
devices:
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
security_opt:
- seccomp:unconfined
cap_add:
- SYS_MODULE
- SYS_RAWIO
#docker exec -it ollama ollama pull qwen3:4b
recording-server:
image: nextcloud/aio-talk-recording
container_name: nextcloud-recording-server
restart: unless-stopped
environment:
- NEXTCLOUD_URL=https://cloud.poppyglen.cc
- NC_DOMAIN=cloud.poppyglen.cc
- NEXTCLOUD_RECORDING_USERNAME=${NEXTCLOUD_RECORDING_USERNAME}
- NEXTCLOUD_RECORDING_PASSWORD=${NEXTCLOUD_RECORDING_PASSWORD}
- RECORDING_SECRET=${RECORDING_SECRET}
- INTERNAL_SECRET=${INTERNAL_SECRET}
networks:
- nextcloud-net # Must be on the same network as other services
immich-postgres:
container_name: immich_postgres
image: tensorchord/pgvecto-rs:pg16-v0.2.0 # Required for vector search
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${IMMICH_DB_PASSWORD}
POSTGRES_USER: ${IMMICH_DB_USER}
POSTGRES_DB: ${IMMICH_DB_NAME}
DB_STORAGE_TYPE: 'HDD'
volumes:
- /mnt/Nextcloud/immich/immich_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
networks:
- nextcloud-net
immich-redis:
container_name: immich_redis
image: redis:6.2-alpine
restart: unless-stopped
networks:
- nextcloud-net
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:release
#command: [ "start.sh", "immich" ]
extends:
file: ./immich/hwaccel.transcoding.yml
service: vaapi
restart: unless-stopped
depends_on:
immich-postgres:
condition: service_healthy
immich-redis:
condition: service_started
environment:
# This is an anchor that other services will reference
&immich-common-env
DB_HOSTNAME: immich-postgres
DB_USERNAME: ${IMMICH_DB_USER}
DB_PASSWORD: ${IMMICH_DB_PASSWORD}
DB_DATABASE_NAME: ${IMMICH_DB_NAME}
REDIS_HOSTNAME: immich-redis
JWT_SECRET: ${IMMICH_JWT_SECRET}
# You can add other Immich specific env vars here if needed
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
#healthcheck:
# test: ["CMD", "curl", "-f", "http://localhost:2283/api/server/ping"]
# interval: 30s
# timeout: 10s
# retries: 5
# start_period: 30s
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:release
# This command is required to run microservices
#command: [ "start.sh", "microservices" ]
extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
file: ./immich/hwaccel.ml.yml
service: rocm # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
restart: unless-stopped
depends_on:
- immich-server
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
volumes:
- /mnt/Nextcloud/immich/immich_upload:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/photos:/import:ro
networks:
- nextcloud-net
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:release
restart: unless-stopped
volumes:
- /mnt/Nextcloud/immich/immich_models:/cache
environment:
# Use the alias to inherit common environment variables
<<: *immich-common-env
networks:
- nextcloud-net
immich-init:
image: alpine:3.20
container_name: immich-init
env_file: .env
environment:
IMMICH_BASE_URL: http://immich-server:2283/api
depends_on:
- immich-server
#condition: service_healthy
volumes:
- ./immich/init-immich.sh:/init-immich.sh:ro
entrypoint: ["/bin/sh", "/init-immich.sh"]
restart: "no"
networks:
- nextcloud-net
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
restart: unless-stopped
user: 1000:1000
environment:
- TZ=America/Los_Angeles
- PUID=1000 # Replace with your user ID
- PGID=1000 # Replace with your group ID
volumes:
- /mnt/Nextcloud/jellyfin/config:/config
- /mnt/Nextcloud/jellyfin/cache:/cache
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
ports:
- "8096:8096" # Web UI
- "7359:7359/udp" # Client discovery
- "1900:1900/udp" # DLNA
devices:
- /dev/dri:/dev/dri # For Intel Quick Sync or AMD VA-API hardware acceleration
networks:
- nextcloud-net
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8096/Users/Public"]
interval: 15s
timeout: 5s
retries: 40
start_period: 30s
jellyfin-init:
image: alpine:3.20
container_name: jellyfin-init
env_file: .env
environment:
JELLYFIN_BASE_URL: ${JELLYFIN_BASE_URL}
JELLYFIN_INIT_ADMIN_USER: ${JELLYFIN_INIT_ADMIN_USER}
JELLYFIN_INIT_ADMIN_PASSWORD: ${JELLYFIN_INIT_ADMIN_PASSWORD}
JELLYFIN_INIT_SERVERNAME: ${JELLYFIN_INIT_SERVERNAME}
depends_on:
jellyfin:
condition: service_healthy
volumes:
- ./jellyfin/init-jellyfin.sh:/init-jellyfin.sh:ro
- /mnt/Nextcloud/movies:/media/movies
- /mnt/Nextcloud/tvshows:/media/tvshows
- /mnt/Nextcloud/music:/media/music
- /mnt/Nextcloud/photos:/media/photos
entrypoint: ["/bin/sh", "/init-jellyfin.sh"]
restart: "no"
networks:
- nextcloud-net
#sunshine:
# image: ghcr.io/lizardbyte/sunshine:v2025.628.4510-archlinux
# container_name: sunshine
# restart: unless-stopped
# privileged: true
# shm_size: "2gb"
# user: "root"
# group_add:
# - "render"
# - "video"
# - "input"
# environment:
# - TZ=America/Los_Angeles
# - PUID=1000
# - PGID=1000
# - LIBVA_DRIVER_NAME=radeonsi
# - WAYLAND_DISPLAY=${WAYLAND_DISPLAY:-wayland-0}
# - XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/1000} # replace 1000 with your UID
# - SUNSHINE_USERNAME=${SUNSHINE_USERNAME}
# - SUNSHINE_PASSWORD=${SUNSHINE_PASSWORD}
# - DISPLAY=:1
# volumes:
# - /mnt/Nextcloud/sunshine/config:/config
# - /run/user/1000:/run/user/1000
# - /tmp/.X11-unix:/tmp/.X11-unix # optional for X11
# - /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
# - /run/dbus:/run/dbus:ro
# - /run/udev/data:/run/udev/data:ro
# ports:
# - "47984:47984/udp"
# - "47990:47990/tcp"
# - "47989:47989/tcp"
# - "48010:48010/udp"
# - "48010:48010/tcp"
# - "47998-48000:47998-48000/udp"
# devices:
# - /dev/dri:/dev/dri
# - /dev/input:/dev/input
# - /dev/uinput:/dev/uinput
# #networks:
# # - nextcloud-net
# networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
wolf:
image: ghcr.io/games-on-whales/wolf:stable
container_name: wolf
#user: "1000:1000"
privileged: true
environment:
- XDG_RUNTIME_DIR=/tmp/sockets
- HOST_APPS_STATE_FOLDER=/mnt/Nextcloud/wolf
- WAYLAND_DISPLAY=wayland-1
- MESA_LOADER_DRIVER_OVERRIDE=amd
- VDPAU_DRIVER=va_gl
- LIBVA_DRIVER_NAME=radeonsi
- RADV_DEBUG=llvm
- WOLF_LOG_LEVEL:DEBUG
volumes:
- /mnt/Nextcloud/wolf:/mnt/Nextcloud/wolf:rw
- /mnt/Nextcloud/wolf/cfg:/etc/wolf/cfg:rw
- /run/user/1000:/run/user/1000
- /run/udev/data:/run/udev/data:ro
- /tmp/sockets:/tmp/sockets:rw
- /var/run/docker.sock:/var/run/docker.sock:rw
- /var/lib/dbus/machine-id:/var/lib/dbus/machine-id:ro
- /usr/share/vulkan/icd.d:/usr/share/vulkan/icd.d:ro
- /usr/lib/libvulkan.so:/usr/lib/libvulkan.so:ro
- /usr/lib/libvulkan.so.1:/usr/lib/libvulkan.so.1:ro
- /usr/lib/libvulkan.so.1.4.321:/usr/lib/libvulkan.so.1.4.321:ro
- /usr/lib/libvulkan_radeon.so:/usr/lib/libvulkan_radeon.so:ro
- /usr/lib/libEGL_mesa.so:/usr/lib/libEGL_mesa.so:ro
- /usr/lib/libEGL_mesa.so.0:/usr/lib/libEGL_mesa.so.0:ro
- /usr/lib/libEGL_mesa.so.0.0.0:/usr/lib/libEGL_mesa.so.0.0.0:ro
- /usr/lib/libGLX_mesa.so:/usr/lib/libGLX_mesa.so:ro
- /usr/lib/libGLX_mesa.so.0:/usr/lib/libGLX_mesa.so.0:ro
- /usr/lib/libGLX_mesa.so.0.0.0:/usr/lib/libGLX_mesa.so.0.0.0:ro
- /usr/lib/libva.so:/usr/lib/libva.so:ro
- /usr/lib/libva.so.2:/usr/lib/libva.so.2:ro
- /usr/lib/libva.so.2.0.0:/usr/lib/libva.so.2.0.0:ro
- /usr/lib/dri/radeonsi_drv_video.so:/usr/lib/dri/radeonsi_drv_video.so:ro
- /usr/lib/x86_64-linux-gnu/dri:/usr/lib/x86_64-linux-gnu/dri:ro
- /run/dbus:/run/dbus:ro
- /run/udev:/run/udev:rw
device_cgroup_rules:
- 'c 13:* rmw'
devices:
- /dev/dri:/dev/dri:rw
- /dev/dri/renderD128:/dev/dri/renderD128:rw
- /dev/uinput:/dev/uinput
- /dev/uhid:/dev/uhid
group_add:
- "989"
- "video"
- "994"
network_mode: host
restart: unless-stopped
#networks:
# sunshine_net:
# ipv4_address: 192.168.0.200
technitium-dns:
image: technitium/dns-server:latest
container_name: technitium-dns
hostname: technitium-dns
restart: unless-stopped
volumes:
- /mnt/Nextcloud/technitium/config:/etc/dns
environment:
- DNS_SERVER_DOMAIN=home.lan
- DNS_SERVER_ADMIN_PASSWORD=${TECHNITIUM_ADMIN_PASSWORD}
- DNS_SERVER_FORWARDERS=1.1.1.1,8.8.8.8,2606:4700:4700::1111,2001:4860:4860::8888
- DNS_SERVER_FORWARDER_PROTOCOL=Udp
- DNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
- DNS_SERVER_RECURSION_ALLOWED_NETWORKS=192.168.0.0/24,fd00::/8
ports:
- "5380:5380/tcp" # Web UI
- "53:53/tcp" # DNS
- "53:53/udp" # DNS
networks:
- nextcloud-net
#echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
#echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
- WEBSOCKET_ENABLED=true # Required for Caddy reverse proxy
- SIGNUPS_ALLOWED=true # Set to false after you've created your account
- ADMIN_TOKEN=${VAULTWARDEN_ADMIN_TOKEN} # Add a secure token to your .env file
volumes:
- /mnt/Nextcloud/vaultwarden:/data
networks:
- nextcloud-net
wireguard:
image: lscr.io/linuxserver/wireguard:latest
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- SERVERURL=vpn.poppyglen.cc # or your public domain, e.g., vpn.poppyglen.cc
- PEERS=1 # Number of peer configs to generate
- PEERDNS=auto
- INTERNAL_SUBNET=10.13.13.0/24
volumes:
- /mnt/Nextcloud/wireguard/config:/config
- /lib/modules:/lib/modules
ports:
- 51820:51820/udp
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
restart: unless-stopped
networks:
- nextcloud-net
#ipfs:
# image: ipfs/kubo:latest
# container_name: ipfs_node
# restart: unless-stopped
# environment:
# - IPFS_PROFILE=server
# volumes:
# - /mnt/Nextcloud/ipfs_data:/data/ipfs
# ports:
# - "4001:4001/tcp" # Swarm - TCP
# - "4001:4001/udp" # Swarm - QUIC
# - "8080:8080" # Gateway
# - "5001:5001" # API
# networks:
# - nextcloud-net

View File

@@ -0,0 +1,270 @@
networks:
nextcloud-net:
driver: bridge
services:
cloudflare-ddns:
image: docker.io/oznu/cloudflare-ddns:latest
container_name: cloudflare_ddns
restart: unless-stopped
environment:
- API_KEY=${CF_API_KEY}
- ZONE=${CF_ZONE}
- SUBDOMAIN=${CF_SUBDOMAIN}
- PROXIED=${CF_PROXIED}
networks:
- nextcloud-net
db:
image: mariadb:11.4
container_name: nextcloud-db
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "${MYSQL_USER}", "-p${MYSQL_PASSWORD}"]
interval: 10s
timeout: 5s
retries: 5
environment:
- TZ=America/Los_Angeles
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_USER=${MYSQL_USER}
- MYSQL_PASSWORD=${MYSQL_PASSWORD}
volumes:
- /mnt/Nextcloud/nextcloud_db:/var/lib/mysql
networks:
- nextcloud-net
app:
image: nextcloud:latest
container_name: nextcloud-app
restart: unless-stopped
depends_on:
db:
condition: service_healthy # This tells the app to wait for the healthcheck
redis:
condition: service_started
collabora:
condition: service_started
environment:
MYSQL_HOST: db
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
REDIS_HOST: redis
NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: cloud.poppyglen.cc local.poppyglen.cc
OVERWRITEHOST: cloud.poppyglen.cc
OVERWRITEPROTOCOL: https
OVERWRITECLIURL: https://cloud.poppyglen.cc
OVERWRITECONDADDR: 172.16.0.0\/12
TRUSTED_PROXIES: 172.16.0.0/12
USER1_PASSWORD: ${USER1_PASSWORD}
USER2_PASSWORD: ${USER2_PASSWORD}
USER3_PASSWORD: ${USER3_PASSWORD}
USER4_PASSWORD: ${USER4_PASSWORD}
USER5_PASSWORD: ${USER5_PASSWORD}
HOME: /var/www # Set HOME for www-data
GIPHY_API_KEY: ${GIPHY_API_KEY}
WHITEBOARD_JWT: ${WHITEBOARD_JWT}
TURN_SECRET: ${TURN_SECRET}
SIGNALING_SECRET: ${SIGNALING_SECRET}
MAIL_FROM_ADDRESS: admin
MAIL_DOMAIN: poppyglen.cc
MAIL_SMTPAUTH: 1
MAIL_SMTPSECURE: ssl
MAIL_SMTPHOST: mail.poppyglen.cc
MAIL_SMTPPORT: 465
MAIL_SMTPNAME: admin@poppyglen.cc # an existing mail account
MAIL_SMTPPASSWORD: ${MAIL_ADMIN_PASSWORD} # password for the mail account
MAIL_ADMIN_EMAIL: ${MAIL_ADMIN_EMAIL}
MAIL_ADMIN_PASSWORD: ${MAIL_ADMIN_PASSWORD}
EMAIL_IMAP_HOST: ${EMAIL_IMAP_HOST}
EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST}
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
- ./hooks:/docker-entrypoint-hooks.d
#- /mnt/Nextcloud/Nextcloud/custom_apps:/var/www/html/custom_apps
#- /mnt/Nextcloud/Nextcloud/config:/var/www/html/config
#- /mnt/Nextcloud/Nextcloud/data:/var/www/html/data
networks:
- nextcloud-net
cron:
image: nextcloud:latest
container_name: nextcloud-cron
restart: unless-stopped
depends_on:
- app
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html
entrypoint: /cron.sh
#create 32 character password fpr each service
#openssl rand -hex 32
nc-talk:
image: ghcr.io/nextcloud-releases/aio-talk:latest
container_name: nc-talk
init: true
restart: unless-stopped
ports:
- 3478:3478/tcp
- 3478:3478/udp
- 8181:8081/tcp
environment:
- NC_DOMAIN=cloud.poppyglen.cc
- TALK_HOST=signal.poppyglen.cc
- TURN_SECRET=${TURN_SECRET}
- SIGNALING_SECRET=${SIGNALING_SECRET}
- TZ=America/Los_Angeles
- TALK_PORT=3478
- INTERNAL_SECRET=${INTERNAL_SECRET}
depends_on:
- app
networks:
- nextcloud-net
collabora:
image: collabora/code
container_name: collabora
restart: unless-stopped
networks:
- nextcloud-net
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9980/hosting/capabilities || exit 1"]
interval: 30s
timeout: 10s
retries: 3
environment:
#- PUID=1000
#- PGID=1000
- TZ=America/Los_Angeles
- password=${COLLABORA_PASSWORD}
- username=${COLLABORA_USERNAME}
- server_name=office.poppyglen.cc
- aliasgroup1=https://cloud.poppyglen.cc:443
- aliasgroup2=http://local.poppyglen.cc
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
- DONT_GEN_PASSWD=1
#ports:
# - 9980:9980
#volumes:
# - /mnt/Nextcloud/Nextcloud:/var/www/html:ro
cap_add:
- SYS_ADMIN
redis:
image: docker.io/library/redis:alpine
container_name: nextcloud-redis
restart: unless-stopped
mem_limit: 2048m
mem_reservation: 512m
volumes:
- /mnt/Nextcloud/redis:/data
networks:
- nextcloud-net
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- /mnt/Nextcloud/Nextcloud:/var/www/html:ro
- ./Caddyfile:/etc/caddy/Caddyfile
- /mnt/Nextcloud/caddy/data:/data
- /mnt/Nextcloud/caddy/config:/config
networks:
- nextcloud-net
clamav:
image: clamav/clamav:1.3
container_name: clamav
restart: unless-stopped
networks:
- nextcloud-net
volumes:
- /mnt/Nextcloud/clamav_data:/var/lib/clamav
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.15.0
container_name: elasticsearch
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health?wait_for_status=yellow || exit 1"]
interval: 30s
timeout: 10s
retries: 5
environment:
- discovery.type=single-node
- xpack.security.enabled=false # Easiest for internal Docker communication
- "ES_JAVA_OPTS=-Xms512m -Xmx512m" # Recommended to limit memory usage
volumes:
- /mnt/Nextcloud/es_data:/usr/share/elasticsearch/data
networks:
- nextcloud-net
tika:
image: apache/tika:latest
container_name: tika
restart: unless-stopped
networks:
- nextcloud-net
nextcloud-whiteboard-server:
image: ghcr.io/nextcloud-releases/whiteboard:stable
container_name: nextcloud-whiteboard-server
ports:
- 3002:3002
environment:
NEXTCLOUD_URL: https://cloud.poppyglen.cc
JWT_SECRET_KEY: ${WHITEBOARD_JWT}
networks:
- nextcloud-net
#mkdir -p /mnt/Nextcloud/mail/{maildata,mailstate,mail-logs,config,caddy}
mail:
image: docker.io/mailserver/docker-mailserver:latest
container_name: mail
hostname: mail.poppyglen.cc
restart: unless-stopped
environment:
- SSL_TYPE=manual
- SSL_CERT_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.crt
- SSL_KEY_PATH=/caddy-data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.poppyglen.cc/mail.poppyglen.cc.key
- TZ=America/Los_Angeles
- PERMIT_DOCKER=network
- DMS_DEBUG=0
- ONE_DIR=1
- ENABLE_POSTGREY=0
- ENABLE_FAIL2BAN=1
- ENABLE_CLAMAV=1
- CLAMAV_HOST=clamav
- ENABLE_SPAMASSASSIN=1
- ENABLE_OPENDKIM=1
volumes:
- /mnt/Nextcloud/mail/maildata:/var/mail
- /mnt/Nextcloud/mail/mailstate:/var/mail-state
- /mnt/Nextcloud/mail/mail-logs:/var/log/mail
- /mnt/Nextcloud/mail/config:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
- /mnt/Nextcloud/caddy/data:/caddy-data:ro
ports:
- "25:25"
- "143:143"
- "465:465"
- "587:587"
- "993:993"
cap_add:
- NET_ADMIN # Required for Fail2Ban
- SYS_PTRACE
networks:
- nextcloud-net
depends_on:
- clamav
#docker exec -it mail setup config dkim
#docker exec -it mail cat /tmp/docker-mailserver/opendkim/keys/poppyglen.cc/mail.txt

View File

@@ -0,0 +1,86 @@
#!/bin/bash
set -euo pipefail # Exit on error, unset variables are errors
# ---------------------------
# Configuration
# ---------------------------
MAIL_CONFIG_DIR="/mnt/Nextcloud/mail/config"
ACCOUNT_FILE="$MAIL_CONFIG_DIR/postfix-accounts.cf"
ADMIN_EMAIL="admin@poppyglen.cc"
# ---------------------------
# Functions
# ---------------------------
require_root() {
if [[ "$(id -u)" -ne 0 ]]; then
echo "❌ Error: This script must be run as root or with sudo." >&2
exit 1
fi
}
load_env() {
if [[ -f ".env" ]]; then
echo "📄 Loading environment variables from .env..."
# Only export lines in the form KEY=VALUE; ignore everything else
while IFS='=' read -r key value; do
# Skip lines that are empty, comments, or JSON
[[ -z "$key" || "$key" =~ ^# || "$key" =~ \[|\{ ]] && continue
export "$key=$value"
done < .env
fi
}
get_password() {
if [[ -z "${MAIL_ADMIN_PASSWORD:-}" ]]; then
read -sp "Enter a password for the mail admin ($ADMIN_EMAIL): " MAIL_ADMIN_PASSWORD
echo
else
echo "✅ Loaded MAIL_ADMIN_PASSWORD from .env"
fi
if [[ -z "$MAIL_ADMIN_PASSWORD" ]]; then
echo "❌ Error: Password cannot be empty." >&2
exit 1
fi
}
generate_hash() {
echo "🔐 Generating password hash..."
HASH=$(docker run --rm docker.io/mailserver/docker-mailserver:latest \
doveadm pw -s SHA512-CRYPT -p "$MAIL_ADMIN_PASSWORD")
if [[ -z "$HASH" ]]; then
echo "❌ Error: Failed to generate password hash. Is Docker running?" >&2
exit 1
fi
}
create_account_file() {
echo "📝 Creating account file: $ACCOUNT_FILE"
mkdir -p "$MAIL_CONFIG_DIR"
echo "$ADMIN_EMAIL|$HASH" > "$ACCOUNT_FILE"
}
# ---------------------------
# Main
# ---------------------------
echo "📧 Mail Server First-Time Setup"
echo "This script will create the initial admin account required for the server to start."
require_root
# Check for existing account file
if [[ -f "$ACCOUNT_FILE" ]]; then
read -p "⚠️ An account file already exists. Overwrite? (y/N) " -r response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
echo "Operation cancelled. No changes were made."
exit 0
fi
fi
load_env
get_password
generate_hash
create_account_file
echo "✅ Setup complete! The mail server is ready for its first start."
echo "You can now run: docker compose up -d"

7
old_examples/users.json Normal file
View File

@@ -0,0 +1,7 @@
[
{"username":"camonroe","password":"s8KKumVhgp@","name":"Charles Monroe","email":"charles.a.monroe@gmail.com"},
{"username":"smmonroe","password":"TM6HWnaYH6n","name":"Sarah Monroe","email":"sarah.m.monroe@gmail.com"},
{"username":"esmonroe","password":"g86@tPsV3b68R","name":"Ezra Monroe","email":"wolfesm9@gmail.com"},
{"username":"vjmonroe","password":"!44sw%vL]hnE~^","name":"Violet Monroe","email":"foxvjm8@gmail.com"},
{"username":"jsmonroe","password":"A3R,pSWYRR2%","name":"Jane Monroe","email":"kittyjsm5@gmail.com"}
]