Replaces LICENSE (GPLv3 -> AGPLv3) and prepends `SPDX-License-Identifier: AGPL-3.0-or-later` to every source file across decnet/, decnet_web/, tests/, scripts/, and tools/. Rationale: closes the GPLv3 ASP loophole so any party operating a modified DECNET as a network service must offer their modified source. Personal copyright (Samuel Paschuan) + inbound=outbound contributions make a future unilateral relicense infeasible. - LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt) - COPYRIGHT: project copyright notice - tools/add_spdx_headers.py: idempotent header injector (shebang- and PEP 263-aware) Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh). No behavior change; comments only.
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
"""``decnet canary`` — HTTP + DNS callback receiver for canary tokens.
|
|
|
|
Two entry points share this module:
|
|
|
|
* ``decnet canary`` — runs the worker process. Mirrors the shape of
|
|
:mod:`decnet.cli.webhook`. Invoked by the ``decnet-canary.service``
|
|
systemd unit so its argv must stay stable.
|
|
* ``decnet canary-install-toolchain`` — provisions the Node side of
|
|
the fingerprint-canary obfuscator. Idempotent; safe to call from
|
|
the API service unit's ``ExecStartPre``.
|
|
|
|
Not master-only — any host that hosts deckies can run its own
|
|
canary worker (the bus events stay local; the webhook worker on
|
|
each host fans them out to SIEMs independently per the design
|
|
in ``development/let-s-move-to-the-enumerated-pike.md``).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import subprocess # nosec B404 — npm exec is the whole point of the toolchain installer
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
from . import utils as _utils
|
|
from .utils import console, log
|
|
|
|
_TOOLCHAIN_TIMEOUT_S = 180
|
|
|
|
|
|
def register(app: typer.Typer) -> None:
|
|
@app.command(name="canary")
|
|
def canary_cmd(
|
|
daemon: bool = typer.Option(
|
|
False, "--daemon", "-d", help="Detach to background as a daemon process",
|
|
),
|
|
) -> None:
|
|
"""Run the canary HTTP + DNS callback receiver."""
|
|
import asyncio
|
|
|
|
from decnet.canary.worker import run
|
|
|
|
if daemon:
|
|
log.info("canary daemonizing")
|
|
_utils._daemonize()
|
|
|
|
log.info("canary starting")
|
|
console.print("[bold cyan]Canary callback receiver starting[/]")
|
|
|
|
try:
|
|
asyncio.run(run())
|
|
except KeyboardInterrupt:
|
|
console.print("\n[yellow]Canary worker stopped.[/]")
|
|
|
|
@app.command(name="canary-install-toolchain")
|
|
def canary_install_toolchain(
|
|
npm_bin: str = typer.Option(
|
|
"npm", "--npm-bin", help="Path to the npm executable. Defaults to PATH lookup.",
|
|
),
|
|
) -> None:
|
|
"""Install the Node-side toolchain used by fingerprint canaries.
|
|
|
|
Runs ``npm install --omit=dev`` under the installed ``decnet/canary/``
|
|
directory so the obfuscator's helper script can ``require()``
|
|
``javascript-obfuscator`` at mint time. Requires Node >= 18.
|
|
|
|
Idempotent: re-running on an already-installed tree is fast
|
|
(npm short-circuits when ``node_modules/`` is up-to-date).
|
|
"""
|
|
import decnet.canary as _canary_pkg
|
|
canary_dir = Path(_canary_pkg.__file__).resolve().parent
|
|
if not (canary_dir / "package.json").is_file():
|
|
console.print(
|
|
f"[red]canary package.json not found under {canary_dir}; "
|
|
"wheel may be missing the JS toolchain payload.[/]"
|
|
)
|
|
raise typer.Exit(code=2)
|
|
if shutil.which(npm_bin) is None:
|
|
console.print(
|
|
f"[red]npm executable {npm_bin!r} not found on PATH. "
|
|
"Install Node >= 18 and re-run.[/]"
|
|
)
|
|
raise typer.Exit(code=2)
|
|
console.print(
|
|
f"[cyan]installing canary toolchain[/] in {canary_dir}",
|
|
)
|
|
try:
|
|
proc = subprocess.run( # nosec B603 — argv-form, no shell, fixed cwd, npm_bin checked above
|
|
[npm_bin, "install", "--omit=dev", "--no-fund", "--no-audit"],
|
|
cwd=str(canary_dir),
|
|
capture_output=True, text=True,
|
|
timeout=_TOOLCHAIN_TIMEOUT_S, check=False,
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
console.print("[red]npm install timed out after 3 minutes[/]")
|
|
raise typer.Exit(code=3) from None
|
|
if proc.returncode != 0:
|
|
console.print(
|
|
f"[red]npm install failed rc={proc.returncode}[/]\n"
|
|
f"{proc.stderr.strip()}"
|
|
)
|
|
raise typer.Exit(code=proc.returncode)
|
|
console.print("[green]canary toolchain ready[/]")
|