175 lines
8.7 KiB
Bash
Executable File
175 lines
8.7 KiB
Bash
Executable File
#!/bin/sh
|
||
# Fresh installation; existing services, configuration and data are never replaced.
|
||
set -eu
|
||
PATH=/usr/sbin:/usr/bin:/sbin:/bin
|
||
export PATH
|
||
umask 077
|
||
|
||
usage() {
|
||
cat <<'HELP'
|
||
用法:sudo ./install.sh --url 站点地址 [选项]
|
||
|
||
--url URL 完整站点地址,如 https://files.example.com
|
||
--prefix DIR 安装目录,默认 /opt/bluebird
|
||
--listen ADDR 后端监听地址,HTTPS代理默认 127.0.0.1:8080
|
||
--lan-http 允许绑定具体私有IP提供内网HTTP服务
|
||
--access-url URL 增加其他HTTP/HTTPS入口,可重复
|
||
--lan-url URL 增加内网IP入口,并默认监听该IP和端口
|
||
--encrypt 启用新文件的服务端落盘加密
|
||
--no-start 安装并初始化,但暂不启用/启动systemd服务
|
||
-h, --help 显示帮助
|
||
|
||
首次安装会在终端询问管理员密码,账号为 admin。
|
||
自动化可通过 BLUEBIRD_ADMIN_PASSWORD 环境变量传入;密码不写入配置文件。
|
||
系统需使用systemd,并提供getent、useradd、groupadd、runuser和coreutils。
|
||
HTTPS证书由你已有的反向代理管理;脚本不安装代理或修改防火墙。
|
||
已有安装请按 docs/operations.md 升级,脚本不会覆盖它。
|
||
HELP
|
||
}
|
||
die() { printf '%s\n' "安装失败:$*" >&2; exit 1; }
|
||
require_value() { [ "$#" -ge 2 ] && [ -n "$2" ] || die "$1 缺少参数"; }
|
||
prefix=/opt/bluebird
|
||
public_url=
|
||
listen=
|
||
lan_http=false
|
||
extra_urls=
|
||
lan_url=
|
||
encryption=false
|
||
start_service=true
|
||
while [ "$#" -gt 0 ]; do
|
||
case "$1" in
|
||
--url) require_value "$@"; public_url=$2; shift 2 ;;
|
||
--prefix) require_value "$@"; prefix=$2; shift 2 ;;
|
||
--listen) require_value "$@"; listen=$2; shift 2 ;;
|
||
--lan-http) lan_http=true; shift ;;
|
||
--access-url|--lan-url)
|
||
require_value "$@"
|
||
case "$2" in *[[:space:]]*|*'"'*|*'\'*) die '额外入口不能包含空白、引号或反斜杠' ;; esac
|
||
extra_urls="$extra_urls\"$2\","
|
||
if [ "$1" = --lan-url ]; then lan_url=$2; lan_http=true; fi
|
||
shift 2 ;;
|
||
--encrypt) encryption=true; shift ;;
|
||
--no-start) start_service=false; shift ;;
|
||
-h|--help) usage; exit 0 ;;
|
||
*) die "未知选项:$1" ;;
|
||
esac
|
||
done
|
||
[ -n "$public_url" ] || { usage; exit 1; }
|
||
[ "$(id -u)" = 0 ] || die '请使用sudo或root运行'
|
||
[ "$(uname -s)" = Linux ] || die '仅支持Linux'
|
||
# Keep TOML and systemd paths literal; reject newline, quote and expansion syntax.
|
||
case "$public_url" in *[[:space:]]*|*'"'*|*'\'*) die '站点地址不能包含空白、引号或反斜杠' ;; esac
|
||
case "$listen" in *[[:space:]]*|*'"'*|*'\'*) die '监听地址格式无效' ;; esac
|
||
printf '%s\n' "$prefix" | grep -Eq '^/[A-Za-z0-9._/-]+$' || die '安装目录须为不含空格的绝对路径'
|
||
prefix=$(realpath -m -- "$prefix")
|
||
[ "$prefix" != / ] || die '不能安装到根目录'
|
||
case "$prefix" in /home/*|/root/*) die 'systemd保护home目录,请安装到/opt或/srv等目录' ;; esac
|
||
[ ! -e "$prefix" ] && [ ! -L "$prefix" ] || die "$prefix 已存在,未修改已有安装"
|
||
unit=/etc/systemd/system/bluebird.service
|
||
[ ! -e "$unit" ] && [ ! -L "$unit" ] || die "$unit 已存在,未修改已有服务"
|
||
package_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||
[ -x "$package_dir/bluebird" ] && [ -r "$package_dir/config.toml" ] && [ -r "$package_dir/ARCH" ] || die '请从解压后的发布包运行install.sh'
|
||
case "$(cat "$package_dir/ARCH")" in
|
||
amd64) [ "$(uname -m)" = x86_64 ] || die '需要Linux amd64/x86_64主机' ;;
|
||
arm64) [ "$(uname -m)" = aarch64 ] || die '需要Linux arm64/aarch64主机' ;;
|
||
*) die '发布包架构信息无效' ;;
|
||
esac
|
||
for command in getent useradd groupadd runuser install mktemp realpath awk sed; do
|
||
command -v "$command" >/dev/null 2>&1 || die "缺少系统命令:$command"
|
||
done
|
||
if [ "$start_service" = true ]; then
|
||
command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ] || die 'systemd未运行;离线安装请使用--no-start'
|
||
fi
|
||
explicit_listen=$listen
|
||
public_url=${public_url%/}
|
||
case "$public_url" in
|
||
https://*) secure_cookies=true; trusted_proxies='["127.0.0.1/32", "::1/128"]'; [ -n "$listen" ] || listen=127.0.0.1:8080 ;;
|
||
http://*) secure_cookies=false; trusted_proxies='[]'
|
||
authority=${public_url#http://}
|
||
if [ -z "$listen" ]; then
|
||
case "$authority" in
|
||
localhost:*|127.*:*|10.*:*|192.168.*:*|172.*:*|\[*\]:*) listen=$authority ;;
|
||
localhost|127.*|10.*|192.168.*|172.*|\[*\]) die '内网HTTP地址须包含端口,如http://192.168.1.10:8080' ;;
|
||
*) listen=127.0.0.1:8080 ;;
|
||
esac
|
||
case "$listen" in localhost:*) listen="127.0.0.1:${listen#localhost:}" ;; esac
|
||
fi ;;
|
||
*) die '站点地址须以http://或https://开头' ;;
|
||
esac
|
||
if [ -n "$lan_url" ] && [ -z "$explicit_listen" ]; then
|
||
case "$lan_url" in http://*) listen=${lan_url#http://}; listen=${listen%/} ;; *) die '--lan-url须使用http://内网IP:端口' ;; esac
|
||
fi
|
||
port=${listen##*:}
|
||
case "$port" in ''|*[!0-9]*) die '监听地址须包含数字端口,如127.0.0.1:8080' ;; esac
|
||
[ "$port" -ge 1024 ] && [ "$port" -le 65535 ] || die '独立服务账号使用1024至65535端口;80/443交给反向代理'
|
||
parent=$(dirname -- "$prefix")
|
||
[ -d "$parent" ] || install -d -m 0755 -- "$parent"
|
||
stage=$(mktemp -d "$parent/.bluebird-install-XXXXXX")
|
||
cleanup() { [ -z "$stage" ] || rm -rf -- "$stage"; }
|
||
trap cleanup EXIT
|
||
trap 'exit 130' INT
|
||
trap 'exit 143' TERM HUP
|
||
awk -v address="$listen" -v url="$public_url" -v secure="$secure_cookies" -v lan="$lan_http" -v encryption="$encryption" -v proxies="$trusted_proxies" -v extras="[$extra_urls]" '
|
||
/^listen = / {print "listen = \"" address "\"";next}
|
||
/^public_url = / {print "public_url = \"" url "\"";next}
|
||
/^access_urls = / {print "access_urls = " extras;next}
|
||
/^secure_cookies = / {print "secure_cookies = " secure;next}
|
||
/^allow_lan_http = / {print "allow_lan_http = " lan;next}
|
||
/^encryption = / {print "encryption = " encryption;next}
|
||
/^trusted_proxies = / {print "trusted_proxies = " proxies;next}
|
||
{print}
|
||
' "$package_dir/config.toml" > "$stage/config.toml"
|
||
# Validate before creating an account, database or final installation directory.
|
||
if [ -n "$lan_url" ]; then
|
||
"$package_dir/bluebird" configure-access --config "$stage/config.toml" --listen "$listen" --lan-url "$lan_url"
|
||
fi
|
||
"$package_dir/bluebird" validate-config --config "$stage/config.toml"
|
||
if ! getent group bluebird >/dev/null; then groupadd --system bluebird; fi
|
||
[ "$(getent group bluebird | cut -d: -f3)" != 0 ] || die 'bluebird组不能使用GID 0'
|
||
if ! getent passwd bluebird >/dev/null; then
|
||
nologin=/usr/sbin/nologin
|
||
[ -x "$nologin" ] || nologin=/sbin/nologin
|
||
[ -x "$nologin" ] || nologin=/bin/false
|
||
useradd --system --no-create-home --gid bluebird --home-dir "$prefix" --shell "$nologin" bluebird
|
||
fi
|
||
[ "$(id -u bluebird)" != 0 ] || die 'bluebird账号不能使用UID 0'
|
||
chown root:bluebird "$stage"
|
||
chmod 0750 "$stage"
|
||
install -o root -g bluebird -m 0750 "$package_dir/bluebird" "$stage/bluebird"
|
||
chown root:bluebird "$stage/config.toml"
|
||
chmod 0640 "$stage/config.toml"
|
||
install -d -o bluebird -g bluebird -m 0700 "$stage/var"
|
||
if [ -f "$package_dir/THIRD_PARTY_NOTICES.txt" ]; then
|
||
install -o root -g bluebird -m 0644 "$package_dir/THIRD_PARTY_NOTICES.txt" "$stage/THIRD_PARTY_NOTICES.txt"
|
||
fi
|
||
# runuser preserves the terminal and optional bootstrap environment variable.
|
||
runuser --preserve-environment -u bluebird -- "$stage/bluebird" init --config "$stage/config.toml"
|
||
unset BLUEBIRD_ADMIN_PASSWORD
|
||
sed "s|/opt/bluebird|$prefix|g" "$package_dir/deploy/bluebird.service" > "$stage/bluebird.service"
|
||
chown root:bluebird "$stage/bluebird.service"
|
||
chmod 0640 "$stage/bluebird.service"
|
||
# Stage and target share a filesystem; publish the initialized installation once.
|
||
[ ! -e "$prefix" ] && [ ! -L "$prefix" ] || die "$prefix 已出现,未覆盖它"
|
||
[ ! -e "$unit" ] && [ ! -L "$unit" ] || die "$unit 已出现,未覆盖它"
|
||
mv -T -- "$stage" "$prefix"
|
||
stage=
|
||
[ -d /etc/systemd/system ] || install -d -m 0755 /etc/systemd/system
|
||
install -m 0644 "$prefix/bluebird.service" "$unit"
|
||
if [ "$start_service" = true ]; then
|
||
systemctl daemon-reload
|
||
if ! systemctl enable --now bluebird.service; then
|
||
printf '%s\n' '文件和数据已保留;请执行 journalctl -u bluebird -n 50 查看启动错误。' >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
printf '%s\n' '' "蓝鸟已安装:$prefix" "配置:$prefix/config.toml" "数据、数据库和密钥:$prefix/var" "后端监听:$listen" "站点:$public_url" "管理地址:$public_url/#/admin(账号admin)"
|
||
if [ "$secure_cookies" = true ]; then
|
||
printf '%s\n' 'HTTPS入口通过反向代理访问;已配置的HTTP或内网入口也可使用,详见docs/deployment.md。'
|
||
fi
|
||
if [ -n "$extra_urls" ]; then printf '%s\n' "额外入口:[$extra_urls]"; fi
|
||
if [ "$start_service" = false ]; then
|
||
printf '%s\n' '服务尚未启动。配置好代理后执行:' ' systemctl daemon-reload' ' systemctl enable --now bluebird.service'
|
||
else
|
||
printf '%s\n' '已启用开机启动;查看状态:systemctl status bluebird'
|
||
fi
|