|
36 | 36 | import shutil |
37 | 37 | import asyncio |
38 | 38 |
|
| 39 | +import configparser |
| 40 | + |
39 | 41 | # web framework |
40 | 42 | import requests |
41 | 43 | from fastapi import FastAPI, Request, File, UploadFile, Depends, APIRouter, Response |
|
48 | 50 | # pylint: disable=no-name-in-module |
49 | 51 | from pydantic import BaseModel |
50 | 52 |
|
51 | | -from ..auth import CookieOrParamAPIKey, router as auth_router, set_password_hash, unset_password_hash, NotAuthenticatedException, not_authenticated_exception_handler, create_access_key |
| 53 | +from ..auth import CookieOrParamAPIKey, router as auth_router, set_password_hash, unset_password_hash,\ |
| 54 | + NotAuthenticatedException, not_authenticated_exception_handler, create_access_key |
52 | 55 |
|
53 | 56 | app = FastAPI() |
54 | 57 | router = APIRouter(dependencies=[Depends(CookieOrParamAPIKey)]) |
|
62 | 65 | sse_messages: queue.Queue = queue.Queue() |
63 | 66 |
|
64 | 67 |
|
| 68 | +def validate_logging_ini(): |
| 69 | + """Fallback in case the ini file or any individual header doesn't exist, set to default settings. Only really comes up during tests.""" |
| 70 | + tmp = '/tmp/logging.ini.tmp' |
| 71 | + ini = '/var/log/logging.ini' |
| 72 | + conf = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 73 | + |
| 74 | + with open(tmp, "+w", encoding="utf-8") as file: |
| 75 | + if os.path.exists(ini): |
| 76 | + conf.read(ini) |
| 77 | + else: |
| 78 | + conf.read(file) |
| 79 | + |
| 80 | + if not conf.has_section("logging"): |
| 81 | + conf.add_section("logging") |
| 82 | + |
| 83 | + if not conf.has_option("logging", "auto_off_delay"): |
| 84 | + conf.set("logging", "auto_off_delay", "14") |
| 85 | + auto_off_delay = conf.get("logging", "auto_off_delay", fallback="14") |
| 86 | + if not auto_off_delay.isdigit() and bool(re.fullmatch(r'\d*\.\d+', auto_off_delay)): |
| 87 | + # regex to check decimal state, this would lead to "123.45" and ".45" being true but not "123." |
| 88 | + # Exclude anything that isdigit() as to not overwrite valid user settings |
| 89 | + rounded = round(float(auto_off_delay)) if round(float(auto_off_delay)) > 0 else 1 # Avoid instances where it could be zero as to not set the "do not deactivate" setting |
| 90 | + conf.set("logging", "auto_off_delay", str(rounded)) |
| 91 | + elif not auto_off_delay.isdigit(): # Cannot be merged with the first check in an OR case as valid regex catches would be intercepted by that |
| 92 | + conf.set("logging", "auto_off_delay", "14") |
| 93 | + conf.write(file) |
| 94 | + |
| 95 | + subprocess.run(['sudo', 'mv', tmp, ini], check=True) |
| 96 | + |
| 97 | + |
| 98 | +def validate_journald_conf(): |
| 99 | + """Fallback in case the config file or any individual header doesn't exist, set to default settings""" |
| 100 | + tmp = '/tmp/journald.conf.tmp' |
| 101 | + conf = '/etc/systemd/journald.conf' |
| 102 | + confparse = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 103 | + |
| 104 | + with open(tmp, "+w", encoding="utf-8") as file: |
| 105 | + if os.path.exists(conf): |
| 106 | + confparse.read(conf) |
| 107 | + else: |
| 108 | + confparse.read(file) |
| 109 | + |
| 110 | + if not confparse.has_section("Journal"): |
| 111 | + confparse.add_section("Journal") |
| 112 | + |
| 113 | + if not confparse.has_option("Journal", "Storage") or confparse.get("Journal", "Storage") not in ("volatile", "persistent"): |
| 114 | + # While volatile is the system's default, having that value either not exist or be invalid points to there being a system error that caused it to get that way |
| 115 | + # Set to persistent just in case |
| 116 | + confparse.set("Journal", "Storage", "persistent") |
| 117 | + |
| 118 | + # Set everything else to default while preserving user settings |
| 119 | + if not confparse.has_option("Journal", "SyncIntervalSec"): |
| 120 | + confparse.set('Journal', 'SyncIntervalSec', '30s') |
| 121 | + if not confparse.has_option("Journal", "SystemMaxUse"): |
| 122 | + confparse.set('Journal', 'SystemMaxUse', '64M') |
| 123 | + if not confparse.has_option("Journal", "RuntimeMaxUse"): |
| 124 | + confparse.set('Journal', 'RuntimeMaxUse', '64M') |
| 125 | + if not confparse.has_option("Journal", "ForwardToConsole"): |
| 126 | + confparse.set('Journal', 'ForwardToConsole', 'no') |
| 127 | + if not confparse.has_option("Journal", "ForwardToWall"): |
| 128 | + confparse.set('Journal', 'ForwardToWall', 'no') |
| 129 | + |
| 130 | + confparse.write(file) |
| 131 | + subprocess.run(['sudo', 'mv', tmp, conf], check=True) |
| 132 | + |
| 133 | + |
65 | 134 | class ReleaseInfo(BaseModel): |
66 | 135 | """ Software Release Information """ |
67 | 136 | url: str |
@@ -95,6 +164,81 @@ class ReleaseInfo(BaseModel): |
95 | 164 | logger.exception(f'Error loading identity file: {e}') |
96 | 165 |
|
97 | 166 |
|
| 167 | +class Persist_Logs(BaseModel): |
| 168 | + """Basemodel that consists of a bool and int, used to change different config files around the system via POST /settings/persist_logs""" |
| 169 | + persist_logs: bool |
| 170 | + auto_off_delay: int |
| 171 | + |
| 172 | + |
| 173 | +@router.get("/settings/persist_logs") |
| 174 | +def get_log_persist_state(): |
| 175 | + """ |
| 176 | + Checks /etc/systemd/journald.conf to find if the current storage setting is persistent and returns a bool |
| 177 | + Note that returning false doesn't necessarily mean that logs are set to volatile, and could just mean that the config file is missing the line being read |
| 178 | + """ |
| 179 | + validate_journald_conf() |
| 180 | + journalconf = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 181 | + journalconf.read('/etc/systemd/journald.conf') |
| 182 | + |
| 183 | + validate_logging_ini() |
| 184 | + logconf = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 185 | + logconf.read('/var/log/logging.ini') |
| 186 | + |
| 187 | + # Fallback set is the default value of the Storage variable under the Journal header of the conf file |
| 188 | + # Used when the variable cannot be read but the file itself can (implying that the variable is missing, and should be set to a default) |
| 189 | + ret = Persist_Logs(persist_logs=journalconf.get("Journal", "Storage", fallback="volatile") == "persistent", auto_off_delay=logconf.get("logging", "auto_off_delay", fallback="14"),) |
| 190 | + return ret |
| 191 | + |
| 192 | + |
| 193 | +@router.post("/settings/persist_logs") |
| 194 | +def toggle_persist_logs(data: Persist_Logs): |
| 195 | + """Toggles the option within journald to save logs to memory or storage, and sets the length of time before that setting is reset to volatile""" |
| 196 | + try: |
| 197 | + # Just in case |
| 198 | + validate_logging_ini() |
| 199 | + validate_journald_conf() |
| 200 | + |
| 201 | + state = get_log_persist_state() |
| 202 | + |
| 203 | + if state.persist_logs != data.persist_logs: |
| 204 | + journalconf = '/etc/systemd/journald.conf' |
| 205 | + journaltmp = '/tmp/journald.conf.tmp' |
| 206 | + journal = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 207 | + journal.read(journalconf) |
| 208 | + |
| 209 | + if not journal.has_section("Journal"): |
| 210 | + journal.add_section('Journal') |
| 211 | + |
| 212 | + # goal_value is true if you wish to turn persistent logging on and false if you wish to turn it off |
| 213 | + journal.set('Journal', 'Storage', 'persistent' if data.persist_logs else 'volatile') |
| 214 | + |
| 215 | + with open(journaltmp, 'w', encoding="utf-8") as conf_file: |
| 216 | + journal.write(conf_file) |
| 217 | + |
| 218 | + subprocess.run(['sudo', 'mv', journaltmp, journalconf], check=True) |
| 219 | + subprocess.run(['sudo', 'systemctl', 'restart', 'systemd-journald'], check=True) |
| 220 | + logger.info(f"persist_logs set to {data.persist_logs}") |
| 221 | + else: |
| 222 | + logger.info("persist_logs unchanged") |
| 223 | + |
| 224 | + if state.auto_off_delay != data.auto_off_delay: |
| 225 | + logconf = '/var/log/logging.ini' |
| 226 | + logtmp = '/tmp/logging.ini.tmp' |
| 227 | + log = configparser.ConfigParser(strict=False, allow_no_value=True) |
| 228 | + log.read(logconf) |
| 229 | + log.set('logging', 'auto_off_delay', f"{data.auto_off_delay}") # Accept auto_off_delay as an int for type checking, parse to str for configParser validity |
| 230 | + with open(logtmp, 'w', encoding='utf-8') as file: |
| 231 | + log.write(file) |
| 232 | + subprocess.run(['sudo', 'mv', logtmp, logconf], check=True) |
| 233 | + logger.info(f"auto_off_delay set to {data.auto_off_delay}") |
| 234 | + |
| 235 | + else: |
| 236 | + logger.info("auto_off_delay unchanged") |
| 237 | + except Exception as exc: |
| 238 | + logger.exception(str(exc)) |
| 239 | + return 500 |
| 240 | + |
| 241 | + |
98 | 242 | @router.get('/update') |
99 | 243 | def get_index(): |
100 | 244 | """ Get the update website """ |
|
0 commit comments