Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions .github/workflows/analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ permissions:

env:
FLUTTER_VERSION: '3.47.0'
FLUTTER_STORAGE_BASE_URL: https://storage.flutter-io.cn
PUB_HOSTED_URL: https://pub.flutter-io.cn

jobs:
reproducibilityCheck:
Expand Down Expand Up @@ -48,15 +50,38 @@ jobs:
fi

- name: Check locked dependencies
env:
PUB_HOSTED_URL: https://pub.dev
run: |
cp pubspec.lock "$RUNNER_TEMP/pubspec.lock"
trap 'cp "$RUNNER_TEMP/pubspec.lock" pubspec.lock' EXIT
bash scripts/ci/use-pub-mirror.sh pubspec.lock
flutter pub get --enforce-lockfile
cp "$RUNNER_TEMP/pubspec.lock" pubspec.lock
trap - EXIT
git diff --exit-code -- pubspec.yaml pubspec.lock

- name: Check JNI build-id patch
run: scripts/release/patch-jni-build-id.sh

rustTests:
name: Rust tests (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 1
submodules: recursive
persist-credentials: false

- uses: Swatinem/rust-cache@v2

- name: Test Rust workspace
run: cargo test --workspace --no-fail-fast

check:
runs-on: ubuntu-latest

Expand All @@ -72,6 +97,10 @@ jobs:
channel: 'stable'
flutter-version: ${{ env.FLUTTER_VERSION }}

- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh

- name: Install dependencies
run: flutter pub get

Expand Down
161 changes: 159 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ env:
APP_NAME: ServerBox
BUILD_TAG: test-build-${{ github.run_number }}-${{ github.sha }}
FLUTTER_VERSION: "3.47.0"
FLUTTER_STORAGE_BASE_URL: https://storage.flutter-io.cn
PUB_HOSTED_URL: https://pub.flutter-io.cn

jobs:
reproducibilityCheck:
Expand Down Expand Up @@ -65,15 +67,158 @@ jobs:
fi
- name: Check locked dependencies
shell: bash
env:
PUB_HOSTED_URL: https://pub.dev
run: |
cp pubspec.lock "$RUNNER_TEMP/pubspec.lock"
trap 'cp "$RUNNER_TEMP/pubspec.lock" pubspec.lock' EXIT
bash scripts/ci/use-pub-mirror.sh pubspec.lock
flutter pub get --enforce-lockfile
cp "$RUNNER_TEMP/pubspec.lock" pubspec.lock
trap - EXIT
git diff --exit-code -- pubspec.yaml pubspec.lock
- name: Check JNI build-id patch
shell: bash
run: scripts/release/patch-jni-build-id.sh

verifyMacOS:
name: Verify macOS release
if: github.event_name == 'workflow_dispatch' && inputs.release != true
needs: reproducibilityCheck
runs-on: macos-latest
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
persist-credentials: false
fetch-depth: 1
- name: Install Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- uses: Swatinem/rust-cache@v2
- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh
- name: Install dependencies
run: flutter pub get
- name: Sign locally
shell: bash
run: |
set -euo pipefail
project=macos/Runner.xcodeproj/project.pbxproj
sed -i '' \
-e 's/CODE_SIGN_IDENTITY = "Apple Development";/CODE_SIGN_IDENTITY = "-";/' \
-e 's/"CODE_SIGN_IDENTITY\[sdk=macosx\*\]" = "3rd Party Mac Developer Application";/"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";/' \
-e 's/CODE_SIGN_STYLE = Automatic;/CODE_SIGN_STYLE = Manual;/' \
-e 's/DEVELOPMENT_TEAM = BA88US33G6;/DEVELOPMENT_TEAM = "";/' \
-e 's/"DEVELOPMENT_TEAM\[sdk=macosx\*\]" = BA88US33G6;/"DEVELOPMENT_TEAM[sdk=macosx*]" = "";/' \
"$project"
cat > macos/Runner/Release.entitlements <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
</plist>
EOF
- name: Build release
run: flutter build macos --release
- name: Verify release launch
shell: bash
run: |
set -euo pipefail
app="build/macos/Build/Products/Release/Server Box.app"
executable="$app/Contents/MacOS/Server Box"
log_file="$RUNNER_TEMP/server-box.log"

codesign --verify --deep --strict "$app"
"$executable" >"$log_file" 2>&1 &
pid=$!

cleanup() {
if kill -0 "$pid" 2>/dev/null; then
kill "$pid"
wait "$pid" || true
fi
}
trap cleanup EXIT

for _ in {1..10}; do
if ! kill -0 "$pid" 2>/dev/null; then
wait "$pid" || true
cat "$log_file"
log show --last 2m --style compact --predicate 'process == "Server Box"' || true
echo "Server Box exited before the macOS launch smoke test completed."
exit 1
fi
sleep 1
done

cat "$log_file"

verifyMonitor:
name: Verify monitor ${{ matrix.platform }}-${{ matrix.arch }}
if: github.event_name == 'workflow_dispatch' && inputs.release != true
needs: reproducibilityCheck
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
platform: linux
arch: amd64
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
platform: linux
arch: arm64
- os: macos-latest
target: aarch64-apple-darwin
platform: macos
arch: arm64
- os: macos-latest
target: x86_64-apple-darwin
platform: macos
arch: amd64
- os: windows-latest
target: x86_64-pc-windows-msvc
platform: windows
arch: amd64
runs-on: ${{ matrix.os }}
env:
SQLX_OFFLINE: "true"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: recursive
persist-credentials: false
- name: Install musl toolchain
if: matrix.platform == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y musl-tools
- name: Add Rust target
run: rustup target add ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Build
shell: bash
run: cargo build -p server_box_monitor --release --target ${{ matrix.target }}

buildAndroid:
name: Build android
needs: reproducibilityCheck
Expand All @@ -94,6 +239,9 @@ jobs:
with:
distribution: "zulu"
java-version: "21"
- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh
- name: Fetch secrets
run: |
curl --fail --show-error --location -u ${{ secrets.BASIC_AUTH }} -o android/app/app.key ${{ secrets.URL_PREFIX }}app.key
Expand Down Expand Up @@ -180,6 +328,9 @@ jobs:
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh
- name: Resolve build number
shell: bash
run: |
Expand Down Expand Up @@ -250,6 +401,9 @@ jobs:
sudo apt update
sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev mesa-utils libvulkan-dev desktop-file-utils wget
sudo apt install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libunwind-dev libsecret-1-dev
- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh
- name: Build (release)
if: inputs.release == true || github.ref_type == 'tag'
shell: bash
Expand Down Expand Up @@ -303,6 +457,9 @@ jobs:
with:
channel: 'stable'
flutter-version: ${{ env.FLUTTER_VERSION }}
- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh
- name: Build (release)
if: inputs.release == true || github.ref_type == 'tag'
shell: bash
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ permissions:

env:
FLUTTER_VERSION: '3.47.0'
FLUTTER_STORAGE_BASE_URL: https://storage.flutter-io.cn
PUB_HOSTED_URL: https://pub.flutter-io.cn

jobs:
integration:
Expand All @@ -55,6 +57,10 @@ jobs:

- uses: Swatinem/rust-cache@v2

- name: Use pub mirror with locked versions
shell: bash
run: bash scripts/ci/use-pub-mirror.sh

- name: Install dependencies
run: flutter pub get

Expand Down
15 changes: 10 additions & 5 deletions crates/sbm_parser/src/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::types::*;
/// first line must be `cpu`/`cpuN`, stop at the first non-cpu line; skip lines with fewer than 8 fields
pub fn parse_cpu(raw: &str) -> Vec<CpuCore> {
let mut cores = Vec::new();
for line in raw.split('\n') {
for line in raw.lines() {
let line = line.trim();
if line.is_empty() {
continue;
Expand Down Expand Up @@ -319,7 +319,7 @@ pub fn parse_temps(types_raw: &str, values_raw: &str, divisor: f64) -> Temperatu
/// Tcp lines of /proc/net/snmp (Dart `Conn.parse`):
/// take the last `Tcp:` line; MaxConn is column 4, AttemptFails column 7
pub fn parse_conn(raw: &str) -> Option<Conn> {
let line = raw.split('\n').filter(|l| l.starts_with("Tcp:")).next_back()?;
let line = raw.split('\n').rfind(|l| l.starts_with("Tcp:"))?;
let fields: Vec<&str> = line.split_whitespace().collect();
if fields.len() <= 7 {
return None;
Expand Down Expand Up @@ -357,7 +357,7 @@ pub fn parse_diskio(raw: &str) -> Vec<DiskIoPiece> {
pub fn parse_batteries(raw: &str, only_li_poly: bool) -> Vec<Battery> {
let mut batteries = Vec::new();
let mut block: Vec<&str> = Vec::new();
for line in raw.split('\n') {
for line in raw.lines() {
if !line.is_empty() {
block.push(line);
continue;
Expand All @@ -369,6 +369,11 @@ pub fn parse_batteries(raw: &str, only_li_poly: bool) -> Vec<Battery> {
}
block.clear();
}
if let Some(battery) = parse_battery_block(&block)
&& (!only_li_poly || battery.is_li_poly())
{
batteries.push(battery);
}
batteries
}

Expand Down Expand Up @@ -399,7 +404,7 @@ fn parse_battery_block(lines: &[&str]) -> Option<Battery> {
/// each block at least 3 lines [device, adapter, detail...]
pub fn parse_sensors(raw: &str) -> Vec<SensorItem> {
let mut groups: Vec<Vec<&str>> = vec![Vec::new()];
for line in raw.split('\n') {
for line in raw.lines() {
if line.is_empty() {
groups.push(Vec::new());
} else {
Expand Down Expand Up @@ -435,7 +440,7 @@ pub fn parse_sensors(raw: &str) -> Vec<SensorItem> {
/// count, keeping first-seen order
pub fn parse_cpu_brand(raw: &str) -> Vec<(String, u32)> {
let mut brands: Vec<(String, u32)> = Vec::new();
for line in raw.split('\n') {
for line in raw.lines() {
if !line.contains("model name") {
continue;
}
Expand Down
4 changes: 3 additions & 1 deletion crates/sbm_parser/src/smart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ fn is_physical_disk(device: &str) -> bool {
}

pub fn parse(raw: &str) -> Vec<DiskSmart> {
raw.split("\n\n")
let normalized = raw.replace("\r\n", "\n");
normalized
.split("\n\n")
.filter(|s| !s.trim().is_empty())
.filter_map(|block| parse_block(block.trim()))
.collect()
Expand Down
11 changes: 7 additions & 4 deletions lib/core/utils/android_rootfs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,11 +288,14 @@ abstract final class AndroidRootfs {
}
if (parts.isEmpty) return base;

final host = [base, ...parts].join('/');
final separator = Platform.pathSeparator;
final host = [base, ...parts].join(separator);
// And resolved again at the end, because a symlink *inside* the rootfs can
// point out of it — `ln -s / /tmp/out` is one reviewed command away, and
// `File.readAsBytes` would follow it without asking anybody.
final toResolve = forWrite ? host.substring(0, host.lastIndexOf('/')) : host;
final toResolve = forWrite
? host.substring(0, host.lastIndexOf(separator))
: host;
String? real;
try {
real = await Directory(toResolve).resolveSymbolicLinks();
Expand All @@ -305,8 +308,8 @@ abstract final class AndroidRootfs {
return null;
}
}
if (real != base && !real.startsWith('$base/')) return null;
return forWrite ? '$real/${parts.last}' : real;
if (real != base && !real.startsWith('$base$separator')) return null;
return forWrite ? '$real$separator${parts.last}' : real;
}

/// What the guest needs in its environment.
Expand Down
2 changes: 1 addition & 1 deletion lib/core/utils/local_file_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ class LocalFileBackend implements FileBackend {

static Future<String?> _targetOf(Link link) async {
try {
return await link.target();
return (await link.target()).replaceAll(r'\', '/');
} on FileSystemException {
// A link to nowhere is still a link, and still worth listing.
return null;
Expand Down
Loading
Loading