| set -euo pipefail | |
| # NOTE: this container runs as uid 1000 at runtime on HF Spaces, not root β | |
| # every path this script touches was already created and chowned to `user` | |
| # in the Dockerfile. Nothing here uses sudo/gosu/chown, on purpose. | |
| log() { echo "[entrypoint] $*"; } | |
| log "starting up as $(id -un) (uid $(id -u))" | |
| # --------------------------------------------------------------------- | |
| # 1. Basic auth in front of noVNC β creds come from HF Spaces secrets, | |
| # never baked into the image. | |
| # --------------------------------------------------------------------- | |
| : "${BASIC_AUTH_USER:?Set BASIC_AUTH_USER as an HF Space secret}" | |
| : "${BASIC_AUTH_PASS:?Set BASIC_AUTH_PASS as an HF Space secret}" | |
| htpasswd -bc /etc/nginx/.htpasswd "$BASIC_AUTH_USER" "$BASIC_AUTH_PASS" | |
| unset BASIC_AUTH_PASS # scrub from the environment once consumed | |
| # --------------------------------------------------------------------- | |
| # 2. Ephemeral, tmpfs-backed Firefox profile β nothing survives a restart, | |
| # and nothing ever touches the container's writable layer on disk. | |
| # /dev/shm and /tmp are world-writable (sticky 1777), so no chown needed. | |
| # --------------------------------------------------------------------- | |
| PROFILE_DIR=/dev/shm/tbprofile | |
| rm -rf "$PROFILE_DIR" | |
| mkdir -p "$PROFILE_DIR" /tmp/tb-downloads | |
| # --------------------------------------------------------------------- | |
| # 3. Bring nginx up on :7860 IMMEDIATELY with a "connecting" holding page, | |
| # before tor has bootstrapped. HF Spaces needs to see the container | |
| # answering HTTP quickly β gating nginx's startup on tor's bootstrap | |
| # (which can take minutes across bridge retries) makes the container | |
| # look dead/unresponsive. | |
| # --------------------------------------------------------------------- | |
| cat > /var/www/wait/index.html <<'HTML' | |
| <!doctype html><html><body style="font-family:monospace;padding:2rem"> | |
| <h2>Starting upβ¦</h2> | |
| <p>Connecting to Tor. This page refreshes automatically.</p> | |
| <meta http-equiv="refresh" content="5"> | |
| </body></html> | |
| HTML | |
| sed -i 's#proxy_pass http://127.0.0.1:6080;#root /var/www/wait;#' /etc/nginx/nginx.conf | |
| log "starting nginx on :7860 (holding page)" | |
| nginx -g "daemon off;" & | |
| # --------------------------------------------------------------------- | |
| # 4. Start tor and wait for 100% bootstrap, retrying across the | |
| # configured bridge lines if the first one is blocked/dead. | |
| # See bootstrap-test.sh for the retry/fallback logic itself. | |
| # --------------------------------------------------------------------- | |
| log "starting tor..." | |
| tor -f /etc/tor/torrc & | |
| if /usr/local/bin/bootstrap-test.sh; then | |
| log "tor bootstrapped successfully β switching nginx over to noVNC" | |
| # --------------------------------------------------------------- | |
| # 5. Virtual display + browser, single VNC session, no clipboard bridging | |
| # --------------------------------------------------------------- | |
| # Xvfb refuses to create /tmp/.X11-unix itself unless running as root | |
| # (a hardcoded security check) β but it's happy to use it if it already | |
| # exists, and /tmp is world-writable regardless of our uid. | |
| mkdir -p /tmp/.X11-unix | |
| chmod 1777 /tmp/.X11-unix | |
| Xvfb "$DISPLAY" -screen 0 "$SCREEN_GEOMETRY" -nolisten tcp & | |
| sleep 1 | |
| /opt/tor-browser/Browser/firefox --profile "$PROFILE_DIR" --no-remote & | |
| # -once: exit after first client disconnects (forces a clean re-auth per | |
| # session instead of leaving a stale desktop up for the next visitor) | |
| # -noxdamage / clipboard disabled to avoid host<->guest clipboard leaks | |
| x11vnc -display "$DISPLAY" -forever -shared -once -noxdamage \ | |
| -nopw -rfbport 5900 -quiet -nosel -noprimary & | |
| websockify --web=/usr/share/novnc 6080 localhost:5900 & | |
| # Idle auto-disconnect: 15 minutes of no keyboard/mouse input tears the | |
| # session down (see idle-watcher.sh). Restarting the container after this | |
| # also gives the next visitor a guaranteed-clean tmpfs profile. | |
| IDLE_MINUTES=15 /usr/local/bin/idle-watcher.sh & | |
| sed -i 's#root /var/www/wait;#proxy_pass http://127.0.0.1:6080;#' /etc/nginx/nginx.conf | |
| nginx -s reload | |
| else | |
| log "tor failed to bootstrap on any configured bridge." | |
| log "Serving a diagnostic page instead of crash-looping the container." | |
| touch /tmp/tor-bootstrap-failed | |
| cat > /var/www/error/index.html <<'EOF2' | |
| <!doctype html><html><body style="font-family:monospace;padding:2rem"> | |
| <h2>Tor bootstrap failed</h2> | |
| <p>All configured Bridge lines in torrc failed to connect within the | |
| retry window. This is usually because the bridge addresses are still | |
| the placeholders from the template, or the configured bridges have | |
| been blocklisted.</p> | |
| <p>Fix: get fresh bridges from https://bridges.torproject.org/ or by | |
| emailing [email protected] (body: "get transport obfs4" or | |
| "get transport snowflake"), update torrc, and restart the Space.</p> | |
| <p>Check the Space's Logs tab for the full [bootstrap-test] output.</p> | |
| </body></html> | |
| EOF2 | |
| sed -i 's#root /var/www/wait;#root /var/www/error;#' /etc/nginx/nginx.conf | |
| nginx -s reload | |
| fi | |
| wait -n | |