Files
debian/prebuilds/usr/local/lib/libos.sh
T

512 lines
13 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Ver: 1.4 by Endial Fang (endial@126.com)
#
# 操作系统控制函数库
# 加载依赖项
source "/usr/local/lib/liblog.sh"
source "/usr/local/lib/libfs.sh"
source "/usr/local/lib/libvalidations.sh"
# 函数列表
# 检测指定用户账户是否存在
# 参数:
# $1 - 用户账户
# 返回值:
# 0 / 1
is_user_exists() {
local user="${1:?user is missing}"
id "$user" >/dev/null 2>&1
}
# 检测指定用户分组是否存在
# 参数:
# $1 - 用户组
# 返回值:
# 0 / 1
is_group_exists() {
local group="${1:?group is missing}"
getent group "$group" >/dev/null 2>&1
}
# 检测当前是否为 root 用户
# 返回值:
# true / false
is_root() {
if [[ "$(id -u)" = "0" ]]; then
debug "Run as root."
true
else
debug "Run as non-root: $(id -u)"
false
fi
}
# 确保指定用户组在系统中存在
# 参数:
# $1 - 用户组
# 标志位:
# -i|--gid - 用户组 ID
# -s|--system - 创建系统用户 (uid <= 999)
ensure_group_exists() {
local group="${1:?group is missing}"
local gid=""
local is_system_user=false
# 检测标志位
shift 1
while [ "$#" -gt 0 ]; do
case "$1" in
-i | --gid)
shift
gid="${1:?missing gid}"
;;
-s | --system)
is_system_user=true
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
if ! group_exists "$group"; then
local -a args=("$group")
if [[ -n "$gid" ]]; then
if group_exists "$gid"; then
error "The GID $gid is already in use." >&2
return 1
fi
args+=("--gid" "$gid")
fi
$is_system_user && args+=("--system")
groupadd "${args[@]}" >/dev/null 2>&1
fi
}
# 确保指定用户在系统中存在
# 参数:
# $1 - 用户
# 标志位:
# -i|--uid - 用户 ID
# -a|--append-groups - 附加用户组 (逗号分隔)
# -g|--group - 用户组
# -h|--home - 用户家目录
# -s|--system - 创建系统用户 (uid <= 999)
ensure_user_exists() {
local user="${1:?user is missing}"
local uid=""
local group=""
local append_groups=""
local home=""
local is_system_user=false
# Validate arguments
shift 1
while [ "$#" -gt 0 ]; do
case "$1" in
-i | --uid)
shift
uid="${1:?missing uid}"
;;
-g | --group)
shift
group="${1:?missing group}"
;;
-a | --append-groups)
shift
append_groups="${1:?missing append_groups}"
;;
-h | --home)
shift
home="${1:?missing home directory}"
;;
-s | --system)
is_system_user=true
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
if ! user_exists "$user"; then
local -a user_args=("-N" "$user")
if [[ -n "$uid" ]]; then
if user_exists "$uid"; then
error "The UID $uid is already in use."
return 1
fi
user_args+=("--uid" "$uid")
else
$is_system_user && user_args+=("--system")
fi
useradd "${user_args[@]}" >/dev/null 2>&1
fi
if [[ -n "$group" ]]; then
local -a group_args=("$group")
$is_system_user && group_args+=("--system")
ensure_group_exists "${group_args[@]}"
usermod -g "$group" "$user" >/dev/null 2>&1
fi
if [[ -n "$append_groups" ]]; then
local -a groups
read -ra groups <<<"$(tr ',;' ' ' <<<"$append_groups")"
for group in "${groups[@]}"; do
ensure_group_exists "$group"
usermod -aG "$group" "$user" >/dev/null 2>&1
done
fi
if [[ -n "$home" ]]; then
mkdir -p "$home"
usermod -d "$home" "$user" >/dev/null 2>&1
configure_permissions_ownership "$home" -d "775" -f "664" -u "$user" -g "$group"
fi
}
# 获取系统可用内存大小(MB)信息
# 返回值:
# 内存大小(兆字节)
get_total_memory() {
echo $(($(grep MemTotal /proc/meminfo | awk '{print $2}') / 1024))
}
# 获取以内存定量方式描述的机器类型
# 标志位:
# --memory - 内存大小 (MB,可选)
# 返回值:
# 类型名称
get_machine_size() {
local memory=""
# 检测标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
--memory)
shift
memory="${1:?missing memory}"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
if [[ -z "$memory" ]]; then
debug "Memory was not specified, detecting available memory automatically"
memory="$(get_total_memory)"
fi
sanitized_memory=$(convert_to_mb "$memory")
if [[ "$sanitized_memory" -gt 26000 ]]; then
echo 2xlarge
elif [[ "$sanitized_memory" -gt 13000 ]]; then
echo xlarge
elif [[ "$sanitized_memory" -gt 6000 ]]; then
echo large
elif [[ "$sanitized_memory" -gt 3000 ]]; then
echo medium
elif [[ "$sanitized_memory" -gt 1500 ]]; then
echo small
else
echo micro
fi
}
# 获取已定义的所有内存大小描述
# 返回值:
# 描述值列表
get_supported_machine_sizes() {
echo micro small medium large xlarge 2xlarge
}
# 将以字符串表示的内存大小转换为以MB为单位的内存大小值 (i.e. 2G -> 2048)
# 参数:
# $1 - 内存大小
# 返回值:
# 转换后的数值
convert_to_mb() {
local amount="${1:-}"
if [[ $amount =~ ^([0-9]+)(m|M|g|G) ]]; then
size="${BASH_REMATCH[1]}"
unit="${BASH_REMATCH[2]}"
if [[ "$unit" = "g" || "$unit" = "G" ]]; then
amount="$((size * 1024))"
else
amount="$size"
fi
fi
echo "$amount"
}
# 如果禁用调试模式,将输出信息重定向至 /dev/null
# 参数:
# $@ - 待执行的命令
debug_execute() {
if is_boolean_yes "${ENV_DEBUG:-false}"; then
"$@"
else
"$@" >/dev/null 2>&1
fi
}
# 重试执行命令
# 参数:
# $1 - 命令 (字符串)
# $2 - 最大尝试次数. 默认值: 12
# $3 - 重试前等待时间(秒). 默认值: 5
# 返回值:
# 0 / 1
retry_while() {
local cmd="${1:?cmd is missing}"
local retries="${2:-12}"
local sleep_time="${3:-5}"
local return_value=1
read -r -a command <<<"$cmd"
for ((i = 1; i <= retries; i += 1)); do
"${command[@]}" && return_value=0 && break
sleep "$sleep_time"
done
return $return_value
}
# 生成随机字符串
# 标志位:
# -t|--type - 字符串类型 (ascii, alphanumeric, numeric). 默认值: ascii
# -c|--count - 字符串长度. 默认值: 32
# 返回值:
# 字符串
generate_random_string() {
local type="ascii"
local count="32"
local filter
local result
# 检测标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
-t | --type)
shift
type="$1"
;;
-c | --count)
shift
count="$1"
;;
*)
echo "Invalid command line flag $1" >&2
return 1
;;
esac
shift
done
# 检测类型
case "$type" in
ascii)
filter="[:print:]"
;;
numeric)
filter="0-9"
;;
alphanumeric)
filter="a-zA-Z0-9"
;;
alphanumeric+special|special+alphanumeric)
# Limit variety of special characters, so there is a higher chance of containing more alphanumeric characters
# Special characters are harder to write, and it could impact the overall UX if most passwords are too complex
filter='a-zA-Z0-9:@.,/+!='
;;
*)
echo "Invalid type ${type}" >&2
return 1
;;
esac
# Obtain count + 10 lines from /dev/urandom to ensure that the resulting string has the expected size
# Note there is a very small chance of strings starting with EOL character
# Therefore, the higher amount of lines read, this will happen less frequently
result="$(head -n "$((count + 10))" /dev/urandom | tr -dc "$filter" | head -c "$count")"
echo "$result"
}
# 为指定字符串生成 MD5 值
# 参数:
# $1 - 字符串
# 返回值:
# 字符串对应的 MD5
generate_md5_hash() {
local -r str="${1:?missing input string}"
echo -n "$str" | md5sum | awk '{print $1}'
}
# 为指定字符串生成 SHA1 值
# 参数:
# $1 - 字符串
# $2 - 算法 - 1 (default), 224, 256, 384, 512
# 返回值:
# 字符串对应的 SHA1
generate_sha_hash() {
local -r str="${1:?missing input string}"
local -r algorithm="${2:-1}"
echo -n "$str" | "sha${algorithm}sum" | awk '{print $1}'
}
# 将一个字符串转换为其十六进制表示形式
# 参数:
# $1 - 字符串
# 返回值:
# 十六进制表示形式
convert_to_hex() {
local -r str=${1:?missing input string}
local -i iterator
local char
for ((iterator = 0; iterator < ${#str}; iterator++)); do
char=${str:iterator:1}
printf '%x' "'${char}"
done
}
# 获取启动时间
# 返回值:
# 启动时间
get_boot_time() {
stat /proc --format=%Y
}
# 获取机器 ID
# 返回值:
# 机器 ID
get_machine_id() {
local machine_id
if [[ -f /etc/machine-id ]]; then
machine_id="$(cat /etc/machine-id)"
fi
if [[ -z "$machine_id" ]]; then
# Fallback to the boot-time, which will at least ensure a unique ID in the current session
machine_id="$(get_boot_time)"
fi
echo "$machine_id"
}
# 获取根分区的磁盘设备 ID(如: /dev/sda1
# 返回值:
# 根分区磁盘设备 ID
get_disk_device_id() {
local device_id=""
if grep -q ^/dev /proc/mounts; then
device_id="$(grep ^/dev /proc/mounts | awk '$2 == "/" { print $1 }' | tail -1)"
fi
# If it could not be autodetected, fallback to /dev/sda1 as a default
if [[ -z "$device_id" || ! -b "$device_id" ]]; then
device_id="/dev/sda1"
fi
echo "$device_id"
}
# 获取根磁盘设备 ID(如: /dev/sda
# 返回值:
# 根磁盘设备 ID
get_root_disk_device_id() {
get_disk_device_id | sed -E 's/p?[0-9]+$//'
}
# 获取根磁盘大小
# 返回值:
# 根磁盘大小(字节)
get_root_disk_size() {
fdisk -l "$(get_root_disk_device_id)" | grep 'Disk.*bytes' | sed -E 's/.*, ([0-9]+) bytes,.*/\1/' || true
}
# 以指定用户身份运行命令
# 注意:此函数将使用 chroot 命令来模拟指定用户身份运行命令,
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
run_as_user() {
run_chroot "$@"
}
# 以指定用户身份执行命令
# 注意:此命令将替换当前进程映像,
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
exec_as_user() {
run_chroot --replace-process "$@"
}
# 使用 chroot 命令以指定用户身份运行命令
# 参数:
# $1 - 用户(可选)
# $2..$n - 命令及命令参数
# 标志:
# -r | --replace-process - 替换当前进程映像
run_chroot() {
local userspec
local user
local homedir
local replace=false
local -r cwd="$(pwd)"
# 解析并验证标志位
while [[ "$#" -gt 0 ]]; do
case "$1" in
-r | --replace-process)
replace=true
;;
--)
shift
break
;;
-*)
stderr_print "unrecognized flag $1"
return 1
;;
*)
break
;;
esac
shift
done
# 解析并验证参数
if [[ "$#" -lt 2 ]]; then
echo "expected at least 2 arguments"
return 1
else
userspec=$1
shift
# 用户名可以包含组,因此我们解析用户
user=$(echo "$userspec" | cut -d':' -f1)
fi
if ! is_root; then
error "Could not switch to '${userspec}': Operation not permitted"
return 1
fi
# 获取用户的家目录,因为 chroot 不会正确更新此环境变量,并且一些脚本依赖于它
homedir=$(eval echo "~${user}")
if [[ ! -d $homedir ]]; then
homedir="${HOME:-/}"
fi
# 取得 "$@" 的值,以便正确支持 shell 参数扩展
if [[ "$replace" = true ]]; then
exec chroot --userspec="$userspec" / bash -c "cd ${cwd}; export HOME=${homedir}; exec \"\$@\"" -- "$@"
else
chroot --userspec="$userspec" / bash -c "cd ${cwd}; export HOME=${homedir}; exec \"\$@\"" -- "$@"
fi
}