feat: ship native boss android console
This commit is contained in:
49
scripts/bootstrap-server.sh
Executable file
49
scripts/bootstrap-server.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${EUID}" -ne 0 ]]; then
|
||||
echo "Please run with sudo."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
apt-get update
|
||||
apt-get install -y ca-certificates curl gnupg rsync
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
|
||||
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" \
|
||||
>/etc/apt/sources.list.d/nodesource.list
|
||||
apt-get update
|
||||
apt-get install -y nodejs
|
||||
fi
|
||||
|
||||
if ! command -v caddy >/dev/null 2>&1; then
|
||||
apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
|
||||
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key \
|
||||
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
||||
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt \
|
||||
>/etc/apt/sources.list.d/caddy-stable.list
|
||||
apt-get update
|
||||
apt-get install -y caddy
|
||||
fi
|
||||
|
||||
mkdir -p /opt/boss
|
||||
chown -R ubuntu:ubuntu /opt/boss
|
||||
|
||||
if [[ -f /opt/boss/deployment/systemd/boss-web.service ]]; then
|
||||
cp /opt/boss/deployment/systemd/boss-web.service /etc/systemd/system/boss-web.service
|
||||
fi
|
||||
|
||||
if [[ -f /opt/boss/deployment/Caddyfile ]]; then
|
||||
cp /opt/boss/deployment/Caddyfile /etc/caddy/Caddyfile
|
||||
fi
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable boss-web || true
|
||||
systemctl enable caddy || true
|
||||
|
||||
echo "bootstrap-server.sh completed"
|
||||
29
scripts/build-release-apk.sh
Executable file
29
scripts/build-release-apk.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ANDROID_DIR="$ROOT_DIR/android"
|
||||
RELEASE_APK="$ANDROID_DIR/app/build/outputs/apk/release/app-release.apk"
|
||||
BUILD_GRADLE="$ROOT_DIR/android/app/build.gradle"
|
||||
|
||||
VERSION_NAME="$(sed -n 's/.*versionName \"\([^\"]*\)\"/\1/p' "$BUILD_GRADLE" | head -n 1)"
|
||||
VERSIONED_RELEASE_APK="$ANDROID_DIR/app/build/outputs/apk/release/boss-android-v${VERSION_NAME}-release.apk"
|
||||
|
||||
zsh "$ROOT_DIR/scripts/prepare-android-signing.sh"
|
||||
|
||||
cd "$ANDROID_DIR"
|
||||
./gradlew assembleRelease
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
cp "$RELEASE_APK" "$VERSIONED_RELEASE_APK"
|
||||
zsh "$ROOT_DIR/scripts/publish-apk-to-public.sh" "$RELEASE_APK"
|
||||
|
||||
APKSIGNER="$(find "${ANDROID_HOME:-$HOME/Library/Android/sdk}/build-tools" -name apksigner -type f 2>/dev/null | sort | tail -n 1)"
|
||||
if [[ -n "${APKSIGNER:-}" && -f "$APKSIGNER" ]]; then
|
||||
"$APKSIGNER" verify --print-certs "$RELEASE_APK"
|
||||
else
|
||||
echo "apksigner not found, skipped signature verification output" >&2
|
||||
fi
|
||||
|
||||
echo "Signed release APK ready: $RELEASE_APK"
|
||||
echo "Versioned release APK ready: $VERSIONED_RELEASE_APK"
|
||||
53
scripts/deploy-server.sh
Executable file
53
scripts/deploy-server.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
REMOTE_USER="${BOSS_SERVER_USER:-ubuntu}"
|
||||
REMOTE_HOSTNAME="${BOSS_SERVER_HOST:-106.53.170.158}"
|
||||
REMOTE_HOST="${BOSS_REMOTE_HOST:-${REMOTE_USER}@${REMOTE_HOSTNAME}}"
|
||||
REMOTE_DIR="${BOSS_REMOTE_DIR:-/opt/boss}"
|
||||
SSH_OPTS="-o StrictHostKeyChecking=no"
|
||||
KEYCHAIN_SERVICE="${BOSS_KEYCHAIN_SERVICE:-boss-server-debug-ssh}"
|
||||
|
||||
resolve_password() {
|
||||
if [[ -n "${BOSS_SERVER_PASS:-}" ]]; then
|
||||
printf '%s' "${BOSS_SERVER_PASS}"
|
||||
return 0
|
||||
fi
|
||||
if command -v security >/dev/null 2>&1; then
|
||||
local keychain_pass=""
|
||||
keychain_pass="$(security find-generic-password -a "${REMOTE_USER}" -s "${KEYCHAIN_SERVICE}" -w 2>/dev/null || true)"
|
||||
if [[ -n "${keychain_pass}" ]]; then
|
||||
printf '%s' "${keychain_pass}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
PASSWORD="$(resolve_password || true)"
|
||||
|
||||
if [[ -n "${PASSWORD}" ]]; then
|
||||
export SSHPASS="${PASSWORD}"
|
||||
RSYNC_RSH="sshpass -e ssh ${SSH_OPTS}"
|
||||
SSH_PREFIX=(sshpass -e ssh ${=SSH_OPTS})
|
||||
else
|
||||
RSYNC_RSH="ssh ${SSH_OPTS}"
|
||||
SSH_PREFIX=(ssh ${=SSH_OPTS})
|
||||
fi
|
||||
|
||||
BOSS_RUNTIME_ROOT="$ROOT_DIR" BOSS_STATE_FILE="$ROOT_DIR/data/boss-state.json" npm run build
|
||||
|
||||
"${SSH_PREFIX[@]}" "$REMOTE_HOST" "sudo mkdir -p $REMOTE_DIR && sudo chown -R ${REMOTE_USER}:${REMOTE_USER} $REMOTE_DIR && sudo rm -rf $REMOTE_DIR/.next"
|
||||
|
||||
rsync -az --delete \
|
||||
--exclude ".git" \
|
||||
--exclude "node_modules" \
|
||||
--exclude "data/" \
|
||||
--exclude ".DS_Store" \
|
||||
-e "$RSYNC_RSH" \
|
||||
"$ROOT_DIR/" "$REMOTE_HOST:$REMOTE_DIR/"
|
||||
|
||||
"${SSH_PREFIX[@]}" "$REMOTE_HOST" "sudo bash $REMOTE_DIR/scripts/bootstrap-server.sh"
|
||||
"${SSH_PREFIX[@]}" "$REMOTE_HOST" "sudo chown -R ${REMOTE_USER}:${REMOTE_USER} $REMOTE_DIR"
|
||||
"${SSH_PREFIX[@]}" "$REMOTE_HOST" "cd $REMOTE_DIR && npm install --omit=dev && sudo systemctl restart boss-web && sudo systemctl restart caddy && sleep 2 && curl -fsS http://127.0.0.1:3000/api/health"
|
||||
30
scripts/install-local-launchagent.sh
Executable file
30
scripts/install-local-launchagent.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
PLIST_SOURCE="/Users/kris/code/boss/deployment/launchd/com.hyzq.boss.local-agent.plist"
|
||||
PLIST_TARGET="$HOME/Library/LaunchAgents/com.hyzq.boss.local-agent.plist"
|
||||
CONFIG_PATH="${1:-/Users/kris/code/boss/local-agent/config.cloud.json}"
|
||||
|
||||
if [[ "$CONFIG_PATH" != /* ]]; then
|
||||
CONFIG_PATH="/Users/kris/code/boss/${CONFIG_PATH}"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$CONFIG_PATH" ]]; then
|
||||
echo "Config file not found: $CONFIG_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/Library/LaunchAgents"
|
||||
cp "$PLIST_SOURCE" "$PLIST_TARGET"
|
||||
python3 - <<'PY' "$PLIST_TARGET" "$CONFIG_PATH"
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
plist_path = Path(sys.argv[1])
|
||||
config_path = sys.argv[2]
|
||||
text = plist_path.read_text()
|
||||
plist_path.write_text(text.replace("__BOSS_AGENT_CONFIG__", config_path))
|
||||
PY
|
||||
launchctl unload "$PLIST_TARGET" >/dev/null 2>&1 || true
|
||||
launchctl load "$PLIST_TARGET"
|
||||
echo "Loaded $PLIST_TARGET with $CONFIG_PATH"
|
||||
14
scripts/install-server-mail.sh
Executable file
14
scripts/install-server-mail.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SERVER_WRAPPER="${BOSS_SERVER_WRAPPER:-$HOME/.codex/skills/boss-server-debug/scripts/server_ssh.sh}"
|
||||
REMOTE_DIR="${BOSS_REMOTE_DIR:-/opt/boss}"
|
||||
|
||||
if [[ ! -x "${SERVER_WRAPPER}" ]]; then
|
||||
echo "Boss server wrapper not found: ${SERVER_WRAPPER}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${ROOT_DIR}/scripts/deploy-server.sh"
|
||||
"${SERVER_WRAPPER}" exec "sudo bash ${REMOTE_DIR}/deployment/mail/install-postfix-dovecot.sh"
|
||||
56
scripts/prepare-android-signing.sh
Executable file
56
scripts/prepare-android-signing.sh
Executable file
@@ -0,0 +1,56 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ANDROID_DIR="$ROOT_DIR/android"
|
||||
SIGNING_DIR="$ANDROID_DIR/signing"
|
||||
KEYSTORE_DIR="$ANDROID_DIR/keystores"
|
||||
PROPS_FILE="$SIGNING_DIR/release-signing.properties"
|
||||
KEYSTORE_FILE="$KEYSTORE_DIR/boss-release.keystore"
|
||||
KEY_ALIAS="${BOSS_ANDROID_KEY_ALIAS:-bossrelease}"
|
||||
KEY_DNAME="${BOSS_ANDROID_KEY_DNAME:-CN=Boss Release, OU=Boss, O=HYZQ, L=Shenzhen, ST=Guangdong, C=CN}"
|
||||
VALIDITY_DAYS="${BOSS_ANDROID_KEY_VALIDITY_DAYS:-3650}"
|
||||
|
||||
mkdir -p "$SIGNING_DIR" "$KEYSTORE_DIR"
|
||||
|
||||
generate_secret() {
|
||||
python3 - <<'PY'
|
||||
import secrets
|
||||
print(secrets.token_urlsafe(24))
|
||||
PY
|
||||
}
|
||||
|
||||
if [[ ! -f "$PROPS_FILE" ]]; then
|
||||
STORE_PASSWORD="${BOSS_ANDROID_STORE_PASSWORD:-$(generate_secret)}"
|
||||
KEY_PASSWORD="${BOSS_ANDROID_KEY_PASSWORD:-$STORE_PASSWORD}"
|
||||
cat > "$PROPS_FILE" <<EOF
|
||||
storeFile=../keystores/boss-release.keystore
|
||||
storePassword=$STORE_PASSWORD
|
||||
keyAlias=$KEY_ALIAS
|
||||
keyPassword=$KEY_PASSWORD
|
||||
EOF
|
||||
chmod 600 "$PROPS_FILE"
|
||||
else
|
||||
STORE_PASSWORD="$(sed -n 's/^storePassword=//p' "$PROPS_FILE" | head -n 1)"
|
||||
KEY_PASSWORD="$(sed -n 's/^keyPassword=//p' "$PROPS_FILE" | head -n 1)"
|
||||
KEY_ALIAS="$(sed -n 's/^keyAlias=//p' "$PROPS_FILE" | head -n 1)"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$KEYSTORE_FILE" ]]; then
|
||||
keytool -genkeypair \
|
||||
-storetype PKCS12 \
|
||||
-keystore "$KEYSTORE_FILE" \
|
||||
-storepass "$STORE_PASSWORD" \
|
||||
-keypass "$KEY_PASSWORD" \
|
||||
-alias "$KEY_ALIAS" \
|
||||
-keyalg RSA \
|
||||
-keysize 4096 \
|
||||
-validity "$VALIDITY_DAYS" \
|
||||
-dname "$KEY_DNAME"
|
||||
chmod 600 "$KEYSTORE_FILE"
|
||||
fi
|
||||
|
||||
echo "Prepared Android release signing:"
|
||||
echo " properties: $PROPS_FILE"
|
||||
echo " keystore: $KEYSTORE_FILE"
|
||||
echo " alias: $KEY_ALIAS"
|
||||
58
scripts/publish-apk-to-public.sh
Normal file
58
scripts/publish-apk-to-public.sh
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
SOURCE_APK="${1:-$ROOT_DIR/android/app/build/outputs/apk/debug/app-debug.apk}"
|
||||
TARGET_DIR="$ROOT_DIR/public/downloads"
|
||||
TARGET_APK="$TARGET_DIR/boss-android-latest.apk"
|
||||
TARGET_META="$TARGET_DIR/boss-android-latest.json"
|
||||
STANDALONE_DIR="$ROOT_DIR/.next/standalone/public/downloads"
|
||||
BUILD_GRADLE="$ROOT_DIR/android/app/build.gradle"
|
||||
SOURCE_NAME="$(basename "$SOURCE_APK")"
|
||||
BUILD_FLAVOR="debug"
|
||||
|
||||
if [[ "$SOURCE_NAME" == *release* ]]; then
|
||||
BUILD_FLAVOR="release"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SOURCE_APK" ]]; then
|
||||
echo "APK not found: $SOURCE_APK" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_DIR"
|
||||
cp "$SOURCE_APK" "$TARGET_APK"
|
||||
|
||||
SIZE_BYTES="$(stat -f '%z' "$TARGET_APK" 2>/dev/null || stat -c '%s' "$TARGET_APK")"
|
||||
UPDATED_AT="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
SHA256="$(shasum -a 256 "$TARGET_APK" | awk '{print $1}')"
|
||||
VERSION_NAME="$(sed -n 's/.*versionName \"\([^\"]*\)\"/\1/p' "$BUILD_GRADLE" | head -n 1)"
|
||||
VERSION_CODE="$(sed -n 's/.*versionCode \([0-9][0-9]*\).*/\1/p' "$BUILD_GRADLE" | head -n 1)"
|
||||
VERSIONED_APK_NAME="boss-android-v${VERSION_NAME}-${BUILD_FLAVOR}.apk"
|
||||
VERSIONED_TARGET_APK="$TARGET_DIR/$VERSIONED_APK_NAME"
|
||||
|
||||
cp "$TARGET_APK" "$VERSIONED_TARGET_APK"
|
||||
|
||||
cat > "$TARGET_META" <<EOF
|
||||
{
|
||||
"fileName": "$VERSIONED_APK_NAME",
|
||||
"urlPath": "/api/v1/user/ota/package",
|
||||
"sizeBytes": $SIZE_BYTES,
|
||||
"updatedAt": "$UPDATED_AT",
|
||||
"sha256": "$SHA256",
|
||||
"versionName": "$VERSION_NAME",
|
||||
"versionCode": $VERSION_CODE,
|
||||
"buildFlavor": "$BUILD_FLAVOR"
|
||||
}
|
||||
EOF
|
||||
|
||||
if [[ -d "$ROOT_DIR/.next/standalone/public" ]]; then
|
||||
mkdir -p "$STANDALONE_DIR"
|
||||
cp "$TARGET_APK" "$STANDALONE_DIR/boss-android-latest.apk"
|
||||
cp "$VERSIONED_TARGET_APK" "$STANDALONE_DIR/$VERSIONED_APK_NAME"
|
||||
cp "$TARGET_META" "$STANDALONE_DIR/boss-android-latest.json"
|
||||
fi
|
||||
|
||||
echo "Published APK to $TARGET_APK"
|
||||
echo "Published versioned APK to $VERSIONED_TARGET_APK"
|
||||
echo "Metadata written to $TARGET_META"
|
||||
8
scripts/run-web.sh
Executable file
8
scripts/run-web.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
PORT_VALUE="${PORT:-3000}"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
exec node .next/standalone/server.js --hostname 0.0.0.0 --port "$PORT_VALUE"
|
||||
8
scripts/start-local-agent.sh
Executable file
8
scripts/start-local-agent.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/zsh
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
CONFIG_PATH="${1:-$ROOT_DIR/local-agent/config.example.json}"
|
||||
|
||||
cd "$ROOT_DIR"
|
||||
exec node ./local-agent/server.mjs "$CONFIG_PATH"
|
||||
Reference in New Issue
Block a user