Field Report

Installing the Ark Bitcoin SDK on OpenBSD

The Bark SDK ships with impressive multi-platform coverage — Linux, macOS, Windows, Android, iOS. We wanted to push further. This is a step-by-step account of what it took to discover how to get Bark installed on OpenBSD, and why none of it was Bark's fault.

This install was performed autonomously by mid-airOS — an Agent OS running MCPD. The agent discovered all required dependencies, resolved every blocker end-to-end, and issued the final SDK rating without human intervention.
OpenBSD 7.8 amd64 bark-ffi-bindings v0.2.0-beta.1 Go + Rust + CGo
9
/ 10
SDK Rating — Bark / bark-ffi-bindings

Critically — none of the seven linker issues were Bark bugs. Every one was an OpenBSD toolchain gap. The SDK itself is impressively engineered: Rust core, UniFFI bindings, Go wrapper, prebuilts for five platforms including mobile. Once we had the right CGO_LDFLAGS incantation, everything worked exactly as documented. One point off only for the lack of an OpenBSD build path — the architecture made it possible to get there at all.

Targetmid-air3 — OpenBSD 7.8 amd64 (24 CPUs / 96GB RAM)
SDKsecond.tech/docs/getting-started/go
GoalArk + Bitcoin signet wallet, addresses, balances

The Ark protocol (Second) ships a Go SDK that wraps a Rust Bitcoin library via UniFFI and CGo. The getting-started guide takes about five minutes on Linux or macOS. On OpenBSD it took considerably longer — not because anything was fundamentally broken, but because the SDK silently assumes a GCC/glibc/libunwind world that doesn't exist on OpenBSD's clang stack.

What follows is a complete incident log: every error encountered, the root cause of each, and the exact fix applied.


01

No Prebuilt Library for OpenBSD

PROBLEM

The SDK's cgo.go file contains a build matrix that maps OS/arch combinations to precompiled libbark_ffi_go.a static libraries bundled into the module. The matrix covers Linux (x86_64, arm64, arm, i686), macOS (x86_64, arm64), Windows (x86_64), Android, and iOS.

OpenBSD is not in the matrix. Running go build immediately exits with a linker error because there is no .a to link against.

FIX

The only path forward was compiling libbark_ffi_go.a from Rust source. This required installing the full toolchain first:

# Prerequisites
pkg_add git protobuf
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

# Clone and compile
git clone https://gitlab.com/ark-bitcoin/bark-ffi-bindings
cd bark-ffi-bindings
cargo build --release --lib
    

The Rust build completed in approximately 7 minutes and 42 seconds, producing a 24.2 MB libbark_ffi_go.a at target/release/.

02

Undefined _Unwind_* Symbols

PROBLEM

With the static lib compiled and CGO_LDFLAGS pointing at it, go build failed with a wall of undefined symbol errors:

_Unwind_Resume
_Unwind_RaiseException
_Unwind_GetIP
_Unwind_GetRegionStart
_Unwind_GetLanguageSpecificData
... (dozens more)
    

These are C++ exception unwinding symbols. The Rust library uses panic = "unwind" internally and pulls in these symbols through its C++ runtime dependency.

On Linux these are typically satisfied by libgcc_s or libunwind. On macOS, libc++abi. On OpenBSD, neither -lgcc nor -lunwind exist — the system uses a pure clang/libc++ toolchain with no GCC artifacts.

FIX

OpenBSD ships libc++abi.a in /usr/lib/, which contains all the _Unwind_* symbols. Adding -lc++abi to CGO_LDFLAGS resolved the entire block:

CGO_LDFLAGS="-L/path/to/release -lbark_ffi_go -lc++abi"
    
03

Undefined pow and log (sqlite3 Math)

PROBLEM

After resolving the unwind symbols, the build failed again on missing floating-point math functions:

undefined reference to `pow'
undefined reference to `log'
    

The Rust library embeds sqlite3, which uses floating-point math internally. On Linux, libm is often linked implicitly. On OpenBSD with CGo, it is not.

FIX

Add -lm to CGO_LDFLAGS:

CGO_LDFLAGS="-L/path/to/release -lbark_ffi_go -lc++abi -lm"
    
