2026-10-11 11:57:45 +03:30
|
|
|
# lazyntfy
|
|
|
|
|
|
2026-10-11 12:04:54 +03:30
|
|
|
A [lazygit](https://github.com/jesseduffield/lazygit)-style terminal client for
|
|
|
|
|
[ntfy](https://ntfy.sh): topics in the left pane, messages in the middle pane,
|
|
|
|
|
and the selected message in the right pane.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
╭ topics ─────╮ ╭ messages ─────────────────╮ ╭ detail ───────────────╮
|
|
|
|
|
│ ● alerts 12 │ │ 06-14 09:21 CRIT DiskFull │ │ CRITICAL │
|
|
|
|
|
│ ○ backups │ │ 06-14 09:03 high Backup… │ │ DiskFull │
|
|
|
|
|
│ ○ deploy │ │ 06-13 22:47 def Deploy… │ │ 2026-06-14 09:21:07 │
|
|
|
|
|
│ │ │ │ │ mK3n9Qx… │
|
|
|
|
|
│ │ │ │ │ │
|
|
|
|
|
╰─────────────╯ ╰────────────────────────────╯ ╰───────────────────────╯
|
|
|
|
|
lazyntfy alerts 12/12 ● connected ? help q quit
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
lazyntfy talks to the ntfy HTTP API directly (`GET /topic/json` to subscribe,
|
|
|
|
|
`POST /topic` to publish). It reads `~/.config/ntfy/client.yml` for credentials
|
|
|
|
|
and subscriptions, groups topics that share a server and credentials onto one
|
|
|
|
|
stream, and reconnects automatically — resuming from the newest event it has
|
|
|
|
|
already seen so nothing is delivered twice.
|
|
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
|
|
- **lazygit-style layout** — topics, messages, and message detail in three panes.
|
|
|
|
|
- **Account discovery** — with no topic on the command line it lists your
|
|
|
|
|
subscriptions, reservations, and ACL grants.
|
|
|
|
|
- **Alert-aware** — Grafana and Alertmanager webhook bodies are summarized
|
|
|
|
|
(status, alert name, severity, summary); `v` shows the raw JSON.
|
|
|
|
|
- **Persistent** — topics you add and the messages that arrive are stored in a
|
|
|
|
|
small SQLite file and restored on the next start.
|
|
|
|
|
- **Resilient** — reconnects with exponential backoff, honoring `message_delete`
|
|
|
|
|
and `message_clear` events.
|
|
|
|
|
- **Single binary, no CGo** — pure Go, including the SQLite driver.
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
- Go 1.26 or newer (to build)
|
|
|
|
|
- A reachable ntfy server
|
|
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
2026-10-11 12:20:52 +03:30
|
|
|
### Arch Linux
|
|
|
|
|
|
|
|
|
|
A `PKGBUILD` lives in [`packaging/archlinux`](packaging/archlinux). Build and
|
|
|
|
|
install the package with `makepkg`:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
git clone https://src.panahifar.ir/ahp/lazyntfy.git
|
|
|
|
|
cd lazyntfy/packaging/archlinux
|
|
|
|
|
makepkg -si
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
This installs `/usr/bin/lazyntfy`, the man page under `/usr/share/man`, the
|
|
|
|
|
license, and the README, and pulls in `ca-certificates` so TLS to your ntfy
|
|
|
|
|
server works.
|
|
|
|
|
|
|
|
|
|
### Debian and Ubuntu
|
|
|
|
|
|
|
|
|
|
A debhelper-free source package lives in [`debian`](debian). Build a `.deb` and
|
|
|
|
|
install it system-wide:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
git clone https://src.panahifar.ir/ahp/lazyntfy.git
|
|
|
|
|
cd lazyntfy
|
|
|
|
|
make deb # writes dist/lazyntfy_<version>_<arch>.deb
|
|
|
|
|
sudo apt install ./dist/lazyntfy_*.deb
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`apt install ./file.deb` (or `dpkg -i` followed by `apt -f install`) resolves
|
|
|
|
|
the `ca-certificates` dependency. The packaging needs only the Go toolchain and
|
|
|
|
|
`dpkg-dev`; `make deb` passes `-d` so it also builds where `golang-go` is not
|
|
|
|
|
installed as a distribution package.
|
|
|
|
|
|
2026-10-11 12:04:54 +03:30
|
|
|
### From source
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
git clone https://src.panahifar.ir/ahp/lazyntfy.git
|
|
|
|
|
cd lazyntfy
|
2026-10-11 12:20:52 +03:30
|
|
|
make install # into ~/.local, no root needed
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`make install` honors `PREFIX`, `BINDIR`, `MANDIR`, `DOCDIR`, and `DESTDIR`.
|
|
|
|
|
Picking a system prefix installs for every user:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
sudo make install PREFIX=/usr/local # /usr/local/bin/lazyntfy, /usr/local/share/man/man1
|
|
|
|
|
sudo make install PREFIX=/usr # /usr/bin/lazyntfy, /usr/share/man/man1
|
2026-10-11 12:04:54 +03:30
|
|
|
```
|
|
|
|
|
|
2026-10-11 12:20:52 +03:30
|
|
|
Stage a filesystem tree into a temporary root instead of the live system (handy
|
|
|
|
|
for building your own package):
|
2026-10-11 12:04:54 +03:30
|
|
|
|
|
|
|
|
```sh
|
2026-10-11 12:20:52 +03:30
|
|
|
make install DESTDIR="$PWD/stage" PREFIX=/usr
|
2026-10-11 12:04:54 +03:30
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### With `go install`
|
|
|
|
|
|
2026-10-11 12:20:52 +03:30
|
|
|
From a checkout:
|
|
|
|
|
|
2026-10-11 12:04:54 +03:30
|
|
|
```sh
|
2026-10-11 12:20:52 +03:30
|
|
|
go install ./cmd/lazyntfy # binary in $(go env GOPATH)/bin
|
2026-10-11 12:04:54 +03:30
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
lazyntfy # topics from the account, their history, and client.yml
|
|
|
|
|
lazyntfy alerts # one topic, expanded with default-host
|
|
|
|
|
lazyntfy --since 1h alerts # replay the last hour
|
|
|
|
|
lazyntfy --host https://ntfy.example alerts
|
|
|
|
|
lazyntfy --token tk_xxx alerts
|
|
|
|
|
lazyntfy --user name:pass alerts
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
With no topic on the command line, lazyntfy asks the server for your account
|
|
|
|
|
subscriptions, reserved topics, and ACL grants (`GET /v1/account` and, when
|
|
|
|
|
allowed, `GET /v1/users`), then adds the topics remembered from previous
|
|
|
|
|
sessions. You can always add a topic for the current session with `a`.
|
|
|
|
|
|
|
|
|
|
## Key bindings
|
|
|
|
|
|
|
|
|
|
Keys act on the focused pane. Use `tab` / `shift-tab` or `1` / `2` / `3` to
|
|
|
|
|
change panes.
|
|
|
|
|
|
|
|
|
|
| Key | Action |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| `j` / `k`, arrows | move in the focused pane |
|
|
|
|
|
| `g` / `G` | first / last |
|
|
|
|
|
| `ctrl-u` / `ctrl-d` | page up / down |
|
|
|
|
|
| `1` / `2` / `3` | focus topics, messages, detail |
|
|
|
|
|
| `tab` / `shift-tab` | next / previous pane |
|
|
|
|
|
| `enter` | topics → messages → detail |
|
|
|
|
|
| `esc` | back from detail, or clear the filter |
|
|
|
|
|
| `/` | filter title, body, tags, and id |
|
|
|
|
|
| `r` | reconnect |
|
|
|
|
|
| `p` | publish to the focused topic |
|
|
|
|
|
| `a` | subscribe to another topic for this session |
|
|
|
|
|
| `v` | toggle raw JSON |
|
|
|
|
|
| `o` | open the message click URL |
|
|
|
|
|
| `?` | help |
|
|
|
|
|
| `q` | quit |
|
|
|
|
|
|
|
|
|
|
On a narrow terminal the detail pane is hidden until it is focused.
|
|
|
|
|
|
|
|
|
|
## Configuration
|
|
|
|
|
|
|
|
|
|
lazyntfy reads the official ntfy client configuration at
|
|
|
|
|
`~/.config/ntfy/client.yml`:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
default-host: https://ntfy.example
|
|
|
|
|
default-token: tk_xxxxxxxxxxxxxxxx
|
|
|
|
|
subscribe:
|
|
|
|
|
- topic: alerts
|
|
|
|
|
- topic: https://other.example/backups
|
|
|
|
|
user: alice
|
|
|
|
|
password: secret
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Positional topics on the command line replace the file's `subscribe` list.
|
|
|
|
|
Credentials are sent in the `Authorization` header and are never printed or
|
|
|
|
|
written back. See `man lazyntfy` for every flag.
|
|
|
|
|
|
|
|
|
|
## History and storage
|
|
|
|
|
|
|
|
|
|
Topics you add and the messages that arrive are kept in a small SQLite database
|
|
|
|
|
at `$XDG_DATA_HOME/lazyntfy/history.db`
|
|
|
|
|
(`~/.local/share/lazyntfy/history.db` by default), so the last session is
|
|
|
|
|
restored on the next start. Each topic keeps its newest 1000 messages; expired
|
|
|
|
|
messages are pruned on startup. Credentials are never written to the database —
|
|
|
|
|
the current run's authentication is always used. History is best effort: if the
|
|
|
|
|
file cannot be opened, the client still runs.
|
|
|
|
|
|
2026-10-11 12:20:52 +03:30
|
|
|
## Packaging
|
|
|
|
|
|
|
|
|
|
Both package formats are defined in the repository. `make deb` builds a Debian
|
|
|
|
|
package from the working tree; the Arch `PKGBUILD` builds the `v$pkgver` release
|
|
|
|
|
archive from the Gitea instance.
|
|
|
|
|
|
|
|
|
|
| Format | Build | Definition |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| Debian / Ubuntu | `make deb` → `dist/*.deb` | [`debian/`](debian) |
|
|
|
|
|
| Arch Linux | `make arch` → `makepkg` | [`packaging/archlinux/PKGBUILD`](packaging/archlinux/PKGBUILD) |
|
|
|
|
|
|
|
|
|
|
When cutting a release:
|
|
|
|
|
|
|
|
|
|
1. Bump the version in [`debian/changelog`](debian/changelog) and `pkgver` in
|
|
|
|
|
the `PKGBUILD`.
|
|
|
|
|
2. Tag the commit (`git tag -s vX.Y.Z`) and push it.
|
|
|
|
|
3. In `packaging/archlinux`, run `updpkgsums` to refresh the tarball checksum and
|
|
|
|
|
`makepkg --printsrcinfo > .SRCINFO`.
|
|
|
|
|
|
2026-10-11 12:04:54 +03:30
|
|
|
## Development
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
make build # build ./lazyntfy
|
|
|
|
|
make test # go test ./...
|
|
|
|
|
make vet # go vet ./...
|
|
|
|
|
make fmt # gofmt -w .
|
|
|
|
|
make run TOPIC=alerts
|
|
|
|
|
make man # install just the man page
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Run a single package's tests, for example:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
go test ./internal/history/...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Project layout
|
|
|
|
|
|
|
|
|
|
| Path | Purpose |
|
|
|
|
|
| --- | --- |
|
|
|
|
|
| `cmd/lazyntfy` | flags and program startup |
|
|
|
|
|
| `internal/config` | `client.yml` plus CLI overrides |
|
|
|
|
|
| `internal/history` | on-disk topics and messages (SQLite) |
|
|
|
|
|
| `internal/ntfy` | HTTP subscribe, publish, reconnect |
|
|
|
|
|
| `internal/store` | newest-first messages, delete, clear |
|
|
|
|
|
| `internal/alert` | Grafana / Alertmanager summary |
|
|
|
|
|
| `internal/theme` | Catppuccin adaptive palette |
|
|
|
|
|
| `internal/textutil` | width-aware text wrapping and clipping |
|
|
|
|
|
| `internal/tui` | Bubble Tea interface |
|
|
|
|
|
| `man` | `lazyntfy(1)` man page |
|
2026-10-11 12:20:52 +03:30
|
|
|
| `debian` | Debian / Ubuntu package definition |
|
|
|
|
|
| `packaging/archlinux` | Arch Linux `PKGBUILD` |
|
2026-10-11 12:04:54 +03:30
|
|
|
|
|
|
|
|
## Contributing
|
|
|
|
|
|
|
|
|
|
1. Fork the repository and create a topic branch.
|
|
|
|
|
2. Keep commits focused and conventional (`feat:`, `fix:`, `docs:`, …).
|
|
|
|
|
3. Run `make fmt vet test` before opening a pull request.
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
GNU General Public License, version 3 or later. See [LICENSE](LICENSE).
|