Давно хотел сменить домашний роутер, но пока провайдерский работал, не находил повода тратиться на это. А тут внезапно "1Тб навсегда" от Мэйла превратился в тыкву. Да и недоступность Яндекс.Диск доставляет некоторые неудобства, хоть и винить за это ребят из Яндекса я не могу. В общем появилась задача максимально бюджетно организовать хранилище фото и не очень важных файлов. Выбор пал на домашний сетап: роутер Netcraze Hopper + обычный внешний диск с usb3 (в качестве диска Adata HD710 Pro на 4тб). Поднять nextcloud вроде бы не рокет сайнс, но, учитывая ограничения окружения, были свои нюансы, о которых я расскажу в данном гайде.
Вводные требования
Бюджетно
Есть мобильное приложение
Синхронизация фото
Возможность шарить доступ между пользователями
Хранение данных на fs без лишних абстракций (например без s3)
Опционально: доступ из интернета
Подготовка диска
Для меня важна возможность в любой момент воткнуть диск в ПК с виндой, поэтому разбивка получилась следующая:
- swap: 2гб
- ext4: 25гб (под entware)
- ntfs: всё остальное пространство (хранилище nextcloud и smb шара)
Установка Entware хорошо описана в официальной документации, поэтому останавливаться на этом не буду.
Теперь установим необходимые пакеты для nextcloud
Если хочется генерировать превью для видео, нужен ffmpeg.
opkg update opkg install ffmpeg libffmpegthumbnailer
Ставим nginx + php-fpm
На текущий момент в entware версия php 8.4, поэтому проблем с запуском nextvloud 35 не будет.
opkg install bzip2 curl glib2 gnupg nginx-mod-headers-more nginx-ssl imagemagick php8 php8-cgi php8-cli php8-fpm php8-mod-ctype php8-mod-curl php8-mod-dom php8-mod-exif php8-mod-fileinfo php8-mod-filter php8-mod-gd php8-mod-iconv php8-mod-intl php8-mod-mbstring php8-mod-opcache php8-mod-openssl php8-mod-pcntl php8-mod-pdo php8-mod-pdo-sqlite php8-mod-session php8-mod-simplexml php8-mod-sodium php8-mod-sqlite3 php8-mod-xml php8-mod-xmlreader php8-mod-xmlwriter php8-mod-zip php8-pecl-apcu php8-pecl-imagick libopenssl libopenssl-conf wget-ssl zlib zlib-dev jq
Подготовим конфиги
/opt/etc/php.ini
[PHP] engine = On precision = 12 output_buffering = Off zlib.output_compression = Off implicit_flush = Off unserialize_callback_func = serialize_precision = 100 disable_functions = disable_classes = expose_php = Off max_execution_time = 3600 max_input_time = 3600 memory_limit = 128M error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED display_errors = Off display_startup_errors = Off log_errors = Off ignore_repeated_errors = Off ignore_repeated_source = Off report_memleaks = On track_errors = Off variables_order = "EGPCS" request_order = "GP" register_argc_argv = On auto_globals_jit = On post_max_size = 10000M auto_prepend_file = auto_append_file = default_mimetype = "text/html" doc_root = "/opt/share/www" user_dir = extension_dir = "/opt/lib/php8" enable_dl = On cgi.fix_pathinfo = 1 file_uploads = On upload_tmp_dir = "/opt/tmp" upload_max_filesize = 10000M max_file_uploads = 20 allow_url_fopen = On allow_url_include = Off default_socket_timeout = 60 opcache.enable=1 opcache.memory_consumption=64 opcache.interned_strings_buffer=8 opcache.max_accelerated_files=20000 opcache.save_comments=1 opcache.revalidate_freq=60 apc.enabled=1 apc.shm_size=32M apc.enable_cli=0
/opt/etc/php8-fpm.d/www.conf
запускаем от рута, потому что иначе будут проблемы с чтением файлов с ntfs, на котором лежит рабочая директория nextcloud вместе с базой sqlite
[www] user = root group = root listen = /opt/var/run/php8-fpm.sock listen.owner = root listen.group = root listen.mode = 0660 pm = ondemand pm.max_children = 2 pm.process_idle_timeout = 30s; pm.max_requests = 200 php_flag[display_errors] = off php_admin_value[error_log] = /opt/var/log/fpm-php.www.log php_admin_flag[log_errors] = on php_admin_value[memory_limit] = 128M
/opt/etc/nginx/nginx.conf
так же от рута, чтобы читать сокет php-fpm
user root; worker_processes 1; load_module /opt/lib/nginx/modules/ngx_http_headers_more_filter_module.so; events { worker_connections 64; } http { server_tokens off; include mime.types; default_type application/octet-stream; include conf.d/*; }
/opt/etc/nginx/conf.d/nextcloud.conf
почти стандартный из документации, с небольшими доработками
upstream php-handler { server unix:/opt/var/run/php8-fpm.sock; } map $arg_v $asset_immutable { "" ""; default ", immutable"; } server { listen 127.0.0.1:81; server_name <домен>; server_tokens off; more_clear_headers Server; root /opt/share/www; client_max_body_size 10240M; client_body_timeout 300s; client_body_buffer_size 512k; fastcgi_buffers 64 4K; keepalive_timeout 60s; send_timeout 300s; fastcgi_connect_timeout 60s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; gzip on; gzip_vary on; gzip_comp_level 4; gzip_min_length 256; gzip_proxied expired no-cache no-store private no_last_modified no_etag auth; gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy; add_header Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; add_header X-Robots-Tag "noindex, nofollow" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-XSS-Protection "0" always; add_header Permissions-Policy "accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always; add_header Cross-Origin-Opener-Policy "same-origin-allow-popups" always; add_header Cross-Origin-Resource-Policy "same-site" always; add_header X-DNS-Prefetch-Control "off" always; fastcgi_hide_header X-Powered-By; include mime.types; types { text/javascript mjs; } index index.php index.html /index.php$request_uri; location = / { if ($http_user_agent ~ ^DavClnt) { return 302 /remote.php/webdav/$is_args$args; } } location = /robots.txt { allow all; log_not_found off; access_log off; } location ^~ /.well-known { location = /.well-known/carddav { return 301 /remote.php/dav/; } location = /.well-known/caldav { return 301 /remote.php/dav/; } location /.well-known/acme-challenge { try_files $uri $uri/ =404; } location /.well-known/pki-validation { try_files $uri $uri/ =404; } return 301 /index.php$request_uri; } location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; } location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; } location ~ ^/(?:composer\.(?:json|lock)|package(?:-lock)?\.json|core/shipped\.json)$ { return 404; } location ~ \.php(?:$|/) { rewrite ^/(?!index|remote|public|cron|status|ocs\/v[12]|ocs-provider\/.+|core\/ajax\/update|updater\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri; fastcgi_split_path_info ^(.+?\.php)(/.*)$; set $path_info $fastcgi_path_info; try_files $fastcgi_script_name =404; include fastcgi_params; fastcgi_pass php-handler; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $path_info; fastcgi_param HTTPS on; fastcgi_param modHeadersAvailable true; fastcgi_param front_controller_active true; # The Keenetic nginx cuts these request headers. fastcgi_param HTTP_ORIGIN "https://<домен>"; fastcgi_param HTTP_REFERER "https://<домен>/"; fastcgi_intercept_errors on; fastcgi_request_buffering on; fastcgi_max_temp_file_size 0; } location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|heic|png|webp|wasm|tflite|map|ogg|flac|mp4|webm)$ { try_files $uri /index.php$request_uri; add_header Cache-Control "public, max-age=15778463$asset_immutable"; add_header Referrer-Policy "no-referrer" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Permitted-Cross-Domain-Policies "none" always; add_header X-Robots-Tag "noindex, nofollow" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-XSS-Protection "0" always; add_header Permissions-Policy "accelerometer=(), camera=(), display-capture=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always; add_header Cross-Origin-Opener-Policy "same-origin-allow-popups" always; add_header Cross-Origin-Resource-Policy "same-site" always; add_header X-DNS-Prefetch-Control "off" always; access_log off; } location ~ \.(?:otf|woff2?)$ { try_files $uri /index.php$request_uri; expires 7d; access_log off; } location /remote { return 301 /remote.php$request_uri; } location / { try_files $uri $uri/ /index.php$request_uri; } }
Вот тут первый важный нюанс. В конфиге nginx самого роутера для пользовательких приложений в location добавляется следующее
proxy_set_header Referer ""; proxy_set_header Origin ""; proxy_set_header Host "127.0.0.1";
В связи с этим при попытке залогиниться будем получать “303 See Other” с редиректом на “/index.php/login?user=<юзернейм>&direct=1”, потому что в коде nextcloud есть проверка на origin
Поэтому в location с php нужно добавить восстановление этих хедеров.
# The Keenetic nginx cuts these request headers. fastcgi_param HTTP_ORIGIN "https://<домен>"; fastcgi_param HTTP_REFERER "https://<домен>/";
Да, это не очень безопасно, ибо как минимум пропустит запрос с любой вкладки любого другого сайта. Но более элегантного решения, чем такой костыль, я не нашёл. Если знаете такое, буду рад прочитать в комментах.
Ставим nextcloud
cd /opt/root mkdir -p /opt/share/www curl https://download.nextcloud.com/server/releases/nextcloud-35.0.2.tar.bz2 --output nextcloud-35.0.2.tar.bz2 bzip2 -d nextcloud-35.0.2.tar.bz2 tar -xv nextcloud-35.0.2.tar.bz2 -C /opt/share/www/
Стартуем сервисы
/opt/etc/init.d/S79php8-fpm start /opt/etc/init.d/S80nginx start
Базовая настройка nextcloud
cd /opt/share/www php8-cli occ maintenance:install --database=sqlite --admin-user=<админ_юзер> --admin-pass='<админ_пароль>' --data-dir='/tmp/mnt/<ид_диска>/nextcloud' php8-cli occ config:system:set overwrite.cli.url --value="https://<домен>" php8-cli occ config:system:set overwritehost --value=<домен> php8-cli occ config:system:set overwriteprotocol --value=https php8-cli occ config:system:set trusted_domains --type json --value '["localhost","127.0.0.1","192.168.1.1","<домен>"]' php8-cli occ config:system:set memcache.local --value='\OC\Memcache\APCu' # переключаем фоновые задачи на запуск по cron php8-cli occ background:cron # и добавим в cron */5 * * * * /opt/bin/php8-cli -f /opt/share/www/cron.php # если нужны превью php8-cli occ config:system:set enable_previews --value true php8-cli occ config:system:set preview_max_memory --value 128 php8-cli occ config:system:set preview_max_x --value 1080 php8-cli occ config:system:set preview_max_y --value 1920 php8-cli occ config:system:set preview_concurrency_new --value 1 php8-cli occ config:system:set preview_concurrency_all --value 2 # и превью каких файлов хотим видеть (при передаче настройки массивом было некорректное экранирование) php8-cli occ config:system:set enabledPreviewProviders 0 --value 'OC\Preview\JPEG' php8-cli occ config:system:set enabledPreviewProviders 1 --value 'OC\Preview\PNG' php8-cli occ config:system:set enabledPreviewProviders 2 --value 'OC\Preview\GIF' php8-cli occ config:system:set enabledPreviewProviders 3 --value 'OC\Preview\BMP' php8-cli occ config:system:set enabledPreviewProviders 4 --value 'OC\Preview\XBitmap' php8-cli occ config:system:set enabledPreviewProviders 5 --value 'OC\Preview\HEIC' php8-cli occ config:system:set enabledPreviewProviders 6 --value 'OC\Preview\Movie' php8-cli occ config:system:set enabledPreviewProviders 7 --value 'OC\Preview\PDF' php8-cli occ config:system:set enabledPreviewProviders 8 --value 'OC\Preview\MP4' php8-cli occ config:system:set enabledPreviewProviders 9 --value 'OC\Preview\AVI' php8-cli occ config:system:set enabledPreviewProviders 10 --value 'OC\Preview\MOV' php8-cli occ config:system:set enabledPreviewProviders 11 --value 'OC\Preview\WebP' php8-cli occ config:system:set enabledPreviewProviders 12 --value 'OC\Preview\JPG' # если ставили ffmpeg php8-cli occ config:system:set preview_ffmpeg_path --value="/opt/bin/ffmpeg" # если хотим предварительно генерить превьюшки php8-cli occ app:install previewgenerator php8-cli occ config:app:set --value="32 64 256 1024 1920" previewgenerator squareSizes php8-cli occ config:app:set --value="64 128 1024 1080 2030" previewgenerator widthSizes php8-cli occ config:app:set --value="64 256 1024 1080 1920" previewgenerator heightSizes # и добавим в cron. Но если планируете залить большое количество фоток после установки, лучше добавить после этого */15 * * * * /opt/bin/php8-cli /opt/share/www/occ preview:pre-generate # лишние приложения nextcloud можно выключить, чтобы убрать дополнительную нагрузку php8-cli occ app:disable <appname> # для себя определил эти # Disabled: # - activity: 8.0.0 (installed 8.0.0) # - admin_audit: 2.0.0 # - app_api: 35.0.0 (installed 35.0.0) # - circles: 35.0.0 (installed 35.0.0) # - comments: 2.0.0 (installed 2.0.0) # - contactsinteraction: 2.0.0 (installed 2.0.0) # - dashboard: 8.0.0 (installed 8.0.0) # - encryption: 3.0.1 # - federation: 2.0.0 (installed 2.0.0) # - files_downloadlimit: 5.3.0 (installed 5.3.0) # - files_external: 2.0.0 # - files_lock: 35.0.1 (installed 35.0.1) # - files_reminders: 2.0.0 (installed 2.0.0) # - files_versions: 2.0.0 (installed 2.0.0) # - firstrunwizard: 8.0.0 (installed 8.0.0) # - nextcloud_announcements: 7.0.0 (installed 7.0.0) # - office: 1.1.0 (installed 1.1.0) # - recommendations: 8.0.0 (installed 8.0.0) # - sharebymail: 2.0.0 (installed 2.0.0) # - support: 7.0.0 (installed 7.0.0) # - survey_client: 7.0.0 (installed 7.0.0) # - suspicious_login: 13.0.0 # - systemtags: 2.0.0 (installed 2.0.0) # - text: 9.0.1 (installed 9.0.1) # - twofactor_nextcloud_notification: 9.0.0 # - updatenotification: 2.0.0 (installed 2.0.0) # - user_ldap: 2.0.0 # - user_status: 2.0.0 (installed 2.0.0) # - weather_status: 2.0.0 (installed 2.0.0) # - webhook_listeners: 2.0.0 (installed 2.0.0)
Массовая генерация превью
php8-cli occ preview:generate-all -w 1 --path="/<username>/files/Photos" -vvv
Также массовую генерацию можно делегировать более мощному железу, например, своему ПК. Я использовал WSL c debian13, чтобы не доставать рабочий комп во время отпуска.
генерация превью в wsl
# cтавим всё необходимое apt install ffmpeg libffmpegthumbnailer4v5 php8.4 php8.4-apcu php8.4-cli php8.4-common php8.4-curl php8.4-gd php8.4-imagick php8.4-intl php8.4-mbstring php8.4-opcache php8.4-readline php8.4-sqlite3 php8.4-xml php8.4-zip libheif1 rclone # настраиваем remote в rclone (смонтируем smb шару с файлами nextcloud) rclone config # монтируем в тот же путь, что и на роутере (в отдельном окне терминала) rclone mount <название_remote>:/nextcloud /tmp/mnt/<ид_диска>/nextcloud --uid 0 --gid 0 --file-perms 0770 --dir-perms 0770 --vfs-cache-mode writes # копируем /opt/share/www с роутера в wsl по тому же пути, либо распаковываем архив с nextcloud той же версии, что на роутере, главное - не забыть стянуть конфиг с роутера # проверить, что всё получилось cd /opt/share/www php8.4 occ preview:queue-stats # и стартуем генерацию. Так будет гораздо быстрее и без лишней нагрузки на роутер php8.4 occ preview:generate-all --path="/<username>/files/Photos" -vvv
В целом на этом можно было бы и закончить, но у меня зачесалось сгружать не конвертированные в jpeg фотки с телефона, а оригинальный heic. И чтобы preview генерились.
По дефолту - мечта недостижимая, так как imagemagick в репе entware скомпилирован БЕЗ поддержки heic. А отдельно libheif в репе нет, потому опенсорс.
Ну что ж, соберём сами
Нам нужно будет: - собрать pkg-config (https://github.com/pkgconf/pkgconf) - собрать libde265 (https://github.com/strukturag/libde265) - зависимость libheif - собрать libheif (https://github.com/strukturag/libheif) - собрать кодер heif imagemagick - перелинковать собранный декодер heif
Всю сборку делал прямо на роутере, собирать отдельное окружение для сборки было лень. Если решите повторить, тормозите php-fpm и nginx для экономии памяти.
По версиям.
На текущий момент в репе entware лежит GCC/G++ 8.4.0 и ImageMagick 7.1.2-1 Q8.
Настраиваем окружение
opkg update opkg install gcc binutils coreutils make cmake git git-http tar xz libltdl
Создаём новый /opt/bin/gcc_env.sh (тот, что приходит в пакете не совсем правильный).
Для Keenetic Hopper - cortex-a53. Для другой модели с другим процессором, соответственно, нужен соответствующий флаг.
#!/bin/sh export CFLAGS="-O2 -pipe -mcpu=cortex-a53" export CXXFLAGS="-O2 -pipe -mcpu=cortex-a53" export CPPFLAGS="-I/opt/include" export LDFLAGS="-Wl,-rpath=/opt/lib -Wl,--dynamic-linker=/opt/lib/ld-linux-aarch64.so.1 -L/opt/lib"
и даём права на запуск
chmod +x /opt/bin/gcc_env.sh
Перед сборкой для каждой отдельной ssh сессии
unset CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS . /opt/bin/gcc_env.sh
Собираем pkg-config
cd /opt/root git clone --depth 1 https://github.com/pkgconf/pkgconf.git cd pkgconf make -f Makefile.lite -j1 \ CC=/opt/bin/gcc \ SYSTEM_LIBDIR=/lib:/usr/lib \ SYSTEM_INCLUDEDIR=/usr/include \ PKG_DEFAULT_PATH=/opt/lib/pkgconfig:/opt/share/pkgconfig mv pkgconf-lite /opt/bin/pkgconf chmod +x /opt/bin/pkgconf ln -sf /opt/bin/pkgconf /opt/bin/pkg-config
Готовим libltdl
Та, что в репе не имеет dev-файлов (только runtime), необходимых для сборки ImageMagick. Берём той же версии, что и установлена в системе (в моём случае 2.5.4).
Качаем https://ftp.gnu.org/gnu/libtool/libtool-2.5.4.tar.xz перепаковываем в zip и закидываем в /opt/root (в моем случае tar в упор отказывался видеть этот архив как архив)
cd /opt/root unzip libtool-2.5.4.zip cd libtool-2.5.4 mkdir -p /opt/include/libltdl cp libltdl/ltdl.h /opt/include/ltdl.h cp libltdl/libltdl/lt_system.h /opt/include/libltdl/ cp libltdl/libltdl/lt_error.h /opt/include/libltdl/ cp libltdl/libltdl/lt_dlloader.h /opt/include/libltdl/ # смотрим линку ls -l /opt/lib/libltdl.so* # если нет, то создаём (у меня не было) ln -s libltdl.so.7 /opt/lib/libltdl.so
Собираем libde265
В моём случае было достаточно текущей последней версии 1.1.3.
cd /opt/root git clone --depth 1 --branch v1.1.3 https://github.com/strukturag/libde265.git cd libde265 cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/opt \ -DCMAKE_INSTALL_LIBDIR=lib \ -DENABLE_DECODER=OFF \ -DENABLE_SHERLOCK265=OFF cmake --build build --parallel 1 cmake --install build
Собираем libheif
Максимальную версию можем взять 1.18.2, так как GCC 8.4.0, а начиная с libheif 1.19.0 произошёл переход на C++20.
cd /opt/root git clone --depth 1 --branch v1.18.2 https://github.com/strukturag/libheif.git cd libheif export PKG_CONFIG_PATH=/opt/lib/pkgconfig cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_CXX_FLAGS_RELEASE="-O1 -DNDEBUG -g0" \ -DCMAKE_C_FLAGS_RELEASE="-O1 -DNDEBUG -g0" \ -DCMAKE_INSTALL_PREFIX=/opt \ -DCMAKE_INSTALL_LIBDIR=lib \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TESTING=OFF \ -DWITH_EXAMPLES=OFF \ -DWITH_GDK_PIXBUF=OFF \ -DENABLE_PLUGIN_LOADING=OFF \ -DENABLE_MULTITHREADING_SUPPORT=OFF \ -DENABLE_PARALLEL_TILE_DECODING=OFF \ -DWITH_LIBDE265=ON \ -DWITH_LIBDE265_PLUGIN=OFF \ -DWITH_X265=OFF \ -DWITH_KVAZAAR=OFF \ -DWITH_DAV1D=OFF \ -DWITH_AOM_DECODER=OFF \ -DWITH_AOM_ENCODER=OFF \ -DWITH_SvtEnc=OFF \ -DWITH_RAV1E=OFF \ -DWITH_OpenH264_DECODER=OFF \ -DWITH_X264=OFF \ -DWITH_JPEG_DECODER=OFF \ -DWITH_JPEG_ENCODER=OFF \ -DWITH_OpenJPEG_DECODER=OFF \ -DWITH_OpenJPEG_ENCODER=OFF \ -DWITH_FFMPEG_DECODER=OFF \ -DWITH_LIBSHARPYUV=OFF cmake --build build --parallel 1 cmake --install build
Собираем кодер heic
cd /opt/root git clone --depth 1 --branch 7.1.2-1 https://github.com/ImageMagick/ImageMagick.git cd ImageMagick ./configure \ --prefix=/opt \ --disable-cipher \ --disable-docs \ --disable-hdri \ --disable-static \ --enable-dependency-tracking \ --with-modules \ --with-quantum-depth=8 \ --with-heic=yes \ --without-bzlib \ --without-djvu \ --without-fontconfig \ --without-gvc \ --without-jbig \ --without-lcms \ --without-lqr \ --without-lzma \ --without-magick-plus-plus \ --without-openexr \ --without-openjp2 \ --without-pango \ --without-raqm \ --without-raw \ --without-webp \ --without-wmf \ --without-x \ --without-xml \ --without-zstd \ --with-gs-font-dir=/opt/share/fonts/type1/gsfonts \ --with-dejavu-font-dir=/opt/share/fonts/ttf-dejavu make -j1 coders/heic.la # проверим, что всё собралось ls -lh coders/.libs/heic_la-heic.o
Линкуем
Сначала проверим во временной директории
mkdir -p /tmp/im-heic-test cd /opt/root/ImageMagick g++ -shared \ -o /tmp/im-heic-test/heic.so \ coders/.libs/heic_la-heic.o \ /opt/lib/libMagickCore-7.Q8.so.10 \ -L/opt/lib \ -lheif \ -lz \ -lltdl \ -lm \ -lpthread \ -pthread \ -Wl,-rpath,/opt/lib \ -Wl,--no-undefined # и проверим, чтобы не было ссылок на /opt/root/ImageMagick/... readelf -d /tmp/im-heic-test/heic.so | grep -E 'NEEDED|RPATH|RUNPATH' LD_LIBRARY_PATH=/opt/lib ldd /tmp/im-heic-test/heic.so
Создадим файл описания
/tmp/im-heic-test/heic.la
# heic.la - ImageMagick HEIC coder module dlname='heic.so' library_names='heic.so heic.so heic.so' old_library='' inherited_linker_flags=' -pthread' dependency_libs=' /opt/lib/libMagickCore-7.Q8.so.10 -L/opt/lib -lheif -lz -lltdl -lpthread -lm' weak_library_names='' current=0 age=0 revision=0 installed=yes shouldnotlink=yes dlopen='' dlpreopen='' libdir='/tmp/im-heic-test'
Для теста нужно скопировать magick.la
cp /opt/lib/ImageMagick-7.1.2/modules-Q8/coders/magick.la /tmp/im-heic-test/magick.la
И чекнем, подключается ли кодер
MAGICK_CODER_MODULE_PATH=/tmp/im-heic-test magick -list format | grep -Ei 'HEIC|HEIF'
Успешный вывод будет примерно таким
AVCI HEIC --- AVC Image File Format (1.18.2) AVIF HEIC --- AV1 Image File Format (1.18.2) HEIC HEIC r-- High Efficiency Image Format (1.18.2) HEIF HEIC r-- High Efficiency Image Format (1.18.2)
И можем чекнуть на любом реальном heic файле
MAGICK_CODER_MODULE_PATH=/tmp/im-heic-test magick identify /path/to/photo.HEIC
Если всё успешно, можем ставить кодер
cp /tmp/im-heic-test/heic.so /opt/lib/ImageMagick-7.1.2/modules-Q8/coders/heic.so sed "s|/tmp/im-heic-test|/opt/lib/ImageMagick-7.1.2/modules-Q8/coders|g" /tmp/im-heic-test/heic.la cp /tmp/im-heic-test/heic.la /opt/lib/ImageMagick-7.1.2/modules-Q8/coders/heic.la
Если перед сборкой останавливали php-fpm, запускаем и загружаем в nextcloud любую heic фотку, а затем радуемся появлению превью.
Таким образом получаем бюджетное хранилище, доступное для 2-10 пользователей (зависит от выделенных под php ресурсов и скорости диска).
P.S.
Также пробовал:
owncloud (который infinite scale): жрёт ресурсы в простое (в отличии от ondemand воркеров php-fpm), архитектура - моё почтение (куча сервисов в одном бинаре на одном PID)
opencloud (форк первого пункта): аналогично первому пункту, но ест оперативки на 30мб больше
мобильное приложение Pho (которое использует webdav): не совсем то что нужно, так как заливает фото согласно своей структуре (год/месяц/день), настроек нет - кастомизировать не получится
owncloud 10 (до go-шный): не поддерживает php8.4, а тащить из архивов 7ку и заниматься некромантией без докера - желания нет
Поэтому идеальным кандидитом на роль домашнего облака и стал nextcloud.

