diff --git a/brand/icons/dmg-background.png b/brand/icons/dmg-background.png new file mode 100644 index 00000000..443767a6 Binary files /dev/null and b/brand/icons/dmg-background.png differ diff --git a/brand/icons/dmg-background.tiff b/brand/icons/dmg-background.tiff new file mode 100644 index 00000000..976672e8 Binary files /dev/null and b/brand/icons/dmg-background.tiff differ diff --git a/brand/icons/dmg-background@2x.png b/brand/icons/dmg-background@2x.png new file mode 100644 index 00000000..28cec5a7 Binary files /dev/null and b/brand/icons/dmg-background@2x.png differ diff --git a/brand/icons/igneum-256.png b/brand/icons/igneum-256.png new file mode 100644 index 00000000..ada88e8a Binary files /dev/null and b/brand/icons/igneum-256.png differ diff --git a/brand/icons/igneum.icns b/brand/icons/igneum.icns new file mode 100644 index 00000000..202b1f93 Binary files /dev/null and b/brand/icons/igneum.icns differ diff --git a/brand/icons/igneum.ico b/brand/icons/igneum.ico new file mode 100644 index 00000000..b964d2f3 Binary files /dev/null and b/brand/icons/igneum.ico differ diff --git a/brand/icons/inno-banner-150x57.bmp b/brand/icons/inno-banner-150x57.bmp new file mode 100644 index 00000000..6f94c4cc Binary files /dev/null and b/brand/icons/inno-banner-150x57.bmp differ diff --git a/brand/icons/inno-wizard-164x314.bmp b/brand/icons/inno-wizard-164x314.bmp new file mode 100644 index 00000000..041acc6e Binary files /dev/null and b/brand/icons/inno-wizard-164x314.bmp differ diff --git a/brand/icons/inno-wizard-small-55x58.bmp b/brand/icons/inno-wizard-small-55x58.bmp new file mode 100644 index 00000000..08c292bb Binary files /dev/null and b/brand/icons/inno-wizard-small-55x58.bmp differ diff --git a/brand/icons/make-icons.py b/brand/icons/make-icons.py new file mode 100644 index 00000000..aebe9b03 --- /dev/null +++ b/brand/icons/make-icons.py @@ -0,0 +1,179 @@ +#!/usr/bin/env python3 +"""Builds the Igneum Miner icon and installer art from brand/igneum-coin-1024.png (3 October 2026). + +Outputs, all in brand/icons/: + igneum.icns Mac app and DMG volume icon (iconutil from the full 10-file iconset) + igneum.ico Windows icon, 16 24 32 48 64 128 256 px, uncompressed BMP entries + igneum-256.png the coin cut out on transparency (the source of both) + inno-wizard-164x314.bmp Inno Setup WizardImageFile (left side of the welcome and finish pages) + inno-wizard-small-55x58.bmp Inno Setup WizardSmallImageFile (top right of the other pages) + inno-banner-150x57.bmp header banner (NSIS size; kept for the Windows README and any header-style page) + dmg-background.png 660x400 DMG window background + dmg-background@2x.png the same at 1320x800 for Retina; dmg-background.tiff holds both when tiffutil is present +Needs Pillow and, on a Mac, iconutil and tiffutil (both ship with macOS). + python3 brand/icons/make-icons.py +""" +import os, subprocess, sys, shutil, tempfile +from PIL import Image, ImageDraw, ImageFont + +HERE = os.path.dirname(os.path.abspath(__file__)) +BRAND = os.path.dirname(HERE) +COIN = os.path.join(BRAND, 'igneum-coin-1024.png') +FONT = os.path.join(BRAND, 'Unbounded.ttf') +OBSIDIAN = (12, 12, 14) +EMBER = (242, 84, 27) +MOLTEN = (255, 179, 92) +BONE = (244, 241, 236) +RADIUS = 480.0 # the ember ring's outer edge, measured on the 1024 px coin +CENTRE = (511.5, 511.5) + +def font(size, weight='SemiBold'): + f = ImageFont.truetype(FONT, size) + try: + f.set_variation_by_name(weight) + except Exception: + pass + return f + +def coin_rgba(): + """The coin on transparency: a 4x supersampled circular mask at the ring's outer edge.""" + im = Image.open(COIN).convert('RGBA') + big = Image.new('L', (4096, 4096), 0) + d = ImageDraw.Draw(big) + cx, cy, r = CENTRE[0] * 4 + 2, CENTRE[1] * 4 + 2, (RADIUS - 0.5) * 4 + d.ellipse((cx - r, cy - r, cx + r, cy + r), fill=255) + mask = big.resize((1024, 1024), Image.LANCZOS) + im.putalpha(mask) + return im + +def fit(im, size): + return im.resize((size, size), Image.LANCZOS) + +def make_icns(coin): + tmp = tempfile.mkdtemp() + iconset = os.path.join(tmp, 'igneum.iconset') + os.mkdir(iconset) + for s in (16, 32, 128, 256, 512): + fit(coin, s).save(os.path.join(iconset, f'icon_{s}x{s}.png')) + fit(coin, s * 2).save(os.path.join(iconset, f'icon_{s}x{s}@2x.png')) + out = os.path.join(HERE, 'igneum.icns') + subprocess.run(['iconutil', '-c', 'icns', iconset, '-o', out], check=True) + shutil.rmtree(tmp) + return out + +def make_ico(coin): + out = os.path.join(HERE, 'igneum.ico') + sizes = [(s, s) for s in (16, 24, 32, 48, 64, 128, 256)] + base = fit(coin, 256) + try: + base.save(out, format='ICO', sizes=sizes, bitmap_format='bmp') + except TypeError: + base.save(out, format='ICO', sizes=sizes) + return out + +def wordmark(text, size, colour=BONE, weight='SemiBold'): + f = font(size, weight) + l, t, r, b = f.getbbox(text) + im = Image.new('RGBA', (r - l + 4, b - t + 4), (0, 0, 0, 0)) + ImageDraw.Draw(im).text((2 - l, 2 - t), text, font=f, fill=colour) + return im + +def paste_centre(dst, src, cx, cy): + dst.alpha_composite(src, (int(cx - src.width / 2), int(cy - src.height / 2))) + +def bmp(im, path): + im.convert('RGB').save(path, format='BMP') + +def make_inno(coin): + # 164x314: the coin above a two-line wordmark + big = Image.new('RGBA', (164, 314), OBSIDIAN + (255,)) + paste_centre(big, fit(coin, 112), 82, 110) + paste_centre(big, wordmark('IGNEUM', 19, BONE, 'Bold'), 82, 205) + paste_centre(big, wordmark('MINER', 13, MOLTEN, 'Medium'), 82, 232) + bmp(big, os.path.join(HERE, 'inno-wizard-164x314.bmp')) + # 55x58: the coin alone + small = Image.new('RGBA', (55, 58), OBSIDIAN + (255,)) + paste_centre(small, fit(coin, 50), 27.5, 29) + bmp(small, os.path.join(HERE, 'inno-wizard-small-55x58.bmp')) + # 150x57: coin left, wordmark right + banner = Image.new('RGBA', (150, 57), OBSIDIAN + (255,)) + paste_centre(banner, fit(coin, 44), 28, 28.5) + w1 = wordmark('IGNEUM', 15, BONE, 'Bold') + w2 = wordmark('MINER', 10, MOLTEN, 'Medium') + banner.alpha_composite(w1, (56, 14)) + banner.alpha_composite(w2, (57, 34)) + bmp(banner, os.path.join(HERE, 'inno-banner-150x57.bmp')) + +def arrow(draw, x0, x1, y, colour, width): + draw.line((x0, y, x1 - width * 2, y), fill=colour, width=width) + h = width * 3 + draw.polygon([(x1, y), (x1 - h * 1.6, y - h), (x1 - h * 1.6, y + h)], fill=colour) + +# Finder icon slots on the DMG (1x pixels, icon size 96): the app at APP_SLOT, the Applications link at APPS_SLOT, +# README.txt and the Stop command in the row below. build-dmg.sh uses the same numbers. +ICON = 96 +APP_SLOT = (165, 130) +APPS_SLOT = (495, 130) +ROW2_Y = 330 + +def dashed_box(im, cx, cy, r, colour, width=3, dash=16, gap=10, radius=18): + """A dashed rounded square: the outline drawn once, then masked by diagonal bars.""" + W, H = im.size + layer = Image.new('RGBA', (W, H), (0, 0, 0, 0)) + ImageDraw.Draw(layer).rounded_rectangle((cx - r, cy - r, cx + r, cy + r), radius=radius, outline=colour, width=width) + bars = Image.new('L', (W, H), 0) + bd = ImageDraw.Draw(bars) + k = -H + while k < W + H: + bd.line((k, 0, k + H, H), fill=255, width=dash) + k += dash + gap + alpha = layer.split()[3] + alpha = Image.composite(alpha, Image.new('L', (W, H), 0), bars) + layer.putalpha(alpha) + im.alpha_composite(layer) + +def make_dmg_background(coin): + """660x400 at 1x; drawn at 2x and downsampled. The coin is painted under the app slot, so the slot reads as the + product even before Finder draws the app icon on top of it.""" + S = 2 + W, H = 660 * S, 400 * S + im = Image.new('RGBA', (W, H), OBSIDIAN + (255,)) + d = ImageDraw.Draw(im) + ax, ay = APP_SLOT[0] * S, APP_SLOT[1] * S + bx, by = APPS_SLOT[0] * S, APPS_SLOT[1] * S + glow = Image.new('RGBA', (W, H), (0, 0, 0, 0)) + gd = ImageDraw.Draw(glow) + for i, a in ((76 * S, 10), (66 * S, 12), (58 * S, 14)): + gd.ellipse((ax - i, ay - i, ax + i, ay + i), fill=EMBER + (a,)) + im.alpha_composite(glow) + paste_centre(im, fit(coin, ICON * S), ax, ay) + dashed_box(im, bx, by, (ICON // 2 + 14) * S, MOLTEN + (190,), width=2 * S, dash=9 * S, gap=6 * S, radius=9 * S) + arrow(d, (APP_SLOT[0] + 78) * S, (APPS_SLOT[0] - 78) * S, ay, EMBER + (255,), 4 * S) + paste_centre(im, wordmark('Drag Igneum Miner to Applications', 16 * S, BONE, 'Medium'), 330 * S, 236 * S) + paste_centre(im, wordmark('Then open it from Applications. Nobody from Igneum will ever ask for your seed.', 8 * S, (150, 148, 145), 'Regular'), 330 * S, 262 * S) + im2x = im.convert('RGB') + im2x.save(os.path.join(HERE, 'dmg-background@2x.png')) + im1x = im2x.resize((660, 400), Image.LANCZOS) + im1x.save(os.path.join(HERE, 'dmg-background.png')) + tiff = os.path.join(HERE, 'dmg-background.tiff') + if shutil.which('tiffutil'): + subprocess.run(['tiffutil', '-cathidpicheck', os.path.join(HERE, 'dmg-background.png'), + os.path.join(HERE, 'dmg-background@2x.png'), '-out', tiff], check=True) + +def main(): + coin = coin_rgba() + fit(coin, 256).save(os.path.join(HERE, 'igneum-256.png')) + if sys.platform == 'darwin' and shutil.which('iconutil'): + print('icns:', make_icns(coin)) + else: + print('icns skipped: iconutil is a macOS tool') + print('ico:', make_ico(coin)) + make_inno(coin) + make_dmg_background(coin) + for f in sorted(os.listdir(HERE)): + p = os.path.join(HERE, f) + if os.path.isfile(p) and not f.endswith('.py'): + print(f'{os.path.getsize(p):>8} {f}') + +if __name__ == '__main__': + main() diff --git a/packaging/mac/README.md b/packaging/mac/README.md index 25fa3072..10148727 100644 --- a/packaging/mac/README.md +++ b/packaging/mac/README.md @@ -1,38 +1,44 @@ -# Igneum Devnet for Mac (packaging/mac) +# Igneum Miner for Mac (packaging/mac) -A DMG a friend can open on an Apple silicon Mac to run a devnet node and the Metal miner, and show up on -igneum.network/live. Built 3 October 2026 from the binaries already compiled in this repo; nothing is compiled here. +A DMG a miner can open on an Apple silicon Mac to run a node and the Metal miner, and show up on igneum.network/live. +Built 3 October 2026 from the binaries already compiled in this repo; nothing is compiled here. Renamed from "Igneum +Devnet" to "Igneum Miner" the same evening (bundle id network.igneum.miner, version 0.1.0); the devnet is still the only +network and its name appears in the status lines, never in a window title. ## Build packaging/mac/build-dmg.sh -Output: `packaging/mac/dist/igneum-devnet-mac.dmg` (about 17 MB, lzfse). The script takes +Output: `packaging/mac/dist/Igneum-Miner-0.1.0.dmg` (about 16 MB, lzfse). The script takes `vendor/igneum-node/target-rename/release/igneumd` (falls back to `target/release/kaspad` renamed, and says so), `vendor/igneum-node/target/release/igneum-miner` and `proto-metal/igneum-bench` (the plain build, the one whose `--serve` protocol matches that miner), copies them into the bundle, strips the copies and signs them ad hoc again (strip invalidates the linker signature and arm64 macOS refuses an unsigned binary), and checks that each copy still -runs. The originals are untouched. The icon comes from `site/icon-512.png`. `build/` and `dist/` are ignored by git. +runs. The originals are untouched. The icon is `brand/icons/igneum.icns` and the DMG art `brand/icons/dmg-background.tiff` +(both from `python3 brand/icons/make-icons.py`). The image is laid out by `dmgbuild` (`pip3 install dmgbuild`, settings in +`dmg/settings.py`: volume "Igneum Miner", hidden `.VolumeIcon.icns` with the custom-icon flag, background, icon slots app +(165,130), Applications (495,130), README and Stop command on the row below, no Finder scripting needed). Without dmgbuild +the script builds a plain hdiutil image and says so. `build/` and `dist/` are ignored by git. ## What is on the DMG | Item | What it is | |---|---| -| `Igneum Devnet.app` | the launcher bundle (below) | -| `README.txt` | 9 lines for the friend (`dmg/README.txt`) | -| `Stop Igneum.command` | stops a copy whose window was closed without Ctrl+C (`app/Stop Igneum.command`) | +| `Igneum Miner.app` | the launcher bundle (below) | +| `README.txt` | 10 lines for the miner (`dmg/README.txt`), with the seed line | +| `Stop Igneum Miner.command` | stops a copy whose window was closed without Ctrl+C (`app/Stop Igneum Miner.command`) | | `Applications` | symlink, for the drag | ## The app bundle - Igneum Devnet.app/Contents/MacOS/Igneum Devnet app/launcher.sh: removes quarantine from Resources, opens the script in Terminal - Igneum Devnet.app/Contents/Resources/igneum-devnet.sh the run script (app/igneum-devnet.sh) - Igneum Devnet.app/Contents/Resources/bin/igneumd, igneum-miner, igneum-bench - Igneum Devnet.app/Contents/Resources/AppIcon.icns - Igneum Devnet.app/Contents/Info.plist app/Info.plist (CFBundleVersion = build stamp) + Igneum Miner.app/Contents/MacOS/Igneum Miner app/launcher.sh: removes quarantine from Resources, opens the script in Terminal + Igneum Miner.app/Contents/Resources/igneum-miner.sh the run script (app/igneum-miner.sh); sets the Terminal title "Igneum Miner" + Igneum Miner.app/Contents/Resources/bin/igneumd, igneum-miner, igneum-bench + Igneum Miner.app/Contents/Resources/igneum.icns + Igneum Miner.app/Contents/Info.plist app/Info.plist (CFBundleVersion = build stamp, LSMinimumSystemVersion 11.0) Why a shell launcher and not an AppleScript applet: `open -a Terminal