Skip to content

IFM Packaging

Detailed build outputs, formats, and installation methods for each IFM product.


ifm-cli — Command Line Interface

Build Targets

Artifacts per Target

PlatformFormatInstall PathNotes
Linux (gnu)tar.gz~/.local/bin/ifmUniversal tarball
Linux (musl)tar.gz~/.local/bin/ifmStatic, no deps
Linux.deb/usr/bin/ifmdpkg -i / apt install
Linux.rpm/usr/bin/ifmrpm -i / dnf install
Linux.apk/usr/bin/ifmapk add
macOStar.gz~/.local/bin/ifmUniversal binary (x86_64 + arm64)
macOS.pkg/usr/local/bin/ifmSigned, notarized
macOSHomebrewbrew install ifmFormula in homebrew-core
Windows.zip%USERPROFILE%\.local\bin\ifm.exePortable
Windows.msiC:\Program Files\IFM\ifm.exeSigned, per-machine
Windowswingetwinget install IFM.IFMMicrosoft Store repo
WindowsScoopscoop install ifmScoop bucket
WindowsChocolateychoco install ifmChocolatey 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 toolchain

Installer 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" --version

ifm-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"]
TagBaseArchSize
ghcr.io/ifm/ifm-node:latestdistrolesslinux/amd64, linux/arm64~15 MB
ghcr.io/ifm/ifm-node:v1.2.3distrolesslinux/amd64, linux/arm64~15 MB
ghcr.io/ifm/ifm-node:stabledistrolesslinux/amd64, linux/arm64~15 MB
ghcr.io/ifm/ifm-node:betadistrolesslinux/amd64, linux/arm64~15 MB
ghcr.io/ifm/ifm-node:nightlydistrolesslinux/amd64, linux/arm64~15 MB

Linux Packages

DistroPackageRepoMaintainer
Debian/Ubuntuifm-nodeOfficial apt repoIFM Team
Archifm-nodeAUR / extraCommunity
Fedoraifm-nodeCopr / officialIFM Team
Alpineifm-nodeCommunityCommunity
NixOSifm-nodenixpkgsCommunity
openSUSEifm-nodeOBSCommunity

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

PackageRuntimeEntry PointsSize (gz)
@ifm/sdkNode ≥18, Bun, DenoESM + Types~120 KB
@ifm/sdk-wasmBrowser, Edge WorkersESM + WASM~400 KB
@ifm/reactReact 18+ESM + Types~45 KB
@ifm/vueVue 3ESM + 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 publish

CDN 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

PlatformSharedStaticHeaders
Linux x86_64libifm.solibifm.aifm.h
Linux aarch64libifm.solibifm.aifm.h
macOS x86_64libifm.dyliblibifm.aifm.h
macOS arm64libifm.dyliblibifm.aifm.h
Windows x86_64ifm.dll + ifm.libifm.libifm.h
Windows arm64ifm.dll + ifm.libifm.libifm.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_H

CMake 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.xcframework

CocoaPods 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'
end

Swift 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.0

Python (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 implementation

ifm-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 page

manifest.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 updates

PWA Features

FeatureImplementation
Installabilitybeforeinstallprompt → custom "Add to Home Screen" button
OfflinePrecached shell + stale-while-revalidate for station index
Background AudioMedia Session API + audio element persists across navigation
Push NotificationsWeb Push API (VAPID), user opt-in, serverless via Cloudflare Workers
Deep Linking#91.700 → auto-tune, #station/callsign → station page
QR SharingWeb Share API Level 2 + qrcode lib fallback
Media ControlsMedia Session API (lock screen, notification shade, headphones)
Auto-UpdateSW skipWaiting + clients.claim; toast "New version available"

Deploy Targets

TargetURLNotes
Primaryhttps://ifm.sh/pwa/Cloudflare Pages (auto-deploy from main)
Aliashttps://pwa.ifm.sh/CNAME to primary
GitHub Pageshttps://ifm.github.io/ifm-pwa/Backup / mirror
CDNhttps://cdn.jsdelivr.net/gh/ifm/ifm-pwa@latest/jsDelivr (from GitHub releases)
Docker (optional)ghcr.io/ifm/ifm-pwa:latestnginx static, for self-hosting

Release Artifacts

ArtifactFormatSignedSBOM
ifm-pwaifm-pwa-v1.3.0.tar.gz (static site)✅ cosign✅ SPDX
ifm-pwaGitHub Pages deploy✅ (via Pages)✅ SPDX
ifm-pwaCloudflare Pages deploy✅ (via CF)✅ SPDX

Channel Artifacts

ChannelDeploy Target
Nightlyhttps://pwa-nightly.ifm.sh/ (Cloudflare Pages preview)
Betahttps://pwa-beta.ifm.sh/ (Cloudflare Pages preview)
Stablehttps://ifm.sh/pwa/ (Cloudflare Pages production)
LTShttps://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 sharing

Summary: 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

Released under the MIT License.