Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
998732e98b
|
||
|
|
54aa87676d
|
||
|
|
746eeccd5c
|
||
|
|
e03d6bb195
|
||
|
|
764bffac20
|
||
|
|
e6499b50bb
|
+12
@@ -19,3 +19,15 @@ coverage.*
|
|||||||
*.swp
|
*.swp
|
||||||
*~
|
*~
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
# Debian packaging build output
|
||||||
|
/debian/files
|
||||||
|
/debian/lazyntfy/
|
||||||
|
/debian/*.substvars
|
||||||
|
/debian/.debhelper/
|
||||||
|
|
||||||
|
# Arch Linux packaging build output
|
||||||
|
/packaging/archlinux/src/
|
||||||
|
/packaging/archlinux/pkg/
|
||||||
|
/packaging/archlinux/*.pkg.tar.*
|
||||||
|
/packaging/archlinux/*.tar.gz
|
||||||
|
|||||||
@@ -2,33 +2,42 @@ BINARY := lazyntfy
|
|||||||
PREFIX ?= $(HOME)/.local
|
PREFIX ?= $(HOME)/.local
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
MANDIR ?= $(PREFIX)/share/man/man1
|
MANDIR ?= $(PREFIX)/share/man/man1
|
||||||
|
DOCDIR ?= $(PREFIX)/share/doc/$(BINARY)
|
||||||
|
DESTDIR ?=
|
||||||
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null | sed 's/^v//' | grep . || echo 0.1.0)
|
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null | sed 's/^v//' | grep . || echo 0.1.0)
|
||||||
TOPIC ?= alerts
|
TOPIC ?= alerts
|
||||||
|
GO ?= go
|
||||||
|
GOFLAGS ?= -trimpath
|
||||||
|
|
||||||
LDFLAGS := -X main.version=$(VERSION)
|
LDFLAGS := -X main.version=$(VERSION)
|
||||||
|
|
||||||
.PHONY: build install uninstall test vet fmt run man clean
|
.PHONY: all build install uninstall man test vet fmt run deb arch clean
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go build -ldflags "$(LDFLAGS)" -o $(BINARY) ./cmd/lazyntfy
|
$(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY) ./cmd/lazyntfy
|
||||||
|
|
||||||
|
# System-wide install: sudo make install PREFIX=/usr/local
|
||||||
|
# Staged install: make install DESTDIR=/tmp/pkg PREFIX=/usr
|
||||||
install: build
|
install: build
|
||||||
mkdir -p "$(BINDIR)" "$(MANDIR)"
|
install -Dm755 $(BINARY) "$(DESTDIR)$(BINDIR)/$(BINARY)"
|
||||||
install -m 755 $(BINARY) "$(BINDIR)/$(BINARY)"
|
install -Dm644 man/lazyntfy.1 "$(DESTDIR)$(MANDIR)/lazyntfy.1"
|
||||||
install -m 644 man/lazyntfy.1 "$(MANDIR)/lazyntfy.1"
|
install -Dm644 LICENSE "$(DESTDIR)$(DOCDIR)/copyright"
|
||||||
|
install -Dm644 README.md "$(DESTDIR)$(DOCDIR)/README.md"
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
rm -f "$(BINDIR)/$(BINARY)" "$(MANDIR)/lazyntfy.1"
|
rm -f "$(DESTDIR)$(BINDIR)/$(BINARY)" "$(DESTDIR)$(MANDIR)/lazyntfy.1"
|
||||||
|
rm -rf "$(DESTDIR)$(DOCDIR)"
|
||||||
|
|
||||||
man:
|
man:
|
||||||
mkdir -p "$(MANDIR)"
|
install -Dm644 man/lazyntfy.1 "$(DESTDIR)$(MANDIR)/lazyntfy.1"
|
||||||
install -m 644 man/lazyntfy.1 "$(MANDIR)/lazyntfy.1"
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test ./...
|
$(GO) test ./...
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
$(GO) vet ./...
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
gofmt -w .
|
gofmt -w .
|
||||||
@@ -36,5 +45,19 @@ fmt:
|
|||||||
run: build
|
run: build
|
||||||
./$(BINARY) $(TOPIC)
|
./$(BINARY) $(TOPIC)
|
||||||
|
|
||||||
|
# Build a Debian/Ubuntu package. -d skips build-dependency checks so the
|
||||||
|
# package can also be built on a machine without golang-go installed as a
|
||||||
|
# distro package.
|
||||||
|
deb:
|
||||||
|
dpkg-buildpackage -us -uc -b -d
|
||||||
|
mkdir -p dist
|
||||||
|
mv -f ../$(BINARY)_*_*.deb ../$(BINARY)_*_*.changes ../$(BINARY)_*_*.buildinfo dist/ 2>/dev/null || true
|
||||||
|
@echo " packages written to dist/"
|
||||||
|
|
||||||
|
arch:
|
||||||
|
cd packaging/archlinux && makepkg -f
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(BINARY)
|
rm -f $(BINARY)
|
||||||
|
rm -rf dist
|
||||||
|
-$(MAKE) -f debian/rules clean
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
# lazyntfy
|
# lazyntfy
|
||||||
|
|
||||||
A [lazygit](https://github.com/jesseduffield/lazygit)-style terminal client for
|
**A [lazygit](https://github.com/jesseduffield/lazygit)-style terminal client for [ntfy](https://ntfy.sh).**
|
||||||
[ntfy](https://ntfy.sh): topics in the left pane, messages in the middle pane,
|
Follow your topics, read and search messages, and publish — without leaving the terminal.
|
||||||
and the selected message in the right pane.
|
|
||||||
|
[](https://go.dev)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://github.com/charmbracelet/bubbletea)
|
||||||
|
[](#contributing)
|
||||||
|
|
||||||
```
|
```
|
||||||
╭ topics ─────╮ ╭ messages ─────────────────╮ ╭ detail ───────────────╮
|
╭ topics ─────╮ ╭ messages ─────────────────╮ ╭ detail ───────────────╮
|
||||||
@@ -15,92 +19,207 @@ and the selected message in the right pane.
|
|||||||
lazyntfy alerts 12/12 ● connected ? help q quit
|
lazyntfy alerts 12/12 ● connected ? help q quit
|
||||||
```
|
```
|
||||||
|
|
||||||
lazyntfy talks to the ntfy HTTP API directly (`GET /topic/json` to subscribe,
|
lazyntfy is a keyboard-driven TUI for [ntfy](https://ntfy.sh): the left pane lists
|
||||||
`POST /topic` to publish). It reads `~/.config/ntfy/client.yml` for credentials
|
topics, the middle pane lists messages, and the right pane shows the selected
|
||||||
and subscriptions, groups topics that share a server and credentials onto one
|
message in full. It talks to the ntfy HTTP API directly, follows several topics
|
||||||
stream, and reconnects automatically — resuming from the newest event it has
|
over a single stream, and keeps a local cache so your history is there the moment
|
||||||
already seen so nothing is delivered twice.
|
you launch it.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- [Features](#features)
|
||||||
|
- [Install](#install)
|
||||||
|
- [Quick start](#quick-start)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Key bindings](#key-bindings)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [How it works](#how-it-works)
|
||||||
|
- [History and storage](#history-and-storage)
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Development](#development)
|
||||||
|
- [Packaging](#packaging)
|
||||||
|
- [Project layout](#project-layout)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **lazygit-style layout** — topics, messages, and message detail in three panes.
|
- **Three-pane, lazygit-style layout** — topics, messages, and full detail side by side.
|
||||||
- **Account discovery** — with no topic on the command line it lists your
|
- **Follow many topics at once** — topics that share a server and credentials are
|
||||||
subscriptions, reservations, and ACL grants.
|
multiplexed onto a single stream.
|
||||||
- **Alert-aware** — Grafana and Alertmanager webhook bodies are summarized
|
- **Account discovery** — start with no topics and lazyntfy lists your
|
||||||
(status, alert name, severity, summary); `v` shows the raw JSON.
|
subscriptions, reserved topics, and ACL grants.
|
||||||
- **Persistent** — topics you add and the messages that arrive are stored in a
|
- **Alert-aware** — Grafana and Alertmanager webhook payloads are summarized into
|
||||||
small SQLite file and restored on the next start.
|
status, alert name, severity, and summary; `v` reveals the raw JSON.
|
||||||
- **Resilient** — reconnects with exponential backoff, honoring `message_delete`
|
- **Persistent** — topics and messages are cached in a local SQLite database and
|
||||||
and `message_clear` events.
|
restored on the next start, so the list is populated as soon as the app opens.
|
||||||
- **Single binary, no CGo** — pure Go, including the SQLite driver.
|
- **Resilient** — reconnects with exponential backoff and resumes from the newest
|
||||||
|
event it has already seen, so nothing is delivered twice.
|
||||||
|
- **Publish from the terminal** — press `p` to send a title, priority, tags, and body.
|
||||||
|
- **Single static binary** — pure Go, no CGo, including the SQLite driver.
|
||||||
|
|
||||||
## Requirements
|
## Install
|
||||||
|
|
||||||
- Go 1.26 or newer (to build)
|
lazyntfy needs a **Go 1.26+ toolchain only to build**. Prebuilt installs are
|
||||||
- A reachable ntfy server
|
available for Arch Linux and Debian/Ubuntu.
|
||||||
|
|
||||||
## Installation
|
### 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` and the man page under `/usr/share/man`, 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`, and `make deb` passes `-d` so it also builds where `golang-go` is not
|
||||||
|
installed as a distribution package.
|
||||||
|
|
||||||
### From source
|
### From source
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://src.panahifar.ir/ahp/lazyntfy.git
|
git clone https://src.panahifar.ir/ahp/lazyntfy.git
|
||||||
cd lazyntfy
|
cd lazyntfy
|
||||||
make install # installs the binary and the man page under ~/.local
|
make install # into ~/.local, no root needed
|
||||||
```
|
```
|
||||||
|
|
||||||
`make install` honors `PREFIX` (default `~/.local`):
|
`make install` honors `PREFIX`, `BINDIR`, `MANDIR`, `DOCDIR`, and `DESTDIR`.
|
||||||
|
Choosing a system prefix installs for every user:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make install PREFIX=/usr/local
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Stage a filesystem tree into a temporary root instead of the live system (handy
|
||||||
|
for building your own package):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make install DESTDIR="$PWD/stage" PREFIX=/usr
|
||||||
```
|
```
|
||||||
|
|
||||||
### With `go install`
|
### With `go install`
|
||||||
|
|
||||||
|
From a checkout:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install ./cmd/lazyntfy@latest # or: go install lazyntfy/cmd/lazyntfy
|
go install ./cmd/lazyntfy # binary in $(go env GOPATH)/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```sh
|
||||||
|
lazyntfy alerts # follow one topic on the default host
|
||||||
|
lazyntfy # or start with no arguments to load every topic on your account
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `j`/`k` to move, `enter` to open a message, `/` to filter, `p` to publish,
|
||||||
|
and `?` for help.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```sh
|
```
|
||||||
lazyntfy # topics from the account, their history, and client.yml
|
lazyntfy [flags] [topic...]
|
||||||
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
|
### Flags
|
||||||
subscriptions, reserved topics, and ACL grants (`GET /v1/account` and, when
|
|
||||||
allowed, `GET /v1/users`), then adds the topics remembered from previous
|
| Flag | Default | Description |
|
||||||
sessions. You can always add a topic for the current session with `a`.
|
| --- | --- | --- |
|
||||||
|
| `--config path` | `~/.config/ntfy/client.yml` | Read the ntfy client config from `path`. |
|
||||||
|
| `--host url` | from the config file | Override `default-host`. |
|
||||||
|
| `--token token` | from the config file | Send `token` as a Bearer credential. |
|
||||||
|
| `--user user[:password]` | from the config file | Send HTTP basic credentials. |
|
||||||
|
| `--since value` | `all` | Replay window: `all`, a duration, a Unix timestamp, or a message id. |
|
||||||
|
| `--version` | | Print the version and exit. |
|
||||||
|
| `--help` | | Print a short usage summary and exit. |
|
||||||
|
|
||||||
|
### Specifying topics
|
||||||
|
|
||||||
|
Topics can be passed as positional arguments or listed in `client.yml`:
|
||||||
|
|
||||||
|
- A **short name** is expanded with `default-host`: `lazyntfy alerts`.
|
||||||
|
- A **`host/topic`** or a **full URL** carries its own origin:
|
||||||
|
`lazyntfy ntfy.example:8443/alerts` or `lazyntfy https://other.example/backups`.
|
||||||
|
- Short names need a `default-host`; pass `--host` or use a `host/topic` form.
|
||||||
|
|
||||||
|
With **no positional topics**, lazyntfy assembles the subscription list from, in
|
||||||
|
order:
|
||||||
|
|
||||||
|
1. the `subscribe:` list in `client.yml`;
|
||||||
|
2. your account — subscriptions and reservations from `GET /v1/account`, and ACL
|
||||||
|
grants from `GET /v1/users` when the credential may read it;
|
||||||
|
3. topics remembered from previous sessions.
|
||||||
|
|
||||||
|
Press `a` at any time to add a topic for the current session.
|
||||||
|
|
||||||
|
### Replaying history
|
||||||
|
|
||||||
|
`--since` sets the `since` query parameter for each stream: `all` (the default),
|
||||||
|
a duration such as `1h` or `30m`, a Unix timestamp, or a message id.
|
||||||
|
|
||||||
|
When history was restored from disk and `--since` is left at its default,
|
||||||
|
lazyntfy sets `since` to the newest stored event instead, so the first connection
|
||||||
|
picks up where the last session stopped instead of replaying the server's whole
|
||||||
|
cache. An explicit `--since` always wins.
|
||||||
|
|
||||||
|
### Publishing
|
||||||
|
|
||||||
|
Press `p` to open a small form with four fields — **title**, **priority**,
|
||||||
|
**tags**, and **message**:
|
||||||
|
|
||||||
|
- **priority** accepts `1`–`5` or the ntfy names `min`, `low`, `default`, `high`, `max`
|
||||||
|
(empty means `default`).
|
||||||
|
- **tags** is a comma-separated list.
|
||||||
|
- **message** is required; it is published as plain text with `Title`, `Priority`,
|
||||||
|
and `Tags` headers.
|
||||||
|
|
||||||
## Key bindings
|
## Key bindings
|
||||||
|
|
||||||
Keys act on the focused pane. Use `tab` / `shift-tab` or `1` / `2` / `3` to
|
Keys act on the focused pane. Use `tab` / `shift-tab` or `1` / `2` / `3` to change
|
||||||
change panes.
|
panes.
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `j` / `k`, arrows | move in the focused pane |
|
| `j` / `k`, `↓` / `↑` | move in the focused pane |
|
||||||
| `g` / `G` | first / last |
|
| `g` / `G`, `home` / `end` | first / last |
|
||||||
| `ctrl-u` / `ctrl-d` | page up / down |
|
| `ctrl-u` / `ctrl-d`, `pgup` / `pgdn` | page up / down |
|
||||||
| `1` / `2` / `3` | focus topics, messages, detail |
|
| `1` / `2` / `3` | focus topics / messages / detail |
|
||||||
| `tab` / `shift-tab` | next / previous pane |
|
| `tab` / `shift-tab` | next / previous pane |
|
||||||
| `enter` | topics → messages → detail |
|
| `enter` | descend: topics → messages → detail |
|
||||||
| `esc` | back from detail, or clear the filter |
|
| `esc` | leave detail, or clear the filter |
|
||||||
| `/` | filter title, body, tags, and id |
|
| `/` | filter by title, body, tags, and message id |
|
||||||
| `r` | reconnect |
|
| `r` | reconnect all streams |
|
||||||
| `p` | publish to the focused topic |
|
| `p` | publish to the focused topic |
|
||||||
| `a` | subscribe to another topic for this session |
|
| `a` | subscribe to another topic for this session |
|
||||||
| `v` | toggle raw JSON |
|
| `v` | toggle the raw JSON view |
|
||||||
| `o` | open the message click URL |
|
| `o` | open the message's click URL |
|
||||||
| `?` | help |
|
| `?` | in-app help |
|
||||||
| `q` | quit |
|
| `q` / `ctrl-c` | quit |
|
||||||
|
|
||||||
On a narrow terminal the detail pane is hidden until it is focused.
|
The layout adapts to the terminal width: at 100 columns and wider all three panes
|
||||||
|
are shown; from 72–99 columns the topics pane is paired with the focused pane;
|
||||||
|
and below 72 columns only the focused pane is drawn. Terminals smaller than 40×8
|
||||||
|
show `terminal too small`.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -110,26 +229,78 @@ lazyntfy reads the official ntfy client configuration at
|
|||||||
```yaml
|
```yaml
|
||||||
default-host: https://ntfy.example
|
default-host: https://ntfy.example
|
||||||
default-token: tk_xxxxxxxxxxxxxxxx
|
default-token: tk_xxxxxxxxxxxxxxxx
|
||||||
|
# or, instead of a token:
|
||||||
|
# default-user: alice
|
||||||
|
# default-password: s3cret
|
||||||
|
|
||||||
subscribe:
|
subscribe:
|
||||||
- topic: alerts
|
- topic: alerts
|
||||||
- topic: https://other.example/backups
|
- topic: https://other.example/backups # explicit origin
|
||||||
user: alice
|
user: bob
|
||||||
password: secret
|
password: hunter2
|
||||||
|
- topic: private # per-topic token
|
||||||
|
token: tk_yyyyyyyyyyyyyyyy
|
||||||
```
|
```
|
||||||
|
|
||||||
Positional topics on the command line replace the file's `subscribe` list.
|
**Precedence.** `--host`, `--token`, and `--user` override the file. A token beats
|
||||||
Credentials are sent in the `Authorization` header and are never printed or
|
a user: passing `--user` clears the token, and passing `--token` clears the user.
|
||||||
written back. See `man lazyntfy` for every flag.
|
A per-topic `token` or `user` in `subscribe` applies to that topic only. Credentials
|
||||||
|
are sent in the `Authorization` header (Bearer or Basic) and are never printed or
|
||||||
|
written to disk.
|
||||||
|
|
||||||
|
If the default file is missing and you pass topics on the command line, lazyntfy
|
||||||
|
still runs. Point `--config` at a different file to load another one.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
- **Subscribe** — `GET /<topic>[,<topic>...]/json` returning NDJSON, with a `since`
|
||||||
|
query parameter per stream.
|
||||||
|
- **Publish** — `POST /<topic>` with a plain-text body and `Title` / `Priority` /
|
||||||
|
`Tags` headers.
|
||||||
|
- **Grouping** — topics that share an origin and credentials share one connection;
|
||||||
|
each group is supervised independently.
|
||||||
|
- **Reconnect** — on a dropped stream the group retries with exponential backoff
|
||||||
|
(1s, doubling up to 30s) and sets `since` to the newest event time it has already
|
||||||
|
seen, so messages are not delivered twice.
|
||||||
|
- **Events** — `message` adds a message, while `message_delete` and `message_clear`
|
||||||
|
remove messages locally as well. Malformed lines are skipped and reported in the
|
||||||
|
status bar.
|
||||||
|
- **Truncated cache** — if the server caps its replay it sends
|
||||||
|
`X-Messages-Truncated`, and lazyntfy notes that the cache was truncated to the
|
||||||
|
newest messages.
|
||||||
|
|
||||||
## History and storage
|
## History and storage
|
||||||
|
|
||||||
Topics you add and the messages that arrive are kept in a small SQLite database
|
Topics and messages are kept in a small SQLite database so the last session is
|
||||||
at `$XDG_DATA_HOME/lazyntfy/history.db`
|
restored on the next start.
|
||||||
(`~/.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
|
- **Path** — `$XDG_DATA_HOME/lazyntfy/history.db`, defaulting to
|
||||||
messages are pruned on startup. Credentials are never written to the database —
|
`~/.local/share/lazyntfy/history.db`.
|
||||||
the current run's authentication is always used. History is best effort: if the
|
- **Contents** — remembered topics, the newest 1000 messages per topic, and a little
|
||||||
file cannot be opened, the client still runs.
|
UI state such as the last selected topic.
|
||||||
|
- **Maintenance** — messages with an `expires` time are pruned on startup, and the
|
||||||
|
1000-message cap is enforced as messages arrive.
|
||||||
|
- **Privacy** — credentials are never written to the database; the current run's
|
||||||
|
authentication is always used.
|
||||||
|
- **Best effort** — if the database cannot be opened, lazyntfy runs without history.
|
||||||
|
|
||||||
|
To start clean, delete the file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rm -f "${XDG_DATA_HOME:-$HOME/.local/share}/lazyntfy/history.db"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Symptom | What to do |
|
||||||
|
| --- | --- |
|
||||||
|
| `no topics yet — press a to add one` | Pass a topic, add `subscribe:` entries, or use a token so account discovery can list topics. |
|
||||||
|
| `HTTP 401/403: not authorized to list topics` | The credential cannot read `/v1/account`; pass topics explicitly instead. |
|
||||||
|
| `no default-host` | Short topic names need a host. Pass `--host`, or use `host/topic`. |
|
||||||
|
| `x509: certificate signed by unknown authority` | Install CA certificates (`ca-certificates`). |
|
||||||
|
| `terminal too small` | Resize to at least 40×8. |
|
||||||
|
| `o` does nothing | Opening the click URL uses `xdg-open`; install it or a desktop opener. |
|
||||||
|
| First launch shows a flood of messages | Narrow the window with `--since 1h`. |
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@@ -140,28 +311,51 @@ make vet # go vet ./...
|
|||||||
make fmt # gofmt -w .
|
make fmt # gofmt -w .
|
||||||
make run TOPIC=alerts
|
make run TOPIC=alerts
|
||||||
make man # install just the man page
|
make man # install just the man page
|
||||||
|
make clean # remove the binary and build output
|
||||||
```
|
```
|
||||||
|
|
||||||
Run a single package's tests, for example:
|
Run one package's tests, or the full suite with the race detector:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test ./internal/history/...
|
go test ./internal/history/...
|
||||||
|
go test -race ./...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 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`.
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `cmd/lazyntfy` | flags and program startup |
|
| `cmd/lazyntfy` | flags and program startup |
|
||||||
| `internal/config` | `client.yml` plus CLI overrides |
|
| `internal/config` | `client.yml`, topic resolution, CLI overrides |
|
||||||
| `internal/history` | on-disk topics and messages (SQLite) |
|
| `internal/history` | on-disk topics and messages (SQLite) |
|
||||||
| `internal/ntfy` | HTTP subscribe, publish, reconnect |
|
| `internal/ntfy` | HTTP subscribe, publish, grouping, reconnect |
|
||||||
| `internal/store` | newest-first messages, delete, clear |
|
| `internal/store` | newest-first messages, delete, clear, filter |
|
||||||
| `internal/alert` | Grafana / Alertmanager summary |
|
| `internal/alert` | Grafana / Alertmanager summary |
|
||||||
| `internal/theme` | Catppuccin adaptive palette |
|
| `internal/theme` | Catppuccin adaptive palette |
|
||||||
| `internal/textutil` | width-aware text wrapping and clipping |
|
| `internal/textutil` | width-aware text wrapping and clipping |
|
||||||
| `internal/tui` | Bubble Tea interface |
|
| `internal/tui` | Bubble Tea interface |
|
||||||
| `man` | `lazyntfy(1)` man page |
|
| `man` | `lazyntfy(1)` man page |
|
||||||
|
| `debian` | Debian / Ubuntu package definition |
|
||||||
|
| `packaging/archlinux` | Arch Linux `PKGBUILD` |
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
lazyntfy (0.1.0) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- Amir Husayn Panahifar <ahp@panahifar.ir> Sun, 11 Oct 2026 12:00:00 +0330
|
||||||
Vendored
+23
@@ -0,0 +1,23 @@
|
|||||||
|
Source: lazyntfy
|
||||||
|
Section: utils
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Amir Husayn Panahifar <ahp@panahifar.ir>
|
||||||
|
Build-Depends: golang-go (>= 2:1.26)
|
||||||
|
Standards-Version: 4.7.0
|
||||||
|
Homepage: https://src.panahifar.ir/ahp/lazyntfy
|
||||||
|
Vcs-Git: https://src.panahifar.ir/ahp/lazyntfy.git
|
||||||
|
Vcs-Browser: https://src.panahifar.ir/ahp/lazyntfy
|
||||||
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: lazyntfy
|
||||||
|
Architecture: any
|
||||||
|
Depends: ca-certificates
|
||||||
|
Description: lazygit-style terminal client for ntfy
|
||||||
|
lazyntfy is a terminal user interface for the ntfy pub-sub notification
|
||||||
|
service, laid out like lazygit: topics in the left pane, messages in the
|
||||||
|
middle pane, and the selected message in the right pane.
|
||||||
|
.
|
||||||
|
It speaks the ntfy HTTP API directly, subscribes to several topics over a
|
||||||
|
single stream, reconnects automatically, summarizes Grafana and Alertmanager
|
||||||
|
webhook bodies, and remembers topics and messages between runs in a small
|
||||||
|
SQLite database.
|
||||||
Vendored
+19
@@ -0,0 +1,19 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: lazyntfy
|
||||||
|
Source: https://src.panahifar.ir/ahp/lazyntfy
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 Amir Husayn Panahifar <ahp@panahifar.ir>
|
||||||
|
License: GPL-3.0-or-later
|
||||||
|
This program is free software: you can redistribute it and/or modify it
|
||||||
|
under the terms of the GNU General Public License as published by the
|
||||||
|
Free Software Foundation, either version 3 of the License, or (at your
|
||||||
|
option) any later version.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful, but
|
||||||
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
General Public License for more details.
|
||||||
|
.
|
||||||
|
On Debian systems the full text of the GNU General Public License can be
|
||||||
|
found in /usr/share/common-licenses/GPL-3.
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# Debian packaging without debhelper: the Go toolchain builds the binary and
|
||||||
|
# the rules stage it by hand. This keeps the build dependencies to just Go.
|
||||||
|
|
||||||
|
export CGO_ENABLED = 0
|
||||||
|
|
||||||
|
PACKAGE := lazyntfy
|
||||||
|
VERSION := $(shell dpkg-parsechangelog -SVersion)
|
||||||
|
PKGDIR := debian/$(PACKAGE)
|
||||||
|
|
||||||
|
.PHONY: clean build build-arch build-indep binary binary-arch binary-indep
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(PKGDIR) $(PACKAGE)
|
||||||
|
rm -f debian/files
|
||||||
|
|
||||||
|
build: build-arch build-indep
|
||||||
|
|
||||||
|
build-arch: $(PACKAGE)
|
||||||
|
|
||||||
|
build-indep:
|
||||||
|
|
||||||
|
$(PACKAGE):
|
||||||
|
go build -trimpath -ldflags "-s -w -X main.version=$(VERSION)" -o $(PACKAGE) ./cmd/$(PACKAGE)
|
||||||
|
|
||||||
|
binary: binary-arch binary-indep
|
||||||
|
|
||||||
|
binary-arch: build-arch
|
||||||
|
install -d $(PKGDIR)/DEBIAN
|
||||||
|
install -Dm755 $(PACKAGE) $(PKGDIR)/usr/bin/$(PACKAGE)
|
||||||
|
install -Dm644 man/lazyntfy.1 $(PKGDIR)/usr/share/man/man1/lazyntfy.1
|
||||||
|
install -Dm644 LICENSE $(PKGDIR)/usr/share/doc/$(PACKAGE)/copyright
|
||||||
|
install -Dm644 README.md $(PKGDIR)/usr/share/doc/$(PACKAGE)/README.md
|
||||||
|
dpkg-gencontrol -p$(PACKAGE) -P$(PKGDIR)
|
||||||
|
dpkg-deb --build --root-owner-group $(PKGDIR) ..
|
||||||
|
|
||||||
|
binary-indep:
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
+2
-1
@@ -86,7 +86,8 @@ or
|
|||||||
to change panes.
|
to change panes.
|
||||||
.TP
|
.TP
|
||||||
.B j ", " k ", " \(dqup\(dq ", " \(dqdown\(dq
|
.B j ", " k ", " \(dqup\(dq ", " \(dqdown\(dq
|
||||||
Move within the focused pane..TP
|
Move within the focused pane.
|
||||||
|
.TP
|
||||||
.B g ", " G
|
.B g ", " G
|
||||||
Jump to the first or last item.
|
Jump to the first or last item.
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
pkgbase = lazyntfy
|
||||||
|
pkgdesc = A lazygit-style terminal client for ntfy
|
||||||
|
pkgver = 0.1.0
|
||||||
|
pkgrel = 1
|
||||||
|
url = https://src.panahifar.ir/ahp/lazyntfy
|
||||||
|
arch = x86_64
|
||||||
|
arch = aarch64
|
||||||
|
license = GPL-3.0-or-later
|
||||||
|
makedepends = go
|
||||||
|
depends = ca-certificates
|
||||||
|
options = !strip
|
||||||
|
options = !debug
|
||||||
|
source = lazyntfy-0.1.0.tar.gz::https://src.panahifar.ir/ahp/lazyntfy/archive/v0.1.0.tar.gz
|
||||||
|
sha256sums = 72c8d2675b4b3241197748793b2e16afd7e1c61102433efbaf0bf93dd59c9141
|
||||||
|
|
||||||
|
pkgname = lazyntfy
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
# Maintainer: Amir Husayn Panahifar <ahp@panahifar.ir>
|
||||||
|
|
||||||
|
pkgname=lazyntfy
|
||||||
|
pkgver=0.1.0
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="A lazygit-style terminal client for ntfy"
|
||||||
|
arch=('x86_64' 'aarch64')
|
||||||
|
url="https://src.panahifar.ir/ahp/lazyntfy"
|
||||||
|
license=('GPL-3.0-or-later')
|
||||||
|
depends=('ca-certificates')
|
||||||
|
makedepends=('go')
|
||||||
|
options=('!strip' '!debug')
|
||||||
|
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
|
||||||
|
sha256sums=('72c8d2675b4b3241197748793b2e16afd7e1c61102433efbaf0bf93dd59c9141')
|
||||||
|
|
||||||
|
prepare() {
|
||||||
|
cd "$pkgname"
|
||||||
|
go mod download
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$pkgname"
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
go build -trimpath -ldflags "-s -w -X main.version=$pkgver" -o lazyntfy ./cmd/lazyntfy
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
cd "$pkgname"
|
||||||
|
go test ./...
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$pkgname"
|
||||||
|
install -Dm755 lazyntfy "$pkgdir/usr/bin/lazyntfy"
|
||||||
|
install -Dm644 man/lazyntfy.1 "$pkgdir/usr/share/man/man1/lazyntfy.1"
|
||||||
|
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||||
|
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user