Compare commits
2 Commits
fix/mu4e-e
...
boxy-app-l
| Author | SHA1 | Date | |
|---|---|---|---|
| f8ee011d27 | |||
| 4a4ea6316d |
104
packages/app-launcher-server/app-launcher-server.py
Normal file
104
packages/app-launcher-server/app-launcher-server.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Allowlisted applications that can be launched
|
||||
ALLOWED_APPS = {
|
||||
'firefox': 'firefox',
|
||||
'kodi': 'kodi'
|
||||
}
|
||||
|
||||
class AppLauncherHandler(BaseHTTPRequestHandler):
|
||||
def log_message(self, format, *args):
|
||||
logger.info(format % args)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
response = {
|
||||
'status': 'running',
|
||||
'available_apps': list(ALLOWED_APPS.keys()),
|
||||
'usage': 'POST /launch/<app_name> to launch an application'
|
||||
}
|
||||
self.wfile.write(json.dumps(response, indent=2).encode())
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
def do_POST(self):
|
||||
parsed_path = urlparse(self.path)
|
||||
path_parts = parsed_path.path.strip('/').split('/')
|
||||
|
||||
if len(path_parts) == 2 and path_parts[0] == 'launch':
|
||||
app_name = path_parts[1]
|
||||
self.launch_app(app_name)
|
||||
else:
|
||||
self.send_error(404, "Invalid endpoint. Use /launch/<app_name>")
|
||||
|
||||
def launch_app(self, app_name):
|
||||
if app_name not in ALLOWED_APPS:
|
||||
self.send_error(400, f"Application '{app_name}' not allowed. Available apps: {list(ALLOWED_APPS.keys())}")
|
||||
return
|
||||
|
||||
command = ALLOWED_APPS[app_name]
|
||||
|
||||
try:
|
||||
# Launch the application in the background
|
||||
# Ensure we have the proper environment for GUI apps
|
||||
env = os.environ.copy()
|
||||
|
||||
logger.info(f"Launching application: {command}")
|
||||
process = subprocess.Popen(
|
||||
[command],
|
||||
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'Successfully launched {app_name}',
|
||||
'pid': process.pid
|
||||
}
|
||||
self.wfile.write(json.dumps(response).encode())
|
||||
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Application not found: {command}")
|
||||
self.send_error(500, f"Application '{app_name}' not found on system")
|
||||
except Exception as e:
|
||||
logger.error(f"Error launching {command}: {e}")
|
||||
self.send_error(500, f"Failed to launch {app_name}: {str(e)}")
|
||||
|
||||
def main():
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8081
|
||||
|
||||
server = HTTPServer(('0.0.0.0', port), AppLauncherHandler)
|
||||
logger.info(f"App launcher server starting on port {port}")
|
||||
logger.info(f"Available applications: {list(ALLOWED_APPS.keys())}")
|
||||
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Server shutting down...")
|
||||
server.server_close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
5
packages/app-launcher-server/default.nix
Normal file
5
packages/app-launcher-server/default.nix
Normal file
@@ -0,0 +1,5 @@
|
||||
{ pkgs }:
|
||||
|
||||
pkgs.writeShellScriptBin "app-launcher-server" ''
|
||||
exec ${pkgs.python3}/bin/python3 ${./app-launcher-server.py} "$@"
|
||||
''
|
||||
@@ -2,4 +2,5 @@
|
||||
{
|
||||
vulkanHDRLayer = pkgs.callPackage ./vulkan-hdr-layer {};
|
||||
tea-rbw = pkgs.callPackage ./tea-rbw {};
|
||||
app-launcher-server = pkgs.callPackage ./app-launcher-server {};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ with lib;
|
||||
|
||||
let
|
||||
cfg = config.roles.kodi;
|
||||
customPkgs = pkgs.callPackage ../../packages {};
|
||||
in
|
||||
{
|
||||
options.roles.kodi = {
|
||||
@@ -14,6 +15,18 @@ in
|
||||
wayland = mkOption {
|
||||
default = true;
|
||||
};
|
||||
appLauncherServer = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "Enable HTTP app launcher server for remote control";
|
||||
};
|
||||
port = mkOption {
|
||||
type = types.int;
|
||||
default = 8081;
|
||||
description = "Port for the app launcher HTTP server";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -33,17 +46,35 @@ in
|
||||
};
|
||||
|
||||
networking.firewall = {
|
||||
allowedTCPPorts = [ 8080 ];
|
||||
allowedTCPPorts = [ 8080 ] ++ optional cfg.appLauncherServer.enable cfg.appLauncherServer.port;
|
||||
allowedUDPPorts = [ 8080 ];
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
kodiPkg
|
||||
wget
|
||||
];
|
||||
firefox
|
||||
] ++ optional cfg.appLauncherServer.enable customPkgs.app-launcher-server;
|
||||
|
||||
programs.kdeconnect.enable = true;
|
||||
|
||||
systemd.user.services = mkIf cfg.appLauncherServer.enable {
|
||||
app-launcher-server = {
|
||||
description = "HTTP App Launcher Server";
|
||||
wantedBy = [ "graphical-session.target" ];
|
||||
after = [ "graphical-session.target" ];
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${customPkgs.app-launcher-server}/bin/app-launcher-server ${toString cfg.appLauncherServer.port}";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
Environment = [
|
||||
"PATH=${pkgs.firefox}/bin:${kodiPkg}/bin:/run/current-system/sw/bin"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
services = if cfg.autologin then {
|
||||
displayManager = {
|
||||
autoLogin.enable = true;
|
||||
|
||||
Reference in New Issue
Block a user