#!/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