Skip to content

disuye/fangit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

60 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

fangit

Fang it! β€” a lightweight system tray app that turns a GitHub repo into a notification and file archiving system. No server, no extra apps, no infrastructure. Just a GitHub account, one private repo to relay monitoring data, and the GitHub iOS/Android app.

Fast, zero-config local file watching β†’ auto git commit β†’ push β†’ GitHub as the auth/notification/data layer.


War Boy: "Shall we run them around into our backup?"
Furiosa: "No, we're good. We fang it!"

Modes

Fangit has three modes:

Mode What it does Latency Best for
Sync Watches folders, auto-commits + pushes files to GitHub ~45-60s Archiving outputs, logs, generated files
Notify (dispatch) Triggers a GitHub Action that posts an issue comment ~30-35s Alerts with one GitHub account
Notify (direct) Posts an issue comment via API ~20-30s Fastest alerts (needs a second GitHub account)

All three deliver push notifications to your phone via the GitHub mobile app.

Watches can be routed to either sync or notify mode per-entry β€” the same folder can archive some files to git while sending lightweight notifications for others. An optional regex filter (match) lets you only act on lines that matter.

Why

You have a machine doing something β€” rendering video, running a generative music stream, processing batch jobs β€” and you want to know what it's doing from your phone. The existing options are either complex (Prometheus, Grafana) or require extra infrastructure (self-hosted notification servers).

Fangit uses GitHub as the entire backend: hosting, auth, iOS/Android notifications, web viewer, history, and collaboration. You get all of that for free with a GitHub account you probably already have.

Quick start

1. Create a GitHub repo

Create a new private repository on GitHub (e.g. my-fangit). This is where fangit stores files and posts notifications.

2. Set up authentication

Fangit uses git's built-in credential system. If you can git push from the terminal, fangit can too.

HTTPS (recommended): Generate a Personal Access Token and enter it when git prompts for a password on first push.

  • Classic PAT: check repo + workflow scopes. Can be set to never expire.
  • Fine-grained PAT: select your fangit repo only, grant Contents: Read & Write + Issues: Read & Write + Actions: Read & Write. Can be set to never expire for personal repos.

SSH: Configure an SSH key as usual. Use a host alias in ~/.ssh/config if you need multiple identities.

3. Install and configure fangit

Launch fangit. Click the tray icon β†’ Open config.toml. Edit the config:

[general]
github_user = "yourname"
batch_interval = 60
scan_interval = 30

[repo]
url = "https://github.com/yourname/my-fangit.git"
branch = "main"
auth = "https"

Add [[watch]] blocks to monitor folders and/or [[channel]] blocks for notifications. See Configuration below.

4. Restart fangit

On first launch with a valid config, fangit will clone the repo and push a notify.yml GitHub Actions workflow. This workflow handles push notifications β€” fangit creates it automatically.

5. Create GitHub issues for notification channels

Each [[channel]] posts to a specific GitHub issue. Create issues in your repo:

  • Issue #1: "Status"
  • Issue #2: "Errors"
  • Issue #3: "Log"

(Or whatever makes sense for your setup. Issue titles are what appear in the iOS notification.)

6. Install the GitHub mobile app

Install GitHub for iOS or Android. Sign in, enable push notifications. You'll receive alerts whenever fangit posts to an issue.


Configuration

Config file location:

  • macOS: ~/Library/Application Support/fangit/config.toml
  • Linux: ~/.config/fangit/config.toml

General settings

[general]
# GitHub username β€” used in @mentions for notifications
github_user = "yourname"

# Seconds to wait before committing file changes (30-300)
# Lower = more responsive, higher = fewer commits
batch_interval = 60

# Filesystem scan interval in seconds (10-300)
# Supplements QFileSystemWatcher for reliability
scan_interval = 30

# Tray icon style: "dot" | "logo" | "tint"
#   dot  = colored circle (default, minimal)
#   logo = app icon with colored status dot in corner
#   tint = app icon tinted entirely in status color
tray_style = "dot"

Repository

[repo]
url = "https://github.com/yourname/my-fangit.git"
branch = "main"
auth = "https"    # "https" or "ssh"

Watch directories

Each [[watch]] block monitors a local folder. What happens when files change depends on the action and match fields.

[[watch]]
path_name = "SMPLR"                # required β€” unique ID + repo subdirectory name
path = "~/SMPLR/recipes"           # local folder to watch
emoji = "🎡"                       # prefix for commit/notification messages
extensions = ["txt", "json"]       # optional β€” only watch these file types
action = "sync"                    # optional β€” see routing below
match = "ERROR|WARN"               # optional β€” regex filter, see below

