mirror of https://github.com/ospab/ostp.git
Compare commits
9 Commits
88e0634f09
...
ebfc751471
| Author | SHA1 | Date |
|---|---|---|
|
|
ebfc751471 | |
|
|
3cda1a9bd4 | |
|
|
77a45d7642 | |
|
|
6abae68f35 | |
|
|
cb57347d51 | |
|
|
32c36afc3b | |
|
|
a8aba8f4b8 | |
|
|
2ede607027 | |
|
|
0c69617725 |
|
|
@ -378,7 +378,15 @@ jobs:
|
||||||
~/.cargo/registry/cache/
|
~/.cargo/registry/cache/
|
||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
|
ostp-gui/src-tauri/target/
|
||||||
key: cargo-windows-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
key: cargo-windows-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
# Without a prefix fallback this cache NEVER restored on a release:
|
||||||
|
# cutting a release rewrites every Cargo.lock (version bump), which
|
||||||
|
# changes hashFiles(), which misses the exact key — so each release
|
||||||
|
# rebuilt every dependency from scratch. That is why the GUI jobs ran
|
||||||
|
# 2-4x longer than the plain release targets, which had this all along.
|
||||||
|
restore-keys: |
|
||||||
|
cargo-windows-gui-${{ matrix.target }}-
|
||||||
|
|
||||||
- name: Download wintun
|
- name: Download wintun
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
@ -460,7 +468,10 @@ jobs:
|
||||||
~/.cargo/registry/cache/
|
~/.cargo/registry/cache/
|
||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
|
ostp-gui/src-tauri/target/
|
||||||
key: cargo-linux-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
key: cargo-linux-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
cargo-linux-gui-${{ matrix.target }}-
|
||||||
|
|
||||||
- name: Build Tauri App
|
- name: Build Tauri App
|
||||||
working-directory: ostp-gui
|
working-directory: ostp-gui
|
||||||
|
|
@ -522,7 +533,10 @@ jobs:
|
||||||
~/.cargo/registry/cache/
|
~/.cargo/registry/cache/
|
||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
|
ostp-gui/src-tauri/target/
|
||||||
key: cargo-macos-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
key: cargo-macos-gui-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
cargo-macos-gui-${{ matrix.target }}-
|
||||||
|
|
||||||
- name: Build Tauri App
|
- name: Build Tauri App
|
||||||
working-directory: ostp-gui
|
working-directory: ostp-gui
|
||||||
|
|
@ -587,27 +601,107 @@ jobs:
|
||||||
with:
|
with:
|
||||||
ndk-version: r26b
|
ndk-version: r26b
|
||||||
|
|
||||||
- name: Install cargo-ndk
|
# The Android jobs had no Rust caching at all, so every release recompiled
|
||||||
run: cargo install cargo-ndk
|
# the whole ostp-jni dependency graph from scratch — the main reason these
|
||||||
|
# were among the slowest jobs in the matrix.
|
||||||
|
- name: Cache cargo
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
target/
|
||||||
|
key: cargo-android-${{ matrix.arch }}-${{ hashFiles('**/Cargo.lock') }}
|
||||||
|
restore-keys: |
|
||||||
|
cargo-android-${{ matrix.arch }}-
|
||||||
|
|
||||||
|
# cargo-ndk was built from source on every run. Cache the binary the same
|
||||||
|
# way the cross-compilation jobs already cache `cross`.
|
||||||
|
- name: Restore cargo-ndk binary cache
|
||||||
|
id: cargo-ndk-cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cargo/bin/cargo-ndk
|
||||||
|
key: cargo-ndk-bin-${{ runner.os }}-v1
|
||||||
|
|
||||||
|
- name: Install cargo-ndk (if not cached)
|
||||||
|
if: steps.cargo-ndk-cache.outputs.cache-hit != 'true'
|
||||||
|
run: cargo install cargo-ndk --locked
|
||||||
|
|
||||||
- name: Build Android APK
|
- name: Build Android APK
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: ostp-flutter
|
working-directory: ostp-flutter
|
||||||
|
env:
|
||||||
|
OSTP_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||||
|
OSTP_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
|
OSTP_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||||
|
OSTP_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
# 1. Compile JNI
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 1. Materialise the upload keystore from secrets. Android keys an app
|
||||||
|
# by applicationId + signing key and refuses to update across a key
|
||||||
|
# change, so every published build MUST use this one key. Releases
|
||||||
|
# used to fall through to the per-machine debug keystore, which on
|
||||||
|
# ephemeral CI runners meant a different random key every build -
|
||||||
|
# hence "App not installed" on upgrade.
|
||||||
|
if [ -z "${OSTP_KEYSTORE_B64:-}" ]; then
|
||||||
|
echo "::error::ANDROID_KEYSTORE_BASE64 secret is not set. Refusing to publish a"
|
||||||
|
echo "::error::debug-signed APK: users could not update over it and the key is"
|
||||||
|
echo "::error::not reproducible. See docs for the one-time keystore setup."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export OSTP_KEYSTORE_PATH="$RUNNER_TEMP/ostp-upload.jks"
|
||||||
|
# Strip any stray CR/LF before decoding: the secret is pasted from a
|
||||||
|
# shell whose line endings we don't control, and a single trailing \r
|
||||||
|
# is enough to corrupt the decode.
|
||||||
|
printf '%s' "$OSTP_KEYSTORE_B64" | tr -d '\r\n' | base64 -d > "$OSTP_KEYSTORE_PATH"
|
||||||
|
|
||||||
|
# Verify the keystore opens BEFORE spending four minutes on Gradle only
|
||||||
|
# to fail at the packaging step. The size/SHA-256 are safe to print (a
|
||||||
|
# hash reveals nothing) and let the operator compare against the local
|
||||||
|
# file to tell a transport problem apart from a wrong password.
|
||||||
|
echo "keystore: $(stat -c%s "$OSTP_KEYSTORE_PATH") bytes, sha256 $(sha256sum "$OSTP_KEYSTORE_PATH" | cut -d' ' -f1)"
|
||||||
|
if ! keytool -list -keystore "$OSTP_KEYSTORE_PATH" \
|
||||||
|
-storepass "$OSTP_KEYSTORE_PASSWORD" >/dev/null 2>&1; then
|
||||||
|
echo "::error::The keystore did not open with ANDROID_KEYSTORE_PASSWORD."
|
||||||
|
echo "::error::If the SHA-256 above matches your local ostp-upload.jks, the file"
|
||||||
|
echo "::error::arrived intact and the password secret itself is wrong - note that"
|
||||||
|
echo "::error::PowerShell expands \$ inside double quotes, so a password containing"
|
||||||
|
echo "::error::one gets mangled unless it was set with single quotes."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! keytool -list -keystore "$OSTP_KEYSTORE_PATH" \
|
||||||
|
-storepass "$OSTP_KEYSTORE_PASSWORD" -alias "$OSTP_KEY_ALIAS" >/dev/null 2>&1; then
|
||||||
|
echo "::error::Keystore opened, but it has no key under ANDROID_KEY_ALIAS."
|
||||||
|
echo "::error::Aliases present in the keystore:"
|
||||||
|
keytool -list -keystore "$OSTP_KEYSTORE_PATH" -storepass "$OSTP_KEYSTORE_PASSWORD" \
|
||||||
|
| grep -i "PrivateKeyEntry" || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Compile JNI
|
||||||
mkdir -p android/app/src/main/jniLibs/${{ matrix.arch }}
|
mkdir -p android/app/src/main/jniLibs/${{ matrix.arch }}
|
||||||
|
|
||||||
cd ../ostp-jni
|
cd ../ostp-jni
|
||||||
cargo ndk -t ${{ matrix.arch }} -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
|
cargo ndk -t ${{ matrix.arch }} -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
|
||||||
cd ../ostp-flutter
|
cd ../ostp-flutter
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 3. Build Flutter APK
|
# 3. Build Flutter APK
|
||||||
flutter build apk --release --target-platform ${{ matrix.flutter_target }}
|
flutter build apk --release --target-platform ${{ matrix.flutter_target }}
|
||||||
|
|
||||||
# 4. Copy to output
|
# 4. Fail loudly if the APK somehow still came out debug-signed, rather
|
||||||
cp build/app/outputs/flutter-apk/app-release.apk ostp-android-${{ matrix.arch }}.apk
|
# than shipping another un-updatable build.
|
||||||
|
APK=build/app/outputs/flutter-apk/app-release.apk
|
||||||
|
if "$ANDROID_HOME"/build-tools/*/apksigner verify --print-certs "$APK" 2>/dev/null \
|
||||||
|
| grep -qi "CN=Android Debug"; then
|
||||||
|
echo "::error::APK is signed with the Android debug certificate - aborting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Copy to output
|
||||||
|
cp "$APK" ostp-android-${{ matrix.arch }}.apk
|
||||||
|
|
||||||
- name: Upload to GitHub Release
|
- name: Upload to GitHub Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,13 @@ test_route.ps1
|
||||||
config.json
|
config.json
|
||||||
wintun.dll
|
wintun.dll
|
||||||
|
|
||||||
|
# Android signing keys. The upload keystore is the ONE key every published APK
|
||||||
|
# must be signed with (Android refuses to update an app across a key change),
|
||||||
|
# so losing or leaking it is unrecoverable — it can never be committed.
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
key.properties
|
||||||
|
|
||||||
# Server runtime cache (public IP autodetect) — must never be committed,
|
# Server runtime cache (public IP autodetect) — must never be committed,
|
||||||
# it's regenerated locally and leaks whatever host it ran on last.
|
# it's regenerated locally and leaks whatever host it ran on last.
|
||||||
.ostp_public_ip
|
.ostp_public_ip
|
||||||
|
|
@ -39,6 +46,7 @@ turn-harvesting-idea.md
|
||||||
|
|
||||||
# Private tooling (closed-source)
|
# Private tooling (closed-source)
|
||||||
ostp-prober/
|
ostp-prober/
|
||||||
|
ostp-lab/
|
||||||
|
|
||||||
ostp-brain/
|
ostp-brain/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"target_version": "0.4.2",
|
"target_version": "0.4.2",
|
||||||
"branch": "beta",
|
"branch": "master",
|
||||||
"alpha_iteration": 0,
|
"alpha_iteration": 0,
|
||||||
"beta_iteration": 4
|
"beta_iteration": 5
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("kotlin-android")
|
id("kotlin-android")
|
||||||
|
|
@ -5,6 +8,37 @@ plugins {
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Release signing material ────────────────────────────────────────────────
|
||||||
|
// Supplied out-of-band and never committed: either an `android/key.properties`
|
||||||
|
// file (local release builds) or OSTP_KEYSTORE_* environment variables (CI).
|
||||||
|
//
|
||||||
|
// This exists because the release build used to be signed with the DEBUG
|
||||||
|
// keystore (the stock Flutter template TODO). Android identifies an app by
|
||||||
|
// applicationId + signing key, and refuses to update across a key change. The
|
||||||
|
// debug keystore is auto-generated per machine, and CI runners are ephemeral,
|
||||||
|
// so every published build carried a different random key — which is why
|
||||||
|
// updating on top of a previous install failed with "App not installed" /
|
||||||
|
// "unable to parse the package" and only a full uninstall+reinstall worked.
|
||||||
|
val keystoreProperties = Properties().apply {
|
||||||
|
val propsFile = rootProject.file("key.properties")
|
||||||
|
if (propsFile.exists()) {
|
||||||
|
FileInputStream(propsFile).use { load(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blank counts as absent. GitHub Actions substitutes an EMPTY STRING (not an
|
||||||
|
// unset variable) for a secret that doesn't exist, so `getenv(...) ?: fallback`
|
||||||
|
// silently kept the empty value — the elvis operator only catches null. That is
|
||||||
|
// how an unset ANDROID_KEY_PASSWORD ended up being used as the literal key
|
||||||
|
// password instead of falling back to the store password, producing Gradle's
|
||||||
|
// "Get Key failed: Given final block not properly padded".
|
||||||
|
fun signingSetting(propKey: String, envKey: String): String? =
|
||||||
|
(keystoreProperties.getProperty(propKey) ?: System.getenv(envKey))
|
||||||
|
?.takeIf { it.isNotBlank() }
|
||||||
|
|
||||||
|
val releaseStorePath: String? = signingSetting("storeFile", "OSTP_KEYSTORE_PATH")
|
||||||
|
val hasReleaseSigning: Boolean = !releaseStorePath.isNullOrBlank()
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.ospab.ostp_client"
|
namespace = "com.ospab.ostp_client"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
|
@ -34,11 +68,43 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
create("release") {
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
val store = signingSetting("storePassword", "OSTP_KEYSTORE_PASSWORD")
|
||||||
|
storeFile = file(releaseStorePath!!)
|
||||||
|
storePassword = store
|
||||||
|
keyAlias = signingSetting("keyAlias", "OSTP_KEY_ALIAS")
|
||||||
|
// PKCS12 (the keytool default since Java 9, and what our upload
|
||||||
|
// keystore is) cannot hold a key password that differs from the
|
||||||
|
// store password — the format simply has no place to put one. So
|
||||||
|
// treat a missing key password as "same as the store password"
|
||||||
|
// instead of demanding a secret that, for this keystore, can only
|
||||||
|
// ever be a duplicate. An explicit value still wins, for the older
|
||||||
|
// JKS format where the two genuinely can differ.
|
||||||
|
keyPassword = signingSetting("keyPassword", "OSTP_KEY_PASSWORD") ?: store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
// Use the real upload key when one was supplied; otherwise fall back to
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
// the debug keystore so a plain local `flutter build apk --release`
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
// still works for development. Anything PUBLISHED must take the first
|
||||||
|
// branch — a debug-signed build cannot be updated over, and its key is
|
||||||
|
// machine-local, so it also can't be reproduced later.
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"OSTP: no release keystore configured (android/key.properties or " +
|
||||||
|
"OSTP_KEYSTORE_PATH) - falling back to the DEBUG keystore. This APK " +
|
||||||
|
"is for local use only: users cannot update over it, and the key is " +
|
||||||
|
"not reproducible on another machine."
|
||||||
|
)
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
}
|
||||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 0.4.2+23
|
version: 0.4.2+25
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.4
|
sdk: ^3.11.4
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,12 @@
|
||||||
--c-accent-dim: rgba(var(--c-fg-rgb),0.08);
|
--c-accent-dim: rgba(var(--c-fg-rgb),0.08);
|
||||||
--c-accent-glow: rgba(var(--c-fg-rgb),0.18);
|
--c-accent-glow: rgba(var(--c-fg-rgb),0.18);
|
||||||
|
|
||||||
/* Green only for "connected" state */
|
/* Green only for "connected" state — the one deliberate break from the
|
||||||
--c-green: #e8e8e8;
|
monochrome palette, so a successful connection reads at a glance. */
|
||||||
--c-green-glow: rgba(232,232,232,0.25);
|
--c-green-rgb: 46, 230, 109;
|
||||||
--c-green-dim: rgba(232,232,232,0.07);
|
--c-green: #2ee66d;
|
||||||
|
--c-green-glow: rgba(var(--c-green-rgb),0.28);
|
||||||
|
--c-green-dim: rgba(var(--c-green-rgb),0.09);
|
||||||
|
|
||||||
--c-red: #ff5f5f;
|
--c-red: #ff5f5f;
|
||||||
--c-amber: #f0b840;
|
--c-amber: #f0b840;
|
||||||
|
|
@ -55,9 +57,11 @@
|
||||||
--c-accent: #18181b;
|
--c-accent: #18181b;
|
||||||
--c-accent-dim: rgba(0,0,0,0.08);
|
--c-accent-dim: rgba(0,0,0,0.08);
|
||||||
--c-accent-glow: rgba(0,0,0,0.14);
|
--c-accent-glow: rgba(0,0,0,0.14);
|
||||||
--c-green: #18181b;
|
/* Deeper green so it stays legible against the light background. */
|
||||||
--c-green-glow: rgba(0,0,0,0.16);
|
--c-green-rgb: 22, 163, 74;
|
||||||
--c-green-dim: rgba(0,0,0,0.05);
|
--c-green: #16a34a;
|
||||||
|
--c-green-glow: rgba(var(--c-green-rgb),0.22);
|
||||||
|
--c-green-dim: rgba(var(--c-green-rgb),0.08);
|
||||||
--c-red: #dc2626;
|
--c-red: #dc2626;
|
||||||
--c-amber: #d97706;
|
--c-amber: #d97706;
|
||||||
--c-txt-1: #18181b;
|
--c-txt-1: #18181b;
|
||||||
|
|
@ -155,7 +159,7 @@ a { text-decoration: none; }
|
||||||
transition: background var(--t-med), box-shadow var(--t-med);
|
transition: background var(--t-med), box-shadow var(--t-med);
|
||||||
}
|
}
|
||||||
.brand-dot.connecting { animation: dot-blink 1.4s infinite ease-in-out; background: var(--c-accent); }
|
.brand-dot.connecting { animation: dot-blink 1.4s infinite ease-in-out; background: var(--c-accent); }
|
||||||
.brand-dot.connected { background: var(--c-accent); box-shadow: 0 0 10px var(--c-accent-glow); }
|
.brand-dot.connected { background: var(--c-green); box-shadow: 0 0 10px var(--c-green-glow); }
|
||||||
|
|
||||||
@keyframes dot-blink {
|
@keyframes dot-blink {
|
||||||
0%,100% { opacity: 1; }
|
0%,100% { opacity: 1; }
|
||||||
|
|
@ -233,11 +237,11 @@ a { text-decoration: none; }
|
||||||
|
|
||||||
.orbit-wrap.connected .orbit {
|
.orbit-wrap.connected .orbit {
|
||||||
animation: orbit-spin 4s linear infinite;
|
animation: orbit-spin 4s linear infinite;
|
||||||
border-color: rgba(var(--c-fg-rgb),0.14);
|
border-color: rgba(var(--c-green-rgb),0.30);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.orbit-wrap.connected .orbit-2 { animation-duration: 6s; animation-direction: reverse; border-color: rgba(var(--c-fg-rgb),0.08); }
|
.orbit-wrap.connected .orbit-2 { animation-duration: 6s; animation-direction: reverse; border-color: rgba(var(--c-green-rgb),0.18); }
|
||||||
.orbit-wrap.connected .orbit-3 { animation-duration: 9s; border-color: rgba(var(--c-fg-rgb),0.04); }
|
.orbit-wrap.connected .orbit-3 { animation-duration: 9s; border-color: rgba(var(--c-green-rgb),0.10); }
|
||||||
|
|
||||||
@keyframes orbit-spin {
|
@keyframes orbit-spin {
|
||||||
from { transform: rotate(0deg); }
|
from { transform: rotate(0deg); }
|
||||||
|
|
@ -270,9 +274,9 @@ a { text-decoration: none; }
|
||||||
animation: btn-breathe 2s infinite ease-in-out;
|
animation: btn-breathe 2s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
.power-btn.connected {
|
.power-btn.connected {
|
||||||
border-color: rgba(var(--c-fg-rgb),0.8);
|
border-color: var(--c-green);
|
||||||
color: var(--c-txt-1);
|
color: var(--c-green);
|
||||||
box-shadow: 0 0 0 8px rgba(var(--c-fg-rgb),0.04), 0 0 50px rgba(var(--c-fg-rgb),0.12), 0 8px 32px rgba(0,0,0,0.5);
|
box-shadow: 0 0 0 8px var(--c-green-dim), 0 0 50px var(--c-green-glow), 0 8px 32px rgba(0,0,0,0.5);
|
||||||
}
|
}
|
||||||
.power-btn.error {
|
.power-btn.error {
|
||||||
border-color: var(--c-red);
|
border-color: var(--c-red);
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ use clap::Parser;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about = "OSTP Core - Ospab Stealth Transport Protocol", long_about = None)]
|
#[command(author, version, about = "OSTP Core - Ospab Stealth Transport Protocol", long_about = None)]
|
||||||
|
|
@ -725,7 +724,14 @@ fn run_setup_wizard(config_path: &std::path::Path) -> Result<()> {
|
||||||
// this used to be a DefaultHasher (SipHash) placeholder that produced a
|
// this used to be a DefaultHasher (SipHash) placeholder that produced a
|
||||||
// differently-shaped digest, so a password set up through this wizard could
|
// differently-shaped digest, so a password set up through this wizard could
|
||||||
// never actually log into the panel it just configured.
|
// never actually log into the panel it just configured.
|
||||||
let pass_hash = format!("{:x}", sha2::Sha256::digest(password.as_bytes()));
|
// Trait-qualified so this compiles whether or not `sha2::Digest` happens
|
||||||
|
// to be in scope: `digest` is a trait method, and relying on the import
|
||||||
|
// alone broke the CI build once (v0.4.2-beta.3) while resolving fine
|
||||||
|
// locally.
|
||||||
|
let pass_hash = format!(
|
||||||
|
"{:x}",
|
||||||
|
<sha2::Sha256 as sha2::Digest>::digest(password.as_bytes())
|
||||||
|
);
|
||||||
|
|
||||||
wizard_step(4, TOTAL, "Saving configuration");
|
wizard_step(4, TOTAL, "Saving configuration");
|
||||||
let panel_bind = format!("0.0.0.0:{}", panel_port);
|
let panel_bind = format!("0.0.0.0:{}", panel_port);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue