From ee8494370d9aace38deb4234bcf0ae340fc30aeb Mon Sep 17 00:00:00 2001 From: Morgan Date: Wed, 1 Jan 2025 00:00:00 +0900 Subject: [PATCH] Init --- BLEMouse.ino | 152 +++++++++++++++++++++++++ anthropic.py | 282 ++++++++++++++++++++++++++++++++++++++++++++++ cfddns.py | 77 +++++++++++++ desktop-file-list | 43 +++++++ en_XX | 108 ++++++++++++++++++ extract | 79 +++++++++++++ fallback.py | 68 +++++++++++ hls_dl | 48 ++++++++ hwplibre | 8 ++ netwatch | 47 ++++++++ pkgupdate | 131 +++++++++++++++++++++ snap | 27 +++++ 12 files changed, 1070 insertions(+) create mode 100644 BLEMouse.ino create mode 100644 anthropic.py create mode 100644 cfddns.py create mode 100644 desktop-file-list create mode 100644 en_XX create mode 100644 extract create mode 100644 fallback.py create mode 100644 hls_dl create mode 100644 hwplibre create mode 100644 netwatch create mode 100644 pkgupdate create mode 100644 snap diff --git a/BLEMouse.ino b/BLEMouse.ino new file mode 100644 index 0000000..ef2abe0 --- /dev/null +++ b/BLEMouse.ino @@ -0,0 +1,152 @@ +#include "BLEDevice.h" +#include "BLEHIDDevice.h" +#include "HIDTypes.h" + +#define DEVICE_NAME "ESP32 Mouse" +#define BLE_MANUFACTURER "TinyUSB" + +BLEHIDDevice* hid; +BLECharacteristic* input; +BLECharacteristic* output; +bool isBleConnected = false; + +#define MOUSE_LEFT 1 +#define MOUSE_RIGHT 2 +#define MOUSE_MIDDLE 4 + +struct InputReport { + uint8_t buttons; + int8_t x; + int8_t y; + int8_t w; + int8_t hw; +}; + + +void setup() { + Serial.begin(115200); + Serial2.begin(115200, SERIAL_8N1, 22, 23); + xTaskCreate(bluetoothTask, "bluetooth", 20000, NULL, 5, NULL); +} + +void loop() { + while (Serial2.available()) { + String uartData = Serial2.readStringUntil('\n'); + if (uartData.startsWith("REPORT: ")) { + int r, m, l, w, x, y; + sscanf(uartData.c_str(), "REPORT: BTN %d %d %d WHEEL %d X %d Y %d", &l, &m, &r, &w, &x, &y); + + uint8_t button = 0; + + if (r) button |= MOUSE_RIGHT; + else button &= ~MOUSE_RIGHT; + + if (m) button |= MOUSE_MIDDLE; + else button &= ~MOUSE_MIDDLE; + + if (l) button |= MOUSE_LEFT; + else button &= ~MOUSE_LEFT; + + InputReport report = { + .buttons = button, + .x = x, + .y = y, + .w = w, + .hw = 0 + }; + + if (isBleConnected) { + input->setValue((uint8_t*)&report, sizeof(report)); + input->notify(); + } + } + // Serial.println(uartData); + } +} + +static const uint8_t _hidReportDescriptor[] = { + USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop) + USAGE(1), 0x02, // USAGE (Mouse) + COLLECTION(1), 0x01, // COLLECTION (Application) + USAGE(1), 0x01, // USAGE (Pointer) + COLLECTION(1), 0x00, // COLLECTION (Physical) + // ------------------------------------------------- Buttons (Left, Right, Middle, Back, Forward) + USAGE_PAGE(1), 0x09, // USAGE_PAGE (Button) + USAGE_MINIMUM(1), 0x01, // USAGE_MINIMUM (Button 1) + USAGE_MAXIMUM(1), 0x05, // USAGE_MAXIMUM (Button 5) + LOGICAL_MINIMUM(1), 0x00, // LOGICAL_MINIMUM (0) + LOGICAL_MAXIMUM(1), 0x01, // LOGICAL_MAXIMUM (1) + REPORT_SIZE(1), 0x01, // REPORT_SIZE (1) + REPORT_COUNT(1), 0x05, // REPORT_COUNT (5) + HIDINPUT(1), 0x02, // INPUT (Data, Variable, Absolute) ;5 button bits + // ------------------------------------------------- Padding + REPORT_SIZE(1), 0x03, // REPORT_SIZE (3) + REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) + HIDINPUT(1), 0x03, // INPUT (Constant, Variable, Absolute) ;3 bit padding + // ------------------------------------------------- X/Y position, Wheel + USAGE_PAGE(1), 0x01, // USAGE_PAGE (Generic Desktop) + USAGE(1), 0x30, // USAGE (X) + USAGE(1), 0x31, // USAGE (Y) + USAGE(1), 0x38, // USAGE (Wheel) + LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127) + LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127) + REPORT_SIZE(1), 0x08, // REPORT_SIZE (8) + REPORT_COUNT(1), 0x03, // REPORT_COUNT (3) + HIDINPUT(1), 0x06, // INPUT (Data, Variable, Relative) ;3 bytes (X,Y,Wheel) + // ------------------------------------------------- Horizontal wheel + USAGE_PAGE(1), 0x0c, // USAGE PAGE (Consumer Devices) + USAGE(2), 0x38, 0x02, // USAGE (AC Pan) + LOGICAL_MINIMUM(1), 0x81, // LOGICAL_MINIMUM (-127) + LOGICAL_MAXIMUM(1), 0x7f, // LOGICAL_MAXIMUM (127) + REPORT_SIZE(1), 0x08, // REPORT_SIZE (8) + REPORT_COUNT(1), 0x01, // REPORT_COUNT (1) + HIDINPUT(1), 0x06, // INPUT (Data, Var, Rel) + END_COLLECTION(0), // END_COLLECTION + END_COLLECTION(0) // END_COLLECTION +}; + +class BleHIDCallbacks : public BLEServerCallbacks { + void onConnect(BLEServer* server) { + isBleConnected = true; + BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); + cccDesc->setNotifications(true); + Serial.println("CONNECTED"); + } + + void onDisconnect(BLEServer* server) { + isBleConnected = false; + BLE2902* cccDesc = (BLE2902*)input->getDescriptorByUUID(BLEUUID((uint16_t)0x2902)); + cccDesc->setNotifications(false); + Serial.println("DISCONNECTED"); + } +}; + +void bluetoothTask(void*) { + BLEDevice::init(DEVICE_NAME); + BLEServer* server = BLEDevice::createServer(); + server->setCallbacks(new BleHIDCallbacks()); + + hid = new BLEHIDDevice(server); + input = hid->inputReport(0); + + hid->manufacturer()->setValue(BLE_MANUFACTURER); + hid->pnp(0x02, 0xe502, 0xa111, 0x0210); + hid->hidInfo(0x00, 0x02); + + BLESecurity* security = new BLESecurity(); + security->setAuthenticationMode(ESP_LE_AUTH_BOND); + + hid->reportMap((uint8_t*)_hidReportDescriptor, sizeof(_hidReportDescriptor)); + hid->startServices(); + hid->setBatteryLevel(100); + + BLEAdvertising* advertising = server->getAdvertising(); + advertising->setAppearance(HID_MOUSE); + advertising->addServiceUUID(hid->hidService()->getUUID()); + advertising->addServiceUUID(hid->deviceInfo()->getUUID()); + advertising->addServiceUUID(hid->batteryService()->getUUID()); + advertising->start(); + + Serial.println("BLE READY"); + delay(portMAX_DELAY); +}; diff --git a/anthropic.py b/anthropic.py new file mode 100644 index 0000000..300e14b --- /dev/null +++ b/anthropic.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +import json +import re +import time + +import httpx +from fastapi import FastAPI, Request, HTTPException +from fastapi.responses import Response, StreamingResponse, JSONResponse +from starlette.middleware.cors import CORSMiddleware + +CLAUDE_BASE_URL = "https://api.anthropic.com/v1/messages" + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], + allow_credentials=True, +) + + +def get_api_key(headers: dict) -> str: + auth = headers.get("authorization") + if auth: + parts = auth.split(" ") + if len(parts) > 1: + return parts[1] + return + + +def format_stream_response_json(claude_response: dict) -> dict: + typ = claude_response.get("type") + if typ == "message_start": + return { + "id": claude_response["message"]["id"], + "model": claude_response["message"]["model"], + "inputTokens": claude_response["message"]["usage"]["input_tokens"], + } + elif typ in ("content_block_start", "ping", "content_block_stop", "message_stop"): + return None + elif typ == "content_block_delta": + return {"content": claude_response["delta"]["text"]} + elif typ == "message_delta": + return { + "stopReason": claude_response["delta"].get("stop_reason"), + "outputTokens": claude_response["usage"]["output_tokens"], + } + elif typ == "error": + return { + "errorType": claude_response["error"].get("type"), + "errorMsg": claude_response["error"]["message"], + } + else: + return None + + +def claude_to_chatgpt_response(claude_response: dict, meta_info: dict, stream: bool = False) -> dict: + timestamp = int(time.time()) + completion_tokens = meta_info.get("outputTokens", 0) or 0 + prompt_tokens = meta_info.get("inputTokens", 0) or 0 + + if meta_info.get("stopReason") and stream: + return { + "id": meta_info.get("id"), + "object": "chat.completion.chunk", + "created": timestamp, + "model": meta_info.get("model"), + "choices": [ + { + "index": 0, + "delta": {}, + "logprobs": None, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": prompt_tokens + completion_tokens, + }, + } + + message_content = claude_response.get("content", "") + result = { + "id": meta_info.get("id", "unknown"), + "created": timestamp, + "model": meta_info.get("model"), + "usage": { + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "total_tokens": prompt_tokens + completion_tokens, + }, + "choices": [{"index": 0}], + } + message = {"role": "assistant", "content": message_content} + if not stream: + result["object"] = "chat.completion" + result["choices"][0]["message"] = message + result["choices"][0]["finish_reason"] = "stop" if meta_info.get("stopReason") == "end_turn" else None + else: + result["object"] = "chat.completion.chunk" + result["choices"][0]["delta"] = message + return result + + +async def stream_generator(response: httpx.Response, model: str): + meta_info = {"model": model} + buffer = "" + regex = re.compile(r"event:\s*.*?\s*\ndata:\s*(.*?)(?=\n\n|\s*$)", re.DOTALL) + async for chunk in response.aiter_text(): + buffer += chunk + for match in regex.finditer(buffer): + try: + decoded_line = json.loads(match.group(1).strip()) + except Exception: + continue + + formated_chunk = format_stream_response_json(decoded_line) + if formated_chunk is None: + continue + if formated_chunk.get("errorType", None): + etyp = formated_chunk.get("errorType") + emsg = formated_chunk.get("errorMsg") + data = {"error": {"type": etyp, "code": etyp, "message": emsg, "param": None}} + yield f"data: {json.dumps(data)}\n\n" + + meta_info["id"] = formated_chunk.get("id", meta_info.get("id")) + meta_info["model"] = formated_chunk.get("model", meta_info.get("model")) + meta_info["inputTokens"] = formated_chunk.get("inputTokens", meta_info.get("inputTokens")) + meta_info["outputTokens"] = formated_chunk.get("outputTokens", meta_info.get("outputTokens")) + meta_info["stopReason"] = formated_chunk.get("stopReason", meta_info.get("stopReason")) + transformed_line = claude_to_chatgpt_response(formated_chunk, meta_info, stream=True) + yield f"data: {json.dumps(transformed_line)}\n\n" + + else: + try: + resp = json.loads(buffer) + etyp = resp["error"]["type"] + emsg = resp["error"]["message"] + data = {"error": {"type": etyp, "code": etyp, "message": emsg, "param": None}} + yield f"data: {json.dumps(data)}\n\n" + + except Exception: + pass + + last_end = 0 + for m in regex.finditer(buffer): + last_end = m.end() + buffer = buffer[last_end:] + + yield "data: [DONE]" + + +@app.get("/v1/models") +async def get_models(request: Request): + headers = dict(request.headers) + api_key = get_api_key(headers) + if not api_key: + raise HTTPException(status_code=403, detail="Not Allowed") + + async with httpx.AsyncClient() as client: + anthro_resp = await client.get( + "https://api.anthropic.com/v1/models", + headers={ + "x-api-key": api_key, + "anthropic-version": "2023-06-01", + }, + ) + if anthro_resp.status_code != 200: + raise HTTPException(status_code=anthro_resp.status_code, detail="Error getting models") + + data = anthro_resp.json() + models_list = [ + {"id": m["id"], "object": m["type"], "owned_by": "Anthropic"} + for m in data.get("data", []) + ] + + return JSONResponse(content={"object": "list", "data": models_list}) + + +@app.post("/v1/chat/completions") +async def chat_completions(request: Request): + headers = dict(request.headers) + api_key = get_api_key(headers) + if not api_key: + raise HTTPException(status_code=403, detail="Not Allowed") + + try: + body = await request.json() + except Exception: + raise HTTPException(status_code=400, detail="Invalid JSON payload") + + model = body.get("model") + messages = body.get("messages", []) + temperature = body.get("n", 1) + max_tokens = body.get("max_tokens", 4096) + stop = body.get("stop") + stream = body.get("stream", False) + + system_message = next((m for m in messages if m.get("role") == "system"), []) + filtered_messages = [m for m in messages if m.get("role") != "system"] + + claude_request_body = { + "model": model, + "messages": filtered_messages, + "temperature": temperature, + "max_tokens": max_tokens, + "stop_sequences": stop, + "system": system_message.get("content") if system_message else [], + "stream": stream, + } + + if not stream: + async with httpx.AsyncClient(timeout=None) as client: + claude_resp = await client.post( + CLAUDE_BASE_URL, + headers={ + "Content-Type": "application/json", + "x-api-key": api_key, + "anthropic-version": "2023-06-01", + }, + json=claude_request_body, + ) + + if claude_resp.status_code != 200: + print(claude_resp.content) + return Response(status_code=claude_resp.status_code, content=claude_resp.content) + + resp_json = claude_resp.json() + + if resp_json.get("type") == "error": + result = { + "error": { + "message": resp_json.get("error", {}).get("message"), + "type": resp_json.get("error", {}).get("type"), + "param": None, + "code": resp_json.get("error", {}).get("type"), + } + } + else: + formated_info = { + "id": resp_json.get("id"), + "model": resp_json.get("model"), + "inputTokens": resp_json.get("usage", {}).get("input_tokens"), + "outputTokens": resp_json.get("usage", {}).get("output_tokens"), + "stopReason": resp_json.get("stop_reason"), + } + + content = "" + try: + content = resp_json.get("content", [])[0].get("text", "") + except Exception: + pass + result = claude_to_chatgpt_response({"content": content}, formated_info) + + return JSONResponse( + status_code=claude_resp.status_code, + content=result + ) + else: + async def stream_response(model: str, claude_request_body: dict, api_key: str): + async with httpx.AsyncClient(timeout=None) as client: + async with client.stream( + "POST", + CLAUDE_BASE_URL, + headers={ + "Content-Type": "application/json", + "x-api-key": api_key, + "anthropic-version": "2023-06-01", + }, + json=claude_request_body, + ) as response: + async for event in stream_generator(response, model): + yield event + + return StreamingResponse( + stream_response(model, claude_request_body, api_key), + media_type = "text/event-stream" + + ) \ No newline at end of file diff --git a/cfddns.py b/cfddns.py new file mode 100644 index 0000000..35f529f --- /dev/null +++ b/cfddns.py @@ -0,0 +1,77 @@ +import requests +import socket +import sys +import os + +CF_GLOBAL_KEY = os.environ.get("CF_GLOBAL_KEY") +CF_EMAIL = os.environ.get("CF_EMAIL") + +auth = {"X-Auth-Email": CF_EMAIL, "X-Auth-Key": CF_GLOBAL_KEY, "Content-Type": "application/json"} + + +def update_dns(domain, new_ip): + for i in list_zones(): + if domain.endswith(i[1]): + zone = i[0] + if not zone: + print("ERROR DOMAIN NOT FOUND") + + if create_domain(zone, domain, new_ip): + print(f"RECORD {domain} -> {new_ip} CREATED") + return + + list_api = f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records?name=" + edit_api = f"https://api.cloudflare.com/client/v4/zones/{zone}/dns_records" + dns_info = {"type": "A", "name": domain, "ttl": 3600, "proxied": False} + + try: + old_ip = socket.gethostbyname(dns_info["name"]) + except socket.gaierror: + old_ip = None + + if old_ip == new_ip: + print(f"RECORD {dns_info['name']} = {old_ip} NOT CHANGED") + else: + dns_id = requests.get(f"{list_api}{domain}", headers=auth).json()["result"][0]["id"] + dns_info["content"] = new_ip + upd = requests.put(f"{edit_api}/{dns_id}", headers=auth, json=dns_info).json() + dn_name = upd["result"]["name"] + dn_type = upd["result"]["type"] + dn_content = upd["result"]["content"] + print(f"RECORD {dn_name} {dn_type} {dn_content} UPDATED") + + +def list_zones(): + api_url = "https://api.cloudflare.com/client/v4/zones" + resp = requests.get(api_url, headers=auth).json() + return [(z["id"], z["name"]) for z in resp["result"]] + + +def list_domains(zid): + api_url = f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records" + resp = requests.get(api_url, headers=auth).json() + return resp["result"] + + +def create_domain(zone, domain, ip=None): + existing = [i["name"] for i in list_domains(zone)] + if domain not in existing: + create_record(domain, zone, ip) + return True + else: + return False + + +def create_record(sub, zid, ip="1.1.1.1", record="A"): + api_url = f"https://api.cloudflare.com/client/v4/zones/{zid}/dns_records" + data = {"content": ip, "name": sub, "proxied": False, "type": record, "comment": "", "ttl": 3600} + res_txt = requests.post(api_url, headers=auth, json=data).text + print(res_txt) + return res_txt + + +if __name__ == "__main__": + TARGET_DOMAIN = sys.argv[1] + TARGET_ADDR = requests.get("https://ping.api.morgan.kr").json()["info"]["client"] + print(f"UPDATE {TARGET_DOMAIN} <- {TARGET_ADDR}") + update_dns(TARGET_DOMAIN, TARGET_ADDR) diff --git a/desktop-file-list b/desktop-file-list new file mode 100644 index 0000000..1ef5584 --- /dev/null +++ b/desktop-file-list @@ -0,0 +1,43 @@ +#!/bin/bash + +parse_desktop_file() { + local file="$1" + local section="" + + local mainexec="" + local mainname="" + + while IFS= read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^\;.* || "$line" =~ ^#.* || -z "$line" ]]; then + continue + fi + + if [[ "$line" =~ ^\[.*\]$ ]]; then + section="${line:1:-1}" + continue + fi + + if [[ "$line" =~ ^([^=]+)=(.*)$ ]]; then + key="${BASH_REMATCH[1]}" + value="${BASH_REMATCH[2]}" + # echo "[$section] $key = $value" + + if [ "$section" == "Desktop Entry" ]; then + if [ "$key" == "Exec" ]; then mainexec=$value; fi + if [ "$key" == "Name" ]; then mainname=$value; fi + fi + fi + done < "$file" + + echo "{\"file\": \"$file\", \"name\": \"$mainname\", \"exec\": \"$mainexec\"}" +} + +shopt -s nullglob +IFS=':' read -r -a paths <<< "$XDG_DATA_DIRS:/home/$USER/.local/share/" +for path in "${paths[@]}"; do + AP=$(realpath "$path/applications") + for appfile in $AP/*.desktop; do + parse_desktop_file $appfile; + done +done +shopt -u nullglob diff --git a/en_XX b/en_XX new file mode 100644 index 0000000..393a378 --- /dev/null +++ b/en_XX @@ -0,0 +1,108 @@ +comment_char % +escape_char / + +LC_IDENTIFICATION +title "English International" +source "" +address "" +contact "" +email "" +tel "" +fax "" +language "" +territory "" +revision "" +date "" +category "i18n:2012";LC_IDENTIFICATION +category "i18n:2012";LC_CTYPE +category "i18n:2012";LC_COLLATE +category "i18n:2012";LC_TIME +category "i18n:2012";LC_NUMERIC +category "i18n:2012";LC_MONETARY +category "i18n:2012";LC_MESSAGES +category "i18n:2012";LC_PAPER +category "i18n:2012";LC_NAME +category "i18n:2012";LC_ADDRESS +category "i18n:2012";LC_TELEPHONE +category "i18n:2012";LC_MEASUREMENT +END LC_IDENTIFICATION + +LC_CTYPE +copy "en_US" +END LC_CTYPE + +LC_COLLATE +copy "iso14651_t1" +END LC_COLLATE + +LC_MONETARY +int_curr_symbol "" +currency_symbol "" +mon_decimal_point "," +mon_thousands_sep " " +mon_grouping 3 +positive_sign "" +negative_sign "-" +int_frac_digits 2 +frac_digits 2 +p_cs_precedes 1 +p_sep_by_space 0 +n_cs_precedes 1 +n_sep_by_space 0 +p_sign_posn 1 +n_sign_posn 1 +END LC_MONETARY + +LC_NUMERIC +decimal_point "," +thousands_sep " " +grouping 3 +END LC_NUMERIC + +LC_TIME +abday "Sun";"Mon";"Tue";"Wed";"Thu";"Fri";"Sat" +day "Sunday";"Monday";"Tuesday";"Wednesday";"Thursday";/ + "Friday";"Saturday" +abmon "Jan";"Feb";"Mar";"Apr";"May";"Jun";"Jul";"Aug";"Sep";/ + "Oct";"Nov";"Dec" +mon "January";"February";"March";"April";"May";"June";"July";/ + "August";"September";"October";"November";"December" +week 7;19971130;4 +first_weekday 1 +first_workday 2 +d_t_fmt "%a %Y-%m-%d %H:%M:%S" +d_fmt "%Y-%m-%d" +t_fmt "%H:%M:%S" +t_fmt_ampm "" +am_pm "";"" +date_fmt "%a %Y-%m-%d %H:%M:%S %Z" +END LC_TIME + + +LC_MESSAGES +yesexpr "^[yY]" +noexpr "^[nN]" +yesstr "" +nostr "" +END LC_MESSAGES + +LC_PAPER +height 297 +width 210 +END LC_PAPER + +LC_NAME +name_fmt "%p%t%g%t%m%t%f" +END LC_NAME + +LC_ADDRESS +postal_fmt "%a%N%f%N%d%N%b%N%s %h %e %r%N%C-%z %T%N%c%N" +END LC_ADDRESS + +LC_TELEPHONE +tel_int_fmt "+%c %a%t%l" +END LC_TELEPHONE + +LC_MEASUREMENT +measurement 1 +END LC_MEASUREMENT \ No newline at end of file diff --git a/extract b/extract new file mode 100644 index 0000000..a137360 --- /dev/null +++ b/extract @@ -0,0 +1,79 @@ +#!/bin/bash + +# Archive eXtractor + +error() { + echo "Error: $1" >&2 +} + +run() { + echo "Debug: $@" + $@ +} + +if [ ! -f "$1" ]; then + error "No file provided" + exit 1 +fi + +BN="${1%.*}" +BASE="${1%.*}" +TRY=0 + +while [ -d "$BASE" ]; do + if [ $TRY -gt 10 ]; then + error "Already Exists (10)" + exit 1 + fi + error "Already exists ($BASE)" + TRY=$((TRY + 1)) + BASE="${BN}_${TRY}" +done + +FILE_TYPE=$(file --mime-type -b "$1") +EXT="${1##*.}" + +case "$FILE_TYPE" in + application/x-tar) FORMAT="tar" ;; + application/zip) FORMAT="zip" ;; + application/x-7z-compressed) FORMAT="7z" ;; + *) + case "$EXT" in + tar.gz|tar.bz2|tar.xz|tar.zst|tgz|tbz2|txz) FORMAT="tar" ;; + gz) FORMAT="gzip" ;; + bz2) FORMAT="bzip2" ;; + xz) FORMAT="xz" ;; + *) + error "Unsupported file format" + exit 1 + ;; + esac + ;; +esac + +run mkdir "$BASE" + +case $FORMAT in + tar) + run tar -xvf "$1" -C "$BASE" + ;; + zip) + run unzip "$1" -d "$BASE" + ;; + 7z) + run 7z x "$1" -o"$BASE" + ;; + gzip) + run gunzip -c "$1" > "$BASE/$(basename "$BN")" + ;; + bzip2) + run bunzip2 -c "$1" > "$BASE/$(basename "$BN")" + ;; + xz) + run unxz -c "$1" > "$BASE/$(basename "$BN")" + ;; + *) + error "Unexpected error occurred" + exit 1 + ;; +esac diff --git a/fallback.py b/fallback.py new file mode 100644 index 0000000..e527bc1 --- /dev/null +++ b/fallback.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +import uvicorn +from fastapi import FastAPI, Request, HTTPException +from fastapi.responses import RedirectResponse, FileResponse +import requests +import datetime + +app = FastAPI() +hosts = [ + "SERVER1", + "SERVER2", + "SERVER3", + "SERVER4" + ] +cache = {} + +def find_fit(parent): + print(cache) + hosts_local = hosts.copy() + if parent in cache.keys(): + print(f"DEBUG: Cache Hit {parent}: {cache[parent]}") + idx = hosts.index(cache[parent]) + if idx: + hosts_local[idx], hosts_local[0] = hosts_local[0], hosts_local[idx] + + for host in hosts_local: + try: + print(f"DEBUG: Tesing for http://{host}/{parent}") + if requests.head(f"http://{host}/{parent}", allow_redirects=True).status_code == 200: + cache[parent] = host + return host + else: + cache[parent] = "" + except Exception: + pass + return None + +def log(level, status_code, path, header): + print(f'{level}: [{datetime.datetime.now()}] "{status_code} {path} "{header.get("User-Agent")}"') + +@app.get("/{path:path}") +async def index(request: Request, path: str): + if path in [None, "", "favicon.ico", "index.html"]: + if path in [None, ""]: + path = "index.html" + print(f'RETURN: [{datetime.datetime.now()}] "200 {path} "{request.headers.get("User-Agent")}"') + return FileResponse(path) + + server = find_fit(path.split("/")[0]) + + if server: + print(f'RETURN: [{datetime.datetime.now()}] "301 http://{server}/{path}" "{request.headers.get("User-Agent")}"') + return RedirectResponse(url=f"http://{server}/{path}") + else: + print(f'RETURN: [{datetime.datetime.now()}] "404 Not found" "{request.headers.get("User-Agent")}"') + raise HTTPException(status_code=404, detail="No available server.") + +if __name__=="__main__": + print('Starting Server...') + uvicorn.run( + "fallback:app", + host="0.0.0.0", + port=8080, + log_level="info", + reload=True, + ) + diff --git a/hls_dl b/hls_dl new file mode 100644 index 0000000..19472aa --- /dev/null +++ b/hls_dl @@ -0,0 +1,48 @@ +#!/bin/bash + +CURRENT=$(pwd) +WORKDIR=$(mktemp -d) + +cd $WORKDIR + +M3U8="$1" +NAME="$2" + +curl "$M3U8" > PLAYLIST + +touch MONITOR + +{ + cat PLAYLIST | \ + grep -v '^#.*' | \ + xargs -P 100 -I {} bash -c 'FN=$(echo "{}" | sha1sum | cut -d" " -f1); if [ ! -f $FN ]; then curl "{}" -s --output $FN; fi; echo .'; + rm MONITOR; +} > MONITOR & + +echo DOWNLOADING + +TOTAL=$(cat PLAYLIST | grep -v '^#' | wc -l) + +while [ -f MONITOR ]; do + echo $(cat MONITOR | wc -l)/$TOTAL + sleep 1; +done + +wait + +echo DONE + +for i in $( /dev/null; } \ + && { + FN=$(echo $i | sha1sum | cut -d" " -f1); + echo $FN; + } \ + || echo $i; +done > MODIFIED_PLAYLIST.m3u8 + +ffmpeg -allowed_extensions ALL -extension_picky 0 -i MODIFIED_PLAYLIST.m3u8 -c copy $CURRENT/$NAME + +cd $CURRENT + +rm -r $WORKDIR \ No newline at end of file diff --git a/hwplibre b/hwplibre new file mode 100644 index 0000000..3862d0d --- /dev/null +++ b/hwplibre @@ -0,0 +1,8 @@ +#!/bin/bash +bef=$1 +aft=${bef%.*}.odt + +zenity --info --text="Converting $bef to $aft."; +eval "~/.python38/bin/hwp5odt \"$bef\" --output \"$aft\"" + +eval "libreoffice --writer \"$aft\"" diff --git a/netwatch b/netwatch new file mode 100644 index 0000000..f49432c --- /dev/null +++ b/netwatch @@ -0,0 +1,47 @@ +#!/bin/bash + +cleanup () { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch exited. ($$)" | tee -a /var/log/netwatch.log; + rm /var/.netwatch.lock +} + +if [ -f /var/.netwatch.lock ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch already running. ($$)" | tee -a /var/log/netwatch.log; + exit; +fi + +trap cleanup EXIT + +FAIL=0 +RELOADED=0 + +if [ ! -f /var/.netwatch.lock ]; then + touch /var/.netwatch.lock + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch started ($$)" | tee -a /var/log/netwatch.log; + + while true; do + if [ $FAIL -gt 20 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch triggered ($$) - $FAIL" | tee -a /var/log/netwatch.log; + if [ $RELOADED == 1 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch triggered ($$) - $FAIL - HARD RESET" | tee -a /var/log/netwatch.log; + reboot; + fi + systemctl restart networking + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $(systemctl status networking; ip a;)" | tee -a /var/log/netwatch.log; + FAIL=0 + RELOADED=1 + fi + + curl -s https://ping.api.morgan.kr/ >/dev/null && FAIL=0 || FAIL=$(($FAIL+1)) + ping 1.1.1.1 -c 1 -w 1 >/dev/null && FAIL=0 || FAIL=$(($FAIL+1)) + + if [ $FAIL -ne 0 ]; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Netwatch failed ($$) - $FAIL" | tee -a /var/log/netwatch.log; + else + RELOADED=0 + fi + + sleep 60 + done +fi \ No newline at end of file diff --git a/pkgupdate b/pkgupdate new file mode 100644 index 0000000..eb6ba30 --- /dev/null +++ b/pkgupdate @@ -0,0 +1,131 @@ +#!/bin/bash + +PKGS=() + +update_librewolf () { + PKGNAME="librewolf" + TMPDIR="$(mktemp -d)" + PWD=$(pwd) + + echo "[*] Updating $PKGNAME..." + echo "[*] - Entering $TMPDIR" + cd $TMPDIR + + URL=$(curl -L https://gitlab.com/api/v4/projects/44042130/releases/permalink/latest | jq -r '.assets.links[] | select(.name | test("^librewolf-[0-9\\.-]+-linux-x86_64-package\\.tar\\.xz$")) | .url') + + echo "[*] - Downloading from $URL" + curl "$URL" -o librewolf.tar.xz + + echo "[*] - Extracting..." + sudo tar -xvf librewolf.tar.xz >> log + + echo "[*] - Installing..." + sudo cp -r /opt/librewolf ./previous + sudo rsync -avx librewolf/ /opt/librewolf/ >> log + + echo "[*] - Running post-script..." + sudo chown root:root /opt/librewolf/ /opt/librewolf/librewolf + + echo "[*] - Done!" + cd $PWD +} + +update_1password () { + PKGNAME="1Password" + TMPDIR="$(mktemp -d)" + PWD=$(pwd) + + echo "[*] Updating $PKGNAME..." + echo "[*] - Entering $TMPDIR" + cd $TMPDIR + + URL="https://downloads.1password.com/linux/tar/stable/x86_64/1password-latest.tar.gz" + + echo "[*] - Downloading from $URL" + curl "$URL" -o 1password-latest.tar.gz + + echo "[*] - Extracting..." + sudo tar -xvf 1password-latest.tar.gz >> log + + echo "[*] - Installing..." + sudo cp -r /opt/1Password ./.previous + sudo rsync -avx 1password-*/ /opt/1Password/ >> log + + echo "[*] - Running post-script..." + sudo /opt/1Password/after-install.sh >> log + + echo "[*] - Done!" + cd $PWD +} + +update_ungoogled_chromium () { + PKGNAME="ungoogled-chromium" + TMPDIR="$(mktemp -d)" + PWD=$(pwd) + PROJ_URL="https://api.github.com/repos/ungoogled-software/ungoogled-chromium-portablelinux/releases/latest" + URL=$(curl $PROJ_URL | jq -r '.assets.[] | select(.name | endswith(".tar.xz")) | .browser_download_url') + + download () { + curl -L "$URL" -o package.tar.xz + } + + extract () { + sudo tar -xvf package.tar.xz >> log + } + + install () { + mv ./ungoogled-chromium_* ./NEW + sudo mv /opt/ungoogled-chromium/ ./PREV/ + sudo mv ./NEW /opt/ungoogled-chromium/ | tee log + } + + postscript () { + sudo chown root:root /opt/ungoogled-chromium/ /opt/ungoogled-chromium/chrome + sudo bash -c 'bash <(curl https://raw.githubusercontent.com/morgan9e/chrome-blank-newtab/refs/heads/main/patch.sh) /opt/ungoogled-chromium/resources.pak' + } + + echo "[*] Updating $PKGNAME..." + echo "[*] - Entering $TMPDIR" + cd $TMPDIR + echo "[*] - Downloading from $URL" + download + echo "[*] - Extracting..." + extract + echo "[*] - Installing..." + install + echo "[*] - Running post-script..." + postscript + echo "[*] - Done!" + cd $PWD +} + + +PKGS+=("librewolf") +PKGS+=("1password") +PKGS+=("ungoogled_chromium") + +# main + +if [[ "$1" == "list" ]]; then + echo "${PKGS[*]}" + exit 0 +elif [[ -z "$1" ]]; then + echo "Usage: $(basename $0) [package_name | all | list]" + echo "Available: ${PKGS[*]}" + exit 1 +fi + +if [ "$EUID" -ne 0 ]; then + sudo "$0" "$@" + exit $? +fi + +if [[ "$1" == "all" ]]; then + for pkg in "${PKGS[@]}"; do + "update_$pkg" + done +elif [[ -n "$1" ]]; then + "update_$1" +fi + +exit 0 diff --git a/snap b/snap new file mode 100644 index 0000000..db7613c --- /dev/null +++ b/snap @@ -0,0 +1,27 @@ +#!/bin/bash + +if [ "$EUID" -ne 0 ] + then echo "Please run as root." + exit +fi + +TIME=$(date +%Y_%m_%d-%H_%M) + +echo $TIME > /SNAPSHOT +echo $TIME > /home/SNAPSHOT + +mkdir /tmp/btrfsnap-$TIME + +mount $(findmnt / -no SOURCE | cut -d '[' -f 1) /tmp/btrfsnap-$TIME + +btrfs subvol snap -r /tmp/btrfsnap-$TIME/@root /tmp/btrfsnap-$TIME/@snapshots/@root-$TIME + +btrfs subvol snap -r /tmp/btrfsnap-$TIME/@home /tmp/btrfsnap-$TIME/@snapshots/@home-$TIME + +umount /tmp/btrfsnap-$TIME + +rmdir /tmp/btrfsnap-$TIME + +rm /SNAPSHOT + +rm /home/SNAPSHOT