path_name is important: it becomes the subdirectory in the git repo (for sync mode). Files from ~/SMPLR/recipes/ appear in the repo under SMPLR/.

For multi-machine setups, use path_name = "SMPLR/macFaux" to namespace by machine.

Watch routing β€” the action field

The action field controls what happens when files change in the watched directory:

action value Behaviour
"sync" or omitted Git commit + push (default). Files archived to repo, notification via Actions.
A [[channel]] path_name Skip git. Send a notification via that channel with file content.

If action is set to a string that doesn't match any channel, it falls through to sync mode as a safe default.

Content filtering β€” the match field

The match field is an optional regex pattern. When set, fangit scans new lines added to changed files and only acts if at least one line matches the pattern.

action match Behaviour
"sync" not set Sync all changed files (default)
"sync" set Only sync files where new lines match the regex
channel name not set Notify with last new line from each changed file
channel name set Only notify when new lines match; include the matched line

Fangit only scans new content β€” it tracks byte offsets per file and reads from where it left off. A 50MB log file that grows by 3 lines only scans those 3 lines.

Notification message format

Notifications are designed for the iOS lock screen β€” the most important information comes first:

Single file with match:

β†’ "Session ended: duration 4h32m" (12 new lines, 1 match)
🟒 SMPLR: stream.log

Single file without match filter:

β†’ "ambient_set_042 started" (3 new lines)
🟒 SMPLR: stream.log

Multiple files:

β†’ "FATAL: pipe broke" (2 match across 3 files)
🟒 SMPLR
  stream.log (45 new lines)
  error.log (2 new lines) β†’ "FATAL: pipe broke"

Full routing example

The same folder can have multiple watches with different extensions routed to different behaviours:

# .txt recipe files β†’ archive to git repo
[[watch]]
path_name = "SMPLR-recipes"
path = "/Volumes/Chainsaw/SMPLR/Stream"
emoji = "🎡"
extensions = ["txt"]
action = "sync"

# .log files β†’ notify only when errors occur
[[watch]]
path_name = "SMPLR-errors"
path = "/Volumes/Chainsaw/SMPLR/Stream"
emoji = "πŸ”΄"
extensions = ["log"]
action = "error"
match = "ERROR|FATAL|SIGPIPE"

# .log files β†’ notify on session start/end
[[watch]]
path_name = "SMPLR-status"
path = "/Volumes/Chainsaw/SMPLR/Stream"
emoji = "🟒"
extensions = ["log"]
action = "status"
match = "Started:|Session ended:"

# Channels these route to
[[channel]]
path_name = "status"
issue = 1
emoji = "🟒"
mode = "dispatch"
action = "notify"

[[channel]]
path_name = "error"
issue = 2
emoji = "πŸ”΄"
mode = "dispatch"
action = "notify"

When a .txt file appears, it gets committed and pushed to git. When a .log file is updated with Started: or Session ended:, a notification fires to the status channel. When a .log file contains ERROR, the error channel fires. Normal log lines are silently ignored.

Notification channels

Each [[channel]] block defines a notification destination β€” a specific GitHub issue.

[[channel]]
path_name = "status"          # required β€” unique channel ID
issue = 1                     # GitHub issue number to post to
emoji = "🟒"
mode = "dispatch"             # "dispatch" or "direct" (see below)
action = "notify"             # "notify" or "notify+push"
# push_dir = "~/logs"         # only used with action = "notify+push"

Channels can be triggered in two ways:

  1. From a [[watch]] β€” set the watch's action to the channel's path_name
  2. From the tray menu β€” click Notify β†’ channel name to send a test message

Delivery modes:

Mode How Speed Accounts needed
dispatch Triggers a GitHub Action β†’ github-actions[bot] posts the comment ~30-35s 1 (your account)
direct Posts the comment directly via API ~20-30s 2 (see below)

Why two modes? GitHub suppresses notifications for your own actions. When you post a comment mentioning yourself, no notification fires. The dispatch mode works around this by having the Actions bot post the comment instead. The direct mode is faster but requires a second GitHub account to be the commenter.

Setting up direct mode:

  1. Create a second GitHub account (e.g. yourname-bot)
  2. Invite it as a collaborator on your fangit repo
  3. Configure git credentials for the bot account (or use a PAT)
  4. The bot posts comments mentioning your main account β†’ notification fires

The notify+push action: For channels where you want both a notification and supporting files (e.g. crash logs):

[[channel]]
path_name = "errors"
issue = 2
emoji = "πŸ”΄"
mode = "dispatch"
action = "notify+push"
push_dir = "~/myapp/logs"       # this directory gets committed + pushed

Modes explained

Sync mode (watch β†’ git β†’ notification)