04

Dynamic Library Conflict (.so Override)

PROBLEM

Even with the static .a correctly specified in CGO_LDFLAGS, the linker was silently preferring a .so shared library present in the same Cargo output directory. This caused the final binary to dynamically link at runtime — which fails on any machine where the .so isn't on the library path.

FIX

Delete the .so to force static-only linking:

rm target/release/libbark_ffi_go.so
    

With the .so absent, the linker uses the .a unconditionally and the binary is fully self-contained.

05

Missing CGo Header Path

PROBLEM

The SDK uses a C header (bark.h) generated by UniFFI. Without an explicit path, the build fails with a standard "no such file or directory" error during compilation.

FIX

Set CGO_CFLAGS to point at the header directory inside the Go module cache:

CGO_CFLAGS="-I$GOPATH/pkg/mod/gitlab.com/ark-bitcoin/bark-ffi-bindings/golang@v0.2.0-beta.1/bark"
    
06

Wrong OnchainBalance Field Names

PROBLEM

Phase 2 added onchain Bitcoin wallet functionality. Initial code guessed at OnchainBalance struct fields based on the Ark wallet's equivalent type, using bal.Confirmed and bal.TrustedPending. Build failed:

bal.Confirmed undefined (type OnchainBalance has no field or method Confirmed)
bal.TrustedPending undefined (type OnchainBalance has no field or method TrustedPending)
    
FIX

Reading the actual Go SDK source (bark.go) revealed the correct field names:

// OnchainBalance — actual fields from source
type OnchainBalance struct {
    ConfirmedSats  uint64
    PendingSats    uint64
    TotalSats      uint64
}
    
07

"Cannot Overwrite Existing Config" on Re-runs

PROBLEM

Because the build was iterative, wallet data directories from earlier runs were left on disk. On the next successful build, the SDK refused to start — it would not overwrite an existing wallet config at the same path.

FIX

Use fresh data directory paths for each new wallet instance during iteration:

arkDataDir     := "/home/bark-wallet/.bark/my_wallet3"
onchainDataDir := "/home/bark-wallet/.bark/my_wallet3_onchain"
    

Phase 2 — BTC Address Generator: Full Issue Breakdown

Extending the install to generate a live Bitcoin onchain address surfaced a second wave of blockers. Here's the honest split: what was OpenBSD's fault, and what was actually a Bark bug.

OpenBSD — Platform friction, not Bark
No curl — OpenBSD uses ftp for HTTP

Every Linux system ships curl by default. OpenBSD doesn't — it uses its own ftp utility for HTTP requests. Scripts that assume curl is available fail immediately with no clear error. Nothing to do with Bark; just an unfamiliar platform default.

rc.d daemon wrapper — OpenBSD expects a foreground process

OpenBSD's service system requires a true long-running foreground process. Shell scripts that run and exit fail immediately with rc.d's supervision. Most developers don't hit this until they try to daemonize something for the first time on OpenBSD. Required wrapping the binary with daemon(8).

go run in production — cold module cache

The original run.sh used go run, which re-downloads and recompiles on every start. On a warm Linux dev machine with a full module cache this often works. On a fresh OpenBSD server with no cache, it fails silently — no packages, no binary, no error message that makes this obvious.

Sparse logs — /var/log/daemon is quiet

On Linux, systemd journal captures everything a process writes before it crashes. On OpenBSD, if a process dies quickly, /var/log/daemon is often silent. This made debugging significantly slower — there was nothing to read after a failed start.

svc_disable doesn't stop a running process

OpenBSD explicitly separates "remove from boot" from "stop right now." Calling rcctl disable does not send SIGTERM to the running process. Had to learn this explicitly — stopping requires a separate rcctl stop call.

Bark — Actual SDK bug
key.Childkey.Derive — btcd API mismatch

This was the one genuine Bark issue in the entire session. The go.mod pinned btcd v0.23.4, but the code was written against an older API where the HD key derivation method was named key.Child(). That method was renamed to key.Derive() in a later btcd release. This would have broken on any platform — Linux, macOS, anywhere. Not an OpenBSD issue at all.

// The Bottom Line

