From eee65a473d387281ce609962bf7f735ea52edd3e Mon Sep 17 00:00:00 2001
From: Steven Engelbert
Date: Wed, 24 Jul 2024 11:20:03 -0400
Subject: [PATCH] Create function, endpoint, frontend implementation for log
persistence
Update CHANGELOG.md
Lint ctrl.py
Add persist_logs bool and integrate throughout the config flow
Add many logger.info chunks for testing
Add more logic to persist_logs toggle endpoint
Change how default value is handled
Change default permissions of journald.conf
Fix spelling errors, add logger on frontend
Move toggle_log_persistance away from FastAPI and towards the Api object
Linting
Call reinit at end of log toggle, reword models.py description of persist_logs
Remove all uses of persist_logs from amplipi core, move to updater side
Remove lingering changes from app.py, ctrl.py
pylint
pylint (2)
Finish porting new admin settings tab to updater
Change name of Updates tab on settings page
Fix spelling in comment in app.py
Robustify setPersist function against edge cases, add documentation
pylint
Add delete logs endpoint, cronjob that calls it at midnight
Updates/Admin
Change comment on delete_persisted_logs_crontab
Update CHANGELOG.md
Remove deletion endpoint + supporting files
remove optional "value" bool from post persist_logs
Fix wording in index.html
Change configure.py to only set logs to volatile if the /var/log/journal directory doesn't exist (i.e., only set volatility on first deploy)
Reduce verbocity in configure.py
Add auto_off_delay feature to admin settings
Pylint
Pylint
Rename attempt_persist_logs_auto_off.py to increment_auto_off.py
Change logic paths of POST /settings/persist_logs to avoid interacting with the files more often than necessary
PyCodeStyle
Update crontab to have a user associated with it
remove unused variables from increment_auto_off.py
Swap from using subprocess.run and configparser to just responses in increment_auto_off.py
Copy files properly in configure.py
Cover some oversights in asgi.py
Add hoverover tooltip for Auto Off Delay label
Make sure to pip install requests in configure.py
pylint
segregate standard imports from package import in increment_auto_off.py
Update styling
Move password setting functionality to Admin Settings page, add loading spinner to save log state button
update IDs, classnames to use JavaScript naming instead of Python naming ( - instead of _ )
Add new success and failure notification for persist save button
Fix formatting for smaller screens on the updater
Update CHANGELOG.md
Update CHANGELOG.md
Fix styling of admin panel
Correct error handling, fix styling issue
pylint
Extend safety net
remove pip install requests from configure.py, that should be handled by requirements.txt
Update styling, tooltips
Gate the delay of increment_auto_off.py behind an if statement
Revamp increment_auto_off.py to be more efficient
Update verbiage around the updater
Update increment_auto_off.py to not run or loop if persistance is disabled
Edit github workflows to include more files
Add more, better file validation to /persist_logs endpoints
Remove looping, add checking to increment_auto_off.py
fix typo in python-app.yaml
Add docstring to increment_auto_off.py, add same file to github pylinting
Reconfigure file validation functions
Simplify persistence toggle function
Rework increment_auto_off.py to be able to use the backdoor if the frontdoor doesn't work out
linting
linting (2)
Linting (3)
Simplify file verification functions
Include minor verification in the non-api route of increment_auto_off.py
pycodestyle
Further robustify auto_off.py and validator functions against direct user interventions
pypcodestyle
Update password set function to give feedback in the same way as updating persistence and delay
---
.github/workflows/python-app.yml | 2 +
CHANGELOG.md | 4 +
amplipi/app.py | 2 +-
amplipi/ctrl.py | 2 +-
amplipi/updater/asgi.py | 146 +++++++++++++++++-
amplipi/updater/static/css/styles.css | 61 ++++++++
amplipi/updater/static/index.html | 197 ++++++++++++++++++++++---
amplipi/updater/static/js/update-ui.js | 27 ----
config/deactivate_persist_logs_crontab | 3 +
scripts/configure.py | 20 ++-
scripts/increment_auto_off.py | 126 ++++++++++++++++
web/src/pages/Settings/Settings.jsx | 2 +-
12 files changed, 542 insertions(+), 50 deletions(-)
create mode 100755 config/deactivate_persist_logs_crontab
create mode 100755 scripts/increment_auto_off.py
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index e7a125a1b..bce325608 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -41,10 +41,12 @@ jobs:
pylint --exit-zero amplipi --generated-members "signal.Signals,GPIO.*"
pylint -E amplipi --generated-members "signal.Signals,GPIO.*"
pylint -E streams
+ pylint -E scripts/increment_auto_off.py
- name: Lint with mypy, static type checker
run: |
pip install mypy
mypy amplipi/ --ignore-missing-imports
+ mypy scripts/increment_auto_off.py --ignore-missing-imports
- name: Test mock using pytest # rpi cannot be tested directly due to hardware...
run: |
pip install pytest pytest-cov
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28fe83e74..0be98e620 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@
* Add error handling on browser page for instances where the selected stream isn't browsable
* Add scrollbars to tall modals
* Change how events are handled with Modals to reduce accidental closures
+ * Rename Updater to Admin Panel
+ * Add "Admin Settings" tab to the Updater/Admin Panel
+ * Add toggleable option to persist system logs to Admin Settings
+ * Move "Set password" page to be accessible via Admin Settings tab
* System
* Make update process properly report errors
diff --git a/amplipi/app.py b/amplipi/app.py
index 45ae9762b..f89d9d59a 100644
--- a/amplipi/app.py
+++ b/amplipi/app.py
@@ -226,7 +226,7 @@ def shutdown():
"""
# preemptively save the state (just in case the shutdown procedure doesn't invoke a save)
get_ctrl().save()
- # start the shutdown process and returning immediately (hopeully before the shutdown process begins)
+ # start the shutdown process and returning immediately (hopefully before the shutdown process begins)
Popen('sleep 1 && sudo systemctl poweroff', shell=True)
diff --git a/amplipi/ctrl.py b/amplipi/ctrl.py
index c8236020e..30175b464 100644
--- a/amplipi/ctrl.py
+++ b/amplipi/ctrl.py
@@ -222,7 +222,7 @@ def reinit(self, settings: models.AppSettings = models.AppSettings(), change_not
except Exception as exc:
logger.exception("Error setting is_streamer flag: {exc}")
- # determine if we're in LMS mode, based on a file
+ # determine if we're in LMS mode, based on a file existing
lms_mode_path = Path(defaults.USER_CONFIG_DIR, 'lms_mode')
if lms_mode_path.exists():
logger.info("lms mode")
diff --git a/amplipi/updater/asgi.py b/amplipi/updater/asgi.py
index e5ad73285..b96b7b5e2 100644
--- a/amplipi/updater/asgi.py
+++ b/amplipi/updater/asgi.py
@@ -36,6 +36,8 @@
import shutil
import asyncio
+import configparser
+
# web framework
import requests
from fastapi import FastAPI, Request, File, UploadFile, Depends, APIRouter, Response
@@ -48,7 +50,8 @@
# pylint: disable=no-name-in-module
from pydantic import BaseModel
-from ..auth import CookieOrParamAPIKey, router as auth_router, set_password_hash, unset_password_hash, NotAuthenticatedException, not_authenticated_exception_handler, create_access_key
+from ..auth import CookieOrParamAPIKey, router as auth_router, set_password_hash, unset_password_hash,\
+ NotAuthenticatedException, not_authenticated_exception_handler, create_access_key
app = FastAPI()
router = APIRouter(dependencies=[Depends(CookieOrParamAPIKey)])
@@ -62,6 +65,72 @@
sse_messages: queue.Queue = queue.Queue()
+def validate_logging_ini():
+ """Fallback in case the ini file or any individual header doesn't exist, set to default settings. Only really comes up during tests."""
+ tmp = '/tmp/logging.ini.tmp'
+ ini = '/var/log/logging.ini'
+ conf = configparser.ConfigParser(strict=False, allow_no_value=True)
+
+ with open(tmp, "+w", encoding="utf-8") as file:
+ if os.path.exists(ini):
+ conf.read(ini)
+ else:
+ conf.read(file)
+
+ if not conf.has_section("logging"):
+ conf.add_section("logging")
+
+ if not conf.has_option("logging", "auto_off_delay"):
+ conf.set("logging", "auto_off_delay", "14")
+ auto_off_delay = conf.get("logging", "auto_off_delay", fallback="14")
+ if not auto_off_delay.isdigit() and bool(re.fullmatch(r'\d*\.\d+', auto_off_delay)):
+ # regex to check decimal state, this would lead to "123.45" and ".45" being true but not "123."
+ # Exclude anything that isdigit() as to not overwrite valid user settings
+ 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
+ conf.set("logging", "auto_off_delay", str(rounded))
+ 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
+ conf.set("logging", "auto_off_delay", "14")
+ conf.write(file)
+
+ subprocess.run(['sudo', 'mv', tmp, ini], check=True)
+
+
+def validate_journald_conf():
+ """Fallback in case the config file or any individual header doesn't exist, set to default settings"""
+ tmp = '/tmp/journald.conf.tmp'
+ conf = '/etc/systemd/journald.conf'
+ confparse = configparser.ConfigParser(strict=False, allow_no_value=True)
+
+ with open(tmp, "+w", encoding="utf-8") as file:
+ if os.path.exists(conf):
+ confparse.read(conf)
+ else:
+ confparse.read(file)
+
+ if not confparse.has_section("Journal"):
+ confparse.add_section("Journal")
+
+ if not confparse.has_option("Journal", "Storage") or confparse.get("Journal", "Storage") not in ("volatile", "persistent"):
+ # 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
+ # Set to persistent just in case
+ confparse.set("Journal", "Storage", "persistent")
+
+ # Set everything else to default while preserving user settings
+ if not confparse.has_option("Journal", "SyncIntervalSec"):
+ confparse.set('Journal', 'SyncIntervalSec', '30s')
+ if not confparse.has_option("Journal", "SystemMaxUse"):
+ confparse.set('Journal', 'SystemMaxUse', '64M')
+ if not confparse.has_option("Journal", "RuntimeMaxUse"):
+ confparse.set('Journal', 'RuntimeMaxUse', '64M')
+ if not confparse.has_option("Journal", "ForwardToConsole"):
+ confparse.set('Journal', 'ForwardToConsole', 'no')
+ if not confparse.has_option("Journal", "ForwardToWall"):
+ confparse.set('Journal', 'ForwardToWall', 'no')
+
+ confparse.write(file)
+ subprocess.run(['sudo', 'mv', tmp, conf], check=True)
+
+
class ReleaseInfo(BaseModel):
""" Software Release Information """
url: str
@@ -95,6 +164,81 @@ class ReleaseInfo(BaseModel):
logger.exception(f'Error loading identity file: {e}')
+class Persist_Logs(BaseModel):
+ """Basemodel that consists of a bool and int, used to change different config files around the system via POST /settings/persist_logs"""
+ persist_logs: bool
+ auto_off_delay: int
+
+
+@router.get("/settings/persist_logs")
+def get_log_persist_state():
+ """
+ Checks /etc/systemd/journald.conf to find if the current storage setting is persistent and returns a bool
+ 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
+ """
+ validate_journald_conf()
+ journalconf = configparser.ConfigParser(strict=False, allow_no_value=True)
+ journalconf.read('/etc/systemd/journald.conf')
+
+ validate_logging_ini()
+ logconf = configparser.ConfigParser(strict=False, allow_no_value=True)
+ logconf.read('/var/log/logging.ini')
+
+ # Fallback set is the default value of the Storage variable under the Journal header of the conf file
+ # 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)
+ ret = Persist_Logs(persist_logs=journalconf.get("Journal", "Storage", fallback="volatile") == "persistent", auto_off_delay=logconf.get("logging", "auto_off_delay", fallback="14"),)
+ return ret
+
+
+@router.post("/settings/persist_logs")
+def toggle_persist_logs(data: Persist_Logs):
+ """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"""
+ try:
+ # Just in case
+ validate_logging_ini()
+ validate_journald_conf()
+
+ state = get_log_persist_state()
+
+ if state.persist_logs != data.persist_logs:
+ journalconf = '/etc/systemd/journald.conf'
+ journaltmp = '/tmp/journald.conf.tmp'
+ journal = configparser.ConfigParser(strict=False, allow_no_value=True)
+ journal.read(journalconf)
+
+ if not journal.has_section("Journal"):
+ journal.add_section('Journal')
+
+ # goal_value is true if you wish to turn persistent logging on and false if you wish to turn it off
+ journal.set('Journal', 'Storage', 'persistent' if data.persist_logs else 'volatile')
+
+ with open(journaltmp, 'w', encoding="utf-8") as conf_file:
+ journal.write(conf_file)
+
+ subprocess.run(['sudo', 'mv', journaltmp, journalconf], check=True)
+ subprocess.run(['sudo', 'systemctl', 'restart', 'systemd-journald'], check=True)
+ logger.info(f"persist_logs set to {data.persist_logs}")
+ else:
+ logger.info("persist_logs unchanged")
+
+ if state.auto_off_delay != data.auto_off_delay:
+ logconf = '/var/log/logging.ini'
+ logtmp = '/tmp/logging.ini.tmp'
+ log = configparser.ConfigParser(strict=False, allow_no_value=True)
+ log.read(logconf)
+ 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
+ with open(logtmp, 'w', encoding='utf-8') as file:
+ log.write(file)
+ subprocess.run(['sudo', 'mv', logtmp, logconf], check=True)
+ logger.info(f"auto_off_delay set to {data.auto_off_delay}")
+
+ else:
+ logger.info("auto_off_delay unchanged")
+ except Exception as exc:
+ logger.exception(str(exc))
+ return 500
+
+
@router.get('/update')
def get_index():
""" Get the update website """
diff --git a/amplipi/updater/static/css/styles.css b/amplipi/updater/static/css/styles.css
index 1e0d4ca2b..ed5b3c5e1 100644
--- a/amplipi/updater/static/css/styles.css
+++ b/amplipi/updater/static/css/styles.css
@@ -1,6 +1,20 @@
/*
A couple styles to make the demo page look good
*/
+
+ .column-container {
+ display: flex;
+ flex-direction: column;
+ gap: 64px;
+}
+
+.column-item {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ gap: 16px;
+}
+
body {
padding-bottom: 2rem;
padding-top: 1rem;
@@ -79,6 +93,15 @@ hr {
background: #4a4a4a;
}
+.margin{
+ margin-left: 0px;
+}
+@media (min-width: 768px) {
+ .margin{
+ margin-left: 10px;
+ }
+}
+
.fa-circle-notch {
animation: fa-spin 1s infinite linear;
}
@@ -88,3 +111,41 @@ hr {
background-color: #5a6268;
border-color: #545b62;
}
+
+.container {
+ padding-left: 1rem;
+ padding-right: 1rem;
+}
+
+
+.set-password-dialog {
+ color: #aaaaaa !important;
+}
+
+/* lifted from https://www.w3schools.com/css/css_tooltip.asp */
+
+.hover-label {
+ position: relative;
+ display: inline-block;
+ border-bottom: 1px dotted black;
+}
+
+.hover-label .hover-tooltip{
+ top: 100%;
+ left: 50%;
+ visibility: hidden;
+ width: 750px;
+ max-width: 70vw;
+ background-color: rgba(100, 100, 100, 1);
+ color: #ffffff;
+ text-align: center;
+ padding: 5px 0;
+ border-radius: 6px;
+
+ position: absolute;
+ z-index: 1;
+}
+
+.hover-label:hover .hover-tooltip{
+ visibility: visible;
+}
diff --git a/amplipi/updater/static/index.html b/amplipi/updater/static/index.html
index 7ef414a46..3412bd7f6 100644
--- a/amplipi/updater/static/index.html
+++ b/amplipi/updater/static/index.html
@@ -11,6 +11,7 @@
+
@@ -28,22 +29,22 @@
AmpliPi
- Update your AmpliPi device
+ Update & configure your AmpliPi device
- This form allows you to set a password for the web interface. If the password fields are empty, it will unset the password protection. This will only set a password for the web interface, not the system password. This action will also log out any existing sessions.
-
This dialog allows you to request a support tunnel. Once requested, you must give the tunnel ID and preshared key to support, who can be reached via support@micro-nova.com.
@@ -105,8 +98,178 @@
AmpliPi
+
+
+
+
+
+ This tab allows you to change settings relating to the core of AmpliPi
+
+
+
+
+
+ Set Password
+
+ This form lets you set a web interface password, which will make it so that you must input the same password to access the system's controls. Leaving the fields empty will remove password protection. Note that this only affects the web interface, not the system password, and will log out any active sessions.
+
+
+ Persist Logs
+
+ By default, logs are stored in memory to preserve the limited read/write cycles of the system's EMMC flash storage; this has the effect of deleting all logs on system reboot
+
+ By enabling log persistence, you can save logs to storage instead to persist them between reboots. Over time this may wear out your device; to avoid this, there is also an "auto off delay" that will turn off persistance after X days. Input 0 to not automatically disable log persistance (this is not recommended)
+
+
+ WARNING: Leaving log persistence on for extended periods can damage your system.
+
+
+
+
+
-
+
+
diff --git a/amplipi/updater/static/js/update-ui.js b/amplipi/updater/static/js/update-ui.js
index d918abe69..e98dfa2f5 100644
--- a/amplipi/updater/static/js/update-ui.js
+++ b/amplipi/updater/static/js/update-ui.js
@@ -243,33 +243,6 @@ function populate_available_releases(releases) {
}
}
-async function set_password() {
- password = $('#password').val()
- confirm_password = $('#confirm-password').val()
-
- if(password != confirm_password) {
- alert("Passwords do not match.");
- return;
- }
-
- body = JSON.stringify({'password':password})
-
- res = await fetch('/password', {
- method: 'POST',
- body: body,
- headers: {
- "Content-Type": "application/json",
- }
- });
-
- if(!res.ok) {
- alert(`Error: ${res.statusText}`);
- return;
- }
-
- alert('Password set.');
-}
-
async function requestSupportTunnel() {
// the below is 2 lines long intentionally, because it renders into a
tag.
$('#support-tunnel-detail').text(`Requesting a support tunnel. This may take up to 60s.
diff --git a/config/deactivate_persist_logs_crontab b/config/deactivate_persist_logs_crontab
new file mode 100755
index 000000000..306409fcc
--- /dev/null
+++ b/config/deactivate_persist_logs_crontab
@@ -0,0 +1,3 @@
+# If auto_off setting is active for persist logs, and persist logs is on, tick a counter down every night until it reaches 0 and then deactivate persist logs and reset the counter
+# Installed by AmpliPi
+0 0 * * * root /usr/local/bin/increment_auto_off.py
diff --git a/scripts/configure.py b/scripts/configure.py
index 548ecdaef..5f9067670 100755
--- a/scripts/configure.py
+++ b/scripts/configure.py
@@ -113,6 +113,18 @@
]
},
'logging': {
+ "copy": [
+ {
+ 'from': 'config/deactivate_persist_logs_crontab',
+ 'to': '/etc/cron.d/deactivate_persist_logs',
+ 'sudo': 'true',
+ },
+ {
+ 'from': 'scripts/increment_auto_off.py',
+ 'to': '/usr/local/bin/increment_auto_off.py',
+ 'sudo': 'true',
+ },
+ ],
'script': [
'echo "reconfiguring secondary logging utility rsyslog to only allow remote logging"',
f"echo '{RSYSLOG_CFG}' | sudo tee /etc/rsyslog.conf",
@@ -120,11 +132,15 @@
'sudo systemctl enable rsyslog.service',
'sudo systemctl restart rsyslog.service',
- 'echo "reconfiguring journald to only log to RAM"',
- r'echo -e "[Journal]\nStorage=volatile\nRuntimeMaxUse=64M\nForwardToConsole=no\nForwardToWall=no\n" | sudo tee /etc/systemd/journald.conf',
+ 'echo "If first deploy, reconfiguring journald to only log to RAM"',
+ r'[ ! -d /var/log/journal ] && echo -e "[Journal]\nStorage=volatile\nRuntimeMaxUse=64M\nForwardToConsole=no\nForwardToWall=no\n" | sudo tee /etc/systemd/journald.conf',
'sudo systemctl enable systemd-journald.service',
'sudo systemctl restart systemd-journald.service',
+ 'echo Handle dependencies for log persistence options',
+ 'sudo mkdir -p /var/log/journal',
+ 'sudo systemd-tmpfiles --create --prefix /var/log/journal',
+
'echo "enable socket to the journald server to allow easy access to system logs"',
'sudo systemctl enable systemd-journal-gatewayd.socket',
'sudo systemctl restart systemd-journal-gatewayd.socket',
diff --git a/scripts/increment_auto_off.py b/scripts/increment_auto_off.py
new file mode 100755
index 000000000..446229964
--- /dev/null
+++ b/scripts/increment_auto_off.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+"""A program that ticks a counter down until deactivating log_persistence"""
+
+import logging
+import sys
+import json
+import re
+
+import configparser
+import requests
+import subprocess
+
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+sh = logging.StreamHandler(sys.stdout)
+logger.addHandler(sh)
+
+state_persist: bool
+state_delay: int
+
+api_read_failure = False
+try:
+ response = requests.get('http://localhost:5001/settings/persist_logs', timeout=10)
+ state = response.json()
+
+ state_persist = state["persist_logs"]
+ state_delay = state["auto_off_delay"]
+except KeyError as exc:
+ logger.exception("Unable to read log persistence state through the api, attempting to read directly...")
+ api_read_failure = True
+except Exception as exc:
+ logger.exception(f"increment_auto_off.py has failed with the following exception:\n{exc}")
+ api_read_failure = True
+
+
+if api_read_failure:
+ try:
+ # If the api is inaccessible, assume it is down for whatever reason and extrapolate that there is no race condition to reading it directly
+ # This section is a copy of the /settings/persist_logs GET in asgi.py
+ journalconf = configparser.ConfigParser(strict=False, allow_no_value=True)
+ journalconf.read('/etc/systemd/journald.conf')
+
+ logconf = configparser.ConfigParser(strict=False, allow_no_value=True)
+ logconf.read('/var/log/logging.ini')
+
+ persist = journalconf.get("Journal", "Storage", fallback="volatile")
+ state_persist = True if persist not in ("volatile", "persistent") else persist == "persistent" # If value is somehow invalid, assume it's True just in case
+
+ if not logconf.has_option("logging", "auto_off_delay"):
+ state_delay = 14
+ auto_off = logconf.get("logging", "auto_off_delay", fallback="14")
+ if not auto_off.isdigit() and bool(re.fullmatch(r'\d*\.\d+', auto_off)):
+ # regex to check decimal state, this would lead to "123.45" and ".45" being true but not "123."
+ # Exclude anything that isdigit() as to not overwrite valid user settings
+ state_delay = round(float(auto_off)) if round(float(auto_off)) > 0 else 1 # Avoid instances where it could be zero as to not set the "do not deactivate" setting
+ else:
+ state_delay = int(auto_off) if auto_off.isdigit() else 14
+ except Exception as exc:
+ logger.exception(f"increment_auto_off.py has failed with the following exception:\n{exc}")
+
+
+if state_persist and state_delay is not None:
+ future_persist_state = state_delay != 1
+ delay = state_delay - 1
+ body = {
+ "persist_logs": future_persist_state,
+ "auto_off_delay": delay if future_persist_state else 14, # If no longer persisting, set to default
+ }
+
+ api_write_failure = False
+ try:
+ json_data = json.dumps(body)
+ response = requests.post(
+ url='http://localhost:5001/settings/persist_logs',
+ headers={'Content-Type': 'application/json'},
+ data=json_data,
+ timeout=10,
+ )
+ if response.ok:
+ if future_persist_state:
+ logging.info(f"Persist logs will be automatically turned off in {delay} day(s)")
+ else:
+ logging.info("Persist logs has been turned off automatically")
+ else:
+ logging.exception("Unable to update persist_logs state via api, attempting to write directly...")
+ api_write_failure = True
+ except Exception as exc:
+ logger.exception(f"increment_auto_off.py has failed with the following exception:\n{exc}")
+ api_write_failure = True
+
+ if api_write_failure:
+ # This section is a copy of the /settings/persist_logs POST in asgi.py
+ try:
+ conf = '/etc/systemd/journald.conf'
+ tmp = '/tmp/journald.conf.tmp'
+ journal = configparser.ConfigParser(strict=False, allow_no_value=True)
+ journal.read(conf)
+
+ if not journal.has_section("Journal"):
+ journal.add_section('Journal')
+
+ # goal_value is true if you wish to turn persistent logging on and false if you wish to turn it off
+ if body["persist_logs"]: # Set persist
+ journal.set('Journal', 'Storage', 'persistent')
+ else: # Reset config to default as seen in configure.py
+ journal.set('Journal', 'Storage', 'volatile')
+
+ with open(tmp, 'w', encoding="utf-8") as conf_file:
+ journal.write(conf_file)
+
+ subprocess.run(['sudo', 'mv', tmp, conf], check=True)
+ subprocess.run(['sudo', 'systemctl', 'restart', 'systemd-journald'], check=True)
+
+ if state_delay != body["auto_off_delay"]:
+ logini = '/var/log/logging.ini'
+ logtmp = '/tmp/logging.ini.tmp'
+ log = configparser.ConfigParser(strict=False, allow_no_value=True)
+ log.read(logini)
+ log.set('logging', 'auto_off_delay', f"{body['auto_off_delay']}") # Accept auto_off_delay as an int for type checking, parse to str for configParser validity
+ with open(logtmp, 'w', encoding='utf-8') as file:
+ log.write(file)
+ subprocess.run(['sudo', 'mv', logtmp, logini], check=True)
+ logging.info(f"Persist logs will be automatically turned off in {body['auto_off_delay']} day(s)")
+
+ except Exception as exc:
+ logger.exception(f"increment_auto_off.py has failed with the following exception:\n{exc}")
diff --git a/web/src/pages/Settings/Settings.jsx b/web/src/pages/Settings/Settings.jsx
index 5df59009f..d7c96de02 100644
--- a/web/src/pages/Settings/Settings.jsx
+++ b/web/src/pages/Settings/Settings.jsx
@@ -151,7 +151,7 @@ const Settings = ({ openPage }) => {