🤖 PR generated for branch `fix/cyrillic-fonts-dev-certs-and-page-head`.
### Changes Summary
- **Fonts**: Add Cyrillic glyph subsets for Lato, EB Garamond, and Fira
Code.
- **Dev**: Support remote Root CA and encrypted dev CA key in
`.env.dev`.
- **UI & Kits**: Add size variant to `Breadcrumbs` component and enlarge
`PageHead` breadcrumbs with tuned collapse scroll threshold.
- **ESLint**: Add `no-hanging-particles` i18n & markdown typography rule
with auto-fixer.
- **Content & i18n**: Apply `ru-text` editorial and typography polish
(em dashes, number ranges, «ё» restoration, non-breaking spaces) across
`services/www` and `services/docs`.
4.1 KiB
title, description, weight
| title | description | weight |
|---|---|---|
| Develop actions | Write, override, and test engine actions in commons or proprietaries. | 22 |
Context: all automation ships as actions — one Python module each, dispatched by path. Shared behavior belongs in commons actions/; anything node-specific belongs in that node's proprietaries/actions/. Both use the identical contract, and the launcher puts both trees on sys.path, so a proprietary action imports engine.* exactly like a commons one.
A minimal action
"""One-line module docstring describing the action."""
import os
from engine.descriptor import Meta, Arg, Flag
METADATA = Meta(
summary="Copy a rendered config into place.",
args=[Arg("target_dir", "Destination directory"),
Flag("--dry-run", "Preview only.")],
)
def run(ctx, args):
if not os.path.isdir(args.target_dir):
ctx.log.error(f"No such directory: {args.target_dir}")
raise SystemExit(66)
content = ctx.tpl.render("example/config", ctx.vars())
ctx.host.write_file(os.path.join(args.target_dir, "config"), content,
dry_run=args.dry_run, sudo=True)
ctx.log.success("Config deployed.")
The file's path is its name: the module above dispatches as apex utils/example. Return 0/None for success, an integer for a specific exit code, or raise SystemExit(code); the exit-code convention is 64 usage, 65 data error, 66 missing input, 1 general.
Conventions for unattended actions
Actions that run from cron follow two rules so failures page instead of vanishing:
- Wrap the body in
try/except Exceptionand sendctx.notify.error(...)before re-raising asSystemExit(1)— the Python equivalent of a shell ERR trap. - Gate the exit code on the final notification:
ctx.notify.*returnsFalseon delivery failure; a completed run whose status message never arrived should still exit1.
try:
...work...
log.success("Done.")
if not ctx.notify.success(TITLE, url, "Done.", node):
raise SystemExit(1)
except SystemExit:
raise
except Exception as e:
log.error(f"{type(e).__name__}: {e}")
ctx.notify.error(TITLE, url, f"Critical error: {e}", node)
raise SystemExit(1)
Override, wrap, disable
A proprietary file at the same relative path as a commons action shadows it completely. Inside the shadowing module, ctx.commons.run(args) invokes the shadowed implementation — the wrap pattern:
from engine.descriptor import Meta, Arg
METADATA = Meta(summary="sync/packages with a node-local pre-step.",
args=[Arg("telegram_bot_url", "Telegram Bot URL")])
def run(ctx, args):
ctx.log.info("Node-local preparation...")
return ctx.commons.run(args) # delegate to the commons implementation
To retire an action on one node, assign DISABLED = "reason" at module top level: dispatch refuses it (exit 1) and the listing shows [DISABLED: reason].
Testing
The commons test suite is stdlib unittest:
./tests/run.sh # python3 -m unittest discover -s tests -p 'test_*.py' -v
Engine changes must keep the suite green. A cheap smoke gate for any action — commons or proprietary — is its parser: apex <name> --help must exit 0, which proves the module parses, its METADATA is well-formed, and discovery sees it.
The commons pre-commit hook (wired by init.sh via core.hooksPath) runs apex utils/lint-docker-compose --hook against staged compose files, so composition changes are linted at commit time.
See also: