mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-29 23:27:44 +00:00
StoreAccountSessionStore replaced the roster entry of a saved account with the transient one when the same npub logged in transiently (the login screen's temporary login, or the desktop's startup key), and the roster write drops transient entries, so the saved account fell out of the saved list on disk. The saved entry now stays on disk in the transient one's place (aTransientLoginOfASavedAccountKeepsItSaved reproduces it). Also from the audit of the startup login/sign-up: - A sign-up that throws at start no longer leaves the window on the loading screen: it logs and falls back to the saved account or the login screen. - run-headless.sh --login also refuses an uppercase NSEC1/NCRYPTSEC1 and a seed phrase, which the app reads as private keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NQ76C3JzYqA4GC4y2uUJXb
176 lines
7.1 KiB
Bash
Executable File
176 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Runs the desktop app (the same shared UI Android renders) on a virtual X display, so an agent in a
|
|
# headless container can launch it, look at it and click through it. See README.md next to this file.
|
|
#
|
|
# tools/desktop-run/run-headless.sh start [options] # build + launch, wait for the first paint
|
|
# tools/desktop-run/run-headless.sh shot [file.png] # screenshot (default: $RUN_DIR/shot.png)
|
|
# tools/desktop-run/run-headless.sh logs # tail of the app log, errors first
|
|
# tools/desktop-run/run-headless.sh stop # stop the app (Xvfb keeps running)
|
|
#
|
|
# start options (the login is transient: no key prompt, nothing saved; one of the three at most):
|
|
# --fresh start from an empty profile
|
|
# --login <npub> open logged in with a public key (npub, nprofile, name@domain)
|
|
# --login-file <path> open logged in with the key in that file (an nsec stays out of `ps`), e.g.
|
|
# $RUN_DIR/profile/signup.nsec, read before --fresh clears the profile
|
|
# --signup "<name>" sign up a new account as the Sign Up button does; it publishes to public
|
|
# relays, and its nsec goes to $RUN_DIR/profile/signup.nsec
|
|
#
|
|
# Env: AMETHYST_DISPLAY (default :99), AMETHYST_SCREEN (default 1600x1000x24),
|
|
# AMETHYST_RUN_DIR (default /tmp/amethyst-desktop-run: log, profile, screenshots),
|
|
# AMETHYST_START_TIMEOUT seconds (default 900: a cold Gradle build is slow),
|
|
# AMETHYST_RUN_ARGS extra Gradle arguments, e.g. "-Damethyst.update.devCheck=true",
|
|
# AMETHYST_LOGIN / AMETHYST_SIGNUP, which the app reads itself: what --login and --signup set.
|
|
|
|
set -uo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
DRIVE="$REPO_ROOT/tools/desktop-run/Drive.java"
|
|
DISPLAY_NUM="${AMETHYST_DISPLAY:-:99}"
|
|
SCREEN="${AMETHYST_SCREEN:-1600x1000x24}"
|
|
RUN_DIR="${AMETHYST_RUN_DIR:-/tmp/amethyst-desktop-run}"
|
|
TIMEOUT="${AMETHYST_START_TIMEOUT:-900}"
|
|
LOG="$RUN_DIR/app.log"
|
|
DATA_DIR="$RUN_DIR/profile"
|
|
PID_FILE="$RUN_DIR/gradle.pid"
|
|
|
|
mkdir -p "$RUN_DIR"
|
|
export DISPLAY="$DISPLAY_NUM"
|
|
# Xvfb has no GL: start on the software renderer instead of failing over from OpenGL.
|
|
export SKIKO_RENDER_API="${SKIKO_RENDER_API:-SOFTWARE}"
|
|
# Anchored on the java binary so pgrep/pkill never match a shell whose command line mentions the class.
|
|
APP_PATTERN='^[^ ]*java .*AmethystDesktopKt'
|
|
|
|
drive() { java "$DRIVE" "$@" 2>&1 | grep -v '^Picked up JAVA_TOOL_OPTIONS'; }
|
|
|
|
app_running() { pgrep -f "$APP_PATTERN" > /dev/null; }
|
|
|
|
ensure_xvfb() {
|
|
if xdpy_alive; then return; fi
|
|
command -v Xvfb > /dev/null || { echo "Xvfb is not installed (apt-get install xvfb)" >&2; exit 1; }
|
|
Xvfb "$DISPLAY_NUM" -screen 0 "$SCREEN" -nolisten tcp > "$RUN_DIR/xvfb.log" 2>&1 &
|
|
for _ in $(seq 1 50); do xdpy_alive && return; sleep 0.1; done
|
|
echo "Xvfb did not start on $DISPLAY_NUM; see $RUN_DIR/xvfb.log" >&2
|
|
exit 1
|
|
}
|
|
|
|
# The X socket exists once Xvfb accepts connections.
|
|
xdpy_alive() { [ -S "/tmp/.X11-unix/X${DISPLAY_NUM#:}" ] && pgrep -f "^[^ ]*Xvfb $DISPLAY_NUM" > /dev/null; }
|
|
|
|
stop_app() {
|
|
pkill -f "$APP_PATTERN" 2> /dev/null
|
|
[ -f "$PID_FILE" ] && kill "$(cat "$PID_FILE")" 2> /dev/null
|
|
rm -f "$PID_FILE"
|
|
for _ in $(seq 1 50); do app_running || return 0; sleep 0.2; done
|
|
pkill -9 -f "$APP_PATTERN" 2> /dev/null
|
|
}
|
|
|
|
print_errors() {
|
|
# Uncaught exceptions, crash reports and Compose errors, most useful first.
|
|
grep -nE 'Exception|Error:|FATAL|\bE/|Caused by' "$LOG" | grep -vE 'JAVA_TOOL_OPTIONS' | head -40
|
|
}
|
|
|
|
case "${1:-}" in
|
|
start)
|
|
shift
|
|
fresh=false
|
|
account_options=0
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--fresh) fresh=true ;;
|
|
--login)
|
|
[ $# -ge 2 ] || { echo "--login needs a key" >&2; exit 2; }
|
|
# On the command line the key shows in `ps` and in the shell history: public keys only.
|
|
# Any case, as the app reads keys; a bare 64-char hex key or a seed phrase is a private key.
|
|
login_lower="$(printf '%s' "$2" | tr '[:upper:]' '[:lower:]')"
|
|
if [[ "$login_lower" == nsec1* || "$login_lower" == ncryptsec1* || "$login_lower" =~ ^[0-9a-f]{64}$ || "$2" =~ [[:space:]] ]]; then
|
|
echo "--login takes public keys only: a private key on the command line shows in ps. Use --login-file." >&2
|
|
exit 2
|
|
fi
|
|
export AMETHYST_LOGIN="$2"
|
|
unset AMETHYST_SIGNUP
|
|
account_options=$((account_options + 1))
|
|
shift
|
|
;;
|
|
--login-file)
|
|
[ $# -ge 2 ] || { echo "--login-file needs a path" >&2; exit 2; }
|
|
[ -r "$2" ] || { echo "Cannot read the key file $2" >&2; exit 2; }
|
|
AMETHYST_LOGIN="$(tr -d '[:space:]' < "$2")"
|
|
[ -n "$AMETHYST_LOGIN" ] || { echo "The key file $2 is empty" >&2; exit 2; }
|
|
export AMETHYST_LOGIN
|
|
unset AMETHYST_SIGNUP
|
|
account_options=$((account_options + 1))
|
|
shift
|
|
;;
|
|
--signup)
|
|
[ $# -ge 2 ] && [ -n "$2" ] || { echo "--signup needs a display name" >&2; exit 2; }
|
|
export AMETHYST_SIGNUP="$2"
|
|
unset AMETHYST_LOGIN
|
|
account_options=$((account_options + 1))
|
|
shift
|
|
;;
|
|
*)
|
|
echo "Unknown start option: $1" >&2
|
|
sed -n '3,22p' "$0" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
[ "$account_options" -le 1 ] || { echo "Pass only one of --login, --login-file and --signup" >&2; exit 2; }
|
|
# After the options: --login-file may name the signup.nsec this deletes.
|
|
$fresh && rm -rf "$DATA_DIR"
|
|
# The window sizes itself to the screen only when no bounds were saved; keep it reproducible.
|
|
rm -f "$DATA_DIR/window-bounds.properties"
|
|
stop_app
|
|
ensure_xvfb
|
|
: > "$LOG"
|
|
# Redirected as a whole, so no process keeps the caller's stdout open: `start | tail` returns.
|
|
(cd "$REPO_ROOT" && exec nohup ./gradlew :desktopApp:run --console=plain \
|
|
"-Damethyst.dataDir=$DATA_DIR" -Damethyst.debug=true ${AMETHYST_RUN_ARGS:-}) >> "$LOG" 2>&1 < /dev/null &
|
|
echo $! > "$PID_FILE"
|
|
echo "Building and launching (log: $LOG, profile: $DATA_DIR, DISPLAY=$DISPLAY_NUM)..."
|
|
deadline=$((SECONDS + TIMEOUT))
|
|
until app_running; do
|
|
if ! kill -0 "$(cat "$PID_FILE")" 2> /dev/null; then
|
|
echo "Gradle exited before the app started:" >&2
|
|
tail -40 "$LOG" >&2
|
|
exit 1
|
|
fi
|
|
[ $SECONDS -ge $deadline ] && { echo "Timed out waiting for the app JVM" >&2; tail -20 "$LOG" >&2; exit 1; }
|
|
sleep 2
|
|
done
|
|
if ! drive waitpaint 120000; then
|
|
echo "The app JVM started but never painted a window:" >&2
|
|
print_errors >&2
|
|
exit 1
|
|
fi
|
|
sleep 3 # first frames settle (fonts, theme)
|
|
if ! app_running; then
|
|
echo "The app painted and then died:" >&2
|
|
print_errors >&2
|
|
exit 1
|
|
fi
|
|
drive shot "$RUN_DIR/shot.png"
|
|
echo "Running. Look at $RUN_DIR/shot.png; drive it with: DISPLAY=$DISPLAY_NUM java $DRIVE <cmd>"
|
|
;;
|
|
shot)
|
|
drive shot "${2:-$RUN_DIR/shot.png}"
|
|
;;
|
|
logs)
|
|
app_running && echo "app: running" || echo "app: NOT running"
|
|
echo "--- errors ---"
|
|
print_errors
|
|
echo "--- tail ---"
|
|
tail -30 "$LOG"
|
|
;;
|
|
stop)
|
|
stop_app
|
|
echo "stopped"
|
|
;;
|
|
*)
|
|
sed -n '3,22p' "$0"
|
|
exit 2
|
|
;;
|
|
esac
|