Per installare un server Seafile, seguendo il manuale ufficiale, bisogna seguire una strana procedura non standard, un po' complicata, specialmente se si desidera compilare il software, piuttosto che installare il pacchetto precompilato.

In questo articolo sistemo la faccenda facendo un po' di ordine e usando alcuni pacchetti AUR. Assumo quindi che stiate usando una distribuzione basata su Arch Linux. Per altre distribuzioni bisognerà adattare alcuni passaggi. Resta comunque il fatto che questa guida, in realtà, si basa sul lavoro svolto da uno sviluppatore Debian che ha tentato di pacchettizzare Seafile server.

NB: Vi informo subito che con questa configurazione ci saranno dei problemi per quanto riguarda gli avatar, funzione a mio avviso non fondamentale. Tenterò comunque di sistemare anche questo problema, prima o poi.

È passato un bel po' di tempo da quando vi raccontavo della [migrazione da un server Debian 9 ad un server Manjaro Stable]. Quella volta, così come le altre, ho dovuto re-installare da capo tutti i servizi richiesti e ri-configurarli copiando file di configurazione dai backup. Una procedura manuale che porta inevitabilmente ad errori e vuoti di memoria che fanno sprecare molto tempo. La prossima volta cercherò un sistema che mi consenta di replicare il server su qualsiasi macchina. Ho già messo gli occhi su Fedora CoreOS e NixOS (due sistemi molto che ottengono più o meno gli stessi risultati in 2 modi completamente diversi).

Tornando a Seafile, il modo più rapido per installarlo, sarebbe stato seguire il relativo articolo della Arch Linux Wiki. Purtroppo però l’articolo non è aggiornato e c’è bisogno di rivederlo.

Per questi motivi ho deciso di cercare un’altra strada, e dopo un bel po' di ricerche e ore a brancolare nel buio sono arrivato ad una soluzione funzionante al 99% e, a mio avviso, molto più pulita. Per arrivarci ho cercato di capire che cosa è stato fatto in questi 3 repository Debian di seafile, seahub e ccnet.

Iniziamo con lo stabilire che Seafile è un software ad architettura modulare. Ci sono almeno 3 moduli: ccnet, che si occupa dell’autenticazione degli utenti; seahub, l’interfaccia web il cui backend è scritto in python usando django; seafile-server, la parte che si occupa della gestione del file system e della sincronizzazione dei file. A questi si aggiunge un modulo opzionale, ovvero seafdav che fornisce l’accesso WebDAV alle librerie. Oltre a questi componenti è anche buona pratica aggiungere un server web con funzione di proxy inverso, come NGINX.

Adesso passiamo all’installazione pratica.

Per prima cosa installiamo i pacchetti ccnet-server, seafile-server, seahub e python-wsgidav-seafile (quest’ultimo solo se si è interessati a Web-DAV) dall’AUR. Con yay è sufficiente un yay -S ccnet-server seafile-server seahub python-wsgidav-seafile. Questo comando installa le dipendenze, scarica i sorgenti, li ripulisce, patcha, compila, pacchettizza e infine installa, tutto in automatico. Niente male, vero?

Una volta installati i pacchetti passiamo alla configurazione. Per prima cosa dobbiamo creare un file contenente alcune variabili d’ambiente necessarie per fornire una struttura il più conforme possibile al FHS agli eseguibili di Seafile (che già sono stati installati nelle apposite directory grazie ai precedenti pacchetti AUR).

Creiamo la cartella seafile sotto /etc, lanciando sudo mkdir /etc/seafile.

Poi creiamo un file /etc/seafile/env con il seguente contenuto:

CCNET_CONF_DIR=/var/lib/seafile/ccnet
SEAFILE_CENTRAL_CONF_DIR=/etc/seafile
SEAFILE_CONF_DIR=/var/lib/seafile/storage
SEAFILE_VAR_DIR=/var/lib/seafile
SEAHUB_LOG_DIR=/var/log/seafile
SEAHUB_CACHE_DIR=/var/lib/seafile
SEAHUB_RODATA_DIR=/usr/share/seafile-server/seahub
SEAHUB_CONF_DIR=/etc/seafile
SEAFDAV_CONF=/etc/seafile/seafdav.conf

