IFM Packaging
Detailed build outputs, formats, and installation methods for each IFM product.
ifm-cli — Command Line Interface
Build Targets
Artifacts per Target
| Platform | Format | Install Path | Notes |
|---|---|---|---|
| Linux (gnu) | tar.gz | ~/.local/bin/ifm | Universal tarball |
| Linux (musl) | tar.gz | ~/.local/bin/ifm | Static, no deps |
| Linux | .deb | /usr/bin/ifm | dpkg -i / apt install |
| Linux | .rpm | /usr/bin/ifm | rpm -i / dnf install |
| Linux | .apk | /usr/bin/ifm | apk add |
| macOS | tar.gz | ~/.local/bin/ifm | Universal binary (x86_64 + arm64) |
| macOS | .pkg | /usr/local/bin/ifm | Signed, notarized |
| macOS | Homebrew | brew install ifm | Formula in homebrew-core |
| Windows | .zip | %USERPROFILE%\.local\bin\ifm.exe | Portable |
| Windows | .msi | C:\Program Files\IFM\ifm.exe | Signed, per-machine |
| Windows | winget | winget install IFM.IFM | Microsoft Store repo |
| Windows | Scoop | scoop install ifm | Scoop bucket |
| Windows | Chocolatey | choco install ifm | Chocolatey community |
Cargo Configuration
toml
# Cargo.toml (workspace root)
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
panic = "abort"
# Cross-compilation targets
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"
[target.x86_64-pc-windows-msvc]
# Uses rustup target + MSVC toolchainInstaller Script (install.sh)
bash
#!/usr/bin/env bash
# https://ifm.sh/install.sh
set -euo pipefail
REPO="ifm/ifm"
BINARY="ifm"
INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
# Detect OS/arch
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$OS-$ARCH" in
linux-x86_64) TARGET="x86_64-unknown-linux-musl" ;;
linux-aarch64) TARGET="aarch64-unknown-linux-musl" ;;
linux-armv7l) TARGET="armv7-unknown-linux-gnueabihf" ;;
darwin-x86_64) TARGET="x86_64-apple-darwin" ;;
darwin-arm64) TARGET="aarch64-apple-darwin" ;;
*) echo "Unsupported: $OS-$ARCH"; exit 1 ;;
esac
# Fetch latest release
VERSION=$(curl -s "https://api.github.com/repos/$REPO/releases/latest" | jq -r .tag_name)
URL="https://github.com/$REPO/releases/download/$VERSION/${BINARY}-${TARGET}.tar.gz"
SHA256_URL="https://github.com/$REPO/releases/download/$VERSION/checksums.txt"
# Download & verify
curl -fL "$URL" -o "/tmp/${BINARY}.tar.gz"
EXPECTED_SHA=$(curl -s "$SHA256_URL" | grep "${BINARY}-${TARGET}.tar.gz" | awk '{print $1}')
ACTUAL_SHA=$(sha256sum "/tmp/${BINARY}.tar.gz" | awk '{print $1}')
[[ "$EXPECTED_SHA" == "$ACTUAL_SHA" ]] || { echo "Checksum mismatch"; exit 1; }
# Install
mkdir -p "$INSTALL_DIR"
tar -xzf "/tmp/${BINARY}.tar.gz" -C "$INSTALL_DIR" "$BINARY"
chmod +x "$INSTALL_DIR/$BINARY"
echo "Installed $BINARY $VERSION to $INSTALL_DIR"
"$INSTALL_DIR/$BINARY" --versionifm-node — Headless Node Daemon
Build Targets
Same as CLI + container images.
Container Images (Multi-arch)
dockerfile
# Dockerfile (distroless)
FROM rust:1.80 AS builder
WORKDIR /build
COPY . .
RUN cargo build --release --bin ifm-node --target x86_64-unknown-linux-musl
FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/ifm-node /ifm-node
USER nonroot:nonroot
EXPOSE 8080 8081/udp
ENTRYPOINT ["/ifm-node"]| Tag | Base | Arch | Size |
|---|---|---|---|
ghcr.io/ifm/ifm-node:latest | distroless | linux/amd64, linux/arm64 | ~15 MB |
ghcr.io/ifm/ifm-node:v1.2.3 | distroless | linux/amd64, linux/arm64 | ~15 MB |
ghcr.io/ifm/ifm-node:stable | distroless | linux/amd64, linux/arm64 | ~15 MB |
ghcr.io/ifm/ifm-node:beta | distroless | linux/amd64, linux/arm64 | ~15 MB |
ghcr.io/ifm/ifm-node:nightly | distroless | linux/amd64, linux/arm64 | ~15 MB |
Linux Packages
| Distro | Package | Repo | Maintainer |
|---|---|---|---|
| Debian/Ubuntu | ifm-node | Official apt repo | IFM Team |
| Arch | ifm-node | AUR / extra | Community |
| Fedora | ifm-node | Copr / official | IFM Team |
| Alpine | ifm-node | Community | Community |
| NixOS | ifm-node | nixpkgs | Community |
| openSUSE | ifm-node | OBS | Community |
systemd Service
ini
# /usr/lib/systemd/system/ifm-node.service
[Unit]
Description=IFM Node
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
User=ifm
Group=ifm
ExecStart=/usr/bin/ifm-node --config /etc/ifm/node.toml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
AmbientCapabilities=CAP_NET_BIND_SERVICE
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/ifm /var/log/ifm
[Install]
WantedBy=multi-user.target@ifm/sdk — JavaScript/TypeScript SDK
npm Packages
| Package | Runtime | Entry Points | Size (gz) |
|---|---|---|---|
@ifm/sdk | Node ≥18, Bun, Deno | ESM + Types | ~120 KB |
@ifm/sdk-wasm | Browser, Edge Workers | ESM + WASM | ~400 KB |
@ifm/react | React 18+ | ESM + Types | ~45 KB |
@ifm/vue | Vue 3 | ESM + Types | ~35 KB |
Package.json (Core SDK)
json
{
"name": "@ifm/sdk",
"version": "1.2.3",
"description": "Internet Frequency Modulation SDK",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./react": {
"import": "./dist/react.js",
"types": "./dist/react.d.ts"
}
},
"files": ["dist", "README.md", "LICENSE"],
"engines": { "node": ">=18" },
"sideEffects": false,
"publishConfig": {
"access": "public",
"provenance": true,
"attestation": true
}
}WASM Build
bash
# Build WASM
cd packages/sdk
wasm-pack build --target web --out-dir ../../dist/wasm \
--features wasm --release \
-- --features wasm
# Output:
# dist/wasm/
# ├── ifm_sdk.js # JS glue
# ├── ifm_sdk_bg.wasm # WASM binary (~400 KB gzipped)
# ├── ifm_sdk.d.ts # TypeScript definitions
# └── package.json # For npm publishCDN Distribution
html
<!-- jsDelivr (auto-updates from npm) -->
<script type="module">
import { IFM } from "https://cdn.jsdelivr.net/npm/@ifm/sdk-wasm@1.2.3/dist/ifm_sdk.js";
const radio = await IFM.create();
</script>
<!-- unpkg -->
<script type="module">
import { IFM } from "https://unpkg.com/@ifm/sdk-wasm@1.2.3/dist/ifm_sdk.js";
</script>libifm — C FFI Library
Build Targets
| Platform | Shared | Static | Headers |
|---|---|---|---|
| Linux x86_64 | libifm.so | libifm.a | ifm.h |
| Linux aarch64 | libifm.so | libifm.a | ifm.h |
| macOS x86_64 | libifm.dylib | libifm.a | ifm.h |
| macOS arm64 | libifm.dylib | libifm.a | ifm.h |
| Windows x86_64 | ifm.dll + ifm.lib | ifm.lib | ifm.h |
| Windows arm64 | ifm.dll + ifm.lib | ifm.lib | ifm.h |
Header (ifm.h)
c
#ifndef IFM_H
#define IFM_H
#ifdef __cplusplus
extern "C" {
#endif
// Opaque types
typedef struct ifm_node ifm_node_t;
typedef struct ifm_config ifm_config_t;
// Error codes
typedef enum {
IFM_OK = 0,
IFM_ERR_INVALID_ARG = -1,
IFM_ERR_NOT_CONNECTED = -2,
IFM_ERR_ALREADY_CONNECTED = -3,
IFM_ERR_FREQUENCY_INVALID = -4,
IFM_ERR_CRYPTO = -5,
IFM_ERR_TRANSPORT = -6,
IFM_ERR_NOMEM = -7,
} ifm_error_t;
// Version
const char* ifm_version(void);
// Node lifecycle
ifm_error_t ifm_node_create(const ifm_config_t* config, ifm_node_t** out_node);
void ifm_node_destroy(ifm_node_t* node);
ifm_error_t ifm_node_connect(ifm_node_t* node);
ifm_error_t ifm_node_disconnect(ifm_node_t* node);
// Frequency operations
ifm_error_t ifm_node_tune(ifm_node_t* node, const char* frequency);
ifm_error_t ifm_node_leave(ifm_node_t* node, const char* frequency);
// Broadcast
ifm_error_t ifm_node_broadcast(ifm_node_t* node, const char* frequency,
const uint8_t* data, size_t len,
uint8_t payload_type);
// Callbacks
typedef void (*ifm_packet_cb)(const uint8_t* data, size_t len,
const char* frequency, void* user_data);
ifm_error_t ifm_node_subscribe(ifm_node_t* node, const char* frequency,
ifm_packet_cb cb, void* user_data);
// Stats
typedef struct {
uint64_t peers_connected;
uint64_t frequencies_tuned;
uint64_t packets_sent;
uint64_t packets_received;
uint64_t bytes_sent;
uint64_t bytes_received;
} ifm_stats_t;
ifm_error_t ifm_node_stats(ifm_node_t* node, ifm_stats_t* out_stats);
#ifdef __cplusplus
}
#endif
#endif // IFM_HCMake Integration
cmake
# FindIFM.cmake
find_package(PkgConfig REQUIRED)
pkg_check_modules(IFM REQUIRED ifm>=1.2)
# Or manual
find_library(IFM_LIBRARY NAMES ifm PATHS /usr/lib /usr/local/lib)
find_path(IFM_INCLUDE_DIR NAMES ifm.h PATHS /usr/include /usr/local/include)
add_library(ifm::ifm INTERFACE IMPORTED)
set_target_properties(ifm::ifm PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${IFM_INCLUDE_DIR}"
INTERFACE_LINK_LIBRARIES "${IFM_LIBRARY}"
)pkg-config (ifm.pc)
ini
prefix=/usr
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: IFM
Description: Internet Frequency Modulation C Library
Version: 1.2.3
Libs: -L${libdir} -lifm
Cflags: -I${includedir}Mobile Libraries
Android (AAR)
gradle
// build.gradle.kts (library)
plugins { id("com.android.library") id("org.jetbrains.kotlin.android") }
android {
namespace = "sh.ifm"
compileSdk = 34
defaultConfig {
minSdk = 24
targetSdk = 34
versionCode = 10203
versionName = "1.2.3"
}
externalNativeBuild {
cmake { path = "src/main/cpp/CMakeLists.txt" }
}
}
// Publish to Maven Central
publishing {
publications {
create<MavenPublication>("release") {
groupId = "sh.ifm"
artifactId = "ifm-android"
version = "1.2.3"
from(components["release"])
}
}
}iOS (XCFramework)
bash
# Build XCFramework
xcodebuild -create-xcframework \
-framework build/ios-arm64/IFM.framework \
-framework build/ios-x86_64-simulator/IFM.framework \
-framework build/macos-arm64/IFM.framework \
-framework build/macos-x86_64/IFM.framework \
-output IFM.xcframework
# Package
zip -r IFM-1.2.3-xcframework.zip IFM.xcframeworkCocoaPods Podspec
ruby
# IFM.podspec
Pod::Spec.new do |s|
s.name = 'IFM'
s.version = '1.2.3'
s.summary = 'Internet Frequency Modulation'
s.homepage = 'https://ifm.sh'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'IFM Team' => 'team@ifm.sh' }
s.source = { :http => 'https://github.com/ifmprotocol/ifm/releases/download/v1.2.3/IFM-1.2.3-xcframework.zip' }
s.vendored_frameworks = 'IFM.xcframework'
s.platform = :ios, '15.0'
s.swift_version = '5.9'
endSwift Package Manager
swift
// Package.swift
let package = Package(
name: "IFM",
platforms: [.iOS(.v15), .macOS(.v12)],
products: [.library(name: "IFM", targets: ["IFM"])],
targets: [
.binaryTarget(
name: "IFM",
url: "https://github.com/ifmprotocol/ifm/releases/download/v1.2.3/IFM-1.2.3-xcframework.zip",
checksum: "sha256:..."
)
]
)Flutter/Dart
yaml
# pubspec.yaml (ifm_flutter)
name: ifm_flutter
version: 1.2.3
description: IFM Dart bindings
homepage: https://ifm.sh
environment:
sdk: '>=3.0.0 <4.0.0'
flutter: ">=3.16.0"
dependencies:
flutter:
sdk: flutter
ffi: ^2.1.0
dev_dependencies:
ffigen: ^9.0.0Python (PyPI)
toml
# pyproject.toml
[build-system]
requires = ["maturin>=1.6,<2.0"]
build-backend = "maturin"
[project]
name = "ifm"
version = "1.2.3"
description = "Internet Frequency Modulation Python bindings"
readme = "README.md"
license = {text = "MIT"}
authors = [{name = "IFM Team", email = "team@ifm.sh"}]
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dependencies = []
[tool.maturin]
features = ["pyo3/extension-module"]
compatibility = "linux_gnu=2_31,macos_arm64=11,macos_x86_64=11,windows=10"Go
go
// go.mod
module github.com/ifmprotocol/ifm-go
go 1.22
// No external dependencies - pure Go wrapper around libifm via CGO
// or pure Go implementationifm-pwa — Progressive Web App
Build Target
Single static site (HTTPS), platform-agnostic:
- Runtime: Any modern browser (Chrome 80+, Firefox 72+, Safari 14.1+, Edge 80+)
- Arch: wasm32 (via
@ifm/sdk-wasm) - Output:
dist/pwa/(static files)
Build Process
bash
# 1. Build @ifm/sdk-wasm (shared with SDK)
cd packages/sdk
wasm-pack build --target web --out-dir ../../dist/wasm \
--features wasm --release
# 2. Build PWA application
cd packages/pwa
npm ci
npm run build
# Output: dist/pwa/Build Output (dist/pwa/)
dist/pwa/
├── index.html # Entry point, <link rel="manifest" href="manifest.json">
├── manifest.json # PWA manifest
├── sw.js # Service Worker
├── ifm-sdk.js # JS glue (from @ifm/sdk-wasm)
├── ifm-sdk_bg.wasm # WASM binary (~400 KB gzipped)
├── icons/
│ ├── icon-72.png # 72x72
│ ├── icon-96.png # 96x96
│ ├── icon-128.png # 128x128
│ ├── icon-144.png # 144x144
│ ├── icon-152.png # 152x152
│ ├── icon-192.png # 192x192 (maskable)
│ ├── icon-384.png # 384x384
│ └── icon-512.png # 512x512 (maskable)
├── stations.json # Cached station index (updated via SW)
├── version.json # { "version": "1.3.0", "build": "20241215.0300" }
└── offline.html # Offline fallback pagemanifest.json
json
{
"name": "IFM Radio",
"short_name": "IFM",
"description": "Tune into internet radio frequencies — peer-to-peer, no servers",
"start_url": "/pwa/",
"scope": "/pwa/",
"display": "standalone",
"background_color": "#0f0f0f",
"theme_color": "#0f0f0f",
"orientation": "portrait-primary",
"icons": [
{ "src": "icons/icon-72.png", "sizes": "72x72", "type": "image/png" },
{ "src": "icons/icon-96.png", "sizes": "96x96", "type": "image/png" },
{ "src": "icons/icon-128.png", "sizes": "128x128", "type": "image/png" },
{ "src": "icons/icon-144.png", "sizes": "144x144", "type": "image/png" },
{ "src": "icons/icon-152.png", "sizes": "152x152", "type": "image/png" },
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any maskable" },
{ "src": "icons/icon-384.png", "sizes": "384x384", "type": "image/png" },
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
],
"categories": ["music", "entertainment"],
"shortcuts": [
{
"name": "Last Station",
"short_name": "Resume",
"description": "Continue last played station",
"url": "/pwa/#resume",
"icons": [{ "src": "icons/icon-192.png", "sizes": "192x192" }]
}
],
"screenshots": [
{ "src": "screenshots/home.png", "sizes": "1170x2532", "type": "image/png", "form_factor": "narrow" },
{ "src": "screenshots/tuner.png", "sizes": "1170x2532", "type": "image/png", "form_factor": "narrow" }
]
}Service Worker (sw.js)
javascript
// Workbox-based SW with:
// - Precache: index.html, manifest.json, ifm-sdk.js, ifm-sdk_bg.wasm, icons, offline.html
// - Runtime cache: stations.json (stale-while-revalidate, max 7 days)
// - Runtime cache: audio streams (network-first, fallback to cached metadata)
// - Background sync: queue station searches when offline
// - Push notifications: "Station X is live" (requires VAPID keys)
// - skipWaiting + clients.claim for instant updatesPWA Features
| Feature | Implementation |
|---|---|
| Installability | beforeinstallprompt → custom "Add to Home Screen" button |
| Offline | Precached shell + stale-while-revalidate for station index |
| Background Audio | Media Session API + audio element persists across navigation |
| Push Notifications | Web Push API (VAPID), user opt-in, serverless via Cloudflare Workers |
| Deep Linking | #91.700 → auto-tune, #station/callsign → station page |
| QR Sharing | Web Share API Level 2 + qrcode lib fallback |
| Media Controls | Media Session API (lock screen, notification shade, headphones) |
| Auto-Update | SW skipWaiting + clients.claim; toast "New version available" |
Deploy Targets
| Target | URL | Notes |
|---|---|---|
| Primary | https://ifm.sh/pwa/ | Cloudflare Pages (auto-deploy from main) |
| Alias | https://pwa.ifm.sh/ | CNAME to primary |
| GitHub Pages | https://ifm.github.io/ifm-pwa/ | Backup / mirror |
| CDN | https://cdn.jsdelivr.net/gh/ifm/ifm-pwa@latest/ | jsDelivr (from GitHub releases) |
| Docker (optional) | ghcr.io/ifm/ifm-pwa:latest | nginx static, for self-hosting |
Release Artifacts
| Artifact | Format | Signed | SBOM |
|---|---|---|---|
ifm-pwa | ifm-pwa-v1.3.0.tar.gz (static site) | ✅ cosign | ✅ SPDX |
ifm-pwa | GitHub Pages deploy | ✅ (via Pages) | ✅ SPDX |
ifm-pwa | Cloudflare Pages deploy | ✅ (via CF) | ✅ SPDX |
Channel Artifacts
| Channel | Deploy Target |
|---|---|
| Nightly | https://pwa-nightly.ifm.sh/ (Cloudflare Pages preview) |
| Beta | https://pwa-beta.ifm.sh/ (Cloudflare Pages preview) |
| Stable | https://ifm.sh/pwa/ (Cloudflare Pages production) |
| LTS | https://pwa-lts.ifm.sh/ (separate Pages project) |
Installation (User)
QR Code Distribution
bash
# Generate station QR codes
# URL format: https://ifm.sh/pwa#91.700
# Or: https://ifm.sh/qr/91.700.png (dynamic PNG endpoint)
# Example: Station card shows QR for easy sharingSummary: All Artifacts Published Per Release
Release v1.2.3
├── GitHub Releases
│ ├── ifm-cli-*.tar.gz (9 targets)
│ ├── ifm-cli-*.deb/.rpm/.apk/.msi/.pkg
│ ├── ifm-node-*.tar.gz (9 targets)
│ ├── ifm-node_*.deb/.rpm
│ ├── libifm-*.tar.gz (6 targets)
│ ├── ifm-android-*.aar
│ ├── IFM-*.xcframework.zip
│ ├── ifm-pwa-v1.2.3.tar.gz
│ ├── checksums.txt (SHA256 for all)
│ ├── checksums.txt.sig (cosign)
│ └── sbom.spdx.json
├── Docker
│ └── ghcr.io/ifm/ifm-node:v1.2.3 (multi-arch)
├── npm
│ ├── @ifm/sdk@1.2.3
│ ├── @ifm/sdk-wasm@1.2.3
│ ├── @ifm/react@1.2.3
│ └── @ifm/vue@1.2.3
├── PyPI
│ └── ifm-1.2.3 (wheels for manylinux, macos, windows)
├── Maven Central
│ └── sh.ifm:ifm-android:1.2.3
├── CocoaPods / SwiftPM
│ └── IFM 1.2.3
├── pub.dev
│ └── ifm_flutter 1.2.3
├── crates.io
│ └── ifm-core 1.2.3 (library), ifm-cli 1.2.3 (binary)
├── Homebrew
│ └── ifm 1.2.3 (formula PR)
├── winget
│ └── IFM.IFM 1.2.3 (manifest PR)
├── Chocolatey / Scoop
│ └── ifm 1.2.3 (community PR)
├── Linux Distros
│ └── Package PRs to Debian, Arch, Fedora, Alpine, NixOS, openSUSE
└── CDN
├── jsDelivr: @ifm/sdk-wasm@1.2.3
└── unpkg: @ifm/sdk-wasm@1.2.3