diff --git a/.gitignore b/.gitignore index baba28a..6d70e95 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .DS_Store # Build artifacts -/dist/ +/output/ diff --git a/Scripts/build.sh b/Scripts/build.sh deleted file mode 100755 index d1d8f30..0000000 --- a/Scripts/build.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/sh - -set -eux - -TMP="$(mktemp -d -t immich -p /tmp)" -chmod 755 "$TMP" - -if [ -z "${IMMICH_USER:-}" ]; then - # Load configuration when running as immich user with `sudo` - # shellcheck disable=SC1091 - . "$(dirname "$0")/config.sh" - export HOME="$IMMICH_HOME_DIR" -fi - -if [ "$USER" != "$IMMICH_USER" ]; then - echo "DEBUG: going to switch to immich user" - - # move to a place were immich has permission - echo "DEBUG: copying scripts to accessible location" - script="$TMP/$(basename "$0")" - cp "$0" "config.sh" "$TMP" - chown -R "$IMMICH_USER:$IMMICH_GROUP" "$TMP" - - # Re-run as immich user - sudo -u "$IMMICH_USER" -- "$script" - exit -fi - -echo "INFO: building immich" - -# Clone the remote repository -echo "INFO: cloning immich repo" -git clone --depth 1 --branch "$TAG" https://github.com/immich-app/immich "$TMP" -cd "$TMP" - -# Build the server backend -echo "INFO: building the server" -cd server -npm ci -npm run build -npm prune --omit=dev --omit=optional -cd - - -echo "INFO: building open-api" -cd open-api/typescript-sdk -npm ci -npm run build -cd - - -# Build the web frontend -echo "INFO: building web" -cd web -npm ci -npm run build -cd - - -# Copy application to the installation directory -echo "INFO: copying to destination directory" -rm -rf "$IMMICH_APP_DIR" -mkdir -p "$IMMICH_APP_DIR" -echo "{}" > "$IMMICH_APP_DIR/build-lock.json" -cp -a server/node_modules server/dist server/bin "$IMMICH_APP_DIR/" -cp -a web/build "$IMMICH_APP_DIR/www" -cp -a server/resources server/package.json server/package-lock.json "$IMMICH_APP_DIR/" -cp -a server/start*.sh "$IMMICH_APP_DIR/" -cp -a LICENSE "$IMMICH_APP_DIR/" - -cd "$IMMICH_APP_DIR" -# v1.108.0 and above now loads geodata using IMMICH_BUILD_DATA env var, which appears to also -# be used in other places -ln -sf resources/* . -npm cache clean --force -npm install --os=darwin --cpu=arm64 sharp -cd - - -# Build the machine learning backend -echo "INFO building machine learning" -alias python3=python3.11 -alias pip3=pip3.11 -mkdir -p "$IMMICH_APP_DIR/machine-learning" -python3 -m venv "$IMMICH_APP_DIR/machine-learning/venv" -( - # Set up venv inside subshell - # shellcheck disable=SC1091 - . "$IMMICH_APP_DIR/machine-learning/venv/bin/activate" - pip3 install poetry - cd machine-learning - poetry install --no-root --with dev --with cpu || python3 -m pip install onnxruntime -) -cp -a machine-learning/ann machine-learning/app "$IMMICH_APP_DIR/machine-learning/" - -ln -sf "$IMMICH_INSTALL_DIR/app/resources" "$IMMICH_INSTALL_DIR/" -mkdir -p "$IMMICH_INSTALL_DIR/cache" -sed -i "" -e "s|\"/cache\"|\"$IMMICH_INSTALL_DIR/cache\"|g" "$IMMICH_APP_DIR/machine-learning/app/config.py" -npm install sharp - -# Install GeoNames -cd "$IMMICH_INSTALL_DIR/app/resources" -wget -o - https://download.geonames.org/export/dump/admin1CodesASCII.txt & -wget -o - https://download.geonames.org/export/dump/admin2Codes.txt & -wget -o - https://download.geonames.org/export/dump/cities500.zip & -wget -o - https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson/ne_10m_admin_0_countries.geojson & -wait -unzip cities500.zip -rm cities500.zip -date -Iseconds | tr -d "\n" > geodata-date.txt -ln -s "$IMMICH_INSTALL_DIR/app/resources" "$IMMICH_INSTALL_DIR/app/geodata" - -# Set up upload directory -mkdir -p "$IMMICH_INSTALL_DIR/upload" -ln -s "$IMMICH_INSTALL_DIR/upload" "$IMMICH_APP_DIR/" -ln -s "$IMMICH_INSTALL_DIR/upload" "$IMMICH_APP_DIR/machine-learning/" - -# Create custom start scripts -cat < "$IMMICH_APP_DIR/start.sh" -#!/bin/sh - -set -eu - -set -a -IMMICH_PORT="3001" -. "$IMMICH_INSTALL_DIR/env" -set +a - -cd "$IMMICH_APP_DIR" -exec node "$IMMICH_APP_DIR/dist/main" "\$@" -EOF - -cat < "$IMMICH_APP_DIR/machine-learning/start.sh" -#!/bin/sh - -set -eu - -set -a -. "$IMMICH_INSTALL_DIR/env" -set +a - -cd "$IMMICH_APP_DIR/machine-learning" -. venv/bin/activate - -: "\${MACHINE_LEARNING_HOST:=127.0.0.1}" -: "\${MACHINE_LEARNING_PORT:=3003}" -: "\${MACHINE_LEARNING_WORKERS:=1}" -: "\${MACHINE_LEARNING_WORKER_TIMEOUT:=120}" - -exec gunicorn app.main:app \\ - -k app.config.CustomUvicornWorker \\ - -w "\$MACHINE_LEARNING_WORKERS" \\ - -b "\$MACHINE_LEARNING_HOST:\$MACHINE_LEARNING_PORT" \\ - -t "\$MACHINE_LEARNING_WORKER_TIMEOUT" \\ - --log-config-json log_conf.json \\ - --graceful-timeout 0 -EOF - -cat < "$IMMICH_INSTALL_DIR/env" -# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables - -# Connection secret for postgres. You should change it to a random password -DB_PASSWORD="$DB_PASSWORD" - -# The values below this line do not need to be changed -################################################################################### -NODE_ENV="production" - -DB_USERNAME="immich" -DB_DATABASE_NAME="immich" -DB_VECTOR_EXTENSION="pgvector" - -IMMICH_BUILD_DATA="$IMMICH_INSTALL_DIR/app" - -# The location where your uploaded files are stored -UPLOAD_LOCATION="./library" - -# The Immich version to use. You can pin this to a specific version like "v1.71.0" -IMMICH_VERSION="release" - -# Hosts & ports -DB_HOSTNAME="127.0.0.1" -MACHINE_LEARNING_HOST="127.0.0.1" -IMMICH_MACHINE_LEARNING_URL="http://127.0.0.1:3003" -REDIS_HOSTNAME="127.0.0.1" -EOF - -chmod 700 "$IMMICH_APP_DIR/start.sh" -chmod 700 "$IMMICH_APP_DIR/machine-learning/start.sh" - -# Cleanup -rm -rf "$TMP" diff --git a/Scripts/config.env b/Scripts/config.env new file mode 100644 index 0000000..acaab4a --- /dev/null +++ b/Scripts/config.env @@ -0,0 +1,10 @@ +IMMICH_TAG="v1.121.0" + +IMMICH_INSTALL_DIR="/opt/immich" +IMMICH_SETTINGS_DIR="$IMMICH_INSTALL_DIR/etc" +IMMICH_APP_DIR="$IMMICH_INSTALL_DIR/share" +IMMICH_MEDIA_DIR="$IMMICH_INSTALL_DIR/var/db" + +IMMICH_HOME_DIR="$IMMICH_INSTALL_DIR/home" +IMMICH_USER="immich" +IMMICH_GROUP="immich" diff --git a/Scripts/config.sh b/Scripts/config.sh deleted file mode 100755 index 0e42fc1..0000000 --- a/Scripts/config.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -export TAG="v1.121.0" - -export IMMICH_INSTALL_DIR="/opt/immich" -export IMMICH_APP_DIR="$IMMICH_INSTALL_DIR/app" -export IMMICH_HOME_DIR="/opt/immich/home" - -export IMMICH_USER="immich" -export IMMICH_GROUP="immich" diff --git a/Scripts/configureimmich.sh b/Scripts/configureimmich.sh new file mode 100755 index 0000000..7a5905a --- /dev/null +++ b/Scripts/configureimmich.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +set -eux + +# Create logs directory +mkdir -p /var/log/immich +chown -R "$IMMICH_USER:$IMMICH_GROUP" /var/log/immich + +# Create media directory +mkdir -p "$IMMICH_MEDIA_DIR" + +# Create custom start scripts +mkdir -p "$IMMICH_SETTINGS_DIR" +cat < "$IMMICH_APP_DIR/start.sh" +#!/bin/sh +set -eu +set -a +. "$IMMICH_SETTINGS_DIR/immich_server.env" +set +a +cd "$IMMICH_APP_DIR" +exec node ./dist/main "\$@" +EOF +chmod 700 "$IMMICH_APP_DIR/start.sh" + +cat < "$IMMICH_APP_DIR/machine-learning/start.sh" +#!/bin/sh +set -eu +set -a +. "$IMMICH_SETTINGS_DIR/immich_server.env" +set +a +cd "$IMMICH_APP_DIR/machine-learning" +. ./venv/bin/activate +: "\${MACHINE_LEARNING_HOST:=127.0.0.1}" +: "\${MACHINE_LEARNING_PORT:=3003}" +: "\${MACHINE_LEARNING_WORKERS:=1}" +: "\${MACHINE_LEARNING_WORKER_TIMEOUT:=120}" +exec gunicorn app.main:app \\ + -k app.config.CustomUvicornWorker \\ + -w "\$MACHINE_LEARNING_WORKERS" \\ + -b "\$MACHINE_LEARNING_HOST:\$MACHINE_LEARNING_PORT" \\ + -t "\$MACHINE_LEARNING_WORKER_TIMEOUT" \\ + --log-config-json log_conf.json \\ + --graceful-timeout 0 +EOF +chmod 700 "$IMMICH_APP_DIR/machine-learning/start.sh" + +if [ ! -f "$IMMICH_SETTINGS_DIR/immich_server.env" ]; then + cp "$IMMICH_SETTINGS_DIR/build_info.env" "$IMMICH_SETTINGS_DIR/immich_server.env" + cat <> "$IMMICH_SETTINGS_DIR/immich_server.env" +# Network binding +IMMICH_HOST="0.0.0.0" +IMMICH_PORT="2283" + +# Production settings +NO_COLOR="false" +NODE_ENV="production" +IMMICH_ENV="production" + +# Paths configuration +IMMICH_MEDIA_LOCATION="$IMMICH_MEDIA_DIR" +IMMICH_BUILD_DATA="$IMMICH_APP_DIR/build" + +# Database connection +DB_HOSTNAME="localhost" +DB_USERNAME="immich" +DB_DATABASE_NAME="immich" +DB_PASSWORD="$POSTGRES_PASSWORD" +DB_VECTOR_EXTENSION="pgvector" + +# Redis connection +REDIS_HOSTNAME="localhost" +EOF +fi + +# Adjust permissions +chown -R "$IMMICH_USER:$IMMICH_GROUP" "$IMMICH_INSTALL_DIR" diff --git a/Scripts/configurepostgres.sh b/Scripts/configurepostgres.sh index ca09ede..d3bd63c 100755 --- a/Scripts/configurepostgres.sh +++ b/Scripts/configurepostgres.sh @@ -13,8 +13,8 @@ fi DB_PASSWORD="$1" psql-17 postgres << EOF -create database immich; -create user immich with encrypted password '$DB_PASSWORD'; -grant all privileges on database immich to immich; +CREATE DATABASE immich; +CREATE USER immich WITH ENCRYPTED PASSWORD '$DB_PASSWORD'; +GRANT ALL PRIVILEGES ON DATABASE immich TO immich; ALTER USER immich WITH SUPERUSER; EOF diff --git a/Scripts/createpaths.sh b/Scripts/createpaths.sh deleted file mode 100755 index 6b4665a..0000000 --- a/Scripts/createpaths.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/sh - -set -eux - -echo "INFO: create paths" - -mkdir -p "$IMMICH_INSTALL_DIR" -chown -R "$IMMICH_USER:$IMMICH_GROUP" "$IMMICH_INSTALL_DIR" -mkdir -p /var/log/immich -chown -R "$IMMICH_USER:$IMMICH_GROUP" /var/log/immich diff --git a/Scripts/createuser.sh b/Scripts/createuser.sh index 051c1a7..bf7ed68 100755 --- a/Scripts/createuser.sh +++ b/Scripts/createuser.sh @@ -4,7 +4,7 @@ set -eux echo "INFO: create user" -if dscl . -list /Users/immich > /dev/null 2>&1; then +if dscl . -list "/Users/$IMMICH_USER" > /dev/null 2>&1; then # User already exists exit fi diff --git a/Scripts/installdaemons.sh b/Scripts/installdaemons.sh index d7b22e8..68d9890 100755 --- a/Scripts/installdaemons.sh +++ b/Scripts/installdaemons.sh @@ -4,5 +4,8 @@ set -eux echo "INFO: install daemons" +launchctl bootout system /Library/LaunchDaemons/com.immich.plist || true +launchctl bootout system /Library/LaunchDaemons/com.immich.machine.learning.plist || true + launchctl bootstrap system /Library/LaunchDaemons/com.immich.plist launchctl bootstrap system /Library/LaunchDaemons/com.immich.machine.learning.plist diff --git a/Scripts/postinstall b/Scripts/postinstall index e434562..88d27b5 100755 --- a/Scripts/postinstall +++ b/Scripts/postinstall @@ -2,8 +2,10 @@ set -eux +set -a # shellcheck disable=SC1091 -. "$(pwd)/config.sh" +. "$(pwd)/config.env" +set +a # Configure logging install_logfile="/tmp/immich-install.log" @@ -14,11 +16,8 @@ echo "Running postinstall as $(whoami)" # Create immich user "$(pwd)/createuser.sh" -# Create required directories -"$(pwd)/createpaths.sh" - # Build Immich application -"$(pwd)/build.sh" +"$(pwd)/configureimmich.sh" # Install Launchd daemons "$(pwd)/installdaemons.sh" diff --git a/Scripts/preinstall b/Scripts/preinstall index 38a6651..53e0d6a 100755 --- a/Scripts/preinstall +++ b/Scripts/preinstall @@ -3,13 +3,15 @@ set -eux # Generate database password -DB_PASSWORD="$(uuidgen)" -if ! grep -q "^export DB_PASSWORD=" "$(pwd)/config.sh"; then - echo "export DB_PASSWORD=\"$DB_PASSWORD\"" >> "$(pwd)/config.sh" +POSTGRES_PASSWORD="$(dd if=/dev/urandom bs=1 count=100 status=none | md5 -q)" +if ! grep -q "^POSTGRES_PASSWORD" "$(pwd)/config.env"; then + echo "POSTGRES_PASSWORD=\"$POSTGRES_PASSWORD\"" >> "$(pwd)/config.env" fi +set -a # shellcheck disable=SC1091 -. "$(pwd)/config.sh" +. "$(pwd)/config.env" +set +a # Configure logging install_logfile="/tmp/immich-install.log" @@ -21,7 +23,7 @@ echo "Running preinstall as $(whoami)" # Install dependencies "$(pwd)/installdependencies.sh" homebrew_bindir="$(su -l "$USER" -c "which brew" | xargs -I {} dirname "{}")" -echo "export PATH=\"$homebrew_bindir:\$PATH\"" >> "$(pwd)/config.sh" +echo "PATH=\"$homebrew_bindir:\$PATH\"" >> "$(pwd)/config.env" # Configure database -"$(pwd)/configurepostgres.sh" "$DB_PASSWORD" +"$(pwd)/configurepostgres.sh" "$POSTGRES_PASSWORD" diff --git a/build.sh b/build.sh deleted file mode 100755 index 2fb4861..0000000 --- a/build.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -eux - -# shellcheck disable=SC1091 -. ./Scripts/config.sh - -rm -rf "dist/$TAG" -mkdir -p "dist/$TAG" -pkgbuild \ - --version "$TAG" \ - --root LaunchDaemons \ - --identifier com.unofficial.immich.installer \ - --scripts Scripts \ - --install-location "/Library/LaunchDaemons" \ - "dist/$TAG/Unofficial Immich Installer.pkg" - -# need to increase script timeouts -cd "dist/$TAG" -pkgutil --expand Unofficial\ Immich\ Installer.pkg contents -sed -i ".bak" -e 's/600/3600/g' contents/PackageInfo -rm -f contents/Bom contents/*.bak -mkbom contents contents/Bom -pkgutil --flatten contents Unofficial\ Immich\ Installer.pkg -rm -rf contents -cd - diff --git a/build_pkg.sh b/build_pkg.sh new file mode 100755 index 0000000..c4fbe3b --- /dev/null +++ b/build_pkg.sh @@ -0,0 +1,146 @@ +#!/bin/sh + +set -eu + +if [ "${1:-}" = "-v" ]; then + set -x +fi + +clone_immich() { + git_tag="$1" + repo_dir="$2" + conf_dir="$3" + + # Clone the repository + if [ ! -d "$repo_dir" ]; then + git clone --depth 1 --branch "$git_tag" https://github.com/immich-app/immich "$repo_dir" + fi + + # Dump information about the build revision + mkdir -p "$conf_dir" + cat < "$conf_dir/build_info.env" +# Build information +IMMICH_BUILD="" +IMMICH_BUILD_URL="" +IMMICH_BUILD_IMAGE="" +IMMICH_BUILD_IMAGE_URL="" +IMMICH_REPOSITORY="immich-app/immich" +IMMICH_REPOSITORY_URL="$(git -C "$repo_dir" remote get-url origin)" +IMMICH_SOURCE_REF="" +IMMICH_SOURCE_COMMIT="$(git -C "$repo_dir" rev-parse HEAD)" +IMMICH_SOURCE_URL="" + +EOF +} + +build_immich() { + repo_dir="$1" + dest_dir="$2" + + # Build server backend + cp -R "$repo_dir/server/" "$dest_dir/" + cd "$dest_dir" + npm ci --foreground-scripts + npm run build + npm prune --omit=dev --omit=optional + npm install --os=darwin --cpu=arm64 sharp + cd - + + # Build web frontend + mkdir -p "$dest_dir/open-api" + cp -R "$repo_dir/open-api/typescript-sdk" "$dest_dir/open-api/" + cp -R "$repo_dir/i18n" "$dest_dir" + npm --prefix "$dest_dir/open-api/typescript-sdk" ci + npm --prefix "$dest_dir/open-api/typescript-sdk" run build + npm --prefix "$dest_dir/open-api/typescript-sdk" prune --omit=dev --omit=optional + cp -R "$repo_dir/web" "$dest_dir/" + rm "$dest_dir/web/package-lock.json" + npm --prefix "$dest_dir/web" install --foreground-scripts + npm --prefix "$dest_dir/web" install --os=darwin --cpu=arm64 sharp + npm --prefix "$dest_dir/web" run build + npm --prefix "$dest_dir/web" prune --omit=dev --omit=optional + mkdir "$dest_dir/build" + mv "$dest_dir/web/build" "$dest_dir/build/www" + rm -rf "$dest_dir/open-api" "$dest_dir/i18n" "$dest_dir/web" + + # Generate empty build lockfile + echo "{}" > "$dest_dir/build/build-lock.json" +} + +build_immich_machine_learning() { + repo_dir="$1" + dest_dir="$2" + + # Build the machine learning backend + cp -R "$repo_dir/machine-learning" "$dest_dir/" + cd "$dest_dir/machine-learning" + python3.11 -m venv "./venv" + ( + # shellcheck disable=SC1091 + . "./venv/bin/activate" + pip3.11 install poetry + poetry install --no-root --with dev --with cpu + ) + cd - +} + +fetch_immich_geodata() { + dest_dir="$1" + + # Download geodata + mkdir -p "$dest_dir/build/geodata" + curl -o "$dest_dir/build/geodata/cities500.zip" https://download.geonames.org/export/dump/cities500.zip + unzip "$dest_dir/build/geodata/cities500.zip" -d "$dest_dir/build/geodata" && rm "$dest_dir/build/geodata/cities500.zip" + curl -o "$dest_dir/build/geodata/admin1CodesASCII.txt" https://download.geonames.org/export/dump/admin1CodesASCII.txt + curl -o "$dest_dir/build/geodata/admin2Codes.txt" https://download.geonames.org/export/dump/admin2Codes.txt + curl -o "$dest_dir/build/geodata/ne_10m_admin_0_countries.geojson" https://raw.githubusercontent.com/nvkelso/natural-earth-vector/v5.1.2/geojson/ne_10m_admin_0_countries.geojson + date -u +"%Y-%m-%dT%H:%M:%S%z" | tr -d "\n" > "$dest_dir/build/geodata/geodata-date.txt" + chmod -R 444 "$dest_dir/build/geodata"/* +} + +create_pkg() { + root_dir="$1" + out_pkg="$2" + + # Create PKG installer + pkgbuild \ + --version "$IMMICH_TAG" \ + --root "$root_dir" \ + --identifier com.unofficial.immich.installer \ + --scripts ./Scripts \ + --install-location "/" \ + "$out_pkg" +} + +# Load configuration environment +set -a +# shellcheck disable=SC1091 +. ./Scripts/config.env +set +a +pkg_filename="Unofficial Immich Installer $IMMICH_TAG.pkg" + +# Create staging directories +output_dir="./output" +staging_dir="$output_dir/staging" +root_dir="$staging_dir/root" +dist_dir="$root_dir/$IMMICH_APP_DIR" +conf_dir="$root_dir/$IMMICH_SETTINGS_DIR" +rm -rf "$staging_dir" +mkdir -p "$staging_dir" "$root_dir" "$dist_dir" "$output_dir" + +# Build Immich from source +clone_immich "$IMMICH_TAG" "$staging_dir/immich" "$conf_dir" +build_immich "$staging_dir/immich" "$dist_dir" +build_immich_machine_learning "$staging_dir/immich" "$dist_dir" +fetch_immich_geodata "$dist_dir" + +# Copy PKG resources +mkdir -p "$root_dir/Library/LaunchDaemons" +find ./launchd -type f -name "*.plist" | while read -r f; do + filename="$(basename "$f")" + envsubst < "$f" > "$root_dir/Library/LaunchDaemons/$filename" +done + +# Create PKG installer +create_pkg "$root_dir" "$output_dir/$pkg_filename" +echo "macOS installer created successfully in $output_dir/$pkg_filename" diff --git a/LaunchDaemons/com.immich.machine.learning.plist b/launchd/com.immich.machine.learning.plist similarity index 88% rename from LaunchDaemons/com.immich.machine.learning.plist rename to launchd/com.immich.machine.learning.plist index ca1a484..df00408 100644 --- a/LaunchDaemons/com.immich.machine.learning.plist +++ b/launchd/com.immich.machine.learning.plist @@ -7,10 +7,10 @@ RunAtLoad UserName - immich + ${IMMICH_USER} ProgramArguments - /opt/immich/app/machine-learning/start.sh + ${IMMICH_APP_DIR}/machine-learning/start.sh EnvironmentVariables diff --git a/LaunchDaemons/com.immich.plist b/launchd/com.immich.plist similarity index 89% rename from LaunchDaemons/com.immich.plist rename to launchd/com.immich.plist index c6da290..dd4cc1f 100644 --- a/LaunchDaemons/com.immich.plist +++ b/launchd/com.immich.plist @@ -7,10 +7,10 @@ RunAtLoad UserName - immich + ${IMMICH_USER} ProgramArguments - /opt/immich/app/start.sh + ${IMMICH_APP_DIR}/start.sh EnvironmentVariables diff --git a/uninstall.sh b/uninstall.sh index ce2b661..19abd53 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -3,7 +3,7 @@ set -eux # shellcheck disable=SC1091 -. ./Scripts/config.sh +. Scripts/config.env delete_user() { echo "INFO: deleting user"