7. Workspace and environment

In this step, disk_watch.py writes a report file. The report is built in the temporary workspace of the execution, then published into the report directory of the Scrippy environment.

The code

#!/usr/bin/env python3
"""
--------------------------------------------------------------------------------
  @author         : Luiggi Vercotti
  @date           : 2026-10-08
  @version        : 0.7.0
  @description    : Monitor filesystem usage

--------------------------------------------------------------------------------
  Update:
  0.1.0  2026-10-08 - Luiggi Vercotti - cre: Script creation
  0.2.0  2026-10-08 - Luiggi Vercotti - evo: Several mount points, log levels
  0.3.0  2026-10-08 - Luiggi Vercotti - evo: Monitoring exit status
  0.4.0  2026-10-08 - Luiggi Vercotti - evo: Configuration file
  0.5.0  2026-10-08 - Luiggi Vercotti - evo: Command line options
  0.6.0  2026-10-08 - Luiggi Vercotti - evo: Alerting API token
  0.7.0  2026-10-08 - Luiggi Vercotti - evo: Report file

--------------------------------------------------------------------------------
  List of mandatory configuration parameters:
    @conf:filesystems|mount_points|str|false
    @conf:thresholds|warning|int|false
    @conf:thresholds|critical|int|false
    @conf:alerting|token|str|true

--------------------------------------------------------------------------------
  List of execution options and argument parameters:
    @args:mount-point|str|Mount points to check (default: from configuration)|+||exclude||false|false
    @args:exclude|str|Mount points to skip|+||||false|false
    @args:warning|int|Warning threshold in percent (default: from configuration)|1||||false|false
    @args:critical|int|Critical threshold in percent (default: from configuration)|1||||false|false
    @args:token|str|Alerting API token (default: from configuration)|1||||false|true
    @args:report|bool|Write a report into the report directory||false|||false|false
    @args:format|choice|Report format (default: text)|1|text,csv||report|false|false

--------------------------------------------------------------------------------
  Functioning:
  ---------------
    Checks the usage of each mount point against a warning and a critical
    threshold and exits with a monitoring compatible exit status:
      0: OK, 1: WARNING, 2: CRITICAL

    Configuration:
      - filesystems::mount_points: Comma separated list of mount points
      - thresholds::warning: Warning threshold in percent
      - thresholds::critical: Critical threshold in percent
      - alerting::token: Alerting API token (secret)
"""
# ------------------------------------------------------------------------------
#  Initialization of the environment
# ------------------------------------------------------------------------------
import os
import sys
import shutil
import datetime
import scrippy_core
from scrippy_core import logger

OK, WARNING, CRITICAL = 0, 1, 2
STATUS = {OK: "OK", WARNING: "WARNING", CRITICAL: "CRITICAL"}
EXTENSIONS = {"text": "txt", "csv": "csv"}

# ------------------------------------------------------------------------------
#  Definition of functions and classes
# ------------------------------------------------------------------------------
def get_mount_points(config, args):
  if args.mount_point:
    return args.mount_point
  mount_points = [mp.strip() for mp in config.get("filesystems", "mount_points").split(",")]
  return [mp for mp in mount_points if mp not in (args.exclude or [])]

def get_thresholds(config, args):
  warning = args.warning if args.warning is not None else config.get("thresholds", "warning", "int")
  critical = args.critical if args.critical is not None else config.get("thresholds", "critical", "int")
  return warning, critical

def get_usage(mount_point):
  logger.debug(f"[+] Checking {mount_point}")
  usage = shutil.disk_usage(mount_point)
  return round(usage.used * 100 / usage.total, 1)

def check(mount_points, warning, critical):
  results = []
  for mount_point in mount_points:
    usage = get_usage(mount_point)
    if usage >= critical:
      logger.error(f" '-> {mount_point}: {usage}% [CRITICAL]")
      status = CRITICAL
    elif usage >= warning:
      logger.warning(f" '-> {mount_point}: {usage}% [WARNING]")
      status = WARNING
    else:
      logger.info(f" '-> {mount_point}: {usage}% [OK]")
      status = OK
    results.append((mount_point, usage, status))
  return results

