Hard coded values make a script hard to maintain and impossible to reuse with other parameters. In this step, the mount points and the thresholds of disk_watch.py move to a configuration file, automatically loaded and validated by Scrippy.
Each Scrippy script may have its own configuration file:
confdir directory,.conf extension instead of .py.Create the file /home/luiggi/.local/share/scrippy/conf/disk_watch.conf:
[log]
level = info
[filesystems]
mount_points = /, /home, /var
[thresholds]
warning = 80
critical = 90
The configuration file is an INI file, divided into as many sections as needed:
# or ; is a comment, even if it is indented.Warning: Comments at the end of a line are not supported: In mount_points = /, /home # main disks, the comment is part of the value.
Warning: The % character is used for value interpolation (i.e: %(key)s is replaced by the value of key from the same section). A literal % must be doubled: password = 50%%off is read as 50%off. A single % makes the script fail with an InterpolationSyntaxError.
#!/usr/bin/env python3
"""
--------------------------------------------------------------------------------
@author : Luiggi Vercotti
@date : 2026-10-08
@version : 0.4.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
--------------------------------------------------------------------------------
List of mandatory configuration parameters:
@conf:filesystems|mount_points|str|false
@conf:thresholds|warning|int|false
@conf:thresholds|critical|int|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
"""
# ------------------------------------------------------------------------------
# Initialization of the environment
# ------------------------------------------------------------------------------
import sys
import shutil
import scrippy_core
from scrippy_core import logger
OK, WARNING, CRITICAL = 0, 1, 2
STATUS = {OK: "OK", WARNING: "WARNING", CRITICAL: "CRITICAL"}
# ------------------------------------------------------------------------------
# Definition of functions and classes
# ------------------------------------------------------------------------------
def get_mount_points(config):
return [mp.strip() for mp in config.get("filesystems", "mount_points").split(",")]
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):
worst = OK
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
worst = max(worst, status)
return worst
# ------------------------------------------------------------------------------
# Main processing
# ------------------------------------------------------------------------------
def main():
with scrippy_core.ScriptContext(retention=100) as _context:
config = _context.config
warning = config.get("thresholds", "warning", "int")
critical = config.get("thresholds", "critical", "int")
logger.log_section("disk_watch", f"Thresholds: warning={warning}% critical={critical}%")
status = check(get_mount_points(config), warning, critical)
logger.info(f"[+] Status: {STATUS[status]}")
if status != OK:
sys.exit(status)
# ------------------------------------------------------------------------------
# Entry point
# ------------------------------------------------------------------------------
if __name__ == '__main__':
main()
What changed:
MOUNT_POINTS, WARNING_THRESHOLD and CRITICAL_THRESHOLD constants are gone.@conf lines.main() reads the thresholds from the configuration, and get_mount_points() reads the mount points.Functioning section documents the configuration../disk_watch.py
[20261008 19:10:55] [INFO ] [+] Secrets registration (args)
[20261008 19:10:55] [INFO ] [+] Loading configuration file /home/luiggi/.local/share/scrippy/conf/disk_watch.conf
[20261008 19:10:55] [INFO ] [+] Configuration check
[20261008 19:10:55] [INFO ] [+] Secrets registration (conf)
[20261008 19:10:55] [INFO ] [+] Opening history (retention=100)
[...]
The @conf lines of the header describe the configuration the script needs:
@conf:<section>|<key>|<type>|<secret>
| Field | Content |
|---|---|
section |
The section of the configuration file |
key |
The key within the section |
type |
The type of the value: str, int, float or bool |
secret |
true if the value must be redacted from the logs, false otherwise (see Secrets) |
All fields are mandatory and can not be empty.
When entering the execution context, Scrippy checks that each declared key exists and that its value matches the declared type. If not, the script stops immediately, before running any of your code:
[20261008 19:10:55] [INFO ] [+] Configuration check
[20261008 19:10:55] [CRITICAL] [ScrippyCoreError]: Type error: [thresholds][warning] is not of type 'int'
[20261008 19:10:55] [INFO ] [+] No configuration file /home/luiggi/.local/share/scrippy/conf/disk_watch.conf
[20261008 19:10:55] [INFO ] [+] Configuration check
[20261008 19:10:55] [CRITICAL] [ScrippyCoreError]: Missing or unknown configuration section: [filesystems]
A wrong configuration is detected at startup, not in the middle of the processing after some actions have already been done.
Only the structure and the types are checked, not the values themselves: A negative threshold is a valid int. Checking the values is up to the script, as you will see in the Error handling step.
An invalid @conf declaration (missing or extra field, unknown type...) is also reported at startup:
[CRITICAL] [ScrippyCoreError]: Invalid @conf declaration(s):
- @conf:thresholds|critical|integer: 3 fields found, 4 expected (<section>|<key>|<value_type>|<secret>)
With the --debug option, each declared key is displayed during the check:
[20261008 19:10:55] [DEBUG ] [filesystems][mount_points] = /, /home, /var [OK]
[20261008 19:10:55] [DEBUG ] [thresholds][warning] = 80 [OK]
[20261008 19:10:55] [DEBUG ] [thresholds][critical] = 90 [OK]
Note: The configuration file is optional: A script without @conf declarations runs without configuration file. Sections and keys that are present in the file but not declared are not checked, but remain available to the script.
The configuration is available through _context.config.
get()config.get(section, key, param_type="str")
Returns the value of key in section, converted to param_type: str (default), int, float or bool.
mount_points = config.get("filesystems", "mount_points") # '/, /home, /var'
warning = config.get("thresholds", "warning", "int") # 80
For the bool type, 1, yes, true and on are true, 0, no, false and off are false (case insensitive).
A missing section or key, or a value that does not match the requested type, raises a ScrippyCoreError.
has()config.has(section, key)
Returns True if key exists in section. Use it to read optional, undeclared keys:
if config.has("filesystems", "exclude"):
exclude = config.get("filesystems", "exclude")
get_section()config.get_section(section)
Returns all the keys of a section as a dictionary of strings:
config.get_section("thresholds") # {'warning': '80', 'critical': '90'}
A missing section raises a ScrippyCoreError.
[log] sectionThe level key of the [log] section is reserved: When present, Scrippy applies it as the log level of the script when entering the execution context. It does not need to be declared with @conf.
[log]
level = warning
The value is one of debug, info, warning, error or critical (case insensitive). An invalid value stops the script:
[CRITICAL] [ScrippyCoreError]: Invalid log level 'verbose' (expected: debug, info, warning, error, critical)
With level = warning, a run where all mount points are OK displays nothing after the configuration check. The --debug option has priority over the configuration file:
[20261008 19:10:55] [DEBUG ] [+] Log level is forced: Ignoring log level change to info
Changing a threshold for a single run requires to edit the configuration file. In the next step, command line options will allow to override the configuration.