|
#!/bin/bash |
|
# |
|
# usage: |
|
# wget -O - https://gist.githubusercontent.com/Josh5/7f05e8383d7c8a76e511fd1764b0de2c/raw/install.sh | bash |
|
# |
|
|
|
# Ensure we are run as root... |
|
if [[ $(id -u) -gt 0 ]]; then |
|
echo "ERROR! This needs to be run as root... Exiting..." |
|
exit 1 |
|
fi |
|
|
|
echo |
|
echo "Installing/Upgrading No Frills NAS Config" |
|
echo |
|
echo |
|
|
|
_INSTALL_PATH="/opt/NoFrillsNAS" |
|
mkdir -p ${_INSTALL_PATH} |
|
chmod a+rw ${_INSTALL_PATH} |
|
|
|
_MOUNT_SCRIPT=$(cat << 'EOF' |
|
#!/bin/bash |
|
|
|
ACTION=$1 |
|
DEVBASE=$2 |
|
DEVICE="/dev/${DEVBASE}" |
|
STORAGE_POOL_PATH=/storage/pool |
|
|
|
# See if this drive is already mounted |
|
MOUNT_POINT=$(/bin/mount | /bin/grep ${DEVICE} | /usr/bin/awk '{ print $3 }') |
|
|
|
do_mount() |
|
{ |
|
if [[ -n ${MOUNT_POINT} ]]; then |
|
# Already mounted, exit |
|
echo "Ignoring due to device already mounted" |
|
exit 1 |
|
fi |
|
|
|
|
|
if [[ -e /opt/NoFrillsNAS/config.env ]]; then |
|
source /opt/NoFrillsNAS/config.env |
|
fi |
|
|
|
# Get info for this drive: $ID_FS_LABEL, $ID_FS_UUID, and $ID_FS_TYPE |
|
# This replaces whitespace with underscore |
|
BLKID_DATA="$(/sbin/blkid -o udev ${DEVICE})" |
|
eval $(echo "${BLKID_DATA// /_}") |
|
|
|
# Blacklist of lables... |
|
if [[ "$ID_FS_LABEL_FATBOOT" =~ ^(EFI|BOOT)$ ]]; then |
|
# Dont mount, exit |
|
echo "Ignoring due to ID_FS_LABEL_FATBOOT" |
|
exit 1 |
|
fi |
|
if [[ "$ID_FS_LABEL" =~ ^(EFI|BOOT|SYSTEM|Recovery|RECOVERY|SETTINGS|boot|root0|share0)$ ]]; then |
|
# Dont mount, exit |
|
echo "Ignoring due to ID_FS_LABEL" |
|
exit 1 |
|
fi |
|
if [[ " ${DISK_UUID_BLACKLIST[@]} " =~ " ${ID_FS_UUID} " ]]; then |
|
# Dont mount, exit |
|
echo "Ignoring due to ID_FS_UUID in config.env file DISK_UUID_BLACKLIST list" |
|
exit 1 |
|
fi |
|
|
|
# Figure out a mount point to use |
|
LABEL=${ID_FS_LABEL} |
|
if [[ -z "${LABEL}" ]]; then |
|
LABEL=${DEVBASE} |
|
elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then |
|
# Already in use, make a unique one |
|
LABEL+="-${DEVBASE}" |
|
fi |
|
LABEL=$(echo "${LABEL// /_}") |
|
MOUNT_POINT="/mnt/disks/${LABEL}" |
|
|
|
/bin/mkdir -p ${MOUNT_POINT} |
|
|
|
# Global mount options |
|
OPTS="rw,relatime" |
|
|
|
# File system type specific mount options |
|
if [[ ${ID_FS_TYPE} == "vfat" || ${ID_FS_TYPE} == "ntfs" ]]; then |
|
OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush" |
|
fi |
|
|
|
if ! /bin/mount -o ${OPTS} ${DEVICE} ${MOUNT_POINT}; then |
|
# Error during mount process: cleanup mountpoint |
|
/bin/rmdir ${MOUNT_POINT} |
|
exit 1 |
|
fi |
|
|
|
# Add it to the storage pool |
|
if ! `/usr/bin/grep "${STORAGE_POOL_PATH}" /proc/mounts &> /dev/null` ; then |
|
echo "Could not find mergerfs mount.. Lets create it!" |
|
mkdir -p /storage/pool |
|
mergerfs -o defaults,allow_other,direct_io,use_ino,minfreespace=1G,fsname=mergerfs '/mnt/disks/*' /storage/pool |
|
fi |
|
xattr -w user.mergerfs.srcmounts "+>${MOUNT_POINT}" ${STORAGE_POOL_PATH}/.mergerfs |
|
} |
|
|
|
do_checkmounts() |
|
{ |
|
# Delete all empty dirs in /mnt/disks that aren't being used as mount points. |
|
for f in /mnt/disks/* ; do |
|
|
|
if ! /bin/grep -q " $f " /etc/mtab; then |
|
# This directory is not a mount... |
|
|
|
# Remove all instances of it from the storage pool |
|
if `/usr/bin/grep "mergerfs ${STORAGE_POOL_PATH}" /proc/mounts &> /dev/null` ; then |
|
xattr -w user.mergerfs.srcmounts "-${f}" ${STORAGE_POOL_PATH}/.mergerfs |
|
fi |
|
|
|
# Remove the directory if it is empty |
|
if [[ -n $(/usr/bin/find "$f" -maxdepth 0 -type d -empty) ]]; then |
|
/bin/rmdir "$f" |
|
fi |
|
else |
|
# This directory is a mount... |
|
|
|
# Check that it is already in the pool |
|
if ! `xattr -p user.mergerfs.srcmounts /storage/pool/.mergerfs | grep ${f} &> /dev/null` ; then |
|
# Not already in the pool, time to add it I suppose... |
|
xattr -w user.mergerfs.srcmounts "+>${f}" ${STORAGE_POOL_PATH}/.mergerfs |
|
fi |
|
fi |
|
done |
|
} |
|
|
|
do_unmount() |
|
{ |
|
if [[ -n ${MOUNT_POINT} ]]; then |
|
/bin/umount -l ${DEVICE} |
|
fi |
|
|
|
do_checkmounts |
|
} |
|
|
|
case "${ACTION}" in |
|
add) |
|
do_mount |
|
;; |
|
remove) |
|
do_unmount |
|
;; |
|
check) |
|
do_checkmounts |
|
;; |
|
esac |
|
|
|
EOF |
|
) |
|
|
|
|
|
_UDEV_RULES=$(cat << 'EOF' |
|
# check for blockdevices, /dev/sd*, /dev/sr*, /dev/mmc*, and /dev/nvme* |
|
SUBSYSTEM!="block", KERNEL!="sd*|sr*|mmc*|nvme*", GOTO="modifications" |
|
|
|
# check for special partitions we dont want mount |
|
IMPORT{builtin}="blkid" |
|
ENV{ID_FS_LABEL}=="EFI|BOOT|SYSTEM|Recovery|RECOVERY|SETTINGS|boot|root0|share0", GOTO="exit" |
|
ENV{ID_FS_LABEL_FATBOOT}=="EFI|BOOT", GOTO="exit" |
|
ENV{ID_FS_TYPE}=="vfat", GOTO="exit" |
|
|
|
# /dev/sd*, /dev/mmc*, and /dev/nvme* with partitions/disk and filesystems only, and /dev/sr* disks only |
|
KERNEL=="sd*|mmc*|nvme*", ENV{DEVTYPE}=="partition|disk", ENV{ID_FS_USAGE}=="filesystem", GOTO="harddisk" |
|
GOTO="exit" |
|
|
|
# mount or umount for hdds |
|
LABEL="harddisk" |
|
ACTION=="add", PROGRAM="/usr/bin/sh -c '/usr/bin/grep -E ^/dev/%k\ /proc/mounts || true'", RESULT=="", RUN+="/bin/systemctl start disk-mount@%k.service" |
|
ACTION=="remove", RUN+="/bin/systemctl stop disk-mount@%k.service" |
|
GOTO="exit" |
|
|
|
# Modifications to mounts |
|
LABEL="modifications" |
|
SUBSYSTEM!="bdi", GOTO="exit" |
|
ACTION=="add", RUN+="/usr/local/bin/disk_mount check" |
|
ACTION=="remove", RUN+="/usr/local/bin/disk_mount check" |
|
GOTO="exit" |
|
|
|
# Exit |
|
LABEL="exit" |
|
|
|
EOF |
|
) |
|
|
|
|
|
_SYSTEMD_DISK_MOUNT_UNIT=$(cat << 'EOF' |
|
[Unit] |
|
Description=Mount Drive on %i |
|
|
|
[Service] |
|
Type=oneshot |
|
RemainAfterExit=true |
|
ExecStart=/usr/local/bin/disk_mount add %i |
|
ExecStop=/usr/local/bin/disk_mount remove %i |
|
|
|
EOF |
|
) |
|
|
|
|
|
_SAMBA_SHARE_CONFIG=$(cat << 'EOF' |
|
## NoFrillsNAS Samba Configuration |
|
|
|
[global] |
|
server string = NoFrillsNAS |
|
browseable = yes |
|
writeable = yes |
|
printable = no |
|
deadtime = 30 |
|
mangled names = no |
|
name resolve order = host bcast |
|
printcap name = /dev/null |
|
load printers = no |
|
enable core files = no |
|
passdb backend = smbpasswd |
|
smb encrypt = disabled |
|
fruit:model = Xserve |
|
|
|
# samba share options |
|
map to guest = Bad User |
|
guest account = root |
|
security = user |
|
|
|
# samba tuning options |
|
socket options = TCP_NODELAY IPTOS_LOWDELAY |
|
min receivefile size = 16384 |
|
aio read size = 16384 |
|
aio write size = 16384 |
|
use sendfile = yes |
|
|
|
# Log config |
|
log file = /var/log/samba/%m.log |
|
max log size = 50 |
|
|
|
# Samba Shares |
|
[config] |
|
comment = opt directory |
|
path = /opt/NoFrillsNAS |
|
browseable = yes |
|
read only = no |
|
guest ok = yes |
|
|
|
[storage] |
|
comment = Storage on NoFrillsNAS |
|
path = /storage |
|
browseable = yes |
|
read only = no |
|
guest ok = yes |
|
|
|
EOF |
|
) |
|
|
|
|
|
_NFS_SHARE_CONFIG=$(cat << 'EOF' |
|
# /etc/exports |
|
|
|
# Usually I only use NFS in a read-only situation and this file is configured |
|
# based on that assumption (primarily KODI). |
|
# http://kodi.wiki/view/NFS |
|
|
|
/storage/pool *(fsid=0,ro,sync,no_root_squash,no_subtree_check) |
|
|
|
EOF |
|
) |
|
|
|
|
|
function build_fstab { |
|
if [[ ! -e /etc/fstab.original ]]; then |
|
cp -v /etc/fstab /etc/fstab.original |
|
fi |
|
|
|
original_fstab=$(cat /etc/fstab.original) |
|
|
|
echo "${original_fstab}" > /etc/fstab |
|
echo "" >> /etc/fstab |
|
|
|
echo "# TMPFS for mount points:" >> /etc/fstab |
|
# Create a mount for our merged storage |
|
echo "tmpfs /storage tmpfs nosuid,nodev,noatime 0 0" >> /etc/fstab |
|
# Create a mount point for our disks |
|
echo "tmpfs /mnt/disks tmpfs nosuid,nodev,noatime 0 0" >> /etc/fstab |
|
# Create an initial ramdisk mount of really low limited size for the initial mergerfs pool |
|
echo "tmpfs /mnt/disks/ramdisk tmpfs nosuid,nodev,noatime,size=12k 0 0" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
|
|
if [[ -e ${_INSTALL_PATH}/config.env ]]; then |
|
source ${_INSTALL_PATH}/config.env |
|
if [[ ! -z ${CACHE_DISK_PART1_UUID} ]]; then |
|
echo "# MergerFS Cache:" >> /etc/fstab |
|
cache_disk_part1_fs_type=$(blkid -o value -s TYPE /dev/disk/by-uuid/${CACHE_DISK_PART1_UUID}) |
|
echo "/dev/disk/by-uuid/${CACHE_DISK_PART1_UUID} /storage/cache ${cache_disk_part1_fs_type} defaults 0 0" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
else |
|
echo "# (No cache disk part1 configured)" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
fi |
|
|
|
if [[ ! -z ${CACHE_DISK_PART2_UUID} ]]; then |
|
echo "# MergerFS Cache:" >> /etc/fstab |
|
cache_disk_part2_fs_type=$(blkid -o value -s TYPE /dev/disk/by-uuid/${CACHE_DISK_PART2_UUID}) |
|
echo "/dev/disk/by-uuid/${CACHE_DISK_PART2_UUID} /mnt/disks/cache ${cache_disk_part2_fs_type} defaults 0 0" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
else |
|
echo "# (No cache disk part2 configured)" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
fi |
|
else |
|
echo "# (No config file found)" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
fi |
|
|
|
# # [WIP] Detect all current disks - If they are internal, then add them to fstab |
|
# echo "# Data disks:" >> /etc/fstab |
|
# for disk_uuid in `ls /dev/disk/by-uuid/ 2> /dev/null`; do |
|
# if [[ " ${DISK_UUID_BLACKLIST[@]} " =~ " ${disk_uuid} " ]]; then |
|
# # Dont add to fstab |
|
# echo "# Ignoring disk '${disk_uuid}' due to config.env file DISK_UUID_BLACKLIST list" |
|
# continue |
|
# fi |
|
# # Ensure that they are not already in the fstab file |
|
# if `grep "${disk_uuid}" /etc/fstab &> /dev/null` ; then |
|
# # Dont add duplicate to fstab |
|
# echo "# Ignoring disk '${disk_uuid}' due to UUID existing in fstab" |
|
# continue |
|
# fi |
|
# # Get the disk FS type |
|
# disk_fs_type=$(blkid -o value -s TYPE /dev/disk/by-uuid/${disk_uuid}) |
|
# # If that failed, dont bother adding it to fstab |
|
# [[ -z ${disk_fs_type} ]] && continue |
|
# # Don't add vfat FS partitions to the fstab file |
|
# if [[ "${disk_fs_type,,}" =~ ^(vfat|swap|crypto_luks)$ ]]; then |
|
# # Dont add duplicate to fstab |
|
# echo "# Ignoring disk '${disk_uuid}' due to fs type being '${disk_fs_type}'" |
|
# continue |
|
# fi |
|
# # Get the disk FS type |
|
# disk_part_label=$(blkid -o value -s LABEL /dev/disk/by-uuid/${disk_uuid}) |
|
# if [[ "${disk_part_label,,}" =~ ^(efi|boot|system|recovery|settings|boot|root0|share0)$ ]]; then |
|
# # Dont add duplicate to fstab |
|
# echo "# Ignoring disk '${disk_uuid}' due to part label being '${disk_part_label}'" |
|
# continue |
|
# fi |
|
# # Figure out a mount point to use |
|
# ## Find out devbase |
|
# devbase=$( basename $( readlink -e /dev/disk/by-uuid/${disk_uuid} ) ) |
|
# mount_label=${disk_part_label} |
|
# if [[ -z "${mount_label}" ]]; then |
|
# LABEL=${devbase} |
|
# elif /bin/grep -q " /media/${LABEL} " /etc/mtab; then |
|
# # Already in use, make a unique one |
|
# mount_label+="-${devbase}" |
|
# fi |
|
# mount_label=$(echo "${mount_label// /_}") |
|
# mount_point="/mnt/disks/${mount_label}" |
|
# # Global mount options |
|
# OPTS="rw,relatime" |
|
# # File system type specific mount options |
|
# if [[ "${disk_fs_type,,}" =~ ^(vfat|ntfs)$ ]]; then |
|
# OPTS+=",users,gid=100,umask=000,shortname=mixed,utf8=1,flush" |
|
# fi |
|
# # Create fstab entry |
|
# echo "/dev/disk/by-uuid/${disk_uuid} ${mount_point} ${disk_fs_type} defaults,nofail,${OPTS} 0 0" >> /etc/fstab |
|
# done |
|
# disk_fs_type="" |
|
# echo "" >> /etc/fstab |
|
|
|
# Build MergerFS /storage/pool mount args |
|
storage_pool_mount_options="defaults,allow_other,direct_io,use_ino,minfreespace=1G,fsname=mergerfs" |
|
# Specify the create policy as mspmfs - most free space |
|
storage_pool_mount_options="${storage_pool_mount_options},category.create=mfs" |
|
# # [TODO] Specify the policy for ENOSPC (no space left on device) or EDQUOT (disk quota exceeded) as mfs - most free space |
|
# storage_pool_mount_options="${storage_pool_mount_options},moveonenospc=true" |
|
# Specify the policy for file caching as partial - Enables page caching. Underlying files cached, mergerfs files cached while open. |
|
storage_pool_mount_options="${storage_pool_mount_options},cache.files=partial,dropcacheonclose=true" |
|
|
|
echo "# MergerFS Mounts:" >> /etc/fstab |
|
echo "/mnt/disks/* /storage/pool fuse.mergerfs ${storage_pool_mount_options} 0 0" >> /etc/fstab |
|
echo "" >> /etc/fstab |
|
|
|
|
|
} |
|
|
|
|
|
|
|
# Install the mount script |
|
echo " ... Installing mount scripts ..." |
|
echo "${_MOUNT_SCRIPT}" > /usr/local/bin/disk_mount |
|
chmod 777 /usr/local/bin/disk_mount |
|
chown root:root /usr/local/bin/disk_mount |
|
echo |
|
|
|
# Install systemd unit |
|
echo " ... Installing systemd unit ..." |
|
echo "${_SYSTEMD_DISK_MOUNT_UNIT}" > /etc/systemd/system/[email protected] |
|
chmod 644 /etc/systemd/system/[email protected] |
|
chown root:root /etc/systemd/system/[email protected] |
|
systemctl daemon-reload |
|
echo |
|
|
|
# Install the UDEV rules |
|
echo " ... Installing UDEV rules ..." |
|
echo "${_UDEV_RULES}" > /etc/udev/rules.d/99-media-automount.rules |
|
chmod 664 /etc/udev/rules.d/99-media-automount.rules |
|
chown root:root /etc/udev/rules.d/99-media-automount.rules |
|
udevadm control --reload-rules |
|
echo |
|
|
|
# Ensure MergerFS is installed |
|
`command -v mergerfs &> /dev/null` || apt_deps_list="${apt_deps_list} mergerfs fuse xattr" |
|
`command -v fusermount &> /dev/null` || apt_deps_list="${apt_deps_list} fuse" |
|
`command -v xattr &> /dev/null` || apt_deps_list="${apt_deps_list} xattr" |
|
|
|
# Build the initial mounts in fstab |
|
echo " ... Installing fstab mount config ..." |
|
build_fstab |
|
echo |
|
|
|
# Ensure Docker is installed |
|
if ! `command -v docker &> /dev/null`; then |
|
echo " ... Found missing dep: 'docker' ... " |
|
echo |
|
echo " ... Installing docker-ce ..." |
|
curl https://get.docker.com | sh |
|
systemctl restart docker |
|
echo |
|
fi |
|
id -a 1000 | grep '(docker)' &> /dev/null || usermod -aG docker shuttle |
|
|
|
# Ensure Docker Compose is installed |
|
`command -v pip3 &> /dev/null` || apt_deps_list="${apt_deps_list} python3-pip" |
|
if ! `command -v docker-compose &> /dev/null`; then |
|
echo " ... Found missing dep: 'docker-compose' ..." |
|
`command -v docker-compose &> /dev/null` || pip3_deps_list="${pip3_deps_list} docker-compose" |
|
/etc/environment |
|
echo |
|
fi |
|
grep -q "PUID=1000" /etc/environment 2> /dev/null || echo "PUID=1000" >> /etc/environment |
|
grep -q "PGID=1000" /etc/environment 2> /dev/null || echo "PGID=1000" >> /etc/environment |
|
|
|
# Ensure network share servers are installed |
|
`dpkg -la | grep samba &> /dev/null` || apt_deps_list="${apt_deps_list} samba" |
|
`dpkg -la | grep nfs-kernel-server &> /dev/null` || apt_deps_list="${apt_deps_list} nfs-kernel-server" |
|
|
|
# Ensure cockpit is installed |
|
`dpkg -la | grep cockpit &> /dev/null` || apt_deps_list="${apt_deps_list} cockpit" |
|
`dpkg -la | grep cockpit-docker &> /dev/null` || dpkg_deps_list="${dpkg_deps_list} /opt/cockpit/cockpit-docker_215-1~ubuntu19.10.1_all.deb" |
|
mkdir -p /opt/cockpit |
|
if [[ ! -e /opt/cockpit/cockpit-docker_215-1~ubuntu19.10.1_all.deb ]]; then |
|
wget -o /dev/null \ |
|
-O /opt/cockpit/cockpit-docker_215-1~ubuntu19.10.1_all.deb \ |
|
https://launchpad.net/ubuntu/+source/cockpit/215-1~ubuntu19.10.1/+build/18889196/+files/cockpit-docker_215-1~ubuntu19.10.1_all.deb |
|
fi |
|
|
|
# Install all apt deps |
|
[[ ! -z ${apt_deps_list} ]] && echo " ... Installing deps from apt ..." && apt-get install -y ${apt_deps_list} && echo |
|
|
|
# Install all apt deps |
|
[[ ! -z ${dpkg_deps_list} ]] && echo " ... Installing deps from local deb packages ..." && apt-get install -y ${dpkg_deps_list} && echo |
|
|
|
# Install all pip3 deps |
|
[[ ! -z ${pip3_deps_list} ]] && echo " ... Installing deps from pip ..." && pip3 install --no-cache-dir ${pip3_deps_list} && echo |
|
|
|
# Configure network shares |
|
echo " ... Installing SAMBA config ..." |
|
mkdir -p /etc/samba |
|
mv -f /etc/samba/smb.conf /etc/samba/smb.conf.original |
|
echo "${_SAMBA_SHARE_CONFIG}" > /etc/samba/smb.conf |
|
systemctl restart smbd |
|
echo |
|
echo " ... Installing NFS config ..." |
|
mv -f /etc/exports /etc/exports.original |
|
echo "${_NFS_SHARE_CONFIG}" > /etc/exports |
|
exportfs -ra |
|
systemctl restart nfs-kernel-server |
|
echo |
|
|
|
# Install updater bin |
|
echo " ... Installing updater ..." |
|
cat << EOF > /usr/local/bin/no-frills-nas-update |
|
#!/bin/bash |
|
|
|
# Add header |
|
echo >> /var/log/no-frills-nas-update.log |
|
echo "#############################################" >> /var/log/no-frills-nas-update.log |
|
echo >> /var/log/no-frills-nas-update.log |
|
echo "Running $(date)" >> /var/log/no-frills-nas-update.log |
|
echo >> /var/log/no-frills-nas-update.log |
|
|
|
wget -O - https://gist.githubusercontent.com/Josh5/7f05e8383d7c8a76e511fd1764b0de2c/raw/install.sh | bash | tee -a /var/log/no-frills-nas-update.log |
|
echo >> /var/log/no-frills-nas-update.log |
|
|
|
EOF |
|
chmod +x /usr/local/bin/no-frills-nas-update |
|
echo |
|
|
|
# Update default docker-compose services |
|
echo " ... Updating system Docker stack ..." |
|
docker-compose -f /opt/NoFrillsNAS/default-docker-compose.yml down |
|
wget -o /dev/null \ |
|
-O /opt/NoFrillsNAS/default-docker-compose.yml \ |
|
https://gist.githubusercontent.com/Josh5/7f05e8383d7c8a76e511fd1764b0de2c/raw/docker-compose.yml |
|
sed -i "s|HOST_TIMEZONE|$(cat /etc/timezone)|" /opt/NoFrillsNAS/default-docker-compose.yml |
|
docker-compose -f /opt/NoFrillsNAS/default-docker-compose.yml pull |
|
docker-compose -f /opt/NoFrillsNAS/default-docker-compose.yml up -d |
|
echo |
|
|
|
echo "Done!" |
|
echo |