Debian trixie-based build-compile-deploy workflow

This commit is contained in:
Mikkeli Matlock
2026-01-24 23:18:37 +09:00
parent 90ed757976
commit aabca4915c
8 changed files with 311 additions and 36 deletions

8
.gitignore vendored
View File

@@ -32,16 +32,20 @@ Thumbs.db
# Logs # Logs
*.log *.log
# Flutter platforms we don't use (Pi project - elinux only) # Flutter platform folders (all generated by flutter-elinux create)
pi/ui/android/ pi/ui/android/
pi/ui/ios/ pi/ui/ios/
pi/ui/macos/ pi/ui/macos/
pi/ui/web/ pi/ui/web/
pi/ui/windows/ pi/ui/windows/
pi/ui/linux/
pi/ui/elinux/
pi/ui/test/
# Flutter generated files we don't need in repo # Flutter generated files
pi/ui/.metadata pi/ui/.metadata
pi/ui/README.md pi/ui/README.md
pi/ui/analysis_options.yaml
# Pi (aarch64) dynamic linkers and stuff # Pi (aarch64) dynamic linkers and stuff
pi_sysroot/ pi_sysroot/

View File

@@ -24,14 +24,15 @@ smart-serow/
│ └── ui/ # Flutter app (elinux target) │ └── ui/ # Flutter app (elinux target)
│ ├── lib/main.dart │ ├── lib/main.dart
│ ├── pubspec.yaml │ ├── pubspec.yaml
│ └── elinux/ # Platform-specific build config │ └── elinux/ # Generated by flutter-elinux (gitignored)
├── scripts/ ├── scripts/
│ ├── build.sh # Cross-compile for ARM64 │ ├── build.sh # Cross-compile for ARM64
│ ├── deploy.sh # Push to Pi via rsync │ ├── deploy.sh # Push to Pi via rsync
│ ├── deploy_target.json │ ├── deploy_target.json
│ ├── pi_setup.sh # One-time Pi setup │ ├── pi_setup.sh # One-time Pi setup
│ └── smartserow-ui.service │ └── smartserow-ui.service
── pi_sysroot/ # Pi libraries for cross-linking (gitignored) ── pi_sysroot/ # Pi libraries for cross-linking (gitignored)
└── LICENSE # MIT
``` ```
--- ---
@@ -145,11 +146,10 @@ Flutter-elinux supports multiple backends. We use **GBM** (DRM/KMS direct).
| `x11` | Debug | Needs X server, mouse/keyboard friendly | | `x11` | Debug | Needs X server, mouse/keyboard friendly |
| `wayland` | - | Requires compositor, more dependencies | | `wayland` | - | Requires compositor, more dependencies |
The service runs with `-b drm`. For X11 debugging on Pi: The backend is compiled in (we build with `--target-backend-type=gbm`).
```bash The `-b` flag is for bundle path, not backend selection.
startx &
./smartserow_ui -b x11 For X11 debugging, you'd need to rebuild with `--target-backend-type=x11`.
```
--- ---

205
pi/ui/pubspec.lock Normal file
View File

@@ -0,0 +1,205 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
url: "https://pub.dev"
source: hosted
version: "2.12.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
url: "https://pub.dev"
source: hosted
version: "10.0.8"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
url: "https://pub.dev"
source: hosted
version: "3.0.9"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
url: "https://pub.dev"
source: hosted
version: "3.0.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.16.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
url: "https://pub.dev"
source: hosted
version: "0.7.4"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
url: "https://pub.dev"
source: hosted
version: "14.3.1"
sdks:
dart: ">=3.7.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@@ -11,16 +11,62 @@ UI_DIR="$PROJECT_ROOT/pi/ui"
echo "=== Smart Serow Build ===" echo "=== Smart Serow Build ==="
echo "Project: $UI_DIR" echo "Project: $UI_DIR"
# Check for flutter-elinux
if ! command -v flutter-elinux &> /dev/null; then
echo "ERROR: flutter-elinux not found in PATH"
echo "Install it or check your PATH"
echo ""
echo "Current PATH: $PATH"
which flutter 2>/dev/null && echo "Found flutter at: $(which flutter)"
exit 1
fi
echo "Using: $(which flutter-elinux)"
# Cross-compilation toolchain for ARM64
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export AR=aarch64-linux-gnu-ar
export LD=aarch64-linux-gnu-ld
# CMake-specific vars
export CMAKE_C_COMPILER=aarch64-linux-gnu-gcc
export CMAKE_CXX_COMPILER=aarch64-linux-gnu-g++
echo "Cross-compiler: $CXX"
cd "$UI_DIR" cd "$UI_DIR"
# Clean previous build (optional, comment out for faster incremental builds) # Initialize elinux project if not already configured
# flutter-elinux clean if [ ! -d "elinux" ]; then
echo "Initializing elinux project structure..."
flutter-elinux create . --project-name smartserow_ui --org com.smartserow
fi
# Clean CMake cache on --clean flag
# (CMake caches compiler choice, so stale cache = wrong linker)
if [ "${1:-}" = "--clean" ]; then
echo "Cleaning CMake cache..."
rm -rf build/elinux/arm64
fi
echo "Fetching dependencies..." echo "Fetching dependencies..."
flutter-elinux pub get flutter-elinux pub get
echo "Building for ARM64 (elinux)..." echo "Building for ARM64 (elinux) with DRM-GBM backend..."
flutter-elinux build elinux --target-arch=arm64 --release
# Use Pi sysroot if available (for proper cross-linking)
SYSROOT_FLAG=""
if [ -d "$PROJECT_ROOT/pi_sysroot" ]; then
echo "Using Pi sysroot: $PROJECT_ROOT/pi_sysroot"
SYSROOT_FLAG="--target-sysroot=$PROJECT_ROOT/pi_sysroot"
fi
flutter-elinux build elinux \
--target-arch=arm64 \
--target-backend-type=gbm \
--target-compiler-triple=aarch64-linux-gnu \
$SYSROOT_FLAG \
--release
BUILD_OUTPUT="$UI_DIR/build/elinux/arm64/release/bundle" BUILD_OUTPUT="$UI_DIR/build/elinux/arm64/release/bundle"