In questo modo possiamo tenere ben separati i file di configurazione dai dati variabili, rispettando il FHS. Per qualche strano motivo e contro intuitivamente, la variabile SEAFILE_CONF_DIR indica la posizione su cui salvare i dati sincronizzati. Ciò non stupisce molto perché la maggior parte di queste variabili non sono per niente documentate e sono sparse nel codice sorgente dei vari moduli di Seafile.

/etc/seafile/ccnet.conf:

[General]
SERVICE_URL = https://cloud.elinvention.ovh

/etc/seafile/seafile.conf:

[general]
enable_syslog = false

[fileserver]
port=8082
host=127.0.0.1

/etc/seafile/seafdav.conf (opzionale, solo se si desidera l’accesso WebDAV alle librerie di seafile):

[WEBDAV]
enabled = true
port = 6001
fastcgi = false
share_name = /seafdav

A questo punto abbiamo tutte le configurazioni necessarie (fiuh) e possiamo passare a configurare i 3+1 servizi con SystemD.

Partiamo con sudo systemctl edit --full --force seafile-server.service per creare un nuovo servizio. Inseriamo questa configurazione:

[Unit]
Description=Seafile fileserver daemon
ConditionPathExists=/etc/seafile/ccnet.conf
Requires=seafile-ccnet-server.service
PartOf=seafile.target

[Service]
Type=simple
User=seafile
Group=seafile
EnvironmentFile=/etc/seafile/env
ExecStart=/usr/bin/seaf-server -f \
    -F ${SEAFILE_CENTRAL_CONF_DIR} -c ${CCNET_CONF_DIR} \
    -d ${SEAFILE_CONF_DIR} \
    -l ${SEAHUB_LOG_DIR}/seafile.log
# seafile-server fails when ccnet-server is restarted
Restart=always
# sleep 2, workaround ccnet-server not saying when ready
RestartSec=2

# Hardening
ReadWritePaths=/var/log/seafile /var/lib/seafile
CapabilityBoundingSet=
NoNewPrivileges=True
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
PrivateUsers=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

Tutta la parte sotto hardening non è necessaria, ma aumenta la sicurezza.

sudo systemctl edit --full --force seafile-ccnet.service:

[Unit]
Description=Seafile RPC server daemon ccnet
ConditionPathExists=/etc/seafile/ccnet.conf
ConditionFileIsExecutable=/usr/bin/ccnet-server
PartOf=seafile.target

[Service]
Type=simple
User=seafile
Group=seafile
EnvironmentFile=/etc/seafile/env
ExecStart=/usr/bin/ccnet-server \
    -F ${SEAFILE_CENTRAL_CONF_DIR} -c ${CCNET_CONF_DIR} \
    -f ${SEAHUB_LOG_DIR}/ccnet.log
SuccessExitStatus=1 SIGKILL
ExecStopPost=/bin/rm -f ${CCNET_CONF_DIR}/ccnet.sock

ReadWritePaths=/var/log/seafile /var/lib/seafile/ccnet
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=True
SecureBits=noroot-locked
RestrictAddressFamilies=AF_UNIX
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
PrivateUsers=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

sudo systemctl edit --full --force seafile-seahub.service:

[Unit]
Description=Seafile seahub web interface using uwsgi
ConditionPathExists=/etc/seafile/ccnet.conf
Requires=seafile-ccnet-server.service seafile-server.service
PartOf=seafile.target

[Service]
Type=notify
User=seafile
Group=seafile
EnvironmentFile=/etc/seafile/env
WorkingDirectory=/usr/share/seafile-server/seahub/seahub
ExecStart=/usr/bin/uwsgi --die-on-term -c /etc/uwsgi/seafile.ini
Restart=on-failure
SuccessExitStatus=15 17 29 30
StandardError=journal
NotifyAccess=all
CacheDirectory=seahub

# Hardening
ReadWritePaths=/var/log/seafile/seahub.log /tmp/ /var/lib/seafile/
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
LockPersonality=yes
MemoryDenyWriteExecute=true
NoNewPrivileges=true
PrivateDevices=true
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
RestrictRealtime=true
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

sudo systemctl edit --full --force seafile-seafdav.service (solo se ci interessa il WebDAV):

[Unit]
Description=Seafile fileserver daemon
ConditionPathExists=/etc/seafile/ccnet.conf
Requires=seafile-ccnet-server.service
PartOf=seafile.target

[Service]
Type=simple
User=seafile
EnvironmentFile=/etc/seafile/env
ExecStart=/usr/bin/python -m wsgidav.server.server_cli \
    --server gunicorn --root / --log-file /var/log/seafile/seafdav.log \
    --port 6001 --host 127.0.0.1
# seafile-server fails when ccnet-server is restarted
Restart=always
# sleep 2, workaround ccnet-server not saying when ready
RestartSec=2

# Hardening
ReadWriteDirectories=/var/log/seafile
CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=True
SecureBits=noroot-locked
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
PrivateUsers=true
ProtectHostname=true
ProtectClock=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictRealtime=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
SystemCallFilter=@system-service

[Install]
WantedBy=multi-user.target

Infine una cosa simpatica per agevolare il tutto:

sudo systemctl edit --full --force seafile.target

[Unit]
Description=Seafile server
Requires=seafile-ccnet.service seafile-seafdav.service seafile-seahub.service seafile-server.service

Con quest’ultima configurazione possiamo lanciare sudo systemctl start seafile.target per avviare tutto l’ambaradan. Sostituendo stop o restart al posto di start possiamo fermare o riavviare tutti i servizi di Seafile in un comando solo e comodo.
Una piccola nota: perché funzioni stop e restart è necessario quel PartOf=seafile.target che ho aggiunto ad ogni unità. Ciò è dovuto alla semantica di Requires= definita da SystemD che vincola le unità solo al momento dell’avvio e non dell’arresto.

Infine configuriamo il proxy inverso, che nel mio caso è NGINX. Questo ultimo componente ci permette di accedere a Seafile in modo uniforme: tramite protocollo HTTPS sulla porta 443, ovviamente protetta dal protocollo TLS.

/etc/nginx/sites-available/seafile.conf:

upstream django {
	server 127.0.0.1:3579;
}

server {
	listen 80;
	server_name cloud.elinvention.ovh;
	return 301 https://$server_name$request_uri;
}

server {
	listen 443 http2 ssl;
	server_name cloud.elinvention.ovh;

	server_tokens off;

	ssl_certificate fullchain.pem;
	ssl_certificate_key privkey.pem;

	location / {
		include uwsgi_params;
		uwsgi_pass django;
		uwsgi_param X-Real-IP $remote_addr;
		uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
		uwsgi_param X-Forwarded-Host $server_name;
		uwsgi_read_timeout 36000;
		
	}

	location /seafhttp {
		rewrite ^/seafhttp(.*)$ $1 break;
		proxy_pass http://127.0.0.1:8082;
		client_max_body_size 0;
		proxy_connect_timeout  36000s;
		proxy_read_timeout  36000s;
		proxy_send_timeout  36000s;
		send_timeout  36000s;
		proxy_request_buffering off;
		proxy_http_version 1.1;
		access_log off;
	}

	location /seafdav {
		proxy_pass         http://127.0.0.1:6001;
		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_http_version 1.1;
		proxy_connect_timeout  36000s;
		proxy_read_timeout  36000s;
		proxy_send_timeout  36000s;
		send_timeout  36000s;
		client_max_body_size 0;
		proxy_request_buffering off;
	}

	location /media {
		alias /usr/share/seafile-server/seahub/media/;
	}

	location /media/avatars {
		# piccolo trick per far funzionare gli avatar
		alias /var/lib/seafile/data/avatars;
	}

	location /static {
		alias /usr/share/seafile-server/seahub/static;
	}
}

Questo è quanto. È un sacco di roba… però ne vale la pena. Si imparano un sacco di cose su come funzionano i vari servizi che spesso vengono utilizzati senza pensare a cosa ci sta dietro.

Il software scritto dalla squadra di Seafile non è malaccio: funziona bene, non ho perso file, mantiene le versioni dei file e il cestino in modo efficiente. Tuttavia il codice non è molto pulito… Ad esempio esaminando questo sorgente ci si trova di fronte ad una chiave privata RSA, così senza commenti… Ciò mi lascia non poco perplesso. Per fortuna l’unico socket creato da ccnet è un socket UNIX, quindi possiamo stare abbastanza tranquilli.

Che dire… spero che la squadra che sviluppa Seafile riesca a rendere il software più conforme alle pratiche convenzionali in ambito UNIX. Come dimostra questa configurazione, ciò non è affatto impossibile. Se poi il tutto diventasse più trasparente e documentato non sarebbe male.

Commenti

I commenti vengono gestiti con Commento e vengono salvati solo su questo server.