#!/bin/bash # wsd installer: curl -fsSL https://wsd.wasend.io | bash set -euo pipefail BASE_URL="${WSD_BASE_URL:-https://wsd.wasend.io}" DRY=0 [ "${1:-}" = "--dry-run" ] && DRY=1 say() { echo "==> $*"; } [ "$(uname -s)" = "Darwin" ] || { echo "wsd chỉ hỗ trợ macOS." >&2; exit 1; } if ! command -v brew >/dev/null 2>&1; then echo "Chưa có Homebrew. Cài theo https://brew.sh rồi chạy lại lệnh này." >&2 exit 1 fi BREW_PREFIX="$(brew --prefix)" CONF="$HOME/.config/wsd" HERDR_DIR="$HOME/.config/herdr" AGENTS="$HOME/Library/LaunchAgents" UID_N="$(id -u)" TS="$(date +%s)" say "Cài gói brew: herdr ttyd cloudflared" if [ "$DRY" = 1 ]; then echo "[dry-run] brew install herdr ttyd cloudflared (bỏ qua gói đã có)" else for p in herdr ttyd cloudflared; do brew list --formula "$p" >/dev/null 2>&1 || brew install "$p" done fi HW_UUID="$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')" [ -n "$HW_UUID" ] || { echo "Không đọc được Hardware UUID." >&2; exit 1; } USERNAME="$(id -un)" OLD_SECRET="" [ -f "$CONF/secret" ] && OLD_SECRET="$(cat "$CONF/secret")" if [ "$DRY" = 1 ]; then echo "[dry-run] POST $BASE_URL/provision (uuid=$HW_UUID username=$USERNAME secret=$([ -n "$OLD_SECRET" ] && echo có || echo không))" echo "[dry-run] ghi $CONF/{host,secret,tunnel-token} (600)" HOST="" else mkdir -p "$CONF" chmod 700 "$CONF" say "Đăng ký máy với $BASE_URL" BODY="$(UUID="$HW_UUID" USERNAME="$USERNAME" SECRET="$OLD_SECRET" /usr/bin/python3 -I -c ' import json,os d={"uuid":os.environ["UUID"],"username":os.environ["USERNAME"]} if os.environ["SECRET"]: d["secret"]=os.environ["SECRET"] print(json.dumps(d))')" RESP_FILE="$(mktemp)" trap 'rm -f "$RESP_FILE"' EXIT CODE="$(curl -sS -o "$RESP_FILE" -w '%{http_code}' -X POST "$BASE_URL/provision" \ -H 'content-type: application/json' --data "$BODY")" if [ "$CODE" != "200" ]; then echo "Đăng ký thất bại (HTTP $CODE):" >&2 cat "$RESP_FILE" >&2 echo >&2 exit 1 fi field() { /usr/bin/python3 -I -c 'import json,sys;print(json.load(open(sys.argv[1]))[sys.argv[2]])' "$RESP_FILE" "$1"; } HOST="$(field hostname)" umask 077 printf '%s' "$HOST" >"$CONF/host" printf '%s' "$(field secret)" >"$CONF/secret" printf '%s' "$(field tunnel_token)" >"$CONF/tunnel-token" chmod 600 "$CONF/host" "$CONF/secret" "$CONF/tunnel-token" umask 022 fi say "Cài trang mobile và config herdr" [ "$DRY" = 1 ] || mkdir -p "$CONF" "$HERDR_DIR" "$AGENTS" if [ "$DRY" = 1 ]; then echo "[dry-run] tải $BASE_URL/files/index.html -> $CONF/index.html (backup .bak.$TS nếu đã có)" echo "[dry-run] tải $BASE_URL/files/tabbar-branch.sh -> $HERDR_DIR/tabbar-branch.sh (backup nếu đã có)" if [ -f "$HERDR_DIR/config.toml" ]; then echo "[dry-run] $HERDR_DIR/config.toml đã có: giữ nguyên, chỉ in hướng dẫn" else echo "[dry-run] tải $BASE_URL/files/config.toml -> $HERDR_DIR/config.toml" fi else fetch() { # url dest [ -f "$2" ] && cp "$2" "$2.bak.$TS" curl -fsSL "$1" -o "$2.tmp" && mv "$2.tmp" "$2" } fetch "$BASE_URL/files/index.html" "$CONF/index.html" fetch "$BASE_URL/files/tabbar-branch.sh" "$HERDR_DIR/tabbar-branch.sh" chmod +x "$HERDR_DIR/tabbar-branch.sh" if [ -f "$HERDR_DIR/config.toml" ]; then curl -fsSL "$BASE_URL/files/config.toml" | sed "s#__HOME__#$HOME#g" >"$CONF/herdr-config.suggested.toml" echo "Giữ nguyên $HERDR_DIR/config.toml. Config gợi ý (mobile): $CONF/herdr-config.suggested.toml" else curl -fsSL "$BASE_URL/files/config.toml" | sed "s#__HOME__#$HOME#g" >"$HERDR_DIR/config.toml" fi fi say "Cài CLI wsd vào $BREW_PREFIX/bin" if [ "$DRY" = 1 ]; then echo "[dry-run] tải $BASE_URL/wsd -> $BREW_PREFIX/bin/wsd (chmod +x)" else curl -fsSL "$BASE_URL/wsd" -o "$BREW_PREFIX/bin/wsd.tmp" chmod +x "$BREW_PREFIX/bin/wsd.tmp" mv "$BREW_PREFIX/bin/wsd.tmp" "$BREW_PREFIX/bin/wsd" fi AGENT_PATH="$BREW_PREFIX/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.local/bin" write_ttyd_plist() { cat >"$AGENTS/com.wasend.wsd-ttyd.plist" < Labelcom.wasend.wsd-ttyd ProgramArguments /usr/bin/env -uHERDR_ENV -uHERDR_PANE_ID -uHERDR_TAB_ID -uHERDR_WORKSPACE_ID -uHERDR_SOCKET_PATH -uHERDR_BIN_PATH $BREW_PREFIX/bin/ttyd -W-O -i127.0.0.1-p7681 -I$CONF/index.html -tfontSize=12 -trendererType=dom -tdisableResizeOverlay=true -tdisableLeaveAlert=true $BREW_PREFIX/bin/herdr EnvironmentVariablesPATH$AGENT_PATHLANGen_US.UTF-8 WorkingDirectory$HOME RunAtLoadKeepAlive StandardOutPath$CONF/ttyd.log StandardErrorPath$CONF/ttyd.log PLIST } write_tunnel_plist() { cat >"$AGENTS/com.wasend.wsd-tunnel.plist" < Labelcom.wasend.wsd-tunnel ProgramArguments $BREW_PREFIX/bin/cloudflared--config/dev/null tunnelrun--token-file$CONF/tunnel-token EnvironmentVariablesPATH$AGENT_PATH RunAtLoadKeepAlive StandardOutPath$CONF/tunnel.log StandardErrorPath$CONF/tunnel.log PLIST } say "Ghi và nạp LaunchAgent com.wasend.wsd-ttyd, com.wasend.wsd-tunnel" if [ "$DRY" = 1 ]; then echo "[dry-run] ghi $AGENTS/com.wasend.wsd-{ttyd,tunnel}.plist (PATH có $BREW_PREFIX/bin, log $CONF/*.log)" echo "[dry-run] launchctl bootout + bootstrap gui/$UID_N" else write_ttyd_plist write_tunnel_plist for s in com.wasend.wsd-ttyd com.wasend.wsd-tunnel; do launchctl bootout "gui/$UID_N/$s" 2>/dev/null || true launchctl bootstrap "gui/$UID_N" "$AGENTS/$s.plist" done fi echo echo "Xong. URL của máy bạn: https://$HOST" echo "Mở trên điện thoại, đăng nhập bằng email @trust1.io (nhận mã OTP)." echo "Máy chạy liên tục: bật auto-login (System Settings > Users & Groups) để dịch vụ tự chạy sau reboot." echo "Lệnh: wsd status | url | logs | restart | update | uninstall"