diff --git a/setup/photoprismpi/meta-data b/setup/photoprismpi/meta-data new file mode 100644 index 000000000..63fa08490 --- /dev/null +++ b/setup/photoprismpi/meta-data @@ -0,0 +1,2 @@ +instance-id: photoprism-pi-001 +local-hostname: photoprism-pi \ No newline at end of file diff --git a/setup/photoprismpi/network-config b/setup/photoprismpi/network-config new file mode 100644 index 000000000..d4a9596e3 --- /dev/null +++ b/setup/photoprismpi/network-config @@ -0,0 +1,5 @@ +version: 2 +ethernets: + eth0: + dhcp4: true + optional: false \ No newline at end of file diff --git a/setup/photoprismpi/user-data b/setup/photoprismpi/user-data new file mode 100644 index 000000000..e8a8a788d --- /dev/null +++ b/setup/photoprismpi/user-data @@ -0,0 +1,405 @@ +#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,b,c,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 can access the web interface using 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://:2342/|http://$IP:2342/|g" /etc/motd + + - path: /etc/motd + permissions: '0644' + content: | + + Welcome to PhotoPrism Pi! + + You can access the web interface using one of the + following URLs: + http://: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:arm64 + 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_ORIGINALS_LIMIT: 5000 + PHOTOPRISM_HTTP_COMPRESSION: "none" + PHOTOPRISM_WORKERS: 2 + PHOTOPRISM_LOG_LEVEL: "info" + 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: 85 + PHOTOPRISM_DETECT_NSFW: "false" + PHOTOPRISM_UPLOAD_NSFW: "true" + PHOTOPRISM_DATABASE_DRIVER: "mysql" + PHOTOPRISM_DATABASE_SERVER: "mariadb:3306" + PHOTOPRISM_DATABASE_NAME: "photoprism" + PHOTOPRISM_DATABASE_USER: "photoprism" + PHOTOPRISM_DATABASE_PASSWORD: "insecure" + PHOTOPRISM_INIT: "gpu" + PHOTOPRISM_FFMPEG_ENCODER: "software" + PHOTOPRISM_FFMPEG_BITRATE: "32" + PHOTOPRISM_UID: 1000 + PHOTOPRISM_GID: 1000 + 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: arm64v8/mariadb:10.10 + security_opt: + - seccomp=unconfined + - apparmor=unconfined + command: mysqld --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.4 + 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 +