Skip to main content

Creating a compact, low-power, Linux-based file server

My requirements for this system:

  1. Fully compatible with Linux (in particular, Arch Linux)
  2. Silent (in particular, fanless)
  3. Low-power
  4. Compact and beautiful

Hardware

  1. Motherboard: ASRock Q1900DC-ITX
  2. RAM: Kingston KVR16LS11/8 (2)
  3. Disks: Kingston SV300S37A/240G (1), Western Digital WD10JFCX (2)
  4. Case: Streacom F1CWS Evo
  5. Power adapter: Targus APA042EU

My main reasons for selecting this particular motherboard were the low power consumption, convenience of a decent integrated CPU (Intel J1900), fanless design, Mini-ITX form factor, and complete compatibility with Linux. Other features, also relevant: 4 SATA connectors (2.0 and 3.0), plenty of USB ports (2.0 and 3.0), several graphics outputs (HDMI, DVI-D and D-Sub, all up to a resolution of 1920 by 1200 at 60Hz), and an integrated power supply unit which accepts any voltage between 9 and 19V, ±10%. An additional interesting detail is that this motherboard supports Wake-on-LAN. In terms of RAM, I used 2 8GB DDR3L modules, resulting in the maximum 16GB supported by the motherboard. As for non-volatile memory, I used 1 240GB SSD and 2 1TB HDDs, with the goal of installing the operating system and services on the SSD while leaving the HDDs exclusively for storage. The power required by the motherboard is supplied by a very compact and energy-efficient 90W power adapter. It is worth mentioning that this adapter comes with an integrated 2.1A USB fast charging port. And finally, the case: the F1CWS Evo is compatible with the Mini-ITX form factor, robust, and also quite small, especially considering that it can hold up to 3 2.5″ disks. This case is made entirely of aluminum with a very beautiful sandblast finish.

Software

The software that runs on the previously described hardware is a minimal install of Arch Linux which includes remote administration through SSH, RAID 1, LVM, an SMB server for local file sharing and an Apache HTTP server with WebDAV support. Everything is installed and configured using 3 bash scripts. Running the first script after booting from an Arch Linux image installs the base system on the SSD and configures SSH. In order for the script to execute properly, there must be internet connectivity and the public key key.pub intended to be used together with SSH must be placed in the same directory as the script. A root password is requested at the end of the script. After rebooting, SSH will be ready for a key-based login on port 22.

#!/bin/bash

if [ ! -f 'key.pub' ]; then exit 1; fi

echo 'Server = https://ftp.acc.umu.se/mirror/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist

sgdisk -Z /dev/sda
sgdisk -n 1:2048:+512M -t 1:EF00 /dev/sda
sgdisk -n 2:0:+16G -t 2:8300 /dev/sda
sgdisk -n 3:0:+8G -t 3:8200 /dev/sda
sgdisk -n 4:0:0 -t 4:8300 /dev/sda

mkfs.fat -F 32 /dev/sda1
mkfs.ext4 -F /dev/sda2
mkswap /dev/sda3

mount /dev/sda2 /mnt
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot

pacstrap /mnt base linux linux-firmware efibootmgr gptfdisk intel-ucode man-db lvm2 mdadm openssh apache samba nano rsync

echo "UUID=$(lsblk -no UUID /dev/sda2) / ext4 rw,relatime 0 1
UUID=$(lsblk -no UUID /dev/sda1) /boot vfat rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro 0 2
UUID=$(lsblk -no UUID /dev/sda3) none swap defaults 0 0" > /mnt/etc/fstab

echo 'vm.swappiness=10' > /mnt/etc/sysctl.d/99-sysctl.conf

sed -i -e 's/#en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /mnt/etc/locale.gen
arch-chroot /mnt locale-gen
echo 'LANG=en_US.UTF-8' > /mnt/etc/locale.conf

ln -sf '/usr/share/zoneinfo/Europe/Stockholm' /mnt/etc/localtime
arch-chroot /mnt hwclock --utc

sed -i -e 's/MODULES=()/MODULES=(i915)/' /mnt/etc/mkinitcpio.conf
arch-chroot /mnt mkinitcpio -p linux

arch-chroot /mnt bootctl --esp-path=/boot --no-variables install
arch-chroot /mnt bootctl --esp-path=/boot remove
arch-chroot /mnt bootctl --esp-path=/boot install
echo 'title "Arch Linux"
efi /vmlinuz-linux
options initrd=/intel-ucode.img initrd=/initramfs-linux.img root=/dev/sda2 rw' > /mnt/boot/loader/entries/arch.conf
echo 'default arch
timeout 0
editor no
auto-entries 0
auto-firmware 0' > /mnt/boot/loader/loader.conf

