2022-10-11 05:01:14 +00:00
#! /bin/bash
source /sf/bin/funcs.sh
2022-12-16 23:31:12 +00:00
source /sf/bin/funcs_redis.sh
2022-10-11 05:01:14 +00:00
2022-10-18 17:57:35 +00:00
MARK_FN = "THIS-DIRECTORY-IS-NOT-ENCRYPTED--DO-NOT-USE.txt"
2022-10-11 05:01:14 +00:00
BAD( )
{
local delay
delay = " $1 "
shift 1
2023-02-19 17:15:42 +00:00
echo -e >& 2 " [ ${ CR } BAD ${ CN } ] $* "
2022-10-11 05:01:14 +00:00
sleep " $delay "
}
do_exit_err( )
{
# Kill the redis-loop
[ [ -z $CPID ] ] && { kill $CPID ; unset CPID; }
killall encfs # This will unmount
2022-11-25 11:35:31 +00:00
ERREXIT " $1 " "Exiting main thread"
2022-10-11 05:01:14 +00:00
}
xmkdir( )
{
2022-10-19 05:29:18 +00:00
[ [ -z $1 ] ] && return 255
2022-10-19 13:53:03 +00:00
[ [ -d " $1 " ] ] && return 0
2022-10-11 05:01:14 +00:00
mkdir " $1 "
}
2022-10-18 17:57:35 +00:00
# [name] [secdir] [rawdir]
2022-10-19 05:29:18 +00:00
# Return 1 when already mounted.
2022-10-18 17:57:35 +00:00
encfs_mkdir( )
2022-10-11 05:01:14 +00:00
{
local name
local secdir
2022-10-19 05:29:18 +00:00
local rawdir
2022-10-19 13:53:03 +00:00
2022-10-11 05:01:14 +00:00
name = " $1 "
2022-10-18 17:57:35 +00:00
secdir = " $2 "
2022-10-19 05:29:18 +00:00
rawdir = " $3 "
2022-10-11 05:01:14 +00:00
2022-11-25 11:35:31 +00:00
xmkdir " ${ rawdir } " || return 255
if [ [ -d " ${ secdir } " ] ] ; then
mountpoint " ${ secdir } " >/dev/null && {
2023-02-19 17:15:42 +00:00
# echo "[encfs-${name}] Already mounted."
2022-11-25 11:35:31 +00:00
[ [ ! -e " ${ secdir } / ${ MARK_FN } " ] ] && return 1
ERR " [encfs- ${ name } ] Mounted but markfile exist showing not encrypted. "
return 255
}
return 0
fi
# HERE: $secdir does _NOT_ exist.
2022-10-11 05:01:14 +00:00
2022-10-16 15:14:16 +00:00
# If EncFS died then a stale mount point might still exist.
# -d/-e/-f all fail (Transport endpoint is not connected)
# Force an unmount if it's not a directory (it's 'stale').
2022-11-25 11:35:31 +00:00
fusermount -zu " ${ secdir } " 2>/dev/null
2022-10-11 05:01:14 +00:00
xmkdir " ${ secdir } " || return 255
2022-10-18 17:57:35 +00:00
}
# [name] [SECRET] [SECDIR] [RAWDIR] [noatime,noexec] [info]
encfs_mount( )
{
local name
local s
local err
local secdir
local rawdir
local opts
local info
name = " $1 "
s = " $2 "
secdir = " $3 "
rawdir = " $4 "
opts = " $5 "
info = " $6 "
[ [ ! -e " ${ secdir } / ${ MARK_FN } " ] ] && { echo "THIS-IS-NOT-ENCRYPTED *** DO NOT USE *** " >" ${ secdir } / ${ MARK_FN } " || { BAD 0 "Could not create Markfile" ; return 255; } }
2022-10-11 05:01:14 +00:00
# local cpid
2022-10-18 17:57:35 +00:00
LOG " ${ name } " " Mounting ${ info } "
2022-10-16 15:14:16 +00:00
# echo "$s" | bash -c "exec -a '[encfs-${name:-BAD}]' encfs --standard --public -o nonempty -S \"${rawdir}\" \"${secdir}\" -- -o fsname=/dev/sec-\"${name}\" -o \"${opts}\"" >/dev/null
# --nocache -> Blindly hoping that encfs consumes less memory?!
2022-11-16 10:42:27 +00:00
# -s single thread. Seems to give better I/O performance and uses less memory (!)
2023-02-19 17:15:42 +00:00
ERRSTR = $( echo " $s " | nice -n10 bash -c " exec -a '[encfs- ${ name :- BAD } ]' encfs -s --nocache --standard --public -o nonempty -S \" ${ rawdir } \" \" ${ secdir } \" -- -o \" ${ opts } \" " )
2022-10-11 05:01:14 +00:00
ret = $?
[ [ $ret -eq 0 ] ] && return 0
ERR " [encfs- ${ name } ] failed "
return 255
}
# [name]
encfs_mount_server( )
{
local secdir
local secret
local name
secdir = " /encfs/sec/ ${ 1 } -root "
2022-10-18 17:57:35 +00:00
rawdir = " /encfs/raw/ ${ 1 } -root "
2022-10-11 05:01:14 +00:00
name = " $1 "
secret = " $2 "
2022-10-18 17:57:35 +00:00
encfs_mkdir " ${ name } " " ${ secdir } " " ${ rawdir } " || return
2022-10-11 05:01:14 +00:00
# We use a file as a semaphore so that we dont need to give
# the waiting container access to redis.
[ [ -f " ${ secdir } /.IS-ENCRYPTED " ] ] && rm -f " ${ secdir } /.IS-ENCRYPTED "
2022-11-28 14:36:13 +00:00
# Note: Use SLEEPEXIT to give sf-destructor enough time to start (and aquire `pid: "service:sf-encfsd"`)
# so that sf-encfs has enough time to report the error (likely cause is bad SF_SEED=)
encfs_mount " ${ name } " " ${ secret } " " ${ secdir } " " ${ rawdir } " "noexec,noatime" || SLEEPEXIT 254 15 " EncFS ${ name } -root failed ' ${ ERRSTR } . "
2022-10-16 15:14:16 +00:00
touch " ${ secdir } /.IS-ENCRYPTED "
2022-10-11 05:01:14 +00:00
2022-11-16 10:42:27 +00:00
[ [ ! -d " ${ secdir } / ${ name } " ] ] && mkdir " ${ secdir } / ${ name } "
2022-10-11 05:01:14 +00:00
}
2022-10-18 17:57:35 +00:00
# [LID]
load_limits( )
{
local lid
lid = " $1 "
2022-11-10 10:00:54 +00:00
unset SF_USER_FS_SIZE
unset SF_USER_FS_INODE
unset SF_USER_ROOT_FS_SIZE
unset SF_USER_ROOT_FS_INODE
2024-01-16 13:47:48 +00:00
unset SF_HOSTNAME
2022-11-10 10:00:54 +00:00
2023-11-07 08:17:51 +00:00
source " /sf/run/users/lg- ${ lid } /limits.txt "
2022-10-18 17:57:35 +00:00
}
2022-11-11 18:24:04 +00:00
dir2prjid( )
{
local dir
local p
dir = " $1 "
p = $( lsattr -dp " ${ dir } " )
p = ${ p %% --* }
echo " ${ p ##* } "
}
2022-11-10 10:00:54 +00:00
# Set XFS quota on sub folders. This normally only needs to be done
# when the subfolder is created.
# Note: Set XFS-QUOTA _every time_ just in case we restored from backup
#
# XFS quota on subfolders inside an encfs are tricky:
# - xfs_quota only works on the underlaying encfs raw data (encrypted)
# - We do not know the directory name (inside /raw) because it's encrypted (!)
# - Instead use a trick:
# 1. Create the directory.
# 2. Search for identical inode in RAWDIR
xfs_quota_sub( )
{
local prjid
local base_rawdir
local secdir
local rawdir
local err
prjid = " $1 "
base_rawdir = " $2 "
secdir = " $3 "
if [ [ ! -d " ${ secdir } " ] ] ; then
mkdir " ${ secdir } "
# Dont leak when directory was created as this is also the user's login time.
touch -t 197001011200 " ${ secdir } "
fi
# Find the RAWDIR (it has the same inode; inode is passed through by EncFS)
inode = $( stat -c %i " ${ secdir } " )
rawdir = $( find " ${ base_rawdir } " -maxdepth 1 -type d -inum " $inode " )
[ [ -z " ${ rawdir } " ] ] || [ [ ! -d " ${ rawdir } " ] ] && { ERR "XFS rawdir not found" ; return ; }
2022-11-11 18:24:04 +00:00
local prjid_old
prjid_old = $( dir2prjid " ${ rawdir } " )
[ [ " $prjid_old " != " $prjid " ] ] && {
err = $( xfs_quota -x -c " project -s -p ${ rawdir } ${ prjid } " 2>& 1) || { ERR " XFS Quota /everyone: \n' $err ' " ; }
}
2022-11-10 10:00:54 +00:00
}
# [LID] [SECRET]
cmd_user_mount( )
{
local lid
local secret
local rawdir
local secdir
local prjid
lid = " $1 "
secret = " ${ 2 //[^[ : alnum : ]]/ } "
[ [ ${# secret } -ne 24 ] ] && { BAD 0 " Bad secret=' $secret ' " ; return 255; }
2023-02-19 17:15:42 +00:00
secdir = " /encfs/sec/lg- ${ lid } "
rawdir = " /encfs/raw/user/lg- ${ lid } "
2022-11-10 10:00:54 +00:00
encfs_mkdir " ${ lid } " " ${ secdir } " " ${ rawdir } "
ret = $?
[ [ $ret -eq 1 ] ] && return 0 # Already mounted
2022-11-11 18:24:04 +00:00
[ [ $ret -ne 0 ] ] && return 255
2022-11-10 10:00:54 +00:00
# HERE: Not yet mounted.
# Set XFS limits
load_limits " ${ lid } "
2024-01-16 13:47:48 +00:00
[ [ -z $SF_HOSTNAME ] ] && { SF_HOSTNAME = $( <" /config/db/user/lg- ${ lid } /hostname " ) || return 255; }
[ [ -n $SF_USER_FS_SIZE ] ] && {
2023-02-19 17:15:42 +00:00
SF_NUM = $( <" /config/db/user/lg- ${ lid } /num " ) || return 255
2022-11-10 10:00:54 +00:00
prjid = $(( SF_NUM + 10000000 ))
DEBUGF " SF_NUM= ${ SF_NUM } , prjid= ${ prjid } , SF_HOSTNAME= ${ SF_HOSTNAME } , INODE= ${ SF_USER_FS_INODE } , SIZE= ${ SF_USER_FS_SIZE } "
err = $( xfs_quota -x -c " limit -p ihard= ${ SF_USER_FS_INODE :- 16384 } bhard= ${ SF_USER_FS_SIZE :- 128m } ${ prjid } " 2>& 1) || { ERR " XFS-QUOTA: \n' $err ' " ; return 255; }
2022-11-11 18:24:04 +00:00
# Only set it if it isnt already set (this can take very long to complete)
local prjid_old
prjid_old = $( dir2prjid " ${ rawdir } " )
[ [ " $prjid_old " != " $prjid " ] ] && {
2023-02-19 17:15:42 +00:00
DEBUGF " Creating new prjid= $prjid , old $prjid_old "
2022-11-11 18:24:04 +00:00
err = $( xfs_quota -x -c " project -s -p ${ rawdir } ${ prjid } " 2>& 1) || { ERR " XFS-QUOTA /sec: \n' $err ' " ; return 255; }
}
2022-11-10 10:00:54 +00:00
}
# Mount if not already mounted. Continue on error (let client hang)
encfs_mount " ${ lid } " " ${ secret } " " ${ secdir } " " ${ rawdir } " "noatime" " /sec (INODE_MAX= ${ SF_USER_FS_INODE } , BYTES_MAX= ${ SF_USER_FS_SIZE } ) " || return 255
# Extend same project quota to /onion and /everyone/SF_HOSTNAME
[ [ -n $prjid ] ] && {
2024-01-16 13:47:48 +00:00
xfs_quota_sub " ${ prjid } " " ${ BASE_RAWDIR_EVR } " " /encfs/sec/everyone-root/everyone/ ${ SF_HOSTNAME : ? } "
2022-11-10 10:00:54 +00:00
xfs_quota_sub " ${ prjid } " " ${ BASE_RAWDIR_WWW } " " /encfs/sec/www-root/www/ ${ SF_HOSTNAME ,, } "
}
2023-02-27 17:17:32 +00:00
# Mark as mounted (for destructor to track)
touch " /sf/run/encfsd/user/lg- ${ lid } "
2022-11-10 10:00:54 +00:00
return 0
}
2023-02-19 17:15:42 +00:00
# Set ROOT_FS xfs quota and move encfs to lg's cgroup
# [LID] "[CID] [INODE LIMIT] [relative OVERLAY2 dir]"
cmd_setup_encfsd( )
2022-10-19 05:29:18 +00:00
{
2022-11-10 10:00:54 +00:00
local lid
local ilimit
local dir
local prjid
2023-02-19 17:15:42 +00:00
local cid
local pid
local str
local err
local cg_fn
2022-11-10 10:00:54 +00:00
lid = " $1 "
2023-02-19 17:15:42 +00:00
cid = ${ 2 %% * }
str = ${ 2 #* }
2023-02-27 17:17:32 +00:00
ilimit = ${ str %% * }
2022-11-10 10:00:54 +00:00
ilimit = ${ ilimit //[^0-9]/ }
2023-02-19 17:15:42 +00:00
dir = " /var/lib/docker/overlay2/ ${ str #* } "
# Move lg's encfsd to lg's cgroup.
# Note: We can not use cgexec because encfsd needs to be started before the lg container
# is started. Thus we only know the LG's container-ID _after_ encfsd has started.
pid = $( pgrep " ^\[encfs- ${ lid } " )
unset err
cg_fn = " system.slice/containerd.service/sf.slice/sf-guest.slice/ ${ cid } /tasks "
if [ [ -e " /sys/fs/cgroup/cpu/ ${ cg_fn } " ] ] ; then
## CGROUPv1
# It's really really bad to use cgroup/unified if /sys/fs/cgroup is cgroup-v1:
# It messses up /proc/<PID>/cgroup and nobody really knows the effect of this.
# Note: The 'pid' is local to this namespace. However, linux kernel still accepts
# it for moving between cgroups (but will yield an error).
echo " $pid " >" /sys/fs/cgroup/cpu/ ${ cg_fn } " 2>/dev/null || err = 1
echo " $pid " >" /sys/fs/cgroup/blkio/ ${ cg_fn } " 2>/dev/null || err = 1
else
## CGROUPv2
cg_fn = "/sys/fs/cgroup/sf.slice/sf-guest.slice"
str = " ${ cid } "
cg_fn = " /sys/fs/cgroup/sf.slice/sf-guest.slice/docker- ${ cid } .scope/cgroup.procs "
[ [ ! -e " ${ cg_fn } " ] ] && cg_fn = " /sys/fs/cgroup/sf.slice/sf-guest.slice/ ${ cid } /cgroup.procs "
echo " $pid " >" ${ cg_fn } " || err = 1
fi
grep -F sf-guest.slice " /proc/ ${ pid } /cgroup " & >/dev/null || BAD 0 " Could not move encfs[pid= $pid ] to lg's cgroup[cid= $cid ] "
2023-02-27 17:17:32 +00:00
[ [ -z $ilimit ] ] && { BAD 0 "ilimit is empty" ; return 255; }
[ [ $ilimit -le 0 ] ] && return 0
2023-02-19 17:15:42 +00:00
2023-05-10 10:57:17 +00:00
# Setup LG's Root-FS inode limit (Docker's '--storage-opt' only limits size)
2022-11-10 10:00:54 +00:00
[ [ ! -d " ${ dir } " ] ] && { BAD 0 " Not found: ${ dir } . " ; return 255; }
2022-11-11 18:24:04 +00:00
s = $( lsattr -dp " ${ dir } " )
2022-11-10 10:00:54 +00:00
prjid = ${ s %% --* }
prjid = ${ prjid ##* } # trim leading white spaces
2023-05-10 10:57:17 +00:00
[ [ -z $prjid ] ] || [ [ $prjid -eq 0 ] ] && { BAD 0 " [ $lid ] Invalid prjid=' $prjid ' on ' $dir ' " ; return 255; }
2022-11-10 10:00:54 +00:00
xfs_quota -x -c " limit -p ihard= ${ ilimit } $prjid " || { BAD 0 "XFS_QUOTA filed" ; return 255; }
2022-10-19 05:29:18 +00:00
}
2022-11-25 11:35:31 +00:00
# Note: Started as background process.
2022-10-11 05:01:14 +00:00
redis_loop_forever( )
{
2022-10-18 17:57:35 +00:00
local secdir
2022-11-10 10:00:54 +00:00
local cmd
local lid
local reqid
2022-11-25 11:35:31 +00:00
local n_conn_err
2022-10-18 17:57:35 +00:00
2022-10-11 05:01:14 +00:00
while :; do
2022-12-16 23:31:12 +00:00
res = $( redr BLPOP encfs 0) || {
2022-11-25 11:35:31 +00:00
( ( n_conn_err++) )
[ [ $n_conn_err -gt 180 ] ] && ERREXIT 250 "Giving up..."
WARN "Waiting for Redis..."
sleep 1
continue
}
unset n_conn_err
2022-10-11 05:01:14 +00:00
[ [ -z $res ] ] && {
# HERE: no result
WARN "Redis: Empty results."
sleep 1
continue
}
2022-11-10 10:00:54 +00:00
# Remove all but last line
2022-10-11 05:01:14 +00:00
res = " ${ res ##* $'\n' } "
2022-10-18 17:57:35 +00:00
2022-11-10 10:00:54 +00:00
# [REQID] [LID] [CMD] [ARGS]
reqid = ${ res %% * }
reqid = ${ reqid //[^0-9]/ }
res = ${ res #* }
2022-10-19 13:53:03 +00:00
2022-11-10 10:00:54 +00:00
lid = " ${ res : 0 : 10 } " # the LID
lid = " ${ lid //[^[ : alnum : ]]/ } "
[ [ ${# lid } -ne 10 ] ] && { BAD 0 " Bad lid=' $lid ' " ; continue ; }
res = ${ res : 11 }
2022-10-18 17:57:35 +00:00
2022-11-10 10:00:54 +00:00
cmd = ${ res : 0 : 1 }
res = ${ res : 2 }
if [ [ " $cmd " = = "X" ] ] ; then
2023-02-19 17:15:42 +00:00
cmd_setup_encfsd " ${ lid } " " ${ res } " || continue
2022-11-10 10:00:54 +00:00
elif [ [ " $cmd " = = "M" ] ] ; then
cmd_user_mount " ${ lid } " " ${ res } " || continue
else
continue
fi
2022-10-11 05:01:14 +00:00
2022-11-10 10:00:54 +00:00
# ALL OK
2022-12-16 23:31:12 +00:00
red RPUSH " encfs- ${ reqid } - ${ lid } - ${ cmd } " "OK" >/dev/null
2022-10-11 05:01:14 +00:00
done
}
_trap( ) { :; }
# Install an empty signal handler so that 'wait()' (below) returns
trap _trap SIGTERM
trap _trap SIGINT
[ [ -z $SF_SEED ] ] && ERREXIT 255 "SF_SEED= not set"
[ [ -z $SF_REDIS_AUTH ] ] && ERREXIT 255 "SF_REDIS_AUTH= not set"
ENCFS_SERVER_PASS = $( echo -n " EncFS-SERVER-PASS- ${ SF_SEED } " | sha512sum | base64)
ENCFS_SERVER_PASS = " ${ ENCFS_SERVER_PASS //[^[ : alpha : ]] } "
ENCFS_SERVER_PASS = " ${ ENCFS_SERVER_PASS : 0 : 24 } "
export REDISCLI_AUTH = " ${ SF_REDIS_AUTH } "
2022-10-18 17:57:35 +00:00
2022-10-11 05:01:14 +00:00
# Mount Segfault-wide encrypted file systems
encfs_mount_server "everyone" " ${ ENCFS_SERVER_PASS } "
2022-11-10 10:00:54 +00:00
# Create mountpoint for guest's /everyone/this
[ [ ! -d "/encfs/sec/everyone-root/everyone/this" ] ] && mkdir "/encfs/sec/everyone-root/everyone/this"
2022-11-25 11:35:31 +00:00
cp "/config/etc/sf/WARNING---SHARED-BETWEEN-ALL-SERVERS---README.txt" "/encfs/sec/everyone-root/everyone"
2022-10-11 05:01:14 +00:00
encfs_mount_server "www" " ${ ENCFS_SERVER_PASS } "
2022-11-10 10:00:54 +00:00
BASE_RAWDIR_WWW = $( find /encfs/raw/www-root/ -maxdepth 1 -type d -inum " $( stat -c %i /encfs/sec/www-root/www) " )
BASE_RAWDIR_EVR = $( find /encfs/raw/everyone-root -maxdepth 1 -type d -inum " $( stat -c %i /encfs/sec/everyone-root/everyone) " )
2022-10-18 17:57:35 +00:00
2022-11-10 10:00:54 +00:00
[ [ ! -d " ${ BASE_RAWDIR_WWW : ? } " ] ] && ERREXIT 255 "Cant find encrypted /encfs/raw/www-root/*"
[ [ ! -d " ${ BASE_RAWDIR_EVR : ? } " ] ] && ERREXIT 255 "Cant find encrypted /encfs/raw/everyone-root/*"
2022-10-18 17:57:35 +00:00
# sleep infinity
2022-10-11 05:01:14 +00:00
# Need to start redis-loop in the background. This way the foreground bash
# will still be able to receive SIGTERM.
redis_loop_forever &
CPID = $!
wait $CPID # SIGTERM will wake us
# HERE: Could be a SIGTERM or a legitimate exit by redis_loop process
do_exit_err $?