File appears in ~/watched-folder/
  β†’ fangit detects change (QFileSystemWatcher + periodic scan)
    β†’ Debounce timer waits (batch_interval seconds)
      β†’ [if match set: scan new lines, skip if no match]
        β†’ File copied into local repo clone
          β†’ git add + commit + push
            β†’ GitHub Actions workflow fires
              β†’ github-actions[bot] posts issue comment @mentioning you
                β†’ iOS push notification

Use cases: Archiving generative outputs, collecting batch job results, backing up config changes, monitoring what a machine produces over time.

Latency: ~45-60 seconds (batch_interval + Actions runner ~15s + Apple push ~20s).

Notify via watch (watch β†’ scan β†’ channel β†’ notification)

File changes in ~/watched-folder/
  β†’ fangit detects change
    β†’ FileScanner reads new bytes since last offset
      β†’ [if match set: filter lines by regex]
        β†’ MessageFormatter builds content-first message
          β†’ NotifyManager posts to channel
            β†’ iOS push notification

Notify-mode watches fire immediately β€” no debounce. The scan only reads new bytes appended since the last check, so even large log files are handled efficiently.

Use cases: Monitoring log output, error detection, session tracking, render completion alerts β€” anything where you care about what changed not just that something changed.

Latency: ~30-35s (dispatch) or ~20-30s (direct).

Notify via tray menu

Tray menu β†’ Notify β†’ channel name
  β†’ Notification sent via channel
    β†’ iOS push notification

Use cases: Manual test notifications, status heartbeats, integration with other apps (future CLI).

Latency: ~30-35s (dispatch) or ~20-30s (direct).


Menu bar

Item Description
Status Current state: watching, pending, pushing, error
Last push Time since last successful push
Watch Directories List of monitored folders with action type
Notify Send a test notification to any configured channel
Push now Force immediate commit + push (skip debounce timer)
Pause/Resume Temporarily stop watching
Open git repo Open monitoring repo in browser
Open config.toml Reveal config file (creates default if missing)
Quit Stop watching and exit

Tray icon states: 🟒 idle, 🟑 pending, πŸ”΅ pushing, πŸ”΄ error.

Tray icon style is configurable via tray_style in config: "dot" (colored circle), "logo" (app icon with status dot), or "tint" (app icon tinted in status color).


GitHub Actions workflow

Fangit automatically creates .github/workflows/notify.yml in your repo on first launch. This single workflow handles both sync mode (triggered by push) and dispatch notify mode (triggered by workflow_dispatch). You don't need to create or edit it manually.

If you need to recreate it, delete the file from your repo and restart fangit.


Build from source

Requirements

  • macOS 12+ or Linux (Ubuntu 22.04+)
  • Qt 6.7+ (Core, Widgets, Network)
  • CMake 3.20+
  • Ninja
  • git CLI

Build

./scripts/ninja.sh          # Debug build
./scripts/ninja.sh run      # Build + launch
./scripts/ninja.sh clean    # Clean build directory

Release build (macOS, signed + notarized)

./scripts/ninja-release.sh
./scripts/ninja-release.sh universal   # arm64 + x86_64

Architecture

main.cpp
β”œβ”€β”€ ConfigManager        β€” TOML config (toml11), read/write, path resolution
β”œβ”€β”€ GitManager           β€” git CLI wrapper (clone, add, commit, push, pull)
β”œβ”€β”€ WatcherManager       β€” QFileSystemWatcher + configurable periodic scan, byte offset tracking
β”œβ”€β”€ CommitBatcher        β€” Debounce + routing: sync (git) or notify (channel), uses FileScanner
β”œβ”€β”€ FileScanner          β€” Reads new bytes from files, applies regex, returns structured results
β”œβ”€β”€ MessageFormatter     β€” Formats scan results for notifications, commits, or any output target
β”œβ”€β”€ WorkflowManager      β€” Auto-creates .github/workflows/notify.yml
β”œβ”€β”€ NotifyManager        β€” GitHub API: dispatch (workflow_dispatch) + direct (issue comment)
└── TrayManager          β€” System tray icon, menu, status display (dot/logo/tint styles)

Codebase