View File

@@ -14,14 +14,21 @@ if [ ! -f "$CONFIG_FILE" ]; then
exit 1 exit 1
fi fi
HOST=$(jq -r '.host' "$CONFIG_FILE") # Parse JSON with Python (more universal than jq)
REMOTE_PATH=$(jq -r '.remote_path' "$CONFIG_FILE") read_json() {
SERVICE_NAME=$(jq -r '.service_name' "$CONFIG_FILE") python3 -c "import json; print(json.load(open('$CONFIG_FILE'))['$1'])"
}
PI_USER=$(read_json user)
PI_HOST=$(read_json host)
REMOTE_PATH=$(read_json remote_path)
SERVICE_NAME=$(read_json service_name)
SSH_TARGET="$PI_USER@$PI_HOST"
BUILD_DIR="$PROJECT_ROOT/pi/ui/build/elinux/arm64/release/bundle" BUILD_DIR="$PROJECT_ROOT/pi/ui/build/elinux/arm64/release/bundle"
echo "=== Smart Serow Deploy ===" echo "=== Smart Serow Deploy ==="
echo "Target: $HOST:$REMOTE_PATH" echo "Target: $SSH_TARGET:$REMOTE_PATH"
echo "Source: $BUILD_DIR" echo "Source: $BUILD_DIR"
if [ ! -d "$BUILD_DIR" ]; then if [ ! -d "$BUILD_DIR" ]; then
@@ -34,20 +41,20 @@ echo ""
echo "Syncing files..." echo "Syncing files..."
rsync -avz --delete \ rsync -avz --delete \
"$BUILD_DIR/" \ "$BUILD_DIR/" \
"$HOST:$REMOTE_PATH/bundle/" "$SSH_TARGET:$REMOTE_PATH/bundle/"
# Restart service if requested # Restart service if requested
RESTART="${1:-}" RESTART="${1:-}"
if [ "$RESTART" = "--restart" ] || [ "$RESTART" = "-r" ]; then if [ "$RESTART" = "--restart" ] || [ "$RESTART" = "-r" ]; then
echo "" echo ""
echo "Restarting service: $SERVICE_NAME" echo "Restarting service: $SERVICE_NAME"
ssh "$HOST" "sudo systemctl restart $SERVICE_NAME" ssh "$SSH_TARGET" "sudo systemctl restart $SERVICE_NAME"
sleep 2 sleep 2
ssh "$HOST" "systemctl status $SERVICE_NAME --no-pager" ssh "$SSH_TARGET" "systemctl status $SERVICE_NAME --no-pager"
else else
echo "" echo ""
echo "Deploy complete. To restart service, run:" echo "Deploy complete. To restart service, run:"
echo " ssh $HOST 'sudo systemctl restart $SERVICE_NAME'" echo " ssh $SSH_TARGET 'sudo systemctl restart $SERVICE_NAME'"
echo "" echo ""
echo "Or run this script with --restart flag" echo "Or run this script with --restart flag"
fi fi

View File

@@ -1,5 +1,6 @@
{ {
"host": "pi@smartserow.local", "user": "mikkeli",
"host": "192.168.2.102",
"remote_path": "/opt/smartserow", "remote_path": "/opt/smartserow",
"service_name": "smartserow-ui" "service_name": "smartserow-ui"
} }

View File

