Compare commits
2 Commits
ash/add-pl
...
ash/workou
| Author | SHA1 | Date | |
|---|---|---|---|
| f3b405959e | |||
| e9fd4cf0d6 |
@@ -1,30 +1,46 @@
|
|||||||
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
# Do not modify this file! It was generated by ‘nixos-generate-config’
|
||||||
# and may be overwritten by future invocations. Please make changes
|
# and may be overwritten by future invocations. Please make changes
|
||||||
# to /etc/nixos/configuration.nix instead.
|
# to /etc/nixos/configuration.nix instead.
|
||||||
{ config, lib, pkgs, modulesPath, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
{
|
{
|
||||||
imports =
|
imports = [
|
||||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "thunderbolt" "uas" "usbhid" "usb_storage" "sd_mod" ];
|
boot.initrd.availableKernelModules = [
|
||||||
|
"nvme"
|
||||||
|
"xhci_pci"
|
||||||
|
"thunderbolt"
|
||||||
|
"usbhid"
|
||||||
|
"uas"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
boot.initrd.kernelModules = [ ];
|
boot.initrd.kernelModules = [ ];
|
||||||
boot.kernelModules = [ "kvm-amd" ];
|
boot.kernelModules = [ "kvm-amd" ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|
||||||
fileSystems."/" =
|
fileSystems."/" = {
|
||||||
{ device = "/dev/disk/by-uuid/59c0df78-c6fa-415d-8592-13547a3fada6";
|
device = "/dev/disk/by-uuid/0e75a66e-6c9e-471e-8bd2-fee7b27b74a1";
|
||||||
fsType = "btrfs";
|
fsType = "btrfs";
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSystems."/boot" =
|
fileSystems."/boot" = {
|
||||||
{ device = "/dev/disk/by-uuid/DC66-D04C";
|
device = "/dev/disk/by-uuid/9E2C-F187";
|
||||||
fsType = "vfat";
|
fsType = "vfat";
|
||||||
options = [ "fmask=0022" "dmask=0022" ];
|
options = [
|
||||||
};
|
"fmask=0022"
|
||||||
|
"dmask=0022"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
swapDevices = [ ];
|
swapDevices = [ { device = "/.swapfile"; } ];
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
from datetime import date
|
||||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import psutil
|
import psutil
|
||||||
@@ -22,6 +23,9 @@ ALLOWED_APPS = {
|
|||||||
'kodi': 'kodi'
|
'kodi': 'kodi'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Workout card base URL
|
||||||
|
WORKOUT_CARD_BASE_URL = 'https://ogle.fyi/ash/workout'
|
||||||
|
|
||||||
def is_app_running(app_name):
|
def is_app_running(app_name):
|
||||||
"""Check if an application is already running, returns (is_running, pid)"""
|
"""Check if an application is already running, returns (is_running, pid)"""
|
||||||
command = ALLOWED_APPS.get(app_name)
|
command = ALLOWED_APPS.get(app_name)
|
||||||
@@ -88,7 +92,10 @@ class AppLauncherHandler(BaseHTTPRequestHandler):
|
|||||||
response = {
|
response = {
|
||||||
'status': 'running',
|
'status': 'running',
|
||||||
'available_apps': list(ALLOWED_APPS.keys()),
|
'available_apps': list(ALLOWED_APPS.keys()),
|
||||||
'usage': 'POST /launch/<app_name> to launch an application'
|
'endpoints': {
|
||||||
|
'POST /launch/<app_name>': 'Launch an application (optional JSON body: {"args": ["url"]})',
|
||||||
|
'POST /workout': 'Open today\'s workout card in Firefox'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.wfile.write(json.dumps(response, indent=2).encode())
|
self.wfile.write(json.dumps(response, indent=2).encode())
|
||||||
else:
|
else:
|
||||||
@@ -101,8 +108,21 @@ class AppLauncherHandler(BaseHTTPRequestHandler):
|
|||||||
if len(path_parts) == 2 and path_parts[0] == 'launch':
|
if len(path_parts) == 2 and path_parts[0] == 'launch':
|
||||||
app_name = path_parts[1]
|
app_name = path_parts[1]
|
||||||
self.launch_app(app_name)
|
self.launch_app(app_name)
|
||||||
|
elif len(path_parts) == 1 and path_parts[0] == 'workout':
|
||||||
|
self.open_workout_card()
|
||||||
else:
|
else:
|
||||||
self.send_error(404, "Invalid endpoint. Use /launch/<app_name>")
|
self.send_error(404, "Invalid endpoint. Use /launch/<app_name> or /workout")
|
||||||
|
|
||||||
|
def read_post_body(self):
|
||||||
|
"""Read and parse JSON body from POST request, return dict or empty dict."""
|
||||||
|
content_length = int(self.headers.get('Content-Length', 0))
|
||||||
|
if content_length > 0:
|
||||||
|
try:
|
||||||
|
body = self.rfile.read(content_length)
|
||||||
|
return json.loads(body.decode('utf-8'))
|
||||||
|
except (json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||||
|
logger.warning(f"Failed to parse POST body as JSON: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
def launch_app(self, app_name):
|
def launch_app(self, app_name):
|
||||||
if app_name not in ALLOWED_APPS:
|
if app_name not in ALLOWED_APPS:
|
||||||
@@ -111,30 +131,44 @@ class AppLauncherHandler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
command = ALLOWED_APPS[app_name]
|
command = ALLOWED_APPS[app_name]
|
||||||
|
|
||||||
|
# Read optional args from POST body
|
||||||
|
body = self.read_post_body()
|
||||||
|
extra_args = body.get('args', [])
|
||||||
|
# Validate args are strings
|
||||||
|
if not isinstance(extra_args, list) or not all(isinstance(a, str) for a in extra_args):
|
||||||
|
self.send_error(400, "'args' must be a list of strings")
|
||||||
|
return
|
||||||
|
|
||||||
|
full_command = [command] + extra_args
|
||||||
|
|
||||||
# Check if app is already running
|
# Check if app is already running
|
||||||
is_running, existing_pid = is_app_running(app_name)
|
is_running, existing_pid = is_app_running(app_name)
|
||||||
if is_running:
|
if is_running:
|
||||||
logger.info(f"Application {app_name} is already running (PID: {existing_pid}), skipping launch")
|
# If extra args provided, still launch a new instance (e.g., open a URL)
|
||||||
self.send_response(200)
|
if extra_args:
|
||||||
self.send_header('Content-type', 'application/json')
|
logger.info(f"Application {app_name} already running (PID: {existing_pid}), but extra args provided — launching new instance")
|
||||||
self.end_headers()
|
else:
|
||||||
response = {
|
logger.info(f"Application {app_name} is already running (PID: {existing_pid}), skipping launch")
|
||||||
'status': 'success',
|
self.send_response(200)
|
||||||
'message': f'{app_name} is already running',
|
self.send_header('Content-type', 'application/json')
|
||||||
'pid': existing_pid,
|
self.end_headers()
|
||||||
'already_running': True
|
response = {
|
||||||
}
|
'status': 'success',
|
||||||
self.wfile.write(json.dumps(response).encode())
|
'message': f'{app_name} is already running',
|
||||||
return
|
'pid': existing_pid,
|
||||||
|
'already_running': True
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Launch the application in the background
|
# Launch the application in the background
|
||||||
# Ensure we have the proper environment for GUI apps
|
# Ensure we have the proper environment for GUI apps
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
|
|
||||||
logger.info(f"Launching application: {command}")
|
logger.info(f"Launching application: {' '.join(full_command)}")
|
||||||
process = subprocess.Popen(
|
process = subprocess.Popen(
|
||||||
[command],
|
full_command,
|
||||||
env=env,
|
env=env,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL,
|
stderr=subprocess.DEVNULL,
|
||||||
@@ -159,12 +193,50 @@ class AppLauncherHandler(BaseHTTPRequestHandler):
|
|||||||
logger.error(f"Error launching {command}: {e}")
|
logger.error(f"Error launching {command}: {e}")
|
||||||
self.send_error(500, f"Failed to launch {app_name}: {str(e)}")
|
self.send_error(500, f"Failed to launch {app_name}: {str(e)}")
|
||||||
|
|
||||||
|
def open_workout_card(self):
|
||||||
|
"""Open today's workout card in Firefox."""
|
||||||
|
today = date.today().strftime('%Y-%m-%d')
|
||||||
|
url = f"{WORKOUT_CARD_BASE_URL}/{today}.html"
|
||||||
|
logger.info(f"Opening workout card for {today}: {url}")
|
||||||
|
|
||||||
|
# Always launch Firefox with the URL, even if already running
|
||||||
|
command = ALLOWED_APPS['firefox']
|
||||||
|
env = os.environ.copy()
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
[command, url],
|
||||||
|
env=env,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
start_new_session=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
response = {
|
||||||
|
'status': 'success',
|
||||||
|
'message': f'Opened workout card in Firefox',
|
||||||
|
'url': url,
|
||||||
|
'pid': process.pid
|
||||||
|
}
|
||||||
|
self.wfile.write(json.dumps(response).encode())
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error(f"Firefox not found: {command}")
|
||||||
|
self.send_error(500, "Firefox not found on system")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error launching Firefox with workout card: {e}")
|
||||||
|
self.send_error(500, f"Failed to open workout card: {str(e)}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8081
|
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8081
|
||||||
|
|
||||||
server = HTTPServer(('0.0.0.0', port), AppLauncherHandler)
|
server = HTTPServer(('0.0.0.0', port), AppLauncherHandler)
|
||||||
logger.info(f"App launcher server starting on port {port}")
|
logger.info(f"App launcher server starting on port {port}")
|
||||||
logger.info(f"Available applications: {list(ALLOWED_APPS.keys())}")
|
logger.info(f"Available applications: {list(ALLOWED_APPS.keys())}")
|
||||||
|
logger.info(f"Workout card URL: {WORKOUT_CARD_BASE_URL}/<date>.html")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
server.serve_forever()
|
server.serve_forever()
|
||||||
|
|||||||
Reference in New Issue
Block a user