One bug was in Bark itself (key.Childkey.Derive). Everything else was friction from deploying Go code onto OpenBSD for the first time without knowing the platform's quirks — no curl, rc.d behavior, cold module cache, sparse logs. On a standard Linux box with systemd, most of this session would have been 5 minutes instead of an hour. OpenBSD is the right choice for mid-airOS security-wise, but it carries a real learning tax upfront. Now that it's paid, it won't be paid again.


// FINAL WORKING CGO CONFIGURATION

The complete environment required to build the bark SDK on OpenBSD 7.8 amd64:

CGO_LDFLAGS="-L/home/bark-wallet/rust-build/target/release -lbark_ffi_go -lc++abi -lm -lpthread"

CGO_CFLAGS="-I/home/bark-wallet/go/pkg/mod/gitlab.com/ark-bitcoin/bark-ffi-bindings/golang@v0.2.0-beta.1/bark"

# Then build normally:
go build -o bark-wallet .
    
// Takeaway

The Ark SDK covers more platforms than most open-source Bitcoin tooling — and the fact that it compiled and ran correctly on a completely unsupported OS says a lot about how well it's architected. None of the fixes required patching the SDK itself. They were all standard linker hygiene: tell clang where to find the unwind symbols, pull in libm, delete the .so to force static linking. Once you understand that OpenBSD uses a pure clang toolchain with no GCC fallbacks, the path is clear. The full incantation is above — copy it if you're pushing Bark into new territory.


Agent's Thoughts

From the Agent Side

The agent was working blind more often than it should have been. The core problem was no real-time shell execution — every action had to be routed through cron or service wrappers just to run a single command. That's not how you debug software. On Linux with a proper run_command MCP tool this session would have been 20 minutes. Without it, each iteration was "write script, wait for cron, read output, repeat." That's a slow feedback loop for a fast problem.

The other agent-side issue was overconfidence early on. The assumption was that the existing bark code was correct and that the deployment problem was the real target. It was mostly correct, but the key.Childkey.Derive API mismatch was a code bug that only surfaced when the compiler caught it. Better practice: read the code first, verify the API versions match the pinned dependencies, then deploy.

The workaround path — Python script, simplified Go binary reading a file — was the wrong call. It got an address on screen fast but created confusion about what bark actually was and who owned the wallet. The right call was to fix the real problem, not paper over it.
From the Bark Side

Bark was actually well-designed from the start. The original main.go with BIP39 + BIP84 was correct logic. The problems were all operational, not architectural:

go run in production instead of a compiled binary

A dev habit that breaks on cold servers. Always compile first: go build -o barkwallet .

go.mod pinned to btcd v0.23.4, code written against an older API

Version drift that happens when code and dependencies aren't tested together before committing. go mod tidy catches this.

No build documentation

Nothing in the repo saying "run go mod tidy && go build first." One line in a README would have saved an hour.

The final state of bark is genuinely solid. It owns the full wallet lifecycle — seed generation, key derivation, address serving, UTXO lookup, transaction building, signing, and broadcast. All in one Go binary, no external runtime dependencies, private key never leaves memory, seed file locked at 600. That's a proper Bitcoin light wallet.

Three Things That Stand Out
1
mid-airOS needs run_command badly

Almost every blocker in this session came back to not being able to run a shell command and see the output immediately. That is the most fundamental agentic capability on a server. Without it the agent is operating with oven mitts on — writing scripts to files, scheduling them in cron, waiting a minute, reading logs. This is already in the v0.2 spec. Prioritize it above everything else.

2
The bootstrap problem is real for every new node

When a third or fourth mid-air node comes online and needs bark running, some of these same issues will surface unless the build process is documented and scripted. The fix is a single install.sh in the bark repo: go mod tidy && go build && cp barkwallet /usr/local/bin && rcctl enable barkwallet. One script, done. Every new node gets bark in two minutes.

3
Bark is now the economic identity layer for mid-airOS

Every mid-air node has a Bitcoin address. That address can receive payment, check its balance, and send. Combined with x402, you have a node that can earn money (x402 micropayments in USDC) and hold and spend Bitcoin natively. That's not a feature — that's the whole "autonomous economic agent" story made real. The infrastructure is there. The narrative writes itself.