Category: Linux Server

  • Workflow: Anthem Network uptime Notifications

    Monitoring Source:
    This workflow is triggered by webhook events sent from Uptime Kuma, a self-hosted monitoring system. When a monitored service changes state (UP or DOWN), Uptime Kuma sends a JSON payload to this webhook endpoint, which then processes the data and forwards formatted notifications to Slack.

    Purpose: Receive Uptime Kuma status webhooks and post Slack alerts for DOWN and UP events.


    Nodes

    1) Webhook

    • Type: Webhook (Trigger)
    • What it’s for: Entry point that receives the POST request from Uptime Kuma.
    • Endpoint path: POST /webhook/Anthemnetworkstatus
    • Expected payload: JSON with body.heartbeat and body.monitor

    2) Code in JavaScript

    • Type: Code
    • What it’s for: Converts the raw Uptime Kuma webhook payload into clean fields used by the rest of the workflow.
    • Key logic:
      • Reads:
    - `body.heartbeat` (status, ping, time, msg)
    - `body.monitor` (name, hostname/url/type)
    
    • Creates:
    - `isDown` → `true` when `heartbeat.status === 0` (Kuma: 0=DOWN, 1=UP)
    - `name` → monitor name
    - `hostnameOrURL` → monitor hostname/url fallback
    - `time` → heartbeat time (fallback: now)
    - `status` → `"Down"` or `"Up"`
    - `msg` → human readable summary string
    
    • Also passes through:
    - `heartbeat`
    - `monitor`
    

    3) If

    • Type: IF (Condition)
    • What it’s for: Routes the workflow based on the status.
    • Condition:{{$json.isDown}} is true
      • True path (DOWN) → goes to Only if go Down
      • False path (UP) → goes to Only if go up1

    4) Only if go Down

    • Type: HTTP Request (POST to Slack Incoming Webhook)
    • What it’s for: Sends a DOWN alert to Slack when isDown=true.
    • Message formatting:
      • Title/header: “🚨 Service is DOWN”
      • Color: #e01e5a (red)
      • Includes fields: Service, Status, Target, Time
      • Includes details from heartbeat message / built msg

    5) Only if go up1

    • Type: HTTP Request (POST to Slack Incoming Webhook)
    • What it’s for: Sends an UP alert to Slack when isDown=false.
    • Message formatting:
      • Title/header: “✅ Service is UP”
      • Color: #2eb886 (green)
      • Includes fields: Service, Status, Target, Time
      • Includes message block with the formatted msg

    Connections (flow)

    Webhook → Code in JavaScript → If →

    • True (DOWN) → Only if go Down
    • False (UP) → Only if go up1
  • Working Setup: Caddy (public HTTPS) → Mailu Front (internal HTTPS) + Mailu SMTP/IMAP uses same Sectigo cert

    Goal

    • https://my.richardapplegate.io/webmail and /admin work
    • Caddy serves the site on public port 443
    • Mailu also serves HTTPS internally on front:443
    • SMTP/IMAP ports (465/993/995/587) use the same Sectigo wildcard cert
    • No port conflicts and no redirect loops

    1) Certificates on the host

    You have these two files (already confirmed they match):

    • fullchain.pem
    • privkey.pem

    Store them in a stable host path, example:

    • /mnt/volumes/certs/fullchain.pem
    • /mnt/volumes/certs/privkey.pem

    ✅ Verified match test:

    openssl x509 -noout -modulus -in fullchain.pem | openssl md5
    openssl pkey -noout -modulus -in privkey.pem  | openssl md5
    

    2) Caddy docker-compose

    Caddy publishes public ports and has the certs mounted:

    services:
      caddy:
        image: caddy:2
        restart: unless-stopped
        networks:
          - caddy
        ports:
          - "80:80"
          - "443:443/tcp"
          - "443:443/udp"
        volumes:
          - ./Caddyfile:/etc/caddy/Caddyfile:ro
          - ./data:/data
          - ./config:/config
          - /mnt/volumes/certs:/certs:ro
    networks:
      caddy:
        external: true
    

    3) Mailu env (mailu.env) — corrected values

    These are the critical “must be right” ones:

    DOMAIN=richardapplegate.io
    HOSTNAMES=my,mail
    
    PORTS=25,465,587,993,995,4190
    
    TLS_FLAVOR=cert
    TLS_CERT_FILENAME=fullchain.pem
    TLS_KEYPAIR_FILENAME=privkey.pem
    
    WEB_ADMIN=/admin
    WEB_WEBMAIL=/webmail
    WEBSITE=https://my.richardapplegate.io
    

    ✅ Important notes:

    • DOMAIN is the apex domain: richardapplegate.io
    • HOSTNAMES are labels only: my,mail (no dots)
    • TLS_FLAVOR is cert (not certs)
    • Do not include 80 or 443 in PORTS (Caddy owns those publicly)

    4) Mailu docker-compose networking + cert mounts

    4.1 Attach front to BOTH networks

    Mailu internal network (for smtp/imap/etc) and the caddy network (so Caddy can reach it):

    services:
      front:
        networks:
          - mailu
          - caddy
    

    4.2 Mount certs into Mailu containers that use them

    At minimum, mount into front, smtp, and imap (names may vary, but these are typical):

    services:
      front:
        volumes:
          - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
          - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro
    
      smtp:
        volumes:
          - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
          - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro
    
      imap:
        volumes:
          - /mnt/volumes/certs/fullchain.pem:/certs/fullchain.pem:ro
          - /mnt/volumes/certs/privkey.pem:/certs/privkey.pem:ro
    

    (These filenames match your TLS_CERT_FILENAME / TLS_KEYPAIR_FILENAME.)


    5) Caddyfile (the working site)

    You terminate TLS at Caddy and proxy to Mailu front over internal HTTPS to avoid redirect loops:

    my.richardapplegate.io {
      tls /certs/fullchain.pem /certs/privkey.pem
    
      # optional: make / go to webmail
      @root path /
      redir @root /webmail 302
    
      reverse_proxy https://front:443 {
        transport http {
          tls_server_name my.richardapplegate.io
          # Only if chain issues ever happen:
          # tls_insecure_skip_verify
        }
    
        header_up Host {host}
        header_up X-Forwarded-Proto {scheme}
        header_up X-Forwarded-Host  {host}
        header_up X-Forwarded-For   {remote_host}
      }
    }
    

    ✅ Why HTTPS upstream (front:443)?
    Because with TLS_FLAVOR=cert, Mailu front enforces HTTPS; proxying to http://front:80 triggers redirect loops. HTTPS upstream avoids that entirely.


    6) Restart order (clean rebuild)

    6.1 Restart Mailu (force recreate so config regenerates)

    cd /mnt/volumes/SamsungSSD970EVOPlus2TB/mailu
    docker compose down
    docker compose up -d --force-recreate
    

    6.2 Reload Caddy

    docker exec caddy caddy reload --config /etc/caddy/Caddyfile
    

    7) Verification tests

    7.1 Web cert (public 443 via Caddy)

    openssl s_client -connect my.richardapplegate.io:443 -servername my.richardapplegate.io </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -issuer -dates
    

    7.2 Mailu front cert (internal, from inside caddy container)

    docker exec caddy sh -lc \
    'openssl s_client -connect front:443 -servername my.richardapplegate.io </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates'
    

    7.3 SMTP TLS cert (465)

    openssl s_client -connect my.richardapplegate.io:465 -servername my.richardapplegate.io </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -issuer -dates
    

    7.4 IMAPS cert (993)

    openssl s_client -connect my.richardapplegate.io:993 -servername my.richardapplegate.io </dev/null 2>/dev/null \
    | openssl x509 -noout -subject -issuer -dates
    

    Expected: all show CN=*.richardapplegate.io and Sectigo issuer.

  • How to Send Slack Notifications for Material Issue, Material Transfer, and Purchase Receipt in ERPNext v15 (With Error Fix)

    If you’re using ERPNext v15 and want Slack notifications when stock movements happen, this guide walks you through:

    • Sending Slack alerts for:
      • Material Issue
      • Material Transfer
      • Purchase Receipt
    • Avoiding the common Jinja error: TypeError: 'builtin_function_or_method' object is not iterable

    This method uses ERPNext’s built-in Webhook DocType and Slack Incoming Webhooks.


    Step 1 — Create a Slack Incoming Webhook

    1. Go to https://api.slack.com/apps
    2. Create a new app (or use an existing one).
    3. Enable Incoming Webhooks.
    4. Click Add New Webhook to Workspace.
    5. Choose your channel.
    6. Copy the generated Webhook URL.

    Important:
    Treat this URL like a password. Do not publish or share it publicly.


    Step 2 — Create Webhook for Stock Entry (Material Issue + Transfer)

    In ERPNext:

    Go to:

    Search → Webhook → New

    Configure:

    Document Type:

    Stock Entry
    

    Doc Event:

    on_submit
    

    Request Method:

    POST
    

    Request URL:
    Paste your Slack webhook URL

    Enabled:
    Checked


    Add Condition (Important)

    Only trigger for Material Issue and Material Transfer:

    doc.stock_entry_type in ("Material Issue", "Material Transfer")
    

    Why this matters:
    Stock Entry is one DocType, but it has multiple types. This prevents Slack spam from other stock movements.


    Add Header

    Add one row:

    KeyValue
    Content-Typeapplication/json

    Step 3 — Correct JSON Payload (Avoid the Common Error)

    Many users encounter this error:

    TypeError: 'builtin_function_or_method' object is not iterable
    

    This happens because in Webhooks, doc is treated as a dictionary.
    Using doc.items conflicts with Python’s dict.items() method.

    ❌ Wrong (causes error)

    {% for i in doc.items %}
    

    ✅ Correct

    {% for i in doc.get('items', []) %}
    

    Use This Working JSON Payload (Stock Entry)

    {
      "text": "📦 *Stock Entry Submitted*\n*Type:* {{ doc.get('stock_entry_type', '—') }}\n*ID:* {{ doc.get('name', '—') }}\n*Company:* {{ doc.get('company', '—') }}\n*Posting:* {{ doc.get('posting_date', '—') }} {{ doc.get('posting_time', '') }}\n*From WH:* {{ doc.get('from_warehouse') or '—' }}\n*To WH:* {{ doc.get('to_warehouse') or '—' }}\n\n*Items:*\n{% for i in doc.get('items', []) %}• {{ i.get('item_code', '—') }} — Qty: {{ i.get('qty', 0) }} {{ i.get('uom', '') }} ({{ i.get('s_warehouse') or i.get('t_warehouse') or '—' }})\n{% endfor %}"
    }
    

    Step 4 — Create Separate Webhook for Purchase Receipt

    Important:
    Purchase Receipt is NOT a Stock Entry Type.
    It is a separate DocType.

    So you must create a second webhook.


    Create New Webhook

    Document Type:

    Purchase Receipt
    

    Doc Event:

    on_submit
    

    Request Method:

    POST
    

    Request URL:
    Slack webhook URL

    Header:

    Content-Type: application/json
    

    No condition required unless you want to filter by supplier or company.


    Working JSON Payload (Purchase Receipt)

    {
      "text": "🧾 *Purchase Receipt Submitted*\n*ID:* {{ doc.get('name', '—') }}\n*Supplier:* {{ doc.get('supplier', '—') }}\n*Company:* {{ doc.get('company', '—') }}\n*Posting:* {{ doc.get('posting_date', '—') }} {{ doc.get('posting_time', '') }}\n\n*Items Received:*\n{% for i in doc.get('items', []) %}• {{ i.get('item_code', '—') }} — Qty: {{ i.get('qty', 0) }} {{ i.get('uom', '') }} ({{ i.get('warehouse') or '—' }})\n{% endfor %}"
    }
    

    Step 5 — Testing

    1. Create a Material Transfer.
    2. Submit it.
    3. Check Slack channel.

    Then test:

    • Material Issue
    • Purchase Receipt

    If nothing appears:

    Check Logs (Docker)

    docker compose logs -f backend
    

    Or inside container:

    bench --site yoursite tail
    

    Optional Enhancement — Add Clickable ERP Link

    If your ERP URL is:

    https://erp.yourdomain.com
    

    You can add this inside your Slack message:

    https://erp.yourdomain.com/app/stock-entry/{{ doc.get('name') }}
    

    Slack will auto-link it.


    Why This Fix Works

    In Webhooks:

    • doc behaves as a Python dictionary.
    • doc.items conflicts with dictionary method .items().
    • Using doc.get('items', []) safely retrieves the child table list.

    This prevents:

    TypeError: 'builtin_function_or_method' object is not iterable
    

    Final Result

    You now have Slack alerts for:

    • Material Issue
    • Material Transfer
    • Purchase Receipt

    Triggered automatically on submit, with item-level detail

Secret Link