echo 'iceberg' > /mnt/etc/hostname

echo '[Match]
Name=en*
[Network]
DHCP=yes
LinkLocalAddressing=ipv4
IPv6AcceptRA=no' > /mnt/etc/systemd/network/20-wired.network
mv /mnt/etc/resolv.conf /mnt/etc/resolv.conf.default
ln -s '/run/systemd/resolve/resolv.conf' /mnt/etc/resolv.conf

cp /mnt/etc/ssh/sshd_config /mnt/etc/ssh/sshd_config.default
echo 'AuthorizedKeysFile /etc/ssh/authorized_keys
ChallengeResponseAuthentication no
LogLevel VERBOSE
MaxAuthTries 6
MaxSessions 1
PasswordAuthentication no
PermitRootLogin prohibit-password
Port 22
Protocol 2
PubkeyAuthentication yes
SyslogFacility AUTH
UsePAM no
PrintLastLog no
AddressFamily inet' > /mnt/etc/ssh/sshd_config
cat key.pub > /mnt/etc/ssh/authorized_keys

arch-chroot /mnt systemctl enable systemd-networkd.service systemd-resolved.service sshd.service

echo 'ROOT'
arch-chroot /mnt passwd

umount -R /mnt

The second script configures the last physical partition of the SSD for LVM and creates 2 logical partitions (128GB and 64GB), combines the 2 HDDs in RAID 1, configures the resulting space for LVM and creates 6 logical partitions (1 512GB, 3 128GB and 2 16GB). After rebooting again, these partitions will be automatically mounted.

#!/bin/bash

pvcreate /dev/sda4
vgcreate vg0 /dev/sda4
lvcreate -y --name lv0 --size 128G vg0
lvcreate -y --name lv1 --size 64G vg0

mkfs.ext4 /dev/vg0/lv0
mkfs.ext4 /dev/vg0/lv1

sgdisk -Z /dev/sdb
sgdisk -Z /dev/sdc

sgdisk -n 1:2048:+931G -t 1:FD00 /dev/sdb
sgdisk -n 1:2048:+931G -t 1:FD00 /dev/sdc

mdadm --create --run --metadata=1.2 --raid-devices=2 --level=1 /dev/md0 /dev/sdb1 /dev/sdc1

cp /etc/mdadm.conf /etc/mdadm.conf.default
echo -e "DEVICE partitions\n$(mdadm --detail --scan)" > /etc/mdadm.conf

mdadm --assemble /dev/md0

pvcreate /dev/md0
vgcreate vg1 /dev/md0
lvcreate -y --name lv0 --size 512G vg1
lvcreate -y --name lv1 --size 128G vg1
lvcreate -y --name lv2 --size 128G vg1
lvcreate -y --name lv3 --size 128G vg1
lvcreate -y --name lv4 --size 16G vg1
lvcreate -y --name lv5 --size 16G vg1

mkfs.ext4 /dev/vg1/lv0
mkfs.ext4 /dev/vg1/lv1
mkfs.ext4 /dev/vg1/lv2
mkfs.ext4 /dev/vg1/lv3
mkfs.ext4 /dev/vg1/lv4
mkfs.ext4 /dev/vg1/lv5

mkdir /srv/http/cloud
mkdir /srv/smb
mkdir /srv/smb/n0
mkdir /srv/smb/n1
mkdir /srv/smb/n2
mkdir /srv/smb/n3
mkdir /srv/smb/n4
mkdir /srv/smb/n5

sleep 60

echo "UUID=$(lsblk -no UUID /dev/vg0/lv0) /srv/http/cloud ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg0/lv1) /home ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv0) /srv/smb/n0 ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv1) /srv/smb/n1 ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv2) /srv/smb/n2 ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv3) /srv/smb/n3 ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv4) /srv/smb/n4 ext4 rw,relatime,data=ordered 0 2
UUID=$(lsblk -no UUID /dev/vg1/lv5) /srv/smb/n5 ext4 rw,relatime,data=ordered 0 2" >> /etc/fstab

The third and last script configures the SMB server and the HTTP server. The SMB server consists of 6 shares. In the HTTP server, access is denied except for the files index.html, favicon.ico, iceberg.svg, and any directories inside cloud with names beginning with p-, ending with -d, and with at least 1 character in the middle. Very importantly, WebDAV is enabled automatically in these special directories.

#!/bin/bash