@@ -6,6 +6,11 @@ set -e
echo "=== Smart Serow Pi Setup ===" echo "=== Smart Serow Pi Setup ==="
# Use current user (whoever is running this script on the Pi)
PI_USER="${USER:-$(whoami)}"
PI_UID=$(id -u "$PI_USER")
echo "Setting up for user: $PI_USER (uid: $PI_UID)"
# Check if running on Pi (arm architecture) # Check if running on Pi (arm architecture)
ARCH=$(uname -m) ARCH=$(uname -m)
if [[ "$ARCH" != "aarch64" && "$ARCH" != "armv7l" ]]; then if [[ "$ARCH" != "aarch64" && "$ARCH" != "armv7l" ]]; then
@@ -15,11 +20,12 @@ fi
APP_DIR="/opt/smartserow" APP_DIR="/opt/smartserow"
SERVICE_FILE="/etc/systemd/system/smartserow-ui.service" SERVICE_FILE="/etc/systemd/system/smartserow-ui.service"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Create app directory # Create app directory
echo "Creating app directory: $APP_DIR" echo "Creating app directory: $APP_DIR"
sudo mkdir -p "$APP_DIR/bundle" sudo mkdir -p "$APP_DIR/bundle"
sudo chown -R pi:pi "$APP_DIR" sudo chown -R "$PI_USER:$PI_USER" "$APP_DIR"
# Install runtime dependencies for flutter-elinux # Install runtime dependencies for flutter-elinux
echo "Installing runtime dependencies..." echo "Installing runtime dependencies..."
@@ -41,11 +47,15 @@ sudo apt-get install -y \
libx11-6 \ libx11-6 \
libxkbcommon-x11-0 libxkbcommon-x11-0
# Copy systemd service # Generate systemd service with correct user
echo "Installing systemd service..." echo "Installing systemd service..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/smartserow-ui.service" ]; then if [ -f "$SCRIPT_DIR/smartserow-ui.service" ]; then
sudo cp "$SCRIPT_DIR/smartserow-ui.service" "$SERVICE_FILE" # Patch the template with actual user values
sed -e "s/User=.*/User=$PI_USER/" \
-e "s/Group=.*/Group=$PI_USER/" \
-e "s|HOME=/home/.*|HOME=/home/$PI_USER|" \
-e "s|XDG_RUNTIME_DIR=/run/user/.*|XDG_RUNTIME_DIR=/run/user/$PI_UID|" \
"$SCRIPT_DIR/smartserow-ui.service" | sudo tee "$SERVICE_FILE" > /dev/null
else else
echo "WARNING: Service file not found at $SCRIPT_DIR/smartserow-ui.service" echo "WARNING: Service file not found at $SCRIPT_DIR/smartserow-ui.service"
echo "Copy it manually to $SERVICE_FILE" echo "Copy it manually to $SERVICE_FILE"
@@ -56,11 +66,11 @@ echo "Enabling service..."
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable smartserow-ui sudo systemctl enable smartserow-ui
# Add pi user to required groups for DRM/KMS access # Add user to required groups for DRM/KMS access
echo "Setting up permissions..." echo "Setting up permissions for $PI_USER..."
sudo usermod -aG video pi sudo usermod -aG video "$PI_USER"
sudo usermod -aG input pi sudo usermod -aG input "$PI_USER"
sudo usermod -aG render pi 2>/dev/null || true sudo usermod -aG render "$PI_USER" 2>/dev/null || true
echo "" echo ""
echo "=== Setup Complete ===" echo "=== Setup Complete ==="

View File

@@ -5,12 +5,13 @@ Wants=multi-user.target
[Service] [Service]
Type=simple Type=simple
User=pi User=mikkeli
Group=pi Group=mikkeli
WorkingDirectory=/opt/smartserow/bundle WorkingDirectory=/opt/smartserow/bundle
# DRM/KMS backend for direct framebuffer (no X11 needed) # GBM backend is compiled in. Just specify bundle path.
ExecStart=/opt/smartserow/bundle/smartserow_ui -b drm # Scale factor 2.5 for 5.5" 1080p screen (high DPI)
ExecStart=/opt/smartserow/bundle/smartserow_ui --bundle=/opt/smartserow/bundle --force-scale-factor=2.5
# Restart on crash # Restart on crash
Restart=always Restart=always
@@ -18,7 +19,8 @@ RestartSec=3
# Environment for DRM/KMS access # Environment for DRM/KMS access
Environment=XDG_RUNTIME_DIR=/run/user/1000 Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=HOME=/home/pi Environment=HOME=/home/mikkeli
Environment=LD_LIBRARY_PATH=/opt/smartserow/bundle/lib
# Give time for GPU to initialize on boot # Give time for GPU to initialize on boot
ExecStartPre=/bin/sleep 2 ExecStartPre=/bin/sleep 2