Skip to the content.

12 May 2025

Заметку буду дописывать со временем.

Больше трёх лет назад я сделал сервер из малинки и внешнего жёсткого диска, сейчас решил двигаться дальше и собрать что-то из десктопных комплектующих.

Большая часть деталей у меня уже была “лишняя” после апгрейдов, докупил только материнскую плату, оперативную память и жёсткий диск, получилась сборная солянка. На памяти и материнской плате решил не экономить - для меня сборка и настройка сервера это развлечение и не хочется высчитывать, сколько именно памяти мне нужно или как выкроить на выборе материнской платы (и остаться без возможности разгона или андервольтинга).

Конфигурация

Домашняя сеть

Материнская плата в сервере уже поддерживает 2.5 гигабита и мне захотелось в локальной сети увидеть эту скорость. Итак, я купил свитч TP-link DS105G-M2. С ним ожидаемо никаких проблем не было. Ещё прикольно, что у каждого порта по две лампочки и прям на корпусе написано, что левая зелёная лампочка - 2.5 Гигабита, правая зелёная гигабит и правая жёлтая 100 Мбит. И есть лампочка, которая показывает питание. В общем, всё максимально наглядно и понятно. В десктоп я купил сетевую карту - TP-link TX201. И с ней тоже всё было прекрасно - я её вставил в слот, десктоп загрузился и без установки каких-либо драйверов сеть заработала. Моя ОС - Linux Mint 21.

Вообще раньше я почему-то считал свичи пережитком прошлого, но по-факту очень удобная штука для расширения сети - настраивать не надо, стоит на порядок дешевле, чем роутер с пятью аналогичными портами.

По локальной сети при копировании на сервер через sshfs удалось увидеть скорость 290 мегабайт в секунду. При копировании с сервера скорость почему-то держалась на уровне 110 мегабайт - для меня загадка, почему так - вроде это меньше, чем возможности жёсткого диска.

Hard drive

Посмотреть, какие логические и физические размеры секторов на дисках:

sudo fdisk -l

Disk /dev/sda: 14.55 TiB, 16000900661248 bytes, 3906469888 sectors
Disk model: WDC WD161KRYZ-01
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

По-умолчанию логический размер будет 512 байт из-за соображений совместимости с компьютерами, выпущенными до 2010 года. Но можно выбрать и побольше (например, я хочу чтобы логический размер совпадал с физическим - всё равно диск пишет-читает по 4кб зараз).

Для начала надо посмотреть, какие размеры вообще поддерживаются:

sudo hdparm -I /dev/sdX

На моём диске был логический размер 512 байт и он поддерживает логический размер в 4кб. Я ради интереса почитал интернет и наткнулся на пост какого-то бедолаги, у которого диск подключался через usb и после изменения найстройки через usb-адаптер подключаться перестал. Но к счастью у меня нормальное подключение через sata и диск абсолютно пустой, потому что все данные с диска потеряются при изменении размера сектора.

sudo hdparm --set-sector-size 4096 --please-destroy-my-drive /dev/sda

Так что я поменял размер сектора и перезагрузил сервер. После перезагрузки всё было ок, но надо будет заново разметить разделы на диске.

https://unix.stackexchange.com/questions/606072/change-logical-sector-size-to-4k

А вообще очень красиво получается - оперативная память выделяется страницами по 4кб и блоками в 4кб пишется на диск.

cryptsetup benchmark

cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
PBKDF2-sha1      1744718 iterations per second for 256-bit key
PBKDF2-sha256    3102295 iterations per second for 256-bit key
PBKDF2-sha512    1491573 iterations per second for 256-bit key
PBKDF2-ripemd160  801663 iterations per second for 256-bit key
PBKDF2-whirlpool  685343 iterations per second for 256-bit key
argon2i      10 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
argon2id     10 iterations, 1048576 memory, 4 parallel threads (CPUs) for 256-bit key (requested 2000 ms time)
#     Algorithm |       Key |      Encryption |      Decryption
        aes-cbc        128b      1124.7 MiB/s      2228.8 MiB/s
    serpent-cbc        128b       118.8 MiB/s       675.5 MiB/s
    twofish-cbc        128b       231.2 MiB/s       402.0 MiB/s
        aes-cbc        256b       887.5 MiB/s      2104.9 MiB/s
    serpent-cbc        256b       118.9 MiB/s       675.9 MiB/s
    twofish-cbc        256b       231.2 MiB/s       401.5 MiB/s
        aes-xts        256b      2050.6 MiB/s      2061.8 MiB/s
    serpent-xts        256b       595.5 MiB/s       587.6 MiB/s
    twofish-xts        256b       371.3 MiB/s       370.0 MiB/s
        aes-xts        512b      1958.5 MiB/s      1957.6 MiB/s
    serpent-xts        512b       595.5 MiB/s       587.6 MiB/s
    twofish-xts        512b       371.6 MiB/s       370.2 MiB/s

