# Show HN: Apple Notes, Photos and Find My as Native Linux Apps (Omarchy/Arch)

> Source: <https://github.com/ferdousbhai/icloud-for-omarchy>
> Published: 2026-10-06 09:22:13+00:00

iCloud apps for [Omarchy](https://omarchy.org) (and any Arch Linux), sharing
one Apple sign-in, published as one signed pacman repository.

- **Notes** : your Apple Notes as a folder of Markdown files, synced both
ways. Edit, rename, move or delete them in the app, in Neovim or Obsidian,
or with`mv` and`rm` , and iCloud follows.
- **Photos** : browse, download, upload and delete your iCloud Photos.
- **Find My** : your devices on a map, with play sound, Lost Mode and a
location trail.
- Every window action also works from the terminal, with `--json` output
for scripts and AI agents.

```
curl -fsSL https://github.com/ferdousbhai/icloud-for-omarchy/releases/latest/download/install.sh | sudo bash
```

<sub>Screenshots use demo data.</sub>

| Directory | Package | What it is | 
|---|---|---|
| [notes/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/notes/README.md) ,[notes-sync/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/notes-sync/README.md) | `icloud-notes` | Apple Notes as a Qt/QML app and the `icloud-notes` command, synced with iCloud by its engine, icloud-notes-sync (notes-sync/, in Rust, originally derived from icloud-md), which the package installs off PATH. | 
| [photos/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/photos/README.md) | `icloud-photos` | iCloud Photos in GTK4/libadwaita: browse, download, upload and delete. | 
| [findmy/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/findmy/README.md) | `icloud-findmy` | Find My devices in GTK4/libadwaita: locate, play a sound, Lost Mode, history trail. | 
| [session/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/session/README.md) ,[sessiond/](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/sessiond) | `icloud-session` | The shared sign-in: a D-Bus daemon, a sign-in window and a CLI (sessiond/), plus the Rust client crate every app links (session/). | 

The shared sign-in's design (the daemon, its D-Bus interface, the session
files) is in [session/README.md](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/session/README.md#design).

Everything the windows do can be done from a terminal, or by an AI agent:
`icloud-notes`, `icloud-photos` and `icloud-findmy` take commands
(`icloud-notes list`, `icloud-photos download`, `icloud-findmy locate`, ...)
and run them without a window, and `icloud-session` owns the sign-in. They
share `--json` output, one JSON error shape and one table of exit codes.

- [docs/AGENTS.md](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/docs/AGENTS.md) : the reference to give an agent (auth,
commands with example JSON, safety rules, recipes).
- [docs/skills/icloud/SKILL.md](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/docs/skills/icloud/SKILL.md) : the same as a
Claude Code skill; copy`docs/skills/icloud` into`~/.claude/skills/` .
- [docs/CLI.md](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/docs/CLI.md) : every GUI feature mapped to its command, the
exit and error codes, the JSON shapes.

Signing in is the one thing a person must do: Apple's page (password, 2FA)
opens in a window from `icloud-session sign-in`.

- **You sign in on Apple's own page** , password and two-factor code
included, in a window opened by`icloud-session sign-in` . The apps never
see your password.
- **What's kept** is the session cookies, in`~/.local/state/icloud-session/account.json` , readable only by you.
- **Your password is stored only if you choose to** , for Find My, which asks
for it again from time to time:`icloud-session set-password` puts it in
your system keyring (the Secret Service, e.g. GNOME Keyring), and`icloud-session forget-password` removes it.
- **The apps talk only to Apple** , plus OpenStreetMap for Find My's map
tiles.
- **Notes deletes are recoverable** : a note you delete goes to Recently
Deleted in iCloud (about 30 days).
- **It's all open source** , and the packages are signed with a key whose
fingerprint is pinned in[install.sh](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/install.sh) .

```
curl -fsSL https://github.com/ferdousbhai/icloud-for-omarchy/releases/latest/download/install.sh | sudo bash
```

installs all three apps (icloud-notes, icloud-photos, icloud-findmy), which pull in icloud-session. To install only some, name them:

```
curl -fsSL .../install.sh | sudo bash -s -- icloud-photos icloud-findmy
```

The script ([install.sh](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/install.sh)) trusts the package-signing key (after
checking it against the fingerprint pinned in the script), adds the signed
`[icloud-for-omarchy]` repository as `/etc/pacman.d/icloud-for-omarchy.conf`
with an `Include` line in `/etc/pacman.conf`, installs an Omarchy
`pre-refresh-pacman` hook that restores the repository after
`omarchy refresh pacman`, and installs the packages in one `pacman -Syu`.
Re-running it is safe. Updates then arrive with `omarchy update`.

Rather not pipe a script into `sudo bash`? These are the same steps, one
at a time:

```
# 1. Download the package-signing key and check its fingerprint is
#    35C47A06567940B6796B4D0F9B3C7BDF85268B31
curl -fsSLO https://github.com/ferdousbhai/icloud-for-omarchy/releases/latest/download/icloud-for-omarchy-signing-key.asc
gpg --show-keys icloud-for-omarchy-signing-key.asc

# 2. Let pacman trust it
sudo pacman-key --add icloud-for-omarchy-signing-key.asc
sudo pacman-key --lsign-key 35C47A06567940B6796B4D0F9B3C7BDF85268B31

# 3. Add the signed repository
printf '[icloud-for-omarchy]\nSigLevel = Required DatabaseRequired\nServer = https://github.com/ferdousbhai/icloud-for-omarchy/releases/latest/download\n' \
  | sudo tee /etc/pacman.d/icloud-for-omarchy.conf
echo 'Include = /etc/pacman.d/icloud-for-omarchy.conf' | sudo tee -a /etc/pacman.conf

# 4. Install (on Omarchy: sudo pacman -Sy && omarchy-pkg-add icloud-notes icloud-photos icloud-findmy)
sudo pacman -Syu icloud-notes icloud-photos icloud-findmy
```

On Omarchy, `omarchy refresh pacman` rewrites `/etc/pacman.conf`; the
script installs a hook that adds the `Include` line back, so by hand you
would re-add it after a refresh.

Machines set up from earlier Notes releases, which had a repository of
their own (`[icloud-notes]`), are migrated: once `[icloud-for-omarchy]` is
added, the script removes `/etc/pacman.d/icloud-notes.conf`, its `Include`
line and its Omarchy hook.
The icloud-notes-sync package of earlier releases needs nothing from the
script: icloud-notes now carries the engine and `replaces` it, so the next
`omarchy update` swaps it out.

Every release also carries one installer per app, `install-notes.sh`,
`install-photos.sh` and `install-findmy.sh`: install.sh with its default
set to that one app, generated by `bin/make-installers`. The website (not
in this repository) redirects its one-liners to these assets:
`https://ferdousbhai.com/icloud/install.sh` to `install.sh`, and
`https://ferdousbhai.com/icloud-<app>/install.sh` to that app's installer,
e.g. `.../releases/latest/download/install-photos.sh`, so
`curl ... | sudo bash` keeps installing just that app. The apps' page is
[https://ferdousbhai.com/icloud](https://ferdousbhai.com/icloud).

To uninstall: `omarchy pkg drop <packages>`, then remove
`/etc/pacman.d/icloud-for-omarchy.conf`, its `Include` line in
`/etc/pacman.conf`, and
`~/.config/omarchy/hooks/pre-refresh-pacman.d/icloud-for-omarchy`.

```
Cargo.toml, Cargo.lock   one Cargo workspace: session, sessiond, notes-sync, photos, findmy
session/  sessiond/      icloud-session: client crate / daemon, sign-in window, CLI
notes/                   icloud-notes (qmake project, QML, tests, its own bin/build and bin/test)
notes-sync/              icloud-notes-sync, the sync engine the icloud-notes package ships
photos/  findmy/         icloud-photos, icloud-findmy
packaging/<package>/     one PKGBUILD per package
install.sh               the one installer (per-app copies are generated at release)
bin/                     build, test, release, verify-release, make-installers; dev-install/dev-uninstall for the daemon
tests/                   the add_signed_repo hash pin; install_test.sh, the installers against stubbed pacman
docs/                    the command-line reference (CLI.md, AGENTS.md, skills/)
```

Each directory kept its history: the five former repositories
(ferdousbhai/icloud-session, icloud-notes-sync, icloud-photos, icloud-findmy
and icloud-notes) were imported with `git filter-repo` into their
subdirectories and merged, so `git log --follow` on a file reaches back past
the move. icloud-notes' release tags `v0.1.0`...` v0.3.8` are here as
`notes-v0.1.0`...` notes-v0.3.8`.

Needs `rust`, `sqlite`, `gtk4`, `libadwaita`, `libshumate` (findmy) and
`webkitgtk-6.0` (the sign-in window) for the Rust crates, and `qt6-base`,
`qt6-declarative` and `make` for Notes. The apps talk to the icloud-session
daemon over D-Bus; for development without the package, `bin/dev-install`
puts a release build of it in `~/.local/bin` with a user D-Bus activation
file (`bin/dev-uninstall` undoes it).

```
bin/build                       # every package; or name some: bin/build icloud-photos
bin/test                        # clippy, all Rust tests, notes/bin/test, the installer checks
tests/install_test.sh           # the installers alone: stubbed pacman, scratch /etc in a user namespace
cargo test -p icloud-findmy     # one crate
notes/bin/test                  # the Qt app's tests alone (they run on a private D-Bus)
```

Rust binaries land in `target/release/`, Notes in `notes/build/`. Each app
can also run against a local fake of Apple's servers; its README says how.

notes-sync's recorded scenarios and golden corpora run with `cargo test`;
`ICLOUD_NOTES_SYNC_REGEN=1 cargo test -p icloud-notes-sync` re-records them
from the current code (see notes-sync/README.md).

Releases are cut from a checkout with the package-signing key in its keyring, no CI involved:

```
bin/release icloud-notes 0.4.1
bin/release icloud-session 0.3.0 icloud-notes 0.6.0   # several at once
```

Versions are per package and so are the tags: `<name>-v<version>`, where
`<name>` is the package name without `icloud-` (`session`, `photos`,
`findmy`, `notes`), e.g. `notes-v0.4.1`. Each PKGBUILD takes its
`pkgver` from its own newest tag: at the tag it is the plain version, and a
later commit builds `<version>.r<count>.<sha>` (`0.0.0.r<count>` for a package
never tagged).

`bin/release` runs `bin/test`, sets the named packages' versions (PKGBUILD,
and Cargo.toml for the Rust ones), commits and tags them, and builds only
those packages with `makepkg` from `packaging/`, each from the committed
HEAD via `git archive`. Every other package is downloaded from the latest
release, its signature checked against the pinned key, and carried forward
unchanged, so the new repository database, `icloud-for-omarchy.db`, always
lists all four. It signs the database with the key whose fingerprint
`install.sh` pins and publishes it, the packages, the public key,
`install.sh` and the per-app installers as one GitHub release on the first
tag named; `releases/latest/download` resolves to it.

The Notes sync engine (notes-sync/) is not released on its own: it ships
inside icloud-notes, built from the same commit, so releasing icloud-notes
releases it, and its Cargo.toml version only names the engine
(`icloud-notes-sync --version`). The `notes-sync-v*` tags are historical,
from when it was the separate icloud-notes-sync package (last
`notes-sync-v0.2.0`); the first icloud-notes that carries it must be
released before any other package, and `bin/release` refuses to carry
forward an icloud-notes that still depends on the old package.

A release counts as shipped only once `bin/verify-release` has installed
each named package in a clean Arch container, the apps through their
per-app installers and the shared packages through `install.sh`, and found
that version installed; otherwise `bin/release` deletes the release and the
tags. With `PUBLISH_CRATE=1`, releasing icloud-session also publishes its client
crate to crates.io after the release is verified, unless crates.io already has
that version; by default it does not.

The `add_signed_repo` function in `install.sh` is shared verbatim with the
Ghost installer (ferdousbhai/ghost), and both repositories pin its hash in
their tests (`tests/add_signed_repo.sha256` here): change it in both places,
and both hashes, together.

One key signs these packages and Ghost's; its fingerprint is pinned in both installers and it lives only in the releasing machine's keyring, protected by a passphrase. Losing it would break the trust chain on every machine that installed from these repositories, so keep an encrypted backup somewhere off this machine:

```
gpg --armor --export-secret-keys 35C47A06567940B6796B4D0F9B3C7BDF85268B31 \
  | gpg --symmetric --armor --output package-signing-key.backup.asc
```

Restoring is `gpg --decrypt package-signing-key.backup.asc | gpg --import`.

To rotate the key: generate the new one, publish one release from each
project signed with the old key that also ships the new public key as
`<repository>-signing-key.asc`, update the pinned fingerprint in both
installers and the tests, then sign the next releases with the new key.
Machines that installed earlier pick up the new key by re-running the
one-liner, which is idempotent.

MIT, see [LICENSE](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/LICENSE). Third-party credits (icloud-md, node-diff3,
the mdast/micromark utilities, yaml) are in [NOTICE](https://github.com/ferdousbhai/icloud-for-omarchy/blob/main/NOTICE).