def write_report(results, report_format, workspace_path, reportdir):
  name = f"disk_watch_{datetime.datetime.now():%Y%m%d_%H%M%S}.{EXTENSIONS[report_format]}"
  tmp_report = os.path.join(workspace_path, name)
  logger.info(f"[+] Writing report: {tmp_report}")
  with open(tmp_report, mode="w", encoding="utf-8") as report:
    for mount_point, usage, status in results:
      if report_format == "csv":
        report.write(f"{mount_point};{usage};{STATUS[status]}\n")
      else:
        report.write(f"{mount_point:<20} {usage}% {STATUS[status]}\n")
  report_dir = os.path.join(reportdir, "disk_watch")
  os.makedirs(report_dir, exist_ok=True)
  logger.info(f"[+] Publishing report into {report_dir}")
  shutil.move(tmp_report, os.path.join(report_dir, name))

# ------------------------------------------------------------------------------
#  Main processing
# ------------------------------------------------------------------------------
def main():
  with scrippy_core.ScriptContext(retention=100) as _context:
    config = _context.config
    args = _context.args
    token = args.token or config.get("alerting", "token")
    logger.debug(f"[+] Alerting API token: {token}")
    warning, critical = get_thresholds(config, args)
    logger.log_section("disk_watch", f"Thresholds: warning={warning}% critical={critical}%")
    results = check(get_mount_points(config, args), warning, critical)
    if args.report:
      write_report(results, args.format or "text", _context.workspace_path, _context.env["reportdir"])
    status = max(status for _, _, status in results)
    logger.info(f"[+] Status: {STATUS[status]}")
    if status != OK:
      sys.exit(status)

# ------------------------------------------------------------------------------
#  Entry point
# ------------------------------------------------------------------------------
if __name__ == '__main__':
  main()

What changed:

  • Two options are declared: --report, a bool option, and --format, a choice option that implies --report.
  • check() returns the result of each mount point instead of the worst status, which is now computed in main().
  • write_report() writes the report into the workspace, then moves it into the disk_watch subdirectory of the reportdir directory.
./disk_watch.py --report --format csv
[...]
[20261008 19:13:14] [INFO    ] [+] Using workspace: /home/luiggi/.local/share/scrippy/tmp/disk_watch_1791447194.945487_29448
[...]
[20261008 19:13:14] [INFO    ] [+] Writing report: /home/luiggi/.local/share/scrippy/tmp/disk_watch_1791447194.945487_29448/disk_watch_20261008_191314.csv
[20261008 19:13:14] [INFO    ] [+] Publishing report into /home/luiggi/.local/share/scrippy/reports/disk_watch
[20261008 19:13:14] [INFO    ] [+] Status: OK
[20261008 19:13:14] [INFO    ] [+] Workspace deletion: /home/luiggi/.local/share/scrippy/tmp/disk_watch_1791447194.945487_29448
[20261008 19:13:14] [INFO    ] [+] End: 0
cat ~/.local/share/scrippy/reports/disk_watch/disk_watch_20261008_191314.csv
/;34.9;OK
/home;61.2;OK
/var;83.7;WARNING

The temporary workspace

When entering the execution context, Scrippy creates a temporary workspace: A private directory, located in the tmpdir directory and named after the script and the session identifier:

/home/luiggi/.local/share/scrippy/tmp/disk_watch_1791447194.945487_29448

Its path is available through _context.workspace_path.

The workspace is deleted with all its content when leaving the execution context, whatever the outcome of the execution: Success, error or sys.exit(). Use it for any temporary file: Downloads, extractions, intermediate files... There is no need to clean it, and two simultaneous executions of the same script never share their temporary files.

Note: Since the workspace is always deleted, its content can not be inspected after the execution. To keep a file, move it out of the workspace before the end of the execution, as write_report() does.

Tip: Building a file in the workspace and moving it to its final location once complete ensures that a reader never finds a partially written file.

A script that does not need a workspace can disable it:

with scrippy_core.ScriptContext(workspace=False) as _context:

_context.workspace_path is then None.

The environment

The Scrippy directories defined in the main configuration file are available through the _context.env dictionary:

Key Purpose
confdir Configuration files of the scripts
logdir Execution logs
histdir Execution histories
tmpdir Temporary workspaces
reportdir Persistent data produced by the scripts
datadir Static data used by the scripts
templatedir Template files
reportdir = _context.env["reportdir"]

Using these directories instead of hard coded paths makes the script portable from an installation to another: From a user installation on a workstation to a system wide installation on a server.

Note: Scrippy does not create the reportdir, datadir and templatedir directories (the installer does), nor any subdirectory within them. This is why write_report() creates the disk_watch subdirectory with os.makedirs(..., exist_ok=True).

What's next

What happens when a mount point does not exist, or when the warning threshold is greater than the critical one? In the next step, you will see how Scrippy handles errors and how to handle them yourself.