fangit/
β”œβ”€β”€ CMakeLists.txt
β”œβ”€β”€ config.toml                   # Reference config (bundled into app)
β”œβ”€β”€ macos/Info.plist              # LSUIElement (menu bar only, no dock icon)
β”œβ”€β”€ fonts/FiraCode-VariableFont_wght.ttf
β”œβ”€β”€ images/
β”‚   β”œβ”€β”€ AppIcon.png|.icns
β”‚   β”œβ”€β”€ TrayIcon.png              # White silhouette for tray (dark/light mode)
β”‚   └── we-fangit.png
β”œβ”€β”€ linux/AppIcon-512.png
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ ninja.sh                  # Debug build
β”‚   β”œβ”€β”€ ninja-release.sh          # Signed release build
β”‚   β”œβ”€β”€ dummyAGL.sh               # Legacy Qt compatibility
β”‚   └── png2icns.sh               # Icon conversion
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ main.cpp                  # Entry point, version, component wiring
β”‚   β”œβ”€β”€ resources.qrc             # Embedded fonts, icons, default config
β”‚   β”œβ”€β”€ ConfigManager.h/.cpp      # TOML config, tray style, watch/channel/match parsing
β”‚   β”œβ”€β”€ TrayManager.h/.cpp        # System tray: dot/logo/tint icon styles
β”‚   β”œβ”€β”€ WatcherManager.h/.cpp     # QFileSystemWatcher + periodic scan + byte offset tracking
β”‚   β”œβ”€β”€ GitManager.h/.cpp         # git CLI wrapper
β”‚   β”œβ”€β”€ CommitBatcher.h/.cpp      # Debounce, sync/notify routing, match-based filtering
β”‚   β”œβ”€β”€ FileScanner.h/.cpp        # File diff reader, regex matching, structured scan results
β”‚   β”œβ”€β”€ MessageFormatter.h/.cpp   # Content-first message formatting for all output targets
β”‚   β”œβ”€β”€ WorkflowManager.h/.cpp    # Auto-creates GitHub Actions workflow
β”‚   └── NotifyManager.h/.cpp      # GitHub API: dispatch + direct modes
β”œβ”€β”€ third_party/toml11/           # Vendored, header-only
└── notes/                        # Design docs + test scripts

Design decisions

  • Shells out to git CLI β€” inherits system auth (Keychain, SSH agent, credential helpers). No libgit2 dependency.
  • toml11 vendored β€” header-only C++ TOML parser. No runtime or build-time network dependency.
  • Append-only by design β€” watches for new/modified files, ignores deletes.
  • Single workflow file β€” handles both push-triggered and dispatch-triggered notifications.
  • Watchβ†’channel routing β€” action field on watches connects to channels by name. No special syntax, just matching strings.
  • Byte offset scanning β€” FileScanner tracks where it last read each file. Only new content is scanned, even for large log files.
  • Content-first notifications β€” MessageFormatter puts the most important line first, optimised for iOS lock screen banners.
  • Regex match filtering β€” optional match field works with both sync and notify. Only act when something meaningful happens.
  • LSUIElement β€” menu bar only, no dock icon (macOS).
  • Cross-platform β€” Qt6 with QFileSystemWatcher (fsevents on macOS, inotify on Linux), QSystemTrayIcon for tray.

FAQ

Do I need a second GitHub account? No. Dispatch mode works with a single account. Direct mode is faster but requires a second account because GitHub doesn't notify you about your own actions.

Will this violate GitHub's terms of service? No. Automated commits with small files at reasonable intervals (30-300s) are within GitHub's acceptable use policy. Machine accounts are explicitly permitted.

How much does this cost? Nothing. GitHub free tier includes private repos, Actions minutes, and the mobile app.

Will the repo get huge? Eventually, if you push a lot. For text files and small images, a year of use is fine. For large files, consider Git LFS or periodic history cleanup.

What if my machine goes offline? Sync mode queues commits locally. When the network returns, the next push sends everything. Notify mode requires network β€” missed notifications are not queued.

Can I watch the same folder with different actions? Yes. Use multiple [[watch]] blocks with the same path but different extensions and action values. For example, .txt files sync to git while .log files trigger a notification.

What about large log files? FileScanner tracks byte offsets per file. It only reads new bytes appended since the last scan. A 50MB log file that grows by 3 lines only scans those 3 lines.

Can I filter which log lines trigger notifications? Yes. Set match = "ERROR|FATAL|WARN" (or any regex) on a [[watch]] block. Only lines matching the pattern will trigger the action. Unmatched changes are silently ignored.

Can I use this on Linux? Yes. Qt6 provides QFileSystemWatcher (inotify backend) and QSystemTrayIcon. Build as an AppImage for distribution.


TODO

  • fangit CLI with hooks for external apps & processes
  • onboarding 'wizard' dialogs for people who have a github account but otherwise no clue
  • various levels of 'courtesy' git options to keep monitoring repo clean
  • GitLab, Bitbucket, and Codeberg integration (soon / urgent)

License

MIT / TBC

Licenses

About

Fang it! A system tray app that uses GitHub infra for machine monitoring & iOS notification.

Resources

Stars

Watchers

Forks

Contributors