echo '[global]
disable netbios = yes
disable spoolss = yes
dns proxy = no
guest account = nobody
load printers = no
log file = /var/log/samba/samba.log
log level = 0
map to guest = Bad User
max log size = 5000
printcap name = /dev/null
security = user
server string = SMB
workgroup = GROUP
[n0]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n0/n0
writeable = no
[n1]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n1/n1
writeable = no
[n2]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n2/n2
writeable = yes
[n3]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n3/n3
writeable = yes
[n4]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n4/n4
writeable = yes
[n5]
browseable = no
guest ok = yes
inherit permissions = yes
max connections = 5
guest only = yes
path = /srv/smb/n5/n5
writeable = yes' > /etc/samba/smb.conf

mkdir /srv/smb/n0/n0
mkdir /srv/smb/n1/n1
mkdir /srv/smb/n2/n2
mkdir /srv/smb/n3/n3
mkdir /srv/smb/n4/n4
mkdir /srv/smb/n5/n5

chmod 0777 /srv/smb/n0/n0
chmod 0777 /srv/smb/n1/n1
chmod 0777 /srv/smb/n2/n2
chmod 0777 /srv/smb/n3/n3
chmod 0777 /srv/smb/n4/n4
chmod 0777 /srv/smb/n5/n5

cp /etc/httpd/conf/httpd.conf /etc/httpd/conf/httpd.conf.default

echo 'ServerRoot "/etc/httpd"
Listen 80
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule dir_module modules/mod_dir.so
LoadModule logio_module modules/mod_logio.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule dav_module modules/mod_dav.so
LoadModule dav_fs_module modules/mod_dav_fs.so
User http
Group http
ServerAdmin r12624@gmail.com
DavLockDB "/etc/httpd/conf/davlock/davlock"
RewriteEngine on
RewriteRule "^/$" "/index.html" [PT]
DirectorySlash Off
<Directory "/">
Options -Indexes
AllowOverride None
AllowOverrideList None
Require all denied
</Directory>
DocumentRoot "/srv/http"
<Directory "/srv/http">
<Files "index.html">
Require all granted
</Files>
<Files "favicon.ico">
Require all granted
</Files>
<Files "iceberg.svg">
Require all granted
</Files>
</Directory>
<Directory "/srv/http/cloud/p-?*-d">
Dav On
Require all granted
</Directory>
ErrorLog "/var/log/httpd/error_log"
LogLevel info
LogFormat "%{%Y-%m-%d %H:%M:%S %z}t %h \"%r\" %>s %I %O \"%{Referer}i\" \"%{User-agent}i\"" simple
CustomLog "/var/log/httpd/access_log" simple
AddType text/html html
AddType image/x-icon ico
AddType image/svg+xml svg
PidFile "/run/httpd/httpd.pid"
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 0
MaxMemFree 2048
ErrorDocument 400 /index.html
ErrorDocument 401 /index.html
ErrorDocument 402 /index.html
ErrorDocument 403 /index.html
ErrorDocument 404 /index.html
ErrorDocument 405 /index.html
ErrorDocument 406 /index.html
ErrorDocument 407 /index.html
ErrorDocument 408 /index.html
ErrorDocument 409 /index.html
ErrorDocument 410 /index.html
ErrorDocument 411 /index.html
ErrorDocument 412 /index.html
ErrorDocument 413 /index.html
ErrorDocument 414 /index.html
ErrorDocument 415 /index.html
ErrorDocument 416 /index.html
ErrorDocument 417 /index.html
ErrorDocument 500 /index.html
ErrorDocument 501 /index.html
ErrorDocument 502 /index.html
ErrorDocument 503 /index.html
ErrorDocument 504 /index.html
ErrorDocument 505 /index.html
BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
BrowserMatch "MS FrontPage" redirect-carefully
BrowserMatch "^WebDrive" redirect-carefully
BrowserMatch "^WebDAVFS/1.[01234]" redirect-carefully
BrowserMatch "^gnome-vfs/1.0" redirect-carefully
BrowserMatch "^XML Spy" redirect-carefully
BrowserMatch "^Dreamweaver-WebDAV-SCM1" redirect-carefully
BrowserMatch " Konqueror/4" redirect-carefully
Timeout 60
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
UseCanonicalName Off
AccessFileName .htaccess
ServerTokens Min
ServerSignature Off
HostnameLookups Off
RequestReadTimeout header=20-40,MinRate=500 body=20,MinRate=500' > /etc/httpd/conf/httpd.conf

mkdir /etc/httpd/conf/davlock
chown http:http /etc/httpd/conf/davlock

systemctl enable smb.service httpd.service

After one last reboot, the server will be ready.