Seafile - это некое подобие облачного диска, который умеет синхронизировать папку между несколькими устройствами и делать это не так что сетевая папка, а именно синхронизация локальной, т.е. в момент когда интернета нет ваши файлики у вас под рукой и на всех устройствах должны быть актуальными.
В отличие от Nextcloud/OwnCloud этот сервис работает не на PHP, его ядро написано на Си, само хранилище в специфичном формате в виде блоков, изменения в файлах отправляются только теми кусками, которые изменились (как в rsync), в общем, это одно из самых производительных открытых решений.
Для всего хранилища наверно нет, но вот то что должно быть оперативно под рукой самое то…
Данная статья как будто не про мануал, а про понимание как это работает
Я настраиваю сервис, поэтапно его усложняя и модифицируя…
В конце опишу различные тонкости, с которыми столкнусь в ходе изучения этого продукта.
В официальной документации есть достаточно избыточная инструкция
https://manual.seafile.com/13.0/setup/setup_ce_by_docker/
В ней говорится следующее:
# создаем папку и переходим в нее mkdir /opt/seafile cd /opt/seafile
# скачаем шаблон с env файлом, который нужно будет подредактировать (пароли, домены, логины...) wget -O .env https://manual.seafile.com/13.0/repo/docker/ce/env
#скачаем три шаблона docker-compose wget https://manual.seafile.com/13.0/repo/docker/ce/seafile-server.yml wget https://manual.seafile.com/13.0/repo/docker/seadoc.yml wget https://manual.seafile.com/13.0/repo/docker/caddy.yml
#отредактируем .env nano .env
#стартуем... docker compose up -d
Еще в инструкции подробно расписаны все переменные, которые нужно отредактировать в .env файле
И есть еще одна страничка, которая описывает использование внешнего mysql сервера
https://manual.seafile.com/13.0/setup/setup_with_an_existing_mysql_server/
Зачем такое нужно?
А представьте что у нас несколько десятков подобных сервисов - разные блоги, форумы, планировщики, хранилища, и у каждого свой контейнер с базой…
Что лучше, один сервер mysql или несколько десятков серверов с точки зрения экономии ресурсов на скромном железе? Я считаю что лучше один, но это лично мое.
И второй момент, который пока не подтверждаю на 100%, вероятно я не умею готовить докер,
у меня были такие приложения типа импорт каталога, обработка сотен тысяч зависимых объектов…
Мое приложение выполняло ну очень много операций за секунду по нажатию одной кнопочки и вот когда mysql положил внутрь докера, то вместо 3 секунд эта обработка длилась 5-6 секунд,
но тут опять же, скорее всего я просто не умею готовить докер и тюннить высокоскоростные файловые операции.
Опять кидаю ссылку на орфициальную документацию, используйте ее как первоисточник, т.к. в данной статье будет лишь выжимка конкретно под мою ситуацию
https://manual.seafile.com/13.0/setup/use_other_reverse_proxy/#add-reverse-proxy-for-related-services
Опять отвечаю на вопрос зачем? У меня стоит один единственный nginx, и только он слушает 80 и 443 порты и он проксирует трафик на несколько десятков контейнеров, а контейнеры в свою очередь шарят порты которые доступны только на локальном хосте.
Второй момент - на этом nginx стоит certbot, который автоматически беспокоится о продлении всех сертификатов lets-encrypt…
Итак, приступим, будем делать по официальной документации, но всё равно по-своему.
А вы должны делать по-своему, т.к. каждая ситуация уникальна, вероятно что мой опыт вам пригодится.
У меня есть папка /storage, куда примонтирован RAID массив, т.е. все данные зеркалированы дважды…
Далее я создал папку /storage/DockerApps чтобы локализовать конфиги и данные всех моих сервисов,
если потребуется мигрировать на новый сервер, то я просто подключу внешний диск на 6тб и перенесу на него эту папку.
Аналогично, я локализовал место хранения различных докер-томов и mysql-data чтобы они хранились на рейд-томе.
# 1. Создаем директорию сервиса mkdir -p /storage/DockerApps/seafile/
Если что-то пошло не по плану
docker compose down
и
чистим папку и по новой,
иначе, инициализация дважды не пройдет
Дальше качнем конфиги, но не все, из этого набора нам не нужен caddy
#переходим в папку cd /storage/DockerApps/seafile/ #это делаем, только если не правили и не готовили свой env или он вам не нужен wget -O .env https://manual.seafile.com/13.0/repo/docker/ce/env
wget https://manual.seafile.com/13.0/repo/docker/ce/seafile-server.yml wget https://manual.seafile.com/13.0/repo/docker/seadoc.yml
Ознакомимся… если хотим, то руками поправим, если не хотим - идем дальше, будут команды
nano .env
sed -i "s/,caddy\.yml\|caddy\.yml,//g" .env grep COMPOSE_FILE .env
Должно получиться так
COMPOSE_FILE='seafile-server.yml,seadoc.yml'
В этой папке будут подпапки для остальных контейнеров и их данных
sed -i 's|^BASIC_STORAGE_PATH=/opt|BASIC_STORAGE_PATH=/storage/DockerApps/seafile|g' .env grep BASIC_STORAGE_PATH= .env
Внутри как видим остальные пути строятся относительно этой папки
BASIC_STORAGE_PATH=/storage/DockerApps/seafile SEAFILE_VOLUME=$BASIC_STORAGE_PATH/seafile-data SEAFILE_MYSQL_VOLUME=$BASIC_STORAGE_PATH/seafile-mysql/db SEAFILE_CADDY_VOLUME=$BASIC_STORAGE_PATH/seafile-caddy SEADOC_VOLUME=$BASIC_STORAGE_PATH/seadoc-data
JWT_PRIVATE_KEY
sed -i "s|^JWT_PRIVATE_KEY=.*|JWT_PRIVATE_KEY=$(pwgen 42)|g" .env sed -i "s|^SEAFILE_MYSQL_DB_PASSWORD=.*|SEAFILE_MYSQL_DB_PASSWORD=$(pwgen 32)|g" .env sed -i "s|^INIT_SEAFILE_MYSQL_ROOT_PASSWORD=.*|INIT_SEAFILE_MYSQL_ROOT_PASSWORD=$(pwgen 42)|g" .env sed -i "s|^INIT_SEAFILE_ADMIN_PASSWORD=.*|INIT_SEAFILE_ADMIN_PASSWORD=$(pwgen 42)|g" .env sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(pwgen 42)|g" .env
Проверим
grep -E "^JWT_PRIVATE_KEY=|^SEAFILE_MYSQL_DB_PASSWORD=|^INIT_SEAFILE_MYSQL_ROOT_PASSWORD=|^INIT_SEAFILE_ADMIN_PASSWORD=|^REDIS_PASSWORD=" .env
Должно быть так
JWT_PRIVATE_KEY=ooJ2igier2iekeag1aepaiye6YaibohyooD1ieshuu SEAFILE_MYSQL_DB_PASSWORD=Daeth1aem1aedoh2Reixu6thieree6ba REDIS_PASSWORD=ohm5Aith4mahdaa5ay4ooPooyeijee2eevie2OwaeR INIT_SEAFILE_MYSQL_ROOT_PASSWORD=aGh9wafe3yaej2yei4ohg1vair6ucuRaeDa1adoopa INIT_SEAFILE_ADMIN_PASSWORD=ou1eiwukooTee0ang3feel3EeHa6voozipeeShie8R
Только не спешите меня взламывать, я еще раз эту команду выполню и все пароли поменяются))
На данном этапе мы просто получим результат, который будем наращивать, поэтому мы даже домен не задали, только учтите, тут мы поиграемся и вероятно всё удалим и заново создадим…
nano seafile-server.yml
найдем и раскоментируем, отредактируем строчки в сервисе seafile
seafile: image: ${SEAFILE_IMAGE:-seafileltd/seafile-mc:13.0-latest} container_name: seafile restart: unless-stopped ports: - "8123:80"
таким образом, мы прокинули порт 8123 внутрь контейнера и можем подключаться к нему или с локального пк или в локальной сети (ведь мы за роутером) балуемся,
иначе не оставляйте на долгое время это…
docker compose up -d
ждем пока все контейнеры запустятся…
На этом этапе посмотрим логи
docker logs -f seafile
Если видим фразу
---------------------------------------- Successfully created seafile admin ---------------------------------------- Seahub is started Done.
То открываем в браузере наш адрес по ip и порту 8123 (или какой вы задали)
Берем логин и пароль из файла .env и заходим
INIT_SEAFILE_ADMIN_EMAIL - логин
INIT_SEAFILE_ADMIN_PASSWORD - пароль
Ошибок нет, файлы загружаются, кто молодец?
Мы молодцы, статья закончилась, остальное сами)))
Ну это решение хоть и рабочее, но не продакшн…
В mysql при создании пользователей принято после собаки указывать подсеть из которой ему доступно подключиться
Например так root@127.0.0.1 или root@192.168.1.%
Наш compose создает что-то случайное и мы заранее не сможем создать пользователя и прописать шлюз через который будет подключаться,
а если создали контейнер и сеть, то уже поздно создавать пользователя, у нас что-то обязательно рухнет при инициализации.
Вот такая команда покажет все действующие подсети докера
docker network ls --format "{{.Name}}" | \ xargs -I {} sh -c 'echo "Network: {}"; docker network inspect {} --format '\''{{range .IPAM.Config}}{{.Subnet}} - {{.Gateway}}{{println}}{{end}}'\'' \ 2>/dev/null; echo "---"'
Вот пример ее вывода (у меня как оказалось еще много забытых сетей)
Network: bridge 172.17.0.0/16 - 172.17.0.1 --- Network: host --- Network: nextcloud_net 172.22.0.0/16 - 172.22.0.1 ---
Ну и как мы видим, например 172.23.0.0 не занята, давайте этим воспользуемся и создадим…
docker network create \ --driver bridge \ --subnet=172.23.0.0/16 \ --gateway=172.23.0.1 \ seafile-net
Теперь подсеть создана вме docker-compose и не управляется им и вообще прибита гвоздями…
Если вы раскатаете другой композ а потом этот вернете на место ничего не уедет.
Но есть один момент, открываем наш seadoc и seafile-server.yml
И меняем кусочек
networks: seafile-net: name: seafile-net
на
networks: seafile-net: name: seafile-net external: true
Это знак для compose, что сеть создавать не нужно, она уже кем то была создана.
На этом этапе docker-compose.yml пока рабочий, можно даже снова запустить и проверить, всё работает, мы только прибили гвоздями сеть.
У нас есть два пути - когда сервер чистый и можно всё с нуля на новой БД инициализировать
и когда сервер какое то время поработал и нужно переехать из докера на полноценную базу, вероятно даже на отдельном сервере…
Предлагаю для начала мигрировать
Этот вариант мне чем-то нравится, что не потребуется создавать пользователя root,
можно инициализироваться в докере, а потом перенести на другой сервер имея только ограниченную учетную запись.
сперва подгрузим .env чтобы задействовать его переменные
cd /storage/DockerApps/seafile source .env
создадим на хосте папку с дампами (мы внутри /storage/DockerApps/seafile)
mkdir dumps
Резервируем три базы, используя переменные
docker exec seafile-mysql \ mysqldump -u ${SEAFILE_MYSQL_DB_USER} \ -p${SEAFILE_MYSQL_DB_PASSWORD} \ ${SEAFILE_MYSQL_DB_CCNET_DB_NAME} > ./dumps/ccnet_dump.sql docker exec seafile-mysql \ mysqldump -u ${SEAFILE_MYSQL_DB_USER} \ -p${SEAFILE_MYSQL_DB_PASSWORD} \ ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME} > ./dumps/seafile_dump.sql docker exec seafile-mysql \ mysqldump -u ${SEAFILE_MYSQL_DB_USER} \ -p${SEAFILE_MYSQL_DB_PASSWORD} \ ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME} > ./dumps/seahub_dump.sql
Как помним, мы создали подсеть с шлюзом (первый адрес) 172.23.0.1
Берем и перевинчиваем в файле .env адрес БД
## Database SEAFILE_MYSQL_DB_HOST=172.23.0.1 #SEAFILE_MYSQL_DB_HOST=db
Почистим если существуют (вы должны понимать что делаете)
source .env mariadb -u root << EOF DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_CCNET_DB_NAME}; DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}; DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}; EOF
создадим базы, юзеров и дадим права
mariadb -u root << EOF CREATE DATABASE ${SEAFILE_MYSQL_DB_CCNET_DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER '${SEAFILE_MYSQL_DB_USER}'@'172.23.%' IDENTIFIED BY '${SEAFILE_MYSQL_DB_PASSWORD}'; GRANT ALL PRIVILEGES ON ${SEAFILE_MYSQL_DB_CCNET_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; GRANT ALL PRIVILEGES ON ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; GRANT ALL PRIVILEGES ON ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; -- Добавляем GRANT OPTION, так как это часто требуется для инициализации Seafile GRANT GRANT OPTION ON ${SEAFILE_MYSQL_DB_CCNET_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; GRANT GRANT OPTION ON ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; GRANT GRANT OPTION ON ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}.* TO '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; FLUSH PRIVILEGES; EOF
Ну и грузим дампы, которые создавали
mariadb -u root "${SEAFILE_MYSQL_DB_CCNET_DB_NAME}" < ./dumps/ccnet_dump.sql mariadb -u root "${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}" < ./dumps/seafile_dump.sql mariadb -u root "${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}" < ./dumps/seahub_dump.sql
На данном этапе пока не трогаем композ, мы только поменяли хост в .env, если что, будет шаг назад
docker compose down docker compose up -d
На данном этапе всё получилось, убираем из compose файла контейнер с базой данных, комментируем этот участок в файле seafile-server.yml
# db: # image: ${SEAFILE_DB_IMAGE:-mariadb:10.11} # container_name: seafile-mysql # restart: unless-stopped # environment: # - MYSQL_ROOT_PASSWORD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:-} # - MYSQL_LOG_CONSOLE=true # - MARIADB_AUTO_UPGRADE=1 # volumes: # - "${SEAFILE_MYSQL_VOLUME:-/opt/seafile-mysql/db}:/var/lib/mysql" # networks: # - seafile-net # healthcheck: # test: # [ # "CMD", # "/usr/local/bin/healthcheck.sh", # "--connect", # "--mariadbupgrade", # "--innodb_initialized", # ] # interval: 20s # start_period: 30s # timeout: 5s # retries: 10
остановим бд
docker stop seafile-mysql docker rm seafile-mysql
есть зависимость у seadoc
#depends_on: # db: # condition: service_healthy
А еще в seafile-server
depends_on: # db: # condition: service_healthy redis: condition: service_started
Если у вас новая установка, база была чистой или вы изначально создаете на чистой базе
docker compose down
это опционалдьно, если уже инициализировали
сперва почистим если что то есть на бд хоста
mariadb -u root << EOF DROP USER IF EXISTS '${SEAFILE_MYSQL_DB_USER}'@'172.23.%'; DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_CCNET_DB_NAME}; DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}; DROP DATABASE IF EXISTS ${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}; FLUSH PRIVILEGES; EOF
потом почистим папки, если они есть, то инит не произойдет
rm -r ./seafile-mysql/ ./seafile-data/ ./seadoc-data/
mariadb -u root << EOF CREATE USER IF NOT EXISTS 'root'@'172.23.%' IDENTIFIED BY '${INIT_SEAFILE_MYSQL_ROOT_PASSWORD}'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.23.%' WITH GRANT OPTION; FLUSH PRIVILEGES; EOF
docker up -d
ну и ждем логи
docker logs -r seafile
успех это
---------------------------------------- Successfully created seafile admin ---------------------------------------- Seahub is started Done.
Дальше нужно удалить этого временого рута из бд
mariadb -u root << EOF DROP USER IF EXISTS 'root'@'172.23.%'; FLUSH PRIVILEGES; EOF
Дальше и пользователь оказывается был создан seafile@% т.е. с любых хостов, а нам надо ограничить по сети
Не забываем определиться с доменом, например, super.puper.disk.ru
Этот домен нужно вписать в конфиг .env
SEAFILE_SERVER_HOSTNAME=...
Создаем файлик /etc/nginx/sites-enabled/seadoc
server { listen 80; # Или 443 ssl, если будете настраивать SSL позже server_name super.puper.disk.ru; # Основной прокси для Seafile location / { proxy_pass http://127.0.0.1:8123; # <-- Проксируем на порт хоста, куда проброшен порт 80 контейнера seafile proxy_read_timeout 310s; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Важно для правильного определения HTTPS внутри контейнера proxy_set_header Connection ""; proxy_http_version 1.1; client_max_body_size 0; } # Прокси для Seadoc API location /sdoc-server/ { proxy_pass http://127.0.0.1:8124/; # <-- Проксируем на порт хоста, куда проброшен порт 80 контейнера seadoc proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; proxy_set_header X-Forwarded-Proto $scheme; # Важно client_max_body_size 100m; } # Прокси для Seadoc WebSocket location /socket.io { proxy_pass http://127.0.0.1:8124; # <-- Проксируем на порт хостат 80 контейнера seadoc proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_redirect off; proxy_buffers 8 32k; proxy_buffer_size 64k; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Важно proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; } # Добавьте логи (по желанию) #access_log /var/log/nginx/suped.disk.h.dev66.ru.access.log; #error_log /var/log/nginx/suped.disk.h.dev66.ru.error.log; }
Проверим, перезагрузим…
nginx -t service nginx restart
В файле env
SEAFILE_SERVER_PROTOCOL=https
Запустим certbot, он сам всё поправит…
Теперь открываем по ссылке https://...
https://manual.seafile.com/13.0/extension/only_office/
Я тут не буду рассматривать прикрутку того офиса, который поднял для nextcloud, его у меня скорее всего не будет
Сперва качаем composer…
wget https://manual.seafile.com/13.0/repo/docker/onlyoffice.yml
Допишем в .env еще строчки
# OnlyOffice ONLYOFFICE_IMAGE=onlyoffice/documentserver:8.1.0.1 ONLYOFFICE_VOLUME=/storage/DockerApps/seafile/onlyoffice-data ONLYOFFICE_PORT=6233 ONLYOFFICE_JWT_SECRET=сгенерим его через pwgen
Далее в файле onlyoffice.yml
пропишем опять что у нас сеть внешняя
networks: seafile-net: name: seafile-net external: true
Уберем лейблы caddy
labels: caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}:${ONLYOFFICE_PORT:-6233} caddy.reverse_proxy: "{{upstreams}}"
и сервис тоже caddy уберем
Получим что то такое
services: onlyoffice: image: ${ONLYOFFICE_IMAGE:-onlyoffice/documentserver:8.1.0.1} restart: unless-stopped container_name: seafile-onlyoffice environment: #- DB_TYPE=${DB_TYPE:-mariadb} #- DB_HOST=${SEAFILE_MYSQL_DB_HOST:-db} #- DB_USER=${SEAFILE_MYSQL_DB_USER:-seafile} #- DB_PWD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty} - JWT_ENABLED=true - JWT_SECRET=${ONLYOFFICE_JWT_SECRET:?Variable is not set or empty} volumes: - ${ONLYOFFICE_VOLUME:-/opt/onlyoffice}/logs:/var/log/onlyoffice - ${ONLYOFFICE_VOLUME:-/opt/onlyoffice}/data:/var/www/onlyoffice/Data - ${ONLYOFFICE_VOLUME:-/opt/onlyoffice}/lib:/var/lib/onlyoffice labels: caddy: ${SEAFILE_SERVER_PROTOCOL:-http}://${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}:${ONLYOFFICE_PORT:-6233} caddy.reverse_proxy: "{{upstreams}}" networks: - seafile-net networks: seafile-net: name: seafile-net external: true
Подредактируем файлик .env
COMPOSE_FILE='seafile-server.yml,seadoc.yml,onlyoffice.yml'
Пересоздаем контейнеры
docker compose down docker compose up -d
Проверяем что сервис работает
curl -I http://127.0.0.1:6233/welcome/
Далее правим nginx
nginx -t && systemctl reload nginx
Onlyoffice на отдельном домене… seafile-data/seafile/conf/seahub_settings.py
ENABLE_ONLYOFFICE = True ONLYOFFICE_APIJS_URL = 'http://super.puper.disk.ru/onlyofficeds/web-apps/apps/api/documents/api.js' ONLYOFFICE_JWT_SECRET = 'ваш_секрет_из_этапа_1' # Опционально: OFFICE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024 ONLYOFFICE_EDIT_FILE_EXTENSION = ('docx', 'pptx', 'xlsx', 'csv')