Skip to content
Open
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
5 changes: 5 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ jobs:
-C artifacts/bouwer-ubuntu-latest bouwer \
-C ${{ github.workspace }} LICENSE

chmod +x artifacts/bouwer-macos-latest/bouwer
tar -czf dist/bouwer-${{ github.ref_name }}-macos-aarch64.tar.gz \
-C artifacts/bouwer-macos-latest bouwer \
-C ${{ github.workspace }} LICENSE

zip -j dist/bouwer-${{ github.ref_name }}-windows-x86_64.zip \
artifacts/bouwer-windows-latest/bouwer.exe \
LICENSE
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
build:
strategy:
matrix:
os: [windows-latest, ubuntu-latest]
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ tokio = { version = "1", features = [
] }
ureq = { version = "3", features = ["json"] }

# Native Linux or Windows
[target.'cfg(any(target_os = "linux", target_env = "msvc"))'.dependencies]
# Native Linux, macOS, or Windows
[target.'cfg(any(target_os = "linux", target_os = "macos", target_env = "msvc"))'.dependencies]
slint = { version = "1.17", default-features = false, features = ["backend-winit", "compat-1-2", "renderer-skia"] }

# Cross-compile Windows from Linux
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,24 @@ A working internet connection.
A working installation of [Podman](https://podman.io/docs/installation) or
[Docker](https://www.docker.com/products/docker-desktop/). Bouwer uses these
container engines to run OpenWrt image builder containers. On Linux use your
package manager, on Windows - the official websites to install either of them.
package manager, on macOS - e.g. Homebrew (`brew install podman`), on Windows -
the official websites to install either of them.
Additionally, on Linux, Docker must be accessible _without_ super user
privileges, by e.g. adding your user to the `docker` group.

On macOS, the default Podman machine socket is detected automatically. For a
custom machine name, or for Docker, either make the default Docker API socket
available (e.g. via `sudo podman-mac-helper install` or Docker Desktop) or
point `DOCKER_HOST` at the right socket before starting Bouwer.

1 to 3 gigabytes of free disk space for each target and free disk space for the
firmware images. On Windows, image builder images are stored as part of Podman
or Docker VMs, so you need to account for that when allocating space for them.
firmware images. On Windows and macOS, image builder images are stored as part
of Podman or Docker VMs, so you need to account for that when allocating space
for them.

Currently, modern Linux and Windows running on x86/64 are supported. macOS on
aarch64 is doable, but there's no practical way for me to test this at the
moment, although the basic macOS-specific hooks are there.
Currently, modern Linux and Windows running on x86/64, and macOS running on
Apple silicon (aarch64) are supported. macOS support was tested on macOS 26
with Podman.

## Installation

Expand Down
37 changes: 37 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// SPDX-License-Identifier: GPL-3.0-only

use std::env;
use std::fs;
use std::path::PathBuf;

use winresource::WindowsResource;

Expand All @@ -16,4 +18,39 @@ fn main() {
res.set_icon("./assets/logo.ico");
res.compile().expect("Failed to compile Windows resources");
}

if env::var("CARGO_CFG_TARGET_OS").is_ok_and(|os| os == "macos") {
embed_macos_info_plist();
}
}

/// Embeds an Info.plist into the binary so macOS can show the app name,
/// version and copyright in the About panel without an .app bundle.
fn embed_macos_info_plist() {
let version = env::var("CARGO_PKG_VERSION").unwrap();
let plist = format!(
r#"<?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>CFBundleIdentifier</key>
<string>in.sapk.bouwer</string>
<key>CFBundleName</key>
<string>Bouwer</string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2026 George Sapkin
https://github.com/GeorgeSapkin/bouwer</string>
</dict>
</plist>
"#
);

let path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("Info.plist");
fs::write(&path, plist).expect("Failed to write Info.plist");
println!(
"cargo:rustc-link-arg=-Wl,-sectcreate,__TEXT,__info_plist,{}",
path.display()
);
}
11 changes: 11 additions & 0 deletions src/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,17 @@ impl Containers {
Docker::connect_with_host(&host)?
} else {
cfg_select! {
target_os = "macos" => {
// Check for the default Podman machine API socket
let socket = env::temp_dir().join("podman/podman-machine-default-api.sock");

if socket.exists() {
Docker::connect_with_socket(&socket.to_string_lossy(), 120, bollard::API_DEFAULT_VERSION)?
} else {
// Fallback to defaults
Docker::connect_with_local_defaults()?
}
}
unix => {
// Check for rootless Podman socket
let socket = env::var("XDG_RUNTIME_DIR").map_or_else(
Expand Down