??????????????????????????????? ??????????????????????????????? ??????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>. ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? ??????????????????????????????? >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #!/usr/lib/rads/venv/bin/python3 """Toggle Apache/Nginx access logs off via symlink, or on""" import argparse import os import shutil import subprocess import time from pathlib import Path LOGS = [ ( Path("/var/log/apache2/access_log_global"), Path("/var/log/apache2/access_log_global.toggled"), ), ( Path("/var/log/nginx/access.log"), Path("/var/log/nginx/access.log.toggled"), ), ] DEVNULL = Path("/dev/null") # Map log paths to the service that needs a reload to pick up the new FD SERVICE_FOR_LOG = { Path("/var/log/apache2/access_log_global"): "httpd", Path("/var/log/nginx/access.log"): "nginx", } def status(log_path: Path, backing: Path, disabled: bool, is_sym: bool) -> str: """Provides state of log path with disabled check already given""" if log_path.exists() and not is_sym: return "enabled" state = "" if disabled: state = "disabled" elif is_sym: state = f"symlinked to unexpected target {log_path.readlink()}" else: state = "missing" if not backing.exists(): state += f" with no existing backing file at {backing}" return state def reload_service(log_path: Path): """Reload the service that owns this log so it picks up the new FD.""" service = SERVICE_FOR_LOG.get(log_path) if not service: return try: subprocess.run( ["systemctl", "reload", service], check=True, capture_output=True, ) print(f" reloaded {service}") except subprocess.CalledProcessError as exc: print( f" warning: failed to reload {service}: " f"{exc.stderr.decode().strip()}" ) def enable(log_path: Path, backing: Path, disabled: bool, is_sym: bool): """Ensure log path symlink replaced with backing file or recreate log""" if disabled or is_sym or not log_path.exists(): log_path.unlink(missing_ok=True) if backing.exists(): shutil.move(backing, log_path) else: print(f"{backing} not found, creating new log file") log_path.touch() os.chmod(log_path, 0o600) os.chown(log_path, 0, 0) reload_service(log_path) print(f"{log_path}: enabled") def disable(log_path: Path, backing: Path, disabled: bool, is_sym: bool): """Ensure log path points to /dev/null, move existing log to backing""" if not disabled: if backing.exists(): # A leftover from an earlier disable; set it aside rather than let # the live log overwrite it stamp = time.strftime("%Y%m%d%H%M%S") shutil.move(backing, f"{backing}.{stamp}") print( f"{log_path}: moved existing {backing.name} to " f"{backing.name}.{stamp}" ) if is_sym: log_path.unlink() elif log_path.exists(): shutil.move(log_path, backing) log_path.symlink_to(DEVNULL) reload_service(log_path) print(f"{log_path}: disabled") def parse_args(): """Parse the CLI args that were passed through""" parser = argparse.ArgumentParser( description="Toggle Apache/Nginx access logs off via symlink, or on", ) group = parser.add_mutually_exclusive_group() group.add_argument( "--enable", action="store_true", help="Replaces symlink with backing file, or recreates log if missing", ) group.add_argument( "--disable", action="store_true", help="Moves existing log and replaces with symlink to /dev/null", ) return parser.parse_args() def main(): args = parse_args() for log_path, backing in LOGS: disabled = False is_sym = log_path.is_symlink() if not log_path.exists() and not backing.exists() and not is_sym: if args.enable or args.disable: print(f"{log_path}: skipped (not present)") continue if log_path.resolve() == DEVNULL: disabled = True if args.enable: enable(log_path, backing, disabled, is_sym) elif args.disable: disable(log_path, backing, disabled, is_sym) else: state = status(log_path, backing, disabled, is_sym) print(f"{log_path}: {state}") if __name__ == "__main__": main()