emday

Self-contained server monitoring, in one binary. Your server texts you when something changes.

“em đây!” is Vietnamese for “it’s me!” — when your server’s IP changes, emday pings you: it’s me, here’s my new IP.

emday @ prod-sg-01
👋 em đây! Public IP changed:
113.161.72.9 → 118.69.37.55 rule/wan.v4 · 07:02
⚠️ cpu.percent: value ≥ 90 held for 5m
value: 96.4 rule/cpu.percent · 14:31
✅ cpu.percent: resolved
value: 41.2 rule/cpu.percent · 14:48

watches

Public IP, CPU, RAM, disk, processes, TLS certificates — and anything your own scripts can measure.

notifies

Telegram, ntfy, Lark, Slack, Discord, any webhook. Queued and retried — offline alerts arrive when the network returns.

self-contained

One static binary. Installs itself as a service on Linux, macOS, and Windows. No agent phoning home, no UI.

self-teaching

All docs ship inside the binary: emday docs. AI agents get their own guide: emday docs agent.

install

One line on Linux or macOS — detects your platform, verifies the sha256, installs the binary:

curl -fsSL https://madnh.github.io/emday/install.sh | sh

Prefer to read before you run? Inspect the script — it only downloads the release asset, checks it against checksums.txt, and runs install. Pin a version with EMDAY_VERSION=0.1.0; change the location with EMDAY_INSTALL_DIR.

Or install manually from the latest release (Windows: grab the zip):

curl -fsSL -o emday.tar.gz \
  https://github.com/madnh/emday/releases/latest/download/emday_VERSION_linux_amd64.tar.gz
tar -xzf emday.tar.gz emday
sudo install -m 0755 emday /usr/local/bin/emday
emday version

quick start

sudo emday init            # creates /etc/emday with a commented starter config
sudo vi /etc/emday/emday.yaml   # add your notifier, tweak rules
sudo vi /etc/emday/emday.env.example        # fill in the secrets it lists
sudo install -m 0600 /etc/emday/emday.env.example /etc/sysconfig/emday
emday check-config         # validates config, compiles every rule
emday test-notify my-telegram   # make sure alerts actually arrive
sudo emday install && sudo emday start   # run as a system service
init seeds emday.env.example listing every EMDAY_* variable your notifiers can reference, so you fill in values rather than recall names. emday does not read it — copy it to whatever your unit's EnvironmentFile= points at (the unit emday install generates already reads /etc/sysconfig/emday). Details: emday docs deploy.
Something wrong? emday doctor --verdict diagnoses the deployment — which config resolved, what failed, and the next step — without touching anything.

configure

version: 1

sources:
  wan:
    type: public-ip
    mode: [v4]
    endpoints_v4:              # where to ask "what is my IP" — your choice
      - https://api.ipify.org
      - https://checkip.amazonaws.com
  lan:  {type: local-ip, interfaces: [eth0]}
  cpu:  {type: cpu, interval: 30s}
  disk: {type: disk, mounts: {root: "/"}}
  cert: {type: cert, endpoints: {www: "example.com"}}

rules:
  - metric: wan.v4
    on_change: true            # "em đây, new IP!"
    notify: [my-telegram]
  - metric: cpu.percent
    condition: "value >= 90"   # expression over `value`
    for: 5m                    # must hold 5 minutes (anti-flapping)
    notify: [my-telegram]
  - metric: cert.www.days_left
    condition: "value <= 14"   # renew before it breaks HTTPS
    level: error
    notify: [my-telegram]

notifiers:
  my-telegram:
    type: telegram
    token_env: EMDAY_TG_TOKEN
    chat_id: "-100123456"

Rule conditions

goalwrite
Compare numbersvalue > 90, value <= 10, value == 0
Compare stringsvalue == "failed", value != "ok"
Contains / prefix / suffixvalue contains "err", value startsWith "eth"
In a listvalue in ["a", "b"], value not in ["ok"]
Combine... and ..., ... or ..., not (...)

Try syntax instantly: emday test-rule 'value > 90' --value 95 → true. Rules with for: also send a resolved notification when the condition clears.

extend

An exec source runs your command on an interval. The script appends metrics — or direct notifications — to the file named by $EMDAY_OUTPUT (the GitHub Actions model). stdout stays free for logging.

#!/bin/sh
age=$(( ($(date +%s) - $(stat -c %Y /backup/latest)) / 3600 ))
echo "BACKUP_AGE_HOURS=$age" >> "$EMDAY_OUTPUT"
[ "$age" -gt 26 ] && echo "NOTIFY_ERROR=backup overdue (${age}h)" >> "$EMDAY_OUTPUT"
sources:
  backup:
    type: exec
    command: /opt/scripts/check-backup.sh
    interval: 5m
    notify: [my-telegram]      # targets for NOTIFY_* lines

Every key becomes a metric (backup.BACKUP_AGE_HOURS) that rules can watch, and backup._ok catches a silently broken script. Identical NOTIFY messages are deduped, so a repeating script can’t spam you.

docs

Every topic is a single Markdown file that ships inside the binary (emday docs <topic>) — the same source you (or an AI agent) can read on GitHub. This page only indexes them:

Per-source reference (each built-in: exact metrics, required permissions, rule examples, failure modes):

Point an AI agent at a server with emday and tell it to run emday docs agent — that one page is enough for it to configure, extend, and diagnose emday. doctor, check-config, and test-rule all speak --json.