Лучшая опция - aes-xts с ключом 512 бит. aes-cbc устаревший, использовать его не надо.

Получается, что sata-диски можно почти безболезненно шифровать, а вот с nvme наверно не стоит - шифрование будет медленнее, чем возможная скорость чтения или записи данных.

Создать новый раздел на диске можно с помощью

sudo gdisk

Убедиться, что всё выровнено

sudo parted /dev/sda align-check optimal 1

Создать зашифрованный раздел на sdaX

sudo cryptsetup luksFormat --type=luks2 \
--sector-size=4096 \
-c aes-xts-plain64 \
-s 512 \
-h sha512 \
--pbkdf argon2id \
--pbkdf-memory 4194304 \
--pbkdf-parallel 8 \
--iter-time 5000 \
--use-urandom \
/dev/sdaX

подключить в /dev/mapper/data:

sudo cryptsetup open /dev/sda1 data

создать файловую систему:

sudo mkfs.btrfs -d single -m dup -L databtrfs -f /dev/mapper/data

Примонтировать:

sudo mount -t btrfs -o noatime,nossd,autodefrag,space_cache=v2,compress=zstd:3 /dev/mapper/data /mnt/data/

Immich

По-идее для запуска immich достаточно просто докера, но к сожалению он по-умолчанию запускает всё от рута и сохранённые файлы типа фотографий тоже будут от рута. Мне такое не понравилось. Причём если указать пользователя, то оно не работает, и придётся кое-какие дополнительные папки указывать.

В общем я специально под immich сделал такого же пользователя и добавил себя к нему в группу, чтобы от моего пользователя можно было безболезненно бэкапить файлы.

Прикреплю файл целиком:

#
# WARNING: To install Immich, follow our guide: https://immich.app/docs/install/docker-compose
#
# Make sure to use the docker-compose.yml of the current release:
#
# https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
#
# The compose file on main may not be compatible with the latest release.

name: immich

services:
  immich-server:
    user: $USER_ID:$GROUP_ID
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    # extends:
    #   file: hwaccel.transcoding.yml
    #   service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
    volumes:
      # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro
    env_file:
      - .env
    ports:
      - '2283:2283'
    depends_on:
      - redis
      - database
    restart: always
    healthcheck:
      disable: false

  immich-machine-learning:
    user: $USER_ID:$GROUP_ID
    container_name: immich_machine_learning
    # For hardware acceleration, add one of -[armnn, cuda, rocm, openvino, rknn] to the image tag.
    # Example tag: ${IMMICH_VERSION:-release}-cuda
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    # extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/ml-hardware-acceleration
    #   file: hwaccel.ml.yml
    #   service: cpu # set to one of [armnn, cuda, rocm, openvino, openvino-wsl, rknn] for accelerated inference - use the `-wsl` version for WSL2 where applicable
    volumes:
      - ${MACHINE_LEARNING_CACHE}:/cache # conflicting documentation for /cache or /.cache, have yet to test this
      - ${MACHINE_LEARNING_CONFIG}:/.config  
    env_file:
      - .env
    restart: always
    healthcheck:
      disable: false

  redis:
    user: $USER_ID:$GROUP_ID
    container_name: immich_redis
    image: docker.io/valkey/valkey:8-bookworm@sha256:42cba146593a5ea9a622002c1b7cba5da7be248650cbb64ecb9c6c33d29794b1
    healthcheck:
      test: redis-cli ping || exit 1
    restart: always
    volumes:
      - ${REDIS_DATA}:/data

  database:
    user: $USER_ID:$GROUP_ID
    container_name: immich_postgres
    image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:739cdd626151ff1f796dc95a6591b55a714f341c737e27f045019ceabf8e8c52
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
      POSTGRES_INITDB_ARGS: '--data-checksums'
    volumes:
      # Do not edit the next line. If you want to change the database storage location on your system, edit the value of DB_DATA_LOCATION in the .env file
      - ${DB_DATA_LOCATION}:/var/lib/postgresql/data
    healthcheck:
      test: >-
        pg_isready --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" || exit 1; Chksum="$$(psql --dbname="$${POSTGRES_DB}" --username="$${POSTGRES_USER}" --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
      interval: 5m
      start_interval: 30s
      start_period: 5m
    command: >-
      postgres -c shared_preload_libraries=vectors.so -c 'search_path="$$user", public, vectors' -c logging_collector=on -c max_wal_size=2GB -c shared_buffers=512MB -c wal_compression=on
    restart: always

volumes:
  model-cache:

И .env

# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=./library

# The location where your database files are stored. Network shares are not supported for the database
DB_DATA_LOCATION=./postgres

# To set a timezone, uncomment the next line and change Etc/UTC to a TZ identifier from this list: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
# TZ=Etc/UTC

# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release

# Connection secret for postgres. You should change it to a random password
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
DB_PASSWORD=add-password-here

# The values below this line do not need to be changed
###################################################################################
DB_USERNAME=postgres
DB_DATABASE_NAME=immich

USER_ID=111
GROUP_ID=112

MACHINE_LEARNING_CACHE=./machine_learning/cache # necessary for non-root use
MACHINE_LEARNING_CONFIG=./machine_learning/config # necessary for non-root use
REDIS_DATA=./redis # necessary for non-root use

А ещё я сделал сервис, чтобы запускать/останавливать с помощью systemd (наверняка есть и другие способы, но я сделал так)

[Unit]
Description="Immich in docker"

After=network-online.target nss-lookup.target

[Service]
Type=simple
TimeoutStartSec=86400
ExecStartPre=/bin/bash -c 'while [ ! -e PATH_TO_IMMICH_DIR ]; do sleep 10; done'
ExecStart=docker compose --project-directory PATH_TO_IMMICH_DIR up

[Install]
WantedBy=multi-user.target

Хитрый шаг - сервис запускается, когда нужная папка будет примонтирована. Я сначала узнал что в systemd можно задать путь в специальной юните .path, потом он при запуске запустит сервис .service, а тот выставит .target и я из другого сервиса смогу указать “After=data.target”, но это какое-то извращение. При этом повылазит куча нюансов типа того что делать, если папку отмонтировали и примонтировали снова. Я так попробовал, у меня в итоге не заработало и я вместо этого просто сделал костыль с while [ ! -e PATH_TO_IMMICH_DIR ]; do sleep 10; done.

Очень странно, что нельзя просто взять и указать, что сервис просто ждёт файла в папке, вместо этого надо фигачить стрёмные приседания типа .path -> .service -> .target -> .service waiting for target

Qbittorrent

в документации к qBittorrent волне понятно рассказано.

надо поставить qbittorrent-nox, можно перенести папки из .local и .share на новую систему, комп их подцепит и если торренты лежат в той же папке - то они засчитаются как скачанные.

В этот раз я попробовал для торрента сделать своего пользователя. А для папки для скачивания указал пользователя-торрента и группу своего пользователя. Благодаря этому у меня есть доступ к файлам в папке. Ещё есть вариант добавить себя в группу пользователя для торрента и по-идее доступ тоже будет.

В первый раз надо посмотреть в systemctl status qbittorrent-nox - там qbittorrent напишет временный пароль для входа.

Jellyfin

медиасервер, к которому можно подключаться с телефона/пк и смотреть фильмы или слушать музыку.

Для входа через браузер по-умолчанию порт 8096

Я написал свой docker-compose.yml. По-умолчанию предполагается, что jellyfin ждёт медиайфайлы в :/media, но я ему туда хитро смонтировал ещё пару директорий - для музыки и фильмов.

name: jellyfin

services:
  jellyfin:
    container_name: jellyfin_server
    image: jellyfin/jellyfin:latest
    network_mode: host
    restart: always
    volumes:
      - /mnt/data/services/jellyfin/config:/config
      - /mnt/data/services/jellyfin/cache:/cache
      - /mnt/data/services/jellyfin/media:/media
      - type: bind
        source: /mnt/data/safedata/music
        target: /media/music
      - type: bind
        source: /mnt/data/films
        target: /media/films

Gitea

Кажется, самая беспроблемная установка:

https://docs.gitea.com/installation/install-with-docker

дефолтный порт для веб-доступа: 3000

Можно сделать режим зеркала для репозитория снаружи либо наоборот режим, когда изменения из gitea автоматически пушатся на гитхаб или ещё куда.

Я пока не придумал как это использовать. Возможно, будет удобно там создавать приватные репозитории или миррорить какие-то большие репозитории, чтобы их можно было быстро скачать из локального зеркала.