This commit is contained in:
Your Name
2026-05-16 09:17:27 +02:00
parent 1a0760da68
commit 6f69ebee46
4 changed files with 890 additions and 0 deletions
+219
View File
@@ -0,0 +1,219 @@
Listening on http://0.0.0.0:8080
Config: commands.json
192.168.178.49 [16/May/2026 01:29:20] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 01:36:27] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 01:36:30] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 01:37:00] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 01:44:07] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:44:22] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:44:52] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:45:22] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:45:52] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:46:08] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:46:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:46:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:47:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:47:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:48:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:48:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:49:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:49:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:50:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:50:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:51:27] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:51:57] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:52:10] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:52:15] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:52:41] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:53:11] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:53:29] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:53:33] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:53:36] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:54:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:54:58] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 01:55:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:55:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:56:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:56:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:57:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:57:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:58:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:58:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:59:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 01:59:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:00:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:00:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:01:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:01:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:02:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:02:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:03:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:03:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:04:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:04:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:05:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:05:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:06:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:06:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:07:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:07:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:08:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:08:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:09:17] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:09:47] "GET /run/default-color HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:09:59] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:10:07] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:10:37] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:11:07] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:11:37] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:12:07] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:12:38] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:12:59] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:13:29] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:13:59] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:14:24] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:14:54] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:15:24] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:15:54] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:16:00] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:16:04] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:16:34] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:17:04] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:17:34] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:18:04] "POST /run/default-color HTTP/1.1" 403 -
192.168.178.49 [16/May/2026 02:18:34] "POST /run/default-color HTTP/1.1" 403 -
request_id=08bc90cc-8f80-4670-91fc-34d665e5c815 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:18:59] "POST /run/default-color HTTP/1.1" 200 -
request_id=0779ebeb-ea40-485b-91bf-6dd73b3d8987 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:19:29] "POST /run/default-color HTTP/1.1" 200 -
request_id=7de7058d-9150-4fd8-b101-46dbbe00eaab command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:19:56] "POST /run/default-color HTTP/1.1" 200 -
request_id=82cdc6b3-4a66-4eca-b616-558af970abbe command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:20:26] "POST /run/default-color HTTP/1.1" 200 -
request_id=c12ad6b0-b449-41ca-bdd6-673fad54162b command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:20:56] "POST /run/default-color HTTP/1.1" 200 -
request_id=96c834f2-c3c9-4ffc-97df-9e1f2d9acc49 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:21:26] "POST /run/default-color HTTP/1.1" 200 -
request_id=35ae01f4-2402-4e6e-9620-db0503395af5 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:21:56] "POST /run/default-color HTTP/1.1" 200 -
request_id=60fa399b-792e-4710-bc5e-5fba714a87d7 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:22:26] "POST /run/default-color HTTP/1.1" 200 -
request_id=be4616cd-9033-4f83-934d-928a62352f5f command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:22:56] "POST /run/default-color HTTP/1.1" 200 -
request_id=c4667876-9088-4073-a1b1-aa512800e658 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:23:26] "POST /run/default-color HTTP/1.1" 200 -
request_id=e92e3464-6d07-49d8-a575-413a9227569b command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:23:42] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:23:42] "POST /run/default-color HTTP/1.1" 200 -
request_id=13611a6f-57d6-4e0f-832b-93feed7e1789 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:24:12] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:24:12] "POST /run/default-color HTTP/1.1" 200 -
request_id=39e49f9e-e1ed-4c3e-8e3d-a83b2653ea73 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:24:35] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:24:35] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:25:05] "POST /run/aus HTTP/1.1" 404 -
request_id=a3f3deae-7d43-4cea-8e09-a35ff27a5f93 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:25:05] "POST /run/default-color HTTP/1.1" 200 -
request_id=153f9708-d23f-4ecb-ae6a-966cc854a3bb command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:25:35] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:25:35] "POST /run/default-color HTTP/1.1" 200 -
request_id=8416c145-cd05-43cb-8f37-f1dc58b83cc5 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:26:05] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:26:05] "POST /run/default-color HTTP/1.1" 200 -
request_id=8b5db5b6-fa6b-4678-9057-8c2f9627cd43 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:26:35] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:26:35] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:27:05] "POST /run/aus HTTP/1.1" 404 -
request_id=8dcf8526-654a-436d-94b4-398d5d743c43 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:27:05] "POST /run/default-color HTTP/1.1" 200 -
request_id=02b217d3-efb4-4c27-8617-ae0b3f1bf63a command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:27:35] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:27:35] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:28:05] "POST /run/aus HTTP/1.1" 404 -
request_id=58181ed3-c4c0-4313-ba82-c9bb81c8a382 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:28:05] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:28:35] "POST /run/aus HTTP/1.1" 404 -
request_id=8faa0c58-d86c-4ee1-a94d-725810650d0a command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:28:35] "POST /run/default-color HTTP/1.1" 200 -
request_id=aaa86e58-08c9-4fe3-a9d6-6be11bdbea9b command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:29:05] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:29:05] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:29:35] "POST /run/aus HTTP/1.1" 404 -
request_id=cbb264f0-873b-4643-8eb9-c2951aa2dabf command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:29:35] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:30:05] "POST /run/aus HTTP/1.1" 404 -
request_id=e54719c6-a2f6-4f04-ab26-7bd118608871 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:30:05] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:30:26] "POST /run/default-color HTTP/1.1" 400 -
192.168.178.49 [16/May/2026 02:30:28] "POST /run/default-color HTTP/1.1" 400 -
request_id=ce11713a-fc90-4a8a-9926-809eaf3cb3a1 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:30:35] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:30:35] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:31:05] "POST /run/aus HTTP/1.1" 404 -
request_id=5e6843be-7f8f-4753-b22e-6c3df88d92fa command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:31:05] "POST /run/default-color HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 02:31:10] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:32:47] "POST /run/aus HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 02:36:07] "POST /run/off HTTP/1.1" 400 -
192.168.178.49 [16/May/2026 02:36:20] "POST /run/on HTTP/1.1" 400 -
192.168.178.49 [16/May/2026 02:36:42] "POST /run/default-color HTTP/1.1" 400 -
192.168.178.49 [16/May/2026 02:40:07] "POST /run/default-color HTTP/1.1" 400 -
request_id=0120b342-b821-431b-8c2f-e61c9ccd1779 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:40:38] "POST /run/default-color HTTP/1.1" 200 -
request_id=f0e7ca6c-7a71-4cea-9b28-1e74b503baa8 command_id=off command=['ir-ctl', '--send', '/home/timm/IRSender/off.txt']
192.168.178.49 [16/May/2026 02:41:22] "POST /run/off HTTP/1.1" 200 -
request_id=57597df5-e9dd-4276-b277-bc51fa13e32f command_id=on command=['ir-ctl', '--send', '/home/timm/IRSender/on.txt']
192.168.178.49 [16/May/2026 02:41:23] "POST /run/on HTTP/1.1" 200 -
request_id=f9bf9d3b-7bc2-4995-b287-703d02ae78bf command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:41:24] "POST /run/default-color HTTP/1.1" 200 -
request_id=86f7a4f2-0f4f-4fc1-8da2-d7eb9f75beff command_id=off command=['ir-ctl', '--send', '/home/timm/IRSender/off.txt']
192.168.178.49 [16/May/2026 02:41:38] "POST /run/off HTTP/1.1" 200 -
request_id=3a1b50a2-634a-4382-a10b-e1731fcaff81 command_id=on command=['ir-ctl', '--send', '/home/timm/IRSender/on.txt']
192.168.178.49 [16/May/2026 02:42:04] "POST /run/on HTTP/1.1" 200 -
request_id=15d455b7-5b8e-441e-a65b-96f0e97ec31a command_id=on command=['ir-ctl', '--send', '/home/timm/IRSender/on.txt']
192.168.178.49 [16/May/2026 02:42:04] "POST /run/on HTTP/1.1" 200 -
request_id=ad0500f8-f2b9-43a7-92e6-459c73240b44 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:42:14] "POST /run/default-color HTTP/1.1" 200 -
request_id=b6bf3486-902a-4fe8-94f2-3b13ef421770 command_id=default-color command=['ir-ctl', '--send', '/home/timm/IRSender/defaultColor.txt']
192.168.178.49 [16/May/2026 02:42:14] "POST /run/default-color HTTP/1.1" 200 -
Listening on http://0.0.0.0:8080
Config: commands.json
192.168.178.49 [16/May/2026 08:21:51] "GET / HTTP/1.1" 200 -
192.168.178.49 [16/May/2026 08:21:51] "GET /favicon.ico HTTP/1.1" 404 -
192.168.178.49 [16/May/2026 08:21:53] "GET / HTTP/1.1" 200 -
request_id=ac6710b9-e33b-4df7-8ff4-f94b4f98b918 command_id=brighter command=['ir-ctl', '--send', '/home/timm/IRSender/brighter.txt']
192.168.178.49 [16/May/2026 08:21:55] "POST /web/run/brighter HTTP/1.1" 200 -
request_id=de27675f-156c-44af-b246-be3eeecb0e3c command_id=darker command=['ir-ctl', '--send', '/home/timm/IRSender/darker.txt']
192.168.178.49 [16/May/2026 08:21:59] "POST /web/run/darker HTTP/1.1" 200 -
request_id=28987032-93b2-4635-a5c9-37793334fbdc command_id=darker command=['ir-ctl', '--send', '/home/timm/IRSender/darker.txt']
192.168.178.49 [16/May/2026 08:22:01] "POST /web/run/darker HTTP/1.1" 200 -
request_id=39fbf168-85d4-4b9e-8101-a63279eb9346 command_id=darker command=['ir-ctl', '--send', '/home/timm/IRSender/darker.txt']
192.168.178.49 [16/May/2026 08:22:02] "POST /web/run/darker HTTP/1.1" 200 -
request_id=ad03bc03-fb6e-4498-b6e9-de220201eace command_id=fade command=['ir-ctl', '--send', '/home/timm/IRSender/fade.txt']
192.168.178.49 [16/May/2026 08:22:06] "POST /web/run/fade HTTP/1.1" 200 -
request_id=9d254a73-f211-4d00-bae4-2380151b2170 command_id=green command=['ir-ctl', '--send', '/home/timm/IRSender/green.txt']
192.168.178.49 [16/May/2026 08:22:09] "POST /web/run/green HTTP/1.1" 200 -
request_id=4624c540-7083-470f-9045-3bf8f4a36023 command_id=off command=['ir-ctl', '--send', '/home/timm/IRSender/off.txt']
192.168.178.49 [16/May/2026 08:22:15] "POST /web/run/off HTTP/1.1" 200 -
request_id=ebdf4c20-645b-4e14-9424-c763c848144a command_id=on command=['ir-ctl', '--send', '/home/timm/IRSender/on.txt']
192.168.178.49 [16/May/2026 08:22:18] "POST /web/run/on HTTP/1.1" 200 -
request_id=91d5b868-8ac8-44c7-aeab-e3a4f53cfec7 command_id=red command=['ir-ctl', '--send', '/home/timm/IRSender/red.txt']
192.168.178.49 [16/May/2026 08:22:22] "POST /web/run/red HTTP/1.1" 200 -
request_id=a47da5e7-6ba1-4edc-8234-1693c0cc8291 command_id=purple command=['ir-ctl', '--send', '/home/timm/IRSender/purple.txt']
192.168.178.49 [16/May/2026 08:22:24] "POST /web/run/purple HTTP/1.1" 200 -
request_id=a8af7d54-0341-453f-94c6-5528d006392b command_id=smooth command=['ir-ctl', '--send', '/home/timm/IRSender/smooth.txt']
192.168.178.49 [16/May/2026 08:22:27] "POST /web/run/smooth HTTP/1.1" 200 -
request_id=f75cf627-135b-4d2c-9728-040707de6d87 command_id=white command=['ir-ctl', '--send', '/home/timm/IRSender/white.txt']
192.168.178.49 [16/May/2026 08:22:32] "POST /web/run/white HTTP/1.1" 200 -
request_id=24de491a-786d-4a75-840a-7826310055e6 command_id=yellow command=['ir-ctl', '--send', '/home/timm/IRSender/yellow.txt']
192.168.178.49 [16/May/2026 08:22:35] "POST /web/run/yellow HTTP/1.1" 200 -
request_id=45256d4b-c898-4b6e-a34d-6920fa35d6b5 command_id=white command=['ir-ctl', '--send', '/home/timm/IRSender/white.txt']
192.168.178.49 [16/May/2026 08:22:39] "POST /web/run/white HTTP/1.1" 200 -
Listening on http://0.0.0.0:8080
Config: commands.json
192.168.178.149 [16/May/2026 09:16:43] "GET /apple-touch-icon-precomposed.png HTTP/1.1" 404 -
192.168.178.149 [16/May/2026 09:16:43] "GET /apple-touch-icon.png HTTP/1.1" 404 -
192.168.178.149 [16/May/2026 09:16:43] "GET /favicon.ico HTTP/1.1" 404 -
192.168.178.149 [16/May/2026 09:16:43] "GET / HTTP/1.1" 200 -
192.168.178.149 [16/May/2026 09:16:47] "GET / HTTP/1.1" 200 -
192.168.178.149 [16/May/2026 09:16:50] "GET /favicon.ico HTTP/1.1" 404 -
+168
View File
@@ -0,0 +1,168 @@
{
"host": "0.0.0.0",
"port": 8080,
"max_body_bytes": 65536,
"commands": {
"blue": {
"description": "Send IR command blue.txt",
"web_title": "Blue",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/blue.txt"],
"args": {}
},
"brighter": {
"description": "Send IR command brighter.txt",
"web_title": "Brighter",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/brighter.txt"],
"args": {}
},
"darker": {
"description": "Send IR command darker.txt",
"web_title": "Darker",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/darker.txt"],
"args": {}
},
"default-color": {
"description": "Send IR command defaultColor.txt",
"web_title": "Default Color",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/defaultColor.txt"],
"args": {}
},
"fade": {
"description": "Send IR command fade.txt",
"web_title": "Fade",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/fade.txt"],
"args": {}
},
"green": {
"description": "Send IR command green.txt",
"web_title": "Green",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/green.txt"],
"args": {}
},
"licht-aus": {
"description": "Send IR command LichtAus.txt",
"web_title": "Licht Aus",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/LichtAus.txt"],
"args": {}
},
"licht-ein": {
"description": "Send IR command LichtEin.txt",
"web_title": "Licht Ein",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/LichtEin.txt"],
"args": {}
},
"nachtlicht": {
"description": "Send IR command Nachtlicht.txt",
"web_title": "Nachtlicht",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/Nachtlicht.txt"],
"args": {}
},
"off": {
"description": "Send IR command off.txt",
"web_title": "Off",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/off.txt"],
"args": {}
},
"on": {
"description": "Send IR command on.txt",
"web_title": "On",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/on.txt"],
"args": {}
},
"purple": {
"description": "Send IR command purple.txt",
"web_title": "Purple",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/purple.txt"],
"args": {}
},
"red": {
"description": "Send IR command red.txt",
"web_title": "Red",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/red.txt"],
"args": {}
},
"smooth": {
"description": "Send IR command smooth.txt",
"web_title": "Smooth",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/smooth.txt"],
"args": {}
},
"white": {
"description": "Send IR command white.txt",
"web_title": "White",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/white.txt"],
"args": {}
},
"yellow": {
"description": "Send IR command yellow.txt",
"web_title": "Yellow",
"web_enabled": true,
"api_key": "change-this-global-api-key",
"timeout_seconds": 10,
"command": ["ir-ctl", "--send", "/home/timm/IRSender/yellow.txt"],
"args": {}
}
}
}
Executable
+499
View File
@@ -0,0 +1,499 @@
#!/usr/bin/env python3
import html
import json
import re
import secrets
import subprocess
import sys
import time
import uuid
from pathlib import Path
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import urlparse, parse_qs
CONFIG_PATH = Path(sys.argv[1] if len(sys.argv) > 1 else "./commands.json")
WEB_CSRF_TOKEN = secrets.token_urlsafe(32)
def load_config():
with CONFIG_PATH.open("r", encoding="utf-8") as f:
cfg = json.load(f)
cfg.setdefault("host", "0.0.0.0")
cfg.setdefault("port", 8080)
cfg.setdefault("max_body_bytes", 65536)
cfg.setdefault("commands", {})
if not isinstance(cfg["commands"], dict) or not cfg["commands"]:
raise RuntimeError("No commands configured")
validate_config(cfg)
return cfg
def validate_config(cfg):
for command_id, command in cfg["commands"].items():
if not re.fullmatch(r"[a-zA-Z0-9_-]{1,64}", command_id):
raise RuntimeError(f"Invalid command id: {command_id}")
if "command" not in command or not isinstance(command["command"], list) or not command["command"]:
raise RuntimeError(f"Command '{command_id}' needs a non-empty command list")
command.setdefault("args", {})
command.setdefault("timeout_seconds", 30)
if int(command["timeout_seconds"]) <= 0:
raise RuntimeError(f"Command '{command_id}' has invalid timeout_seconds")
if not command.get("web_enabled", False):
if not command.get("api_key") or len(command["api_key"]) < 16:
raise RuntimeError(f"Command '{command_id}' needs api_key with at least 16 chars")
placeholders = set()
for part in command["command"]:
for match in re.findall(r"{([a-zA-Z0-9_]+)}", str(part)):
placeholders.add(match)
missing_args = placeholders - set(command["args"].keys())
if missing_args:
raise RuntimeError(
f"Command '{command_id}' uses undefined args: {', '.join(sorted(missing_args))}"
)
for arg_name, rule in command["args"].items():
if not re.fullmatch(r"[a-zA-Z0-9_]{1,64}", arg_name):
raise RuntimeError(f"Command '{command_id}' has invalid arg name: {arg_name}")
pattern = rule.get("pattern", r"^[a-zA-Z0-9_.:/ -]{0,128}$")
try:
re.compile(pattern)
except re.error as exc:
raise RuntimeError(
f"Command '{command_id}' arg '{arg_name}' has invalid regex: {exc}"
)
if command.get("web_enabled", False) and rule.get("required", False):
if "web_default" not in rule and "default" not in rule:
raise RuntimeError(
f"Web command '{command_id}' needs web_default/default for required arg '{arg_name}'"
)
CONFIG = load_config()
class ApiHandler(BaseHTTPRequestHandler):
server_version = "RpiScriptApi/1.3"
def log_message(self, fmt, *args):
print(
"%s [%s] %s"
% (self.client_address[0], self.log_date_time_string(), fmt % args)
)
def send_json(self, status, payload):
data = json.dumps(payload, indent=2).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.send_security_headers()
self.end_headers()
self.wfile.write(data)
def send_html(self, status, body):
data = body.encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(data)))
self.send_security_headers()
self.end_headers()
self.wfile.write(data)
def send_security_headers(self):
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("X-Frame-Options", "DENY")
self.send_header("Referrer-Policy", "no-referrer")
self.send_header("Cache-Control", "no-store")
self.send_header(
"Content-Security-Policy",
"default-src 'self'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'"
)
def read_body_raw(self):
try:
length = int(self.headers.get("Content-Length", "0"))
except ValueError:
raise ValueError("invalid content-length")
if length > int(CONFIG["max_body_bytes"]):
raise ValueError("request body too large")
if length == 0:
return b""
return self.rfile.read(length)
def read_json_body(self):
raw = self.read_body_raw()
if not raw:
return {}
try:
return json.loads(raw.decode("utf-8"))
except json.JSONDecodeError as exc:
raise ValueError(f"invalid json: {exc}")
def read_form_body(self):
raw = self.read_body_raw()
if not raw:
return {}
content_type = self.headers.get("Content-Type", "")
if "application/x-www-form-urlencoded" not in content_type:
raise ValueError("invalid form content-type")
parsed = parse_qs(raw.decode("utf-8"), keep_blank_values=True)
return {k: v[0] if v else "" for k, v in parsed.items()}
def do_GET(self):
path = urlparse(self.path).path
if path == "/":
self.send_html(200, render_index())
return
if path == "/health":
self.send_json(200, {"ok": True})
return
if path == "/commands":
self.send_json(200, list_commands())
return
self.send_json(404, {"ok": False, "error": "not found"})
def do_POST(self):
path = urlparse(self.path).path
api_match = re.fullmatch(r"/run/([a-zA-Z0-9_-]+)", path)
web_match = re.fullmatch(r"/web/run/([a-zA-Z0-9_-]+)", path)
if api_match:
self.handle_run_api(api_match.group(1))
return
if web_match:
self.handle_run_web(web_match.group(1))
return
self.send_json(404, {"ok": False, "error": "not found"})
def handle_run_api(self, command_id):
command_cfg = CONFIG["commands"].get(command_id)
if not command_cfg:
self.send_json(404, {"ok": False, "error": "unknown command"})
return
api_key = self.headers.get("X-API-Key", "")
expected_key = command_cfg.get("api_key", "")
if not expected_key or not secrets.compare_digest(api_key, expected_key):
self.send_json(403, {"ok": False, "error": "invalid api key"})
return
try:
payload = self.read_json_body()
args = payload.get("args", {})
if not isinstance(args, dict):
raise ValueError("args must be an object")
validated_args = validate_args(command_cfg, args)
command = build_command(command_cfg, validated_args)
result = run_command(command_id, command, command_cfg)
status = 200 if result["ok"] else 500
self.send_json(status, result)
except subprocess.TimeoutExpired as exc:
self.send_json(504, timeout_result(command_id, exc))
except Exception as exc:
self.send_json(400, {"ok": False, "error": str(exc)})
def handle_run_web(self, command_id):
command_cfg = CONFIG["commands"].get(command_id)
if not command_cfg:
self.send_html(404, render_page("Unknown command", "<p>Unknown command.</p>"))
return
if not command_cfg.get("web_enabled", False):
self.send_html(403, render_page("Forbidden", "<p>Command is not enabled for web UI.</p>"))
return
try:
form = self.read_form_body()
csrf = form.get("csrf", "")
if not secrets.compare_digest(csrf, WEB_CSRF_TOKEN):
raise ValueError("invalid csrf token")
args = {}
for name, rule in command_cfg.get("args", {}).items():
if "web_default" in rule:
args[name] = rule["web_default"]
elif "default" in rule:
args[name] = rule["default"]
elif rule.get("required", False):
raise ValueError(f"missing web_default for required arg: {name}")
validated_args = validate_args(command_cfg, args)
command = build_command(command_cfg, validated_args)
result = run_command(command_id, command, command_cfg)
self.send_html(200, render_result(command_id, result))
except subprocess.TimeoutExpired as exc:
self.send_html(504, render_result(command_id, timeout_result(command_id, exc)))
except Exception as exc:
self.send_html(400, render_page("Error", f"<pre>{html.escape(str(exc))}</pre>"))
def list_commands():
return {
"ok": True,
"commands": [
{
"id": command_id,
"description": command.get("description", ""),
"args": list(command.get("args", {}).keys()),
"web_enabled": bool(command.get("web_enabled", False))
}
for command_id, command in CONFIG["commands"].items()
]
}
def validate_args(command_cfg, incoming_args):
arg_rules = command_cfg.get("args", {})
validated = {}
unknown = set(incoming_args.keys()) - set(arg_rules.keys())
if unknown:
raise ValueError(f"unknown args: {', '.join(sorted(unknown))}")
for name, rule in arg_rules.items():
value = incoming_args.get(name, rule.get("default"))
if value in [None, ""]:
if rule.get("required", False):
raise ValueError(f"missing arg: {name}")
value = ""
value = str(value)
pattern = rule.get("pattern", r"^[a-zA-Z0-9_.:/ -]{0,128}$")
if not re.fullmatch(pattern, value):
raise ValueError(f"invalid arg: {name}")
validated[name] = value
allowlist = command_cfg.get("allowlist")
if allowlist:
checked_values = [
validated.get(name)
for name in command_cfg.get("allowlist_args", [])
]
if checked_values not in allowlist:
raise ValueError(f"not allowed: {checked_values}")
return validated
def build_command(command_cfg, args):
command = []
for part in command_cfg["command"]:
value = str(part)
for key, arg_value in args.items():
value = value.replace("{" + key + "}", arg_value)
command.append(value)
return command
def run_command(command_id, command, command_cfg):
request_id = str(uuid.uuid4())
started = time.time()
timeout = int(command_cfg.get("timeout_seconds", 30))
env = {
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"REQUEST_ID": request_id
}
env.update({str(k): str(v) for k, v in command_cfg.get("environment", {}).items()})
print(f"request_id={request_id} command_id={command_id} command={command}")
completed = subprocess.run(
command,
shell=False,
capture_output=True,
text=True,
timeout=timeout,
check=False,
env=env
)
duration_ms = int((time.time() - started) * 1000)
return {
"ok": completed.returncode == 0,
"request_id": request_id,
"command_id": command_id,
"command": command,
"exit_code": completed.returncode,
"timed_out": False,
"duration_ms": duration_ms,
"stdout": completed.stdout,
"stderr": completed.stderr
}
def timeout_result(command_id, exc):
return {
"ok": False,
"request_id": str(uuid.uuid4()),
"command_id": command_id,
"command": getattr(exc, "cmd", []),
"exit_code": None,
"timed_out": True,
"duration_ms": None,
"stdout": exc.stdout or "",
"stderr": exc.stderr or "command timed out"
}
def render_index():
cards = []
for command_id, command in CONFIG["commands"].items():
if not command.get("web_enabled", False):
continue
title = html.escape(command.get("web_title", command_id))
desc = html.escape(command.get("description", ""))
cards.append(f"""
<form method="post" action="/web/run/{html.escape(command_id)}" class="card">
<input type="hidden" name="csrf" value="{html.escape(WEB_CSRF_TOKEN)}">
<h2>{title}</h2>
<p>{desc}</p>
<button type="submit">Run</button>
</form>
""")
if not cards:
cards.append("<p>No web-enabled commands configured.</p>")
return render_page("Raspberry Pi Script API", "\n".join(cards))
def render_result(command_id, result):
body = f"""
<p><a href="/">Back</a></p>
<h2>Result: {html.escape(command_id)}</h2>
<table>
<tr><th>OK</th><td>{html.escape(str(result.get("ok")))}</td></tr>
<tr><th>Exit Code</th><td>{html.escape(str(result.get("exit_code")))}</td></tr>
<tr><th>Timed out</th><td>{html.escape(str(result.get("timed_out")))}</td></tr>
<tr><th>Duration</th><td>{html.escape(str(result.get("duration_ms")))} ms</td></tr>
<tr><th>Request ID</th><td>{html.escape(str(result.get("request_id")))}</td></tr>
</table>
<h3>stdout</h3>
<pre>{html.escape(str(result.get("stdout", "")))}</pre>
<h3>stderr</h3>
<pre>{html.escape(str(result.get("stderr", "")))}</pre>
"""
return render_page("Command Result", body)
def render_page(title, body):
return f"""<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{html.escape(title)}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {{
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
margin: 2rem;
background: #f6f7f8;
color: #222;
}}
.card {{
background: white;
border: 1px solid #ddd;
border-radius: 12px;
padding: 1rem;
margin-bottom: 1rem;
max-width: 520px;
box-shadow: 0 1px 3px rgba(0,0,0,.08);
}}
button {{
font-size: 1rem;
padding: .65rem 1rem;
border: 0;
border-radius: 8px;
cursor: pointer;
background: #222;
color: white;
}}
pre {{
background: #111;
color: #eee;
padding: 1rem;
border-radius: 8px;
overflow: auto;
}}
table {{
border-collapse: collapse;
background: white;
margin-bottom: 1rem;
}}
th, td {{
border: 1px solid #ddd;
padding: .5rem .8rem;
text-align: left;
}}
</style>
</head>
<body>
<h1>{html.escape(title)}</h1>
{body}
</body>
</html>"""
def main():
server = ThreadingHTTPServer((CONFIG["host"], int(CONFIG["port"])), ApiHandler)
print(f"Listening on http://{CONFIG['host']}:{CONFIG['port']}")
print(f"Config: {CONFIG_PATH}")
server.serve_forever()
if __name__ == "__main__":
main()
+4
View File
@@ -1 +1,5 @@
ir-ctl --send=/home/timm/IRSender/defaultColor.txt ir-ctl --send=/home/timm/IRSender/defaultColor.txt
cd /home/timm/IRSender
nohup python3 -u script-api.py ./commands.json >> apiout.log 2>&1 &