main
sh 204 lines 5.11 KB
Raw
1 #!/usr/bin/env sh
2 set -eu
3
4 OS="$(uname -s)"
5 case "$OS" in
6 Linux) PORTAL_OS="linux" ;;
7 Darwin) PORTAL_OS="darwin" ;;
8 *)
9 echo "Unsupported OS: $OS" >&2
10 exit 1
11 ;;
12 esac
13
14 ARCH="$(uname -m)"
15 case "$ARCH" in
16 x86_64|amd64) PORTAL_ARCH="amd64" ;;
17 arm64|aarch64) PORTAL_ARCH="arm64" ;;
18 *)
19 echo "Unsupported architecture: $ARCH" >&2
20 exit 1
21 ;;
22 esac
23
24 BASE_URL="${BASE_URL:-https://github.com/gosuda/portal-tunnel/releases/latest/download}"
25 BIN_PATH_PREFIX="${BIN_PATH_PREFIX:-}"
26 BIN_SUFFIX="portal-$PORTAL_OS-$PORTAL_ARCH"
27 if [ -n "$BIN_PATH_PREFIX" ]; then
28 BIN_URL="${BIN_URL:-$BASE_URL/$BIN_PATH_PREFIX/$PORTAL_OS-$PORTAL_ARCH}"
29 else
30 BIN_URL="${BIN_URL:-$BASE_URL/$BIN_SUFFIX}"
31 fi
32 CHECKSUM_URL="${CHECKSUM_URL:-${BIN_URL}.sha256}"
33 RELAY_URL="${RELAY_URL:-https://your-relay.example.com}"
34
35 is_local_https_url() {
36 case "$1" in
37 https://*) ;;
38 *) return 1 ;;
39 esac
40
41 URL_HOSTPORT="${1#https://}"
42 URL_HOSTPORT="${URL_HOSTPORT%%/*}"
43 URL_HOSTPORT="${URL_HOSTPORT%%\?*}"
44 URL_HOSTPORT="${URL_HOSTPORT%%#*}"
45
46 case "$URL_HOSTPORT" in
47 \[::1\]:*)
48 URL_HOST="[::1]"
49 URL_PORT="${URL_HOSTPORT#\[::1\]:}"
50 ;;
51 \[::1\])
52 URL_HOST="[::1]"
53 URL_PORT=""
54 ;;
55 *:*)
56 URL_HOST="${URL_HOSTPORT%%:*}"
57 URL_PORT="${URL_HOSTPORT#*:}"
58 ;;
59 *)
60 URL_HOST="$URL_HOSTPORT"
61 URL_PORT=""
62 ;;
63 esac
64
65 case "$URL_PORT" in
66 *[!0-9]*) return 1 ;;
67 esac
68
69 case "$URL_HOST" in
70 localhost|127.0.0.1|\[::1\]|*.localhost)
71 return 0
72 ;;
73 esac
74 return 1
75 }
76
77 download_url() {
78 if is_local_https_url "$1"; then
79 curl -k -fsSL "$1" -o "$2"
80 return
81 fi
82 curl -fsSL "$1" -o "$2"
83 }
84
85 fetch_url() {
86 if is_local_https_url "$1"; then
87 curl -k -fsSL "$1"
88 return
89 fi
90 curl -fsSL "$1"
91 }
92
93 TMPDIR="${TMPDIR:-/tmp}"
94 WORKDIR="$(mktemp -d "$TMPDIR/portal-install.XXXXXX" 2>/dev/null || mktemp -d -t portal-install)"
95 BIN_PATH="$WORKDIR/portal"
96 cleanup() {
97 if [ -n "${TMP_INSTALL_PATH:-}" ]; then
98 rm -f "$TMP_INSTALL_PATH"
99 fi
100 rm -rf "$WORKDIR"
101 }
102 trap cleanup EXIT INT TERM
103
104 echo "Downloading portal ($PORTAL_OS/$PORTAL_ARCH)..." >&2
105 download_url "$BIN_URL" "$BIN_PATH"
106
107 echo "Verifying SHA256 checksum..." >&2
108 CHECKSUM_PAYLOAD="$(fetch_url "$CHECKSUM_URL")" || {
109 echo "Failed to download checksum from $CHECKSUM_URL. Aborting (fail-closed)." >&2
110 exit 1
111 }
112
113 EXPECTED_SHA="$(printf '%s\n' "$CHECKSUM_PAYLOAD" | awk '{print $1}' | tr 'A-Z' 'a-z')"
114 if ! printf '%s\n' "$EXPECTED_SHA" | grep -Eq '^[0-9a-f]{64}$'; then
115 echo "Invalid checksum payload from $CHECKSUM_URL. Aborting (fail-closed)." >&2
116 echo "Hint: expected SHA256 sidecar format '<sha256> <filename>'." >&2
117 exit 1
118 fi
119
120 if command -v sha256sum >/dev/null 2>&1; then
121 ACTUAL_SHA="$(sha256sum "$BIN_PATH" | awk '{print $1}')"
122 elif command -v shasum >/dev/null 2>&1; then
123 ACTUAL_SHA="$(shasum -a 256 "$BIN_PATH" | awk '{print $1}')"
124 else
125 echo "No SHA256 checksum tool found (need sha256sum or shasum)." >&2
126 exit 1
127 fi
128 if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then
129 echo "Checksum mismatch for portal binary. Aborting (fail-closed)." >&2
130 exit 1
131 fi
132
133 pick_install_path() {
134 EXISTING="$(command -v portal 2>/dev/null || true)"
135 if [ -n "$EXISTING" ]; then
136 EXISTING_DIR="$(dirname "$EXISTING")"
137 if [ -d "$EXISTING_DIR" ] && [ -w "$EXISTING_DIR" ]; then
138 printf '%s\n' "$EXISTING"
139 return 0
140 fi
141 fi
142
143 if [ -n "${HOME:-}" ]; then
144 for DIR in "$HOME/.local/bin" "$HOME/bin"; do
145 mkdir -p "$DIR" 2>/dev/null || true
146 if [ -d "$DIR" ] && [ -w "$DIR" ]; then
147 printf '%s\n' "$DIR/portal"
148 return 0
149 fi
150 done
151 fi
152
153 return 1
154 }
155
156 INSTALL_PATH="$(pick_install_path)" || {
157 echo "No writable install directory found. Ensure an existing portal install is writable or create \$HOME/.local/bin or \$HOME/bin." >&2
158 exit 1
159 }
160
161 INSTALL_DIR="$(dirname "$INSTALL_PATH")"
162 TMP_INSTALL_PATH="$(mktemp "$INSTALL_DIR/.portal.tmp.XXXXXX")"
163 cp "$BIN_PATH" "$TMP_INSTALL_PATH"
164 chmod +x "$TMP_INSTALL_PATH"
165 mv -f "$TMP_INSTALL_PATH" "$INSTALL_PATH"
166 TMP_INSTALL_PATH=""
167
168 echo "Installed portal to $INSTALL_PATH" >&2
169
170 case ":$PATH:" in
171 *":$INSTALL_DIR:"*) ;;
172 *)
173 PROFILE_PATH=""
174 if [ -n "${HOME:-}" ]; then
175 SHELL_NAME="${SHELL:-}"
176 SHELL_NAME="${SHELL_NAME##*/}"
177 case "$SHELL_NAME" in
178 zsh) PROFILE_PATH="$HOME/.zshrc" ;;
179 bash) PROFILE_PATH="$HOME/.bashrc" ;;
180 *) PROFILE_PATH="$HOME/.profile" ;;
181 esac
182 fi
183
184 if [ -n "$PROFILE_PATH" ]; then
185 mkdir -p "$(dirname "$PROFILE_PATH")" 2>/dev/null || true
186 if [ ! -f "$PROFILE_PATH" ] || ! grep -F "$INSTALL_DIR" "$PROFILE_PATH" >/dev/null 2>&1; then
187 {
188 printf '\n# Added by Portal installer\n'
189 printf 'export PATH="%s:$PATH"\n' "$INSTALL_DIR"
190 } >> "$PROFILE_PATH" || PROFILE_PATH=""
191 fi
192 fi
193
194 if [ -n "$PROFILE_PATH" ]; then
195 echo "Added $INSTALL_DIR to PATH in $PROFILE_PATH" >&2
196 echo "Open a new shell or run: export PATH=\"$INSTALL_DIR:\$PATH\"" >&2
197 else
198 echo "Warning: $INSTALL_DIR is not on PATH. Add it before running 'portal expose 3000'." >&2
199 fi
200 ;;
201 esac
202
203 echo "Next step:" >&2
204 echo " portal expose 3000 --relays $RELAY_URL" >&2