This site is driven by two keys, like the machine it came from. Reading: j and k scroll, d and u move half a page, gg goes to the top, G to the bottom, H and L switch windows, ? opens the help. Space is the Neovim leader and handles content: Space then h home, r research, p projects, g gear, a about, / tags, or Space then a digit to jump to that window. Home is the tmux prefix and handles windows: Home then c opens a terminal, Home then & closes a window, Home then space goes to the next one. While focus is on the keyboard, h j k l move between keys and Enter opens one.

ResearchR AboutA Gear — G for gear — keyboard, terminal, editor, homelab.G
    ProjectsP Tags — / as in vim: search./
    ×
    Menu

    clowner: Turning a macOS App Into Two Independent Copies

    One Bash script that copies a .app into a second copy with its own bundle identifier — two Braves, two Slacks, two of anything, running side by side with separate data.

    The problem: I wanted two genuinely separate browsers — one personal, one for work — sharing no session, and without switching profiles every time.

    macOS identifies an app by the CFBundleIdentifier in its Info.plist. Two copies with the same identifier are treated as the same app; change the identifier and the copy becomes a different app as far as the system is concerned. The catch is that editing Info.plist breaks the original code signature — so the clone has to be re-signed.

    clowner does both. One Bash script, calling tools that already exist on every Mac.

    curl -fsSL https://raw.githubusercontent.com/arisros/clowner/main/install.sh | sh
    clowner        # no arguments = wizard

    What it does

    1. Copies the bundle with cp -a.
    2. Rewrites CFBundleIdentifier in every Info.plist inside the bundle whose identifier starts with the original’s — the app itself plus its helpers (…helper, …helper.renderer). Unrelated nested bundles are left alone.
    3. Strips the auto-update keys (KSProductID, SUFeedURL, …).
    4. If the clone needs to run as its own instance, replaces Contents/MacOS/<exe> with a two-line wrapper that execs the real binary with isolation flags.
    5. Clears quarantine attributes, then re-signs: codesign --force --deep --sign -.

    Three things I didn’t expect

    The auto-update keys have to go. Otherwise the clone and the original both point at the same Sparkle or Keystone feed and start updating over each other. There’s a --keep-updater for anyone who actually wants that, but the default is to strip them.

    An app’s name isn’t only in Info.plist. Apps that localise their display name — the iWork apps, and plenty of others — store it in Contents/Resources/*.lproj/InfoPlist.strings, and that value wins over Info.plist. Until that surfaced, the menu bar and Finder kept showing the original name even with Info.plist rewritten.

    The icon has its own shortcut too. Modern apps load their icon from a compiled Assets.car via CFBundleIconName, which overrides a plain .icns. So when --icon is used, the CFBundleIconName key is dropped as well, so the new .icns is what actually gets used. And the icon swap has to happen before re-signing, or the signature is invalid again immediately.

    Not just browsers

    The core — copy, re-identify, re-sign — works on any .app. Browsers are just the case that needs one extra thing: a flag so the second copy opens its own window instead of handing the command to the instance already running. Chromium apps are detected automatically and get --user-data-dir; Firefox/Gecko apps get -no-remote --profile.

    Ordinary apps need no wrapper at all and clone bare with --no-profile. For anything with its own single-instance lock, --args takes whatever flags it needs:

    clowner clone --src "/Applications/Foo.app" --name foo2 --args "--its-own-flag=$HOME/foo2"

    Why Bash

    Nothing is compiled and there’s no runtime to install. The script runs on macOS’s stock /bin/bash — version 3.2, which is well over a decade old — so there’s no “first install a newer Bash” step in front of a tool whose whole job is copying a folder. fzf is optional: with it the app picker is fuzzy-searchable, without it you get a numbered menu.

    After cloning, macOS sometimes keeps the old name and icon cached. touch "/Applications/<clone>.app" followed by killall Dock Finder usually settles it.

    id en
    rss gh in