InquirerVP Duterte trial: Defense presents Davao City assessor as first witnessThe Jerusalem PostThe most Zionist holiday, and the Jews Israel keeps waiting - opinionPunchKano gov to present 2027 budget WednesdayZDF heuteEntdecken Sie das ZDF-NachrichtenstudioColliderOG Harry Potter Stars Officially React to Replacement Cast as Hit Spin-Off Series ReturnsWirtualna PolskaLekarze o stanie poszkodowanych. "Stan pacjentów stabilny"EuronewsRussia: HIV spreads beyond risk groups in 27 regions, epidemiologists sayAntara NewsGov't expects boost in F&B, fashion sectors ahead of year-end holidaysCapital FMLinah Jebii Kilimo dismisses claims she remains in Azimio, reaffirms UDA allegianceIl Fatto Quotidiano“Lavoro come tata per famiglie ricchissime, ma mi rifiuto di salire sui loro jet privati. Ho persino perso un lavoro per questo”: la storia di Samantha JordanABC NewsWATCH: Family's gigantic fern named 'Jeff' goes viral on social mediaسكاي نيوز عربيةسهم إنفيديا يتراجع 5% بعد تحقيق بشأن جنسن هوانغ
The Daily Newsstand · Free, Always
Monday, November 9, 2026

Домашнее облако на роутере keenetic/netcraze с nextcloud

Translate

Давно хотел сменить домашний роутер, но пока провайдерский работал, не находил повода тратиться на это. А тут внезапно "1Тб навсегда" от Мэйла превратился в тыкву. Да и недоступность Яндекс.Диск доставляет некоторые неудобства, хоть и винить за это ребят из Яндекса я не могу. В общем появилась задача максимально бюджетно организовать хранилище фото и не очень важных файлов. Выбор пал на домашний сетап: роутер Netcraze Hopper + обычный внешний диск с usb3 (в качестве диска Adata HD710 Pro на 4тб). Поднять nextcloud вроде бы не рокет сайнс, но, учитывая ограничения окружения, были свои нюансы, о которых я расскажу в данном гайде.

Вводные требования

  1. Бюджетно

  2. Есть мобильное приложение

  3. Синхронизация фото

  4. Возможность шарить доступ между пользователями

  5. Хранение данных на fs без лишних абстракций (например без s3)

  6. Опционально: доступ из интернета

Подготовка диска

Для меня важна возможность в любой момент воткнуть диск в ПК с виндой, поэтому разбивка получилась следующая:
- 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.

Также пробовал:

  1. owncloud (который infinite scale): жрёт ресурсы в простое (в отличии от ondemand воркеров php-fpm), архитектура - моё почтение (куча сервисов в одном бинаре на одном PID)

  2. opencloud (форк первого пункта): аналогично первому пункту, но ест оперативки на 30мб больше

  3. мобильное приложение Pho (которое использует webdav): не совсем то что нужно, так как заливает фото согласно своей структуре (год/месяц/день), настроек нет - кастомизировать не получится

  4. owncloud 10 (до go-шный): не поддерживает php8.4, а тащить из архивов 7ку и заниматься некромантией без докера - желания нет

Поэтому идеальным кандидитом на роль домашнего облака и стал nextcloud.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.