RecipesOps, IT & on-chain

Recipes: ops, IT & on-chain

Two pipelines for the things you want to know about the moment they happen — your site going down or a cert expiring, and money moving on-chain.


Uptime + SSL watch

Check that your site responds every hour, and that its TLS certificate isn’t about to expire — alerting you the moment either breaks. Two small jobs, each using an onFailure handler: the check just fails, and the handler does the paging.

The shape:

uptime (http, hourly)      --fails-->  onFailure: uptime-alert (telegram)
ssl-check (terminal, weekly) --fails-->  onFailure: ssl-alert (telegram)

A non-200 fails the http run, which triggers its onFailure handler:

# jobs/ops/uptime.yaml
id: uptime
name: Uptime check
group: ops
type: http
method: GET
url: '{{ $vars.siteUrl }}'
expectStatus: 200
retry:
  maxAttempts: 3
  backoff: exponential
  delayMs: 5000
onFailure: uptime-alert
schedule:
  - { kind: hourly, minute: 0 }
# jobs/ops/uptime-alert.yaml — the handler fires with $parent.error when a check fails
id: uptime-alert
name: Uptime alert
group: ops
type: telegram
credential: telegram_bot
chatId: '{{ $vars.opsChatId }}'
message: |
  :red_circle: {{ $parent.name }} is down: {{ $parent.error }}

openssl x509 -checkend exits non-zero when the cert expires within the given window (1814400s = 21 days), so the terminal job fails → the handler alerts:

# jobs/ops/ssl-check.yaml
id: ssl-check
name: SSL expiry check
group: ops
type: terminal
cwd: /tmp
command: >
  echo | openssl s_client -connect {{ $vars.siteHost }}:443 -servername {{ $vars.siteHost }} 2>/dev/null
  | openssl x509 -checkend 1814400 -noout
onFailure: ssl-alert
schedule:
  - { kind: weekly, weekday: mon, time: '06:00' }
# jobs/ops/ssl-alert.yaml
id: ssl-alert
name: SSL alert
group: ops
type: telegram
credential: telegram_bot
chatId: '{{ $vars.opsChatId }}'
message: ':warning: TLS cert for {{ $vars.siteHost }} expires within 21 days — renew it.'

Tighter interval: hourly is the finest single clock point. For every-15-minutes checks, add more hourly points (minute: 0, 15, 30, 45) to the same job.


Treasury wallet watch

Watch an ERC-20 (e.g. USDC) for transfers into your treasury wallet, alert on the big ones in real time, and log every hit — read-only, no keys, no signing.

The shape:

usdc-in (evm-watch) → usdc-large (filter) → usdc-alert (telegram) + usdc-log (sheets)

An evm-watch trigger fires per matching on-chain event (its credential holds the full RPC URL so a keyed endpoint stays out of the YAML):

# jobs/treasury/usdc-in.yaml
id: usdc-in
name: USDC into treasury
group: treasury
type: evm-watch
credential: eth-rpc
chainId: 8453 # Base — pinned as a wrong-chain guard
contractAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' # USDC on Base
eventSignature: 'Transfer(address indexed from, address indexed to, uint256 value)'
indexedFilters:
  to: '{{ $vars.treasuryWallet }}'
confirmations: 5
pollSeconds: 30
schedule: []

The value is USDC base units (6 decimals), so 10000000000 = $10,000:

# jobs/treasury/large-only.yaml
id: usdc-large
name: Large transfers only
group: treasury
type: filter
input: '{{ $parent.json.args.value }}'
operator: gt
value: '{{ $vars.minUsdcBaseUnits }}'
schedule:
  - { kind: afterParent, parent: usdc-in, on: succeeded }
# jobs/treasury/alert.yaml
id: usdc-alert
name: Big transfer alert
group: treasury
type: telegram
credential: telegram_bot
chatId: '{{ $vars.treasuryChatId }}'
message: |
  :moneybag: USDC in from {{ $parent.json.args.from }} — {{ $parent.json.args.value }} (base units)
  tx {{ $parent.json.transactionHash }}
schedule:
  - { kind: afterParent, parent: usdc-large, on: succeeded, branch: pass }
# jobs/treasury/log.yaml
id: usdc-log
name: Log transfer
group: treasury
type: sheets
credential: google_sa
spreadsheetId: '{{ $vars.treasurySheetId }}'
operation: appendRow
range: Transfers!A:D
values: |
  {{ $now.iso }}
  {{ $parent.json.args.from }}
  {{ $parent.json.args.value }}
  {{ $parent.json.transactionHash }}
schedule:
  - { kind: afterParent, parent: usdc-large, on: succeeded, branch: pass }

Read-only by design: evm-watch only ever calls eth_getLogs and friends — it never holds a private key or sends a transaction. Alerting on money movement is safe; moving money is not something Cronable does.