Files
photoprism/setup/nas/raspberry-pi/cloud-init/user-data
2025-07-24 15:17:17 +02:00

421 lines
16 KiB
Plaintext

#cloud-config
# Set hostname
hostname: photoprism-pi
fqdn: photoprism-pi.local
# Update and upgrade packages
package_update: true
package_upgrade: true
# Install required packages
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- git
- openssl
- avahi-daemon
# Install Docker Engine (latest version)
runcmd:
# Print welcome message
- printf "\e[0s;35m[SETUP] Starting PhotoPrism installation on Raspberry Pi...\e[0m\n"
# Uninstall old versions if they exist
- printf "\e[0;35m[SETUP] Removing old Docker versions if present...\e[0m\n"
- for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do apt-get remove -y $pkg || true; done
- printf "\e[0;35m[SETUP] ✓ Old Docker versions removed or not present\e[0m\n"
# Add Docker's official GPG key
- printf "\e[0;35m[SETUP] Adding Docker repository GPG key...\e[0m\n"
- apt-get update
- apt-get install -y ca-certificates curl
- install -m 0755 -d /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
- chmod a+r /etc/apt/keyrings/docker.asc
- printf "\e[0;35m[SETUP] ✓ Docker GPG key added\e[0m\n"
# Add Docker repository
- printf "\e[0;35m[SETUP] Adding Docker repository...\e[0m\n"
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
- apt-get update
- printf "\e[0;35m[SETUP] ✓ Docker repository added\e[0m\n"
# Install Docker Engine
- printf "\e[0;35m[SETUP] Installing Docker Engine...\e[0m\n"
- apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- printf "\e[0;35m[SETUP] ✓ Docker Engine installed\e[0m\n"
# Create directories for PhotoPrism and Traefik
- printf "\e[0;35m[SETUP] Creating directory structure for PhotoPrism...\e[0m\n"
- mkdir -p /opt/photoprism/photos
- mkdir -p /opt/photoprism/storage
- mkdir -p /opt/photoprism/database
- mkdir -p /opt/photoprism/import
- mkdir -p /opt/photoprism/originals
- mkdir -p /opt/photoprism/traefik/certs
- mkdir -p /opt/photoprism/traefik/conf.d
- mkdir -p /etc/traefik
- chown -R 1000:1000 /opt/photoprism
- chmod -R 755 /opt/photoprism
- chmod -R ug+rwX,o-rwx /opt/photoprism/storage
- chmod -R ug+rwX,o-rwx /opt/photoprism/database
- chmod -R ug+rwX,o-rwx /opt/photoprism/import
- chmod -R ug+rwX,o-rwx /opt/photoprism/originals
- chmod -R ug+rwX,o-rwx /opt/photoprism/photos
- printf "\e[0;35m[SETUP] ✓ PhotoPrism directory structure created\e[0m\n"
# Create mount points for external drives
- printf "\e[0;35m[SETUP] Creating mount points for external drives...\e[0m\n"
- mkdir -p /mnt/a
- mkdir -p /mnt/b
- mkdir -p /mnt/c
- mkdir -p /mnt/d
- chown -R 1000:1000 /mnt
- printf "\e[0;35m[SETUP] ✓ External drive mount points created\e[0m\n"
# Configure external drives in fstab
- printf "\e[0;35m[SETUP] Configuring external drives in fstab...\e[0m\n"
- |
cat >> /etc/fstab << 'EOF'
/dev/sda1 /mnt/a auto nofail,noatime,noauto,x-systemd.automount,x-systemd.device-timeout=10s,uid=1000,gid=1000 0 0
/dev/sdb1 /mnt/b auto nofail,noatime,noauto,x-systemd.automount,x-systemd.device-timeout=10s,uid=1000,gid=1000 0 0
/dev/sdc1 /mnt/c auto nofail,noatime,noauto,x-systemd.automount,x-systemd.device-timeout=10s,uid=1000,gid=1000 0 0
/dev/sdd1 /mnt/d auto nofail,noatime,noauto,x-systemd.automount,x-systemd.device-timeout=10s,uid=1000,gid=1000 0 0
EOF
- printf "\e[0;35m[SETUP] ✓ External drives configured in fstab\e[0m\n"
# Set up swap
- printf "\e[0;35m[SETUP] Setting up swap space...\e[0m\n"
- |
cat > /usr/local/bin/swapon.sh << 'EOF'
#!/usr/bin/env bash
# add 8 GB of swap if no swap was configured yet
if [[ -z $(swapon --show) ]]; then
fallocate -l 8G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
swapon --show
free -h
echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab
fi
EOF
- chmod +x /usr/local/bin/swapon.sh
- /usr/local/bin/swapon.sh
- printf "\e[0;35m[SETUP] ✓ Swap space configured\e[0m\n"
# Verify the installation is successful
- printf "\e[0;35m[SETUP] Verifying Docker installation...\e[0m\n"
- docker run --rm hello-world
- printf "\e[0;35m[SETUP] ✓ Docker installation verified\e[0m\n"
# Ensure docker group exists and pi user is added to it
- printf "\e[0;35m[SETUP] Ensuring pi user is in docker group...\e[0m\n"
- getent group docker || groupadd docker
- usermod -aG docker pi
- printf "\e[0;35m[SETUP] ✓ Pi user added to docker group\e[0m\n"
# Enable services
- printf "\e[0;35m[SETUP] Enabling required services...\e[0m\n"
- systemctl enable docker
- systemctl enable avahi-daemon
- printf "\e[0;35m[SETUP] ✓ Services enabled\e[0m\n"
# Add a service to start PhotoPrism on boot
- printf "\e[0;35m[SETUP] Creating PhotoPrism service...\e[0m\n"
- |
cat > /etc/systemd/system/photoprism.service << 'EOF'
[Unit]
Description=PhotoPrism Service
After=docker.service network-online.target
Requires=docker.service network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/photoprism
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
EOF
- printf "\e[0;35m[SETUP] ✓ PhotoPrism service created\e[0m\n"
# Enable PhotoPrism service
- printf "\e[0;35m[SETUP] Enabling and starting PhotoPrism service...\e[0m\n"
- systemctl enable photoprism.service
- systemctl start photoprism.service
- printf "\e[0;35m[SETUP] ✓ PhotoPrism service started\e[0m\n"
# Configure Avahi for .local domain
- printf "\e[0;35m[SETUP] Restarting and configuring Avahi for .local domain...\e[0m\n"
- systemctl restart avahi-daemon
- printf "\e[0;35m[SETUP] ✓ Avahi configured for .local domain\e[0m\n"
# Update MOTD and issue file with actual IP address
- printf "\e[0;35m[SETUP] Updating MOTD and console login message with actual IP address...\e[0m\n"
- chmod +x /usr/local/bin/update-motd-ip.sh
- /usr/local/bin/update-motd-ip.sh
- PRIMARY_IFACE=$(ip route | grep default | awk '{print $5}')
- IP=$(ip addr show $PRIMARY_IFACE 2>/dev/null | grep -oP 'inet \K[\d.]+' || hostname -I | awk '{print $1}')
- |
cat > /etc/issue << EOF
Welcome to PhotoPrism Pi!
You should soon be able to access the web interface
by navigating to one of the following URLs:
http://$IP:2342/
https://photoprism-pi.local/
For further information and help with troubleshooting:
https://docs.photoprism.app/photoprism-pi/
https://docs.photoprism.app/getting-started/troubleshooting/
EOF
- printf "\e[0;35m[SETUP] ✓ MOTD and console login message updated with actual IP\e[0m\n"
# Write configuration files
write_files:
- path: /usr/local/bin/update-motd-ip.sh
permissions: '0755'
content: |
#!/bin/bash
IP=$(hostname -I | awk '{print $1}')
sed -i "s|http://<your IP address>:2342/|http://$IP:2342/|g" /etc/motd
- path: /etc/motd
permissions: '0644'
content: |
Welcome to PhotoPrism Pi!
You should soon be able to access the web interface
by navigating to one of the following URLs:
http://<IP address>:2342/
https://photoprism-pi.local/
Your initial password for the "admin" account is
"photoprismpi". Please change it after logging in for the
first time, especially if your device is connected to the
Internet or a shared network.
For further information and help with troubleshooting:
https://docs.photoprism.app/photoprism-pi/
https://docs.photoprism.app/getting-started/troubleshooting/
- path: /opt/photoprism/compose.yaml
permissions: '0644'
content: |
name: photoprism
services:
photoprism:
image: photoprism/photoprism:latest
depends_on:
- mariadb
restart: always
security_opt:
- seccomp=unconfined
- apparmor=unconfined
user: "1000:1000"
ports:
- "2342:2342"
labels:
- "traefik.enable=true"
- "traefik.http.routers.photoprism.rule=Host(`photoprism-pi.local`)"
- "traefik.http.routers.photoprism.entrypoints=websecure"
- "traefik.http.routers.photoprism.tls=true"
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
- "traefik.docker.network=photoprism"
environment:
PHOTOPRISM_ADMIN_PASSWORD: "photoprismpi"
PHOTOPRISM_AUTH_MODE: "passwd"
PHOTOPRISM_SITE_URL: "https://photoprism-pi.local/"
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
PHOTOPRISM_HTTP_COMPRESSION: "gzip"
PHOTOPRISM_WORKERS: 3
PHOTOPRISM_LOG_LEVEL: "info"
PHOTOPRISM_TRACE: "false"
PHOTOPRISM_READONLY: "false"
PHOTOPRISM_EXPERIMENTAL: "false"
PHOTOPRISM_DISABLE_CHOWN: "false"
PHOTOPRISM_DISABLE_WEBDAV: "false"
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_DISABLE_TENSORFLOW: "false"
PHOTOPRISM_DISABLE_FACES: "false"
PHOTOPRISM_DISABLE_CLASSIFICATION: "false"
PHOTOPRISM_DISABLE_RAW: "false"
PHOTOPRISM_RAW_PRESETS: "false"
PHOTOPRISM_JPEG_QUALITY: 82
PHOTOPRISM_FFMPEG_SIZE: 1920
PHOTOPRISM_AUTO_INDEX: 300 # delay before automatically indexing files in SECONDS when uploading via WebDAV (-1 to disable)
PHOTOPRISM_AUTO_IMPORT: -1 # delay before automatically importing files in SECONDS when uploading via WebDAV (-1 to disable)
PHOTOPRISM_DETECT_NSFW: "false" # automatically flags photos as private that MAY be offensive (requires TensorFlow)
PHOTOPRISM_UPLOAD_NSFW: "true" # allow uploads that MAY be offensive
PHOTOPRISM_UPLOAD_ALLOW: "" # restricts uploads to these file types (comma-separated list of EXTENSIONS; leave blank to allow all)
PHOTOPRISM_UPLOAD_ARCHIVES: "true" # allows upload of zip archives (will be extracted before import)
PHOTOPRISM_UPLOAD_LIMIT: 5000 # maximum size of uploaded files and uncompressed archive contents in MB
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # maximum size of original media files in MB (larger files are skipped)
PHOTOPRISM_RESOLUTION_LIMIT: 300 # maximum resolution of original media files in Pixels (larger files are skipped)
PHOTOPRISM_SIDECAR_YAML: "false" # creates YAML sidecar files to back up picture metadata
PHOTOPRISM_INDEX_SCHEDULE: "" # indexing SCHEDULE in cron format (e.g. "@every 3h" for every 3 hours; "" to disable)
PHOTOPRISM_BACKUP_ALBUMS: "true" # creates YAML files to back up album metadata
PHOTOPRISM_BACKUP_DATABASE: "true" # creates regular backups based on the configured schedule
PHOTOPRISM_BACKUP_SCHEDULE: "daily" # backup SCHEDULE in cron format (e.g. "0 12 * * *" for daily at noon) or at a random time (daily, weekly)
PHOTOPRISM_DATABASE_DRIVER: "mysql"
PHOTOPRISM_DATABASE_SERVER: "mariadb:3306"
PHOTOPRISM_DATABASE_NAME: "photoprism"
PHOTOPRISM_DATABASE_USER: "photoprism"
PHOTOPRISM_DATABASE_PASSWORD: "insecure"
PHOTOPRISM_INIT: "yt-dlp"
working_dir: "/photoprism"
volumes:
- "/opt/photoprism/storage:/photoprism/storage"
- "/opt/photoprism/originals:/photoprism/originals"
- "/mnt:/photoprism/originals/mnt:slave"
- "/opt/photoprism/import:/photoprism/import"
mariadb:
restart: always
image: mariadb:11
security_opt:
- seccomp=unconfined
- apparmor=unconfined
command: --innodb-buffer-pool-size=256M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120
volumes:
- "/opt/photoprism/database:/var/lib/mysql"
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: "photoprism"
MARIADB_USER: "photoprism"
MARIADB_PASSWORD: "insecure"
MARIADB_ROOT_PASSWORD: "insecure"
traefik:
restart: always
image: traefik:v3.5
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/opt/photoprism/traefik:/etc/traefik:rw"
labels:
- "traefik.enable=true"
command:
- "--api.insecure=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--providers.docker.network=photoprism"
- "--providers.docker.defaultRule=Host(`{{ normalize .Name }}.local`)"
- "--log.level=DEBUG"
watchtower:
restart: always
image: containrrr/watchtower
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_POLL_INTERVAL: 7200
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
networks:
default:
name: photoprism
driver: bridge
- path: /etc/avahi/avahi-daemon.conf
permissions: '0644'
content: |
[server]
#host-name=foo
#domain-name=local
#browse-domains=0pointer.de, zeroconf.org
use-ipv4=yes
use-ipv6=no
allow-interfaces=eth0
#deny-interfaces=eth1
#check-response-ttl=no
#use-iff-running=no
#enable-dbus=yes
#disallow-other-stacks=no
#allow-point-to-point=no
#cache-entries-max=4096
#clients-max=4096
#objects-per-client-max=1024
#entries-per-entry-group-max=32
ratelimit-interval-usec=1000000
ratelimit-burst=1000
[wide-area]
enable-wide-area=yes
[publish]
#disable-publishing=no
#disable-user-service-publishing=no
#add-service-cookie=no
#publish-addresses=yes
publish-hinfo=no
publish-workstation=no
#publish-domain=yes
#publish-dns-servers=192.168.50.1, 192.168.50.2
#publish-resolv-conf-dns-servers=yes
#publish-aaaa-on-ipv4=yes
#publish-a-on-ipv6=no
[reflector]
#enable-reflector=no
#reflect-ipv=no
#reflect-filters=_airplay._tcp.local,_raop._tcp.local
[rlimits]
#rlimit-as=
#rlimit-core=0
#rlimit-data=8388608
#rlimit-fsize=0
#rlimit-nofile=768
#rlimit-stack=8388608
#rlimit-nproc=3
# Configure users
users:
- name: pi
gecos: PhotoPrism User
sudo: ALL=(ALL) NOPASSWD:ALL
groups: adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,netdev,docker
shell: /bin/bash
lock_passwd: false
# Set passwords
chpasswd:
expire: false
list: |
pi:raspberry
# Automatically reboot after cloud-init completes
power_state:
delay: "+1"
mode: reboot
message: "Rebooting system after cloud-init completes setup"
timeout: 30
condition: True