Merge branch 'lrnselfreliance:master' into master

This commit is contained in:
StruckCroissant 2022-02-07 21:26:13 -06:00 • committed by GitHub
commit 3390c8f464
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 208 additions and 70 deletions

View file

@ -487,3 +487,54 @@ export async function setHotspot(on) {
}
}
}
export async function getThrottleStatus() {
let response = await apiGet(`${API_URI}/throttle`);
if (response.status === 200) {
let {status} = await response.json();
return status;
} else {
let code = (await response.json())['code'];
if (response.status === 400 && code === 34) {
// Must not be running on a RPi. Don't warn the user.
return null;
} else {
toast({
type: 'error',
title: "Can't get throttle status",
description: 'Failed to get CPU Throttle status. Check client and server logs.',
time: 5000,
});
}
}
return null;
}
export async function setThrottle(on) {
let response;
if (on) {
response = await apiPost(`${API_URI}/throttle/on`);
} else {
response = await apiPost(`${API_URI}/throttle/off`);
}
if (response.status === 204) {
return null;
} else {
let code = (await response.json())['code'];
if (code === 34) {
toast({
type: 'error',
title: 'Unsupported!',
description: 'Throttle is only supported on a Raspberry Pi!',
time: 5000,
});
} else {
toast({
type: 'error',
title: 'Error!',
description: 'Could not modify throttle. See server logs.',
time: 5000,
});
}
}
}

View file

@ -21,6 +21,7 @@ import {
secondsToFrequency,
TabLinks,
textEllipsis,
ThrottleToggle,
WROLModeMessage
} from "./Common";
import {Route} from "react-router-dom";
@ -80,6 +81,8 @@ class Settings extends React.Component {
<WROLModeMessage content='Settings are disabled because WROL Mode is enabled.'/>
<HotspotToggle/>
<br/>
<ThrottleToggle style={{marginTop: '0.5em'}}/>
<Divider/>
<p>

View file

@ -5,7 +5,7 @@ import Button from "semantic-ui-react/dist/commonjs/elements/Button";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
import {getConfig} from "../api";
import {useDownloaders, useHotspot} from "../hooks/customHooks";
import {useDownloaders, useHotspot, useThrottle} from "../hooks/customHooks";
export const API_URI = `http://${window.location.host}/api`;
export const VIDEOS_API = `${API_URI}/videos`;
@ -645,9 +645,9 @@ export function CardGroupCentered(props) {
)
}
export function HotspotToggle() {
export function HotspotToggle(props) {
let {on, setHotspot} = useHotspot();
return <Checkbox toggle
return <Checkbox toggle {...props}
label='WiFi Hotspot'
disabled={on === null}
checked={on === true}
@ -655,6 +655,16 @@ export function HotspotToggle() {
/>;
}
export function ThrottleToggle(props) {
let {on, setThrottle} = useThrottle();
return <Checkbox toggle {...props}
label='CPU Power-save'
disabled={on === null}
checked={on === true}
onChange={(e, data) => setThrottle(data.checked)}
/>;
}
export function DisableDownloadsToggle() {
let {on, setDownloads} = useDownloaders();
return <Checkbox toggle

View file

@ -5,11 +5,13 @@ import {
getDownloaders,
getFiles,
getHotspotStatus,
getThrottleStatus,
getVersion,
killDownloads,
searchArchives,
searchVideos,
setHotspot,
setThrottle,
startDownloads
} from "../api";
import {useHistory} from "react-router-dom";
@ -247,3 +249,30 @@ export const useDownloaders = () => {
return {on, setOn, downloaders, setDownloads: localSetDownloads};
}
export const useThrottle = () => {
const [on, setOn] = useState(null);
const fetchThrottleStatus = async () => {
const status = await getThrottleStatus();
if (status === 'powersave') {
setOn(true);
} else if (status === 'ondemand') {
setOn(false);
} else {
setOn(null);
}
}
useEffect(() => {
fetchThrottleStatus();
}, []);
const localSetThrottle = async (on) => {
setOn(null);
await setThrottle(on);
await fetchThrottleStatus();
}
return {on, setOn, setThrottle: localSetThrottle};
}

View file

@ -84,6 +84,14 @@ cp /opt/wrolpi/50x.html /var/www/50x.html
grep wrolpi /etc/passwd || useradd -md /home/wrolpi wrolpi -s "$(command -v bash)"
chown -R wrolpi:wrolpi /opt/wrolpi
# Give WROLPi group a few privileged commands via sudo without password.
cat > /etc/sudoers.d/90-wrolpi << 'EOF'
%wrolpi ALL=(ALL) NOPASSWD:/usr/bin/nmcli,/usr/bin/cpufreq-set
EOF
chmod 660 /etc/sudoers.d/90-wrolpi
# Verify this new file is valid.
visudo -c -f /etc/sudoers.d/90-wrolpi
# Create the media directory. This should be mounted by the maintainer.
[ -d /media/wrolpi ] || mkdir /media/wrolpi
chown -R wrolpi:wrolpi /media/wrolpi
@ -106,12 +114,9 @@ sudo -u postgres psql -c '\l' | grep wrolpi || (
sudo -u postgres psql -c "alter user wrolpi password 'wrolpi'" &&
sudo -u postgres createdb -O wrolpi wrolpi
)
# Init/upgrade the WROLPi database.
# Initialize/upgrade the WROLPi database.
(cd /opt/wrolpi && /usr/bin/python3 /opt/wrolpi/main.py db upgrade)
# Configure the hotspot.
bash /opt/wrolpi/scripts/hotspot.sh
set +x
ip=$(hostname -i | cut -d' ' -f1)

View file

@ -1,61 +0,0 @@
#! /usr/bin/env bash
# Configure the WROLPi hotspot.
set -x
set -e
apt install -y network-manager
cat >/etc/netplan/50-cloud-init.yaml <<'EOF'
# This file is generated from information provided by the datasource. Changes
# to it will not persist across an instance reboot. To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
version: 2
renderer: NetworkManager
EOF
cat >/etc/cloud/cloud.cfg.d/99-disable-network-config.cfg <<'EOF'
network: {config: disabled}
EOF
netplan generate
netplan apply
# Initialize the Hotspot config, then overwrite it.
nmcli con add type wifi ifname wlan0 con-name Hotspot autoconnect yes ssid WROLPi
cat >/etc/NetworkManager/system-connections/Hotspot.nmconnection <<'EOF'
[connection]
id=Hotspot
uuid=b72ae32e-877c-4559-aaa1-e41a7a9d7bd5
type=wifi
interface-name=wlan0
permissions=
timestamp=1635780305
[wifi]
band=bg
mac-address-blacklist=
mode=ap
seen-bssids=DC:A6:32:C7:01:EE;
ssid=WROLPi
[wifi-security]
key-mgmt=wpa-psk
psk=wrolpi hotspot
[ipv4]
address1=192.168.0.1/24,192.168.0.1
dns-search=
method=shared
[ipv6]
addr-gen-mode=stable-privacy
dns-search=
method=auto
[proxy]
EOF
systemctl restart NetworkManager.service

View file

@ -3,7 +3,7 @@ import pathlib
import subprocess
from wrolpi.common import logger
from wrolpi.vars import PYTEST
from wrolpi.vars import PYTEST, DEFAULT_CPU_FREQUENCY
logger = logger.getChild(__name__)
@ -11,10 +11,21 @@ logger = logger.getChild(__name__)
SUDO_BIN = pathlib.Path('/usr/bin/sudo')
if not SUDO_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND sudo!!!')
NMCLI_BIN = pathlib.Path('/usr/bin/nmcli')
if not NMCLI_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND nmcli!!!')
CPUFREQ_INFO_BIN = pathlib.Path('/usr/bin/cpufreq-info')
if not CPUFREQ_INFO_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND cpufreq-info!!!')
CPUFREQ_SET_BIN = pathlib.Path('/usr/bin/cpufreq-set')
if not CPUFREQ_SET_BIN.is_file() and not PYTEST:
logger.error('COULD NOT FIND cpufreq-set!!!')
POWER_SAVE_FREQ = 'powersave' # noqa
class HotspotStatus(enum.Enum):
disconnected = enum.auto() # Radio is on, but Hotspot is not connected.
@ -65,3 +76,45 @@ def disable_hotspot():
cmd = (SUDO_BIN, NMCLI_BIN, 'radio', 'wifi', 'off')
subprocess.check_call(cmd)
return True
class GovernorStatus(enum.Enum):
ondemand = enum.auto()
powersave = enum.auto()
unknown = enum.auto()
# These are unused by WROLPi.
# performance = enum.auto()
# schedutil = enum.auto()
# userspace = enum.auto()
# conservative = enum.auto()
GOVERNOR_MAP = {
'The governor "ondemand" may decide ': GovernorStatus.ondemand,
'The governor "powersave" may decide ': GovernorStatus.powersave,
}
def throttle_status() -> GovernorStatus:
cmd = (CPUFREQ_INFO_BIN,)
output = subprocess.check_output(cmd)
output = output.decode().strip()
for line in output.splitlines():
for governor, status in GOVERNOR_MAP.items():
if governor in line:
return status
return GovernorStatus.unknown
def throttle_cpu_on() -> bool:
cmd = (SUDO_BIN, CPUFREQ_SET_BIN, '-g', POWER_SAVE_FREQ)
subprocess.check_call(cmd)
return True
def throttle_cpu_off() -> bool:
cmd = (SUDO_BIN, CPUFREQ_SET_BIN, '-g', DEFAULT_CPU_FREQUENCY)
subprocess.check_call(cmd)
return True

View file

@ -15,7 +15,6 @@ from sanic_ext import validate
from sanic_ext.extensions.openapi import openapi
from wrolpi import admin
from wrolpi.admin import HotspotStatus
from wrolpi.common import set_sanic_url_parts, logger, get_config, wrol_mode_enabled, save_settings_config, \
Base, get_media_directory, wrol_mode_check, native_only
from wrolpi.dates import set_timezone
@ -244,6 +243,38 @@ async def hotspot_off(_: Request):
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
@root_api.get('/throttle')
@openapi.description('Get status of CPU throttling')
@native_only
async def throttle_status(_: Request):
status = admin.throttle_status()
# Get the name of the enum.
status = status.name
ret = dict(status=status)
return json_response(ret)
@root_api.post('/throttle/on')
@openapi.description('Turn on CPU throttling')
@native_only
async def throttle_on(_: Request):
result = admin.throttle_cpu_on()
if result:
return response.empty()
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
@root_api.post('/throttle/off')
@openapi.description('Turn off CPU throttling')
@native_only
async def throttle_off(_: Request):
result = admin.throttle_cpu_off()
if result:
return response.empty()
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):

View file

@ -61,3 +61,19 @@ def test_enable_hotspot_connected():
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'device', 'wifi', 'hotspot', 'ifname',
'wlan0', 'ssid', 'WROLPi', 'password', 'wrolpi hotspot'))]
)
def test_throttle_on():
with mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
assert admin.throttle_cpu_on()
mock_subprocess.check_call.assert_called_once_with(
(PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/cpufreq-set'), '-g', 'powersave')
)
def test_throttle_off():
with mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
assert admin.throttle_cpu_off()
mock_subprocess.check_call.assert_called_once_with(
(PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/cpufreq-set'), '-g', 'ondemand')
)

View file

@ -19,6 +19,7 @@ LAST_MODIFIED_DATE_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
DATE_FORMAT = '%Y-%M-%d'
DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S'
DATETIME_FORMAT_MS = '%Y-%m-%d %H:%M:%S.%f'
DEFAULT_CPU_FREQUENCY = 'ondemand'
DEFAULT_FILE_PERMISSIONS = 0o644