Spaces:
Runtime error
Runtime error
Silicon Valley - Admin
commited on
Commit
路
dd07930
1
Parent(s):
f0762d4
Add initial application files including server implementation, API specification, configuration, and ignore files
Browse files- .cursorignore +13 -0
- hypercorn.toml +9 -0
- openapi.yaml +189 -0
- poetry.toml +2 -0
- requirements.txt +1 -0
- server.py +103 -0
.cursorignore
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.DS_Store
|
2 |
+
.AppleDouble
|
3 |
+
.LSOverride
|
4 |
+
Icon
|
5 |
+
._*
|
6 |
+
.DocumentRevisions-V100
|
7 |
+
.fseventsd
|
8 |
+
.Spotlight-V100
|
9 |
+
.TemporaryItems
|
10 |
+
.Trashes
|
11 |
+
.VolumeIcon.icns
|
12 |
+
.com.apple.timemachine.donotpresent
|
13 |
+
|
hypercorn.toml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Hypercorn configuration file
|
2 |
+
|
3 |
+
# Server binding options
|
4 |
+
bind = "0.0.0.0:8000"
|
5 |
+
workers = 1
|
6 |
+
|
7 |
+
# Logging configuration
|
8 |
+
accesslog = "/var/log/serverwitch/access.log"
|
9 |
+
logconfig = "logging.toml"
|
openapi.yaml
ADDED
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
openapi: 3.1.0
|
2 |
+
info:
|
3 |
+
title: Kaio Buu API
|
4 |
+
description: Execute commands, read files and write files on the user's server
|
5 |
+
version: 0.2.0
|
6 |
+
servers:
|
7 |
+
- url: https://run.pyboxs.com/kaiobuu
|
8 |
+
paths:
|
9 |
+
/command:
|
10 |
+
post:
|
11 |
+
operationId: post_send_command
|
12 |
+
summary: Execute a command on the server.
|
13 |
+
x-openai-isConsequential: false
|
14 |
+
requestBody:
|
15 |
+
description: Request body containing the command and session ID.
|
16 |
+
required: true
|
17 |
+
content:
|
18 |
+
application/json:
|
19 |
+
schema:
|
20 |
+
$ref: '#/components/schemas/CommandRequest'
|
21 |
+
responses:
|
22 |
+
'200':
|
23 |
+
description: Command executed successfully.
|
24 |
+
content:
|
25 |
+
application/json:
|
26 |
+
schema:
|
27 |
+
$ref: '#/components/schemas/CommandResponse'
|
28 |
+
'500':
|
29 |
+
description: Server error.
|
30 |
+
content:
|
31 |
+
application/json:
|
32 |
+
schema:
|
33 |
+
$ref: '#/components/schemas/ErrorResponse'
|
34 |
+
/read:
|
35 |
+
post:
|
36 |
+
operationId: post_read
|
37 |
+
summary: Read a file on the server.
|
38 |
+
x-openai-isConsequential: false
|
39 |
+
requestBody:
|
40 |
+
description: Request body containing the file path and session ID.
|
41 |
+
required: true
|
42 |
+
content:
|
43 |
+
application/json:
|
44 |
+
schema:
|
45 |
+
$ref: '#/components/schemas/ReadRequest'
|
46 |
+
responses:
|
47 |
+
'200':
|
48 |
+
description: File read successfully.
|
49 |
+
content:
|
50 |
+
application/json:
|
51 |
+
schema:
|
52 |
+
$ref: '#/components/schemas/ReadResponse'
|
53 |
+
'500':
|
54 |
+
description: Server error.
|
55 |
+
content:
|
56 |
+
application/json:
|
57 |
+
schema:
|
58 |
+
$ref: '#/components/schemas/ErrorResponse'
|
59 |
+
/write:
|
60 |
+
post:
|
61 |
+
operationId: post_write
|
62 |
+
summary: Write a file on the server.
|
63 |
+
x-openai-isConsequential: false
|
64 |
+
requestBody:
|
65 |
+
description: Request body containing the file path, new content and session ID.
|
66 |
+
required: true
|
67 |
+
content:
|
68 |
+
application/json:
|
69 |
+
schema:
|
70 |
+
$ref: '#/components/schemas/WriteRequest'
|
71 |
+
responses:
|
72 |
+
'200':
|
73 |
+
description: File written successfully.
|
74 |
+
content:
|
75 |
+
application/json:
|
76 |
+
schema:
|
77 |
+
$ref: '#/components/schemas/WriteResponse'
|
78 |
+
'500':
|
79 |
+
description: Server error.
|
80 |
+
content:
|
81 |
+
application/json:
|
82 |
+
schema:
|
83 |
+
$ref: '#/components/schemas/ErrorResponse'
|
84 |
+
/status:
|
85 |
+
get:
|
86 |
+
operationId: get_status
|
87 |
+
summary: Get the status of the server.
|
88 |
+
x-openai-isConsequential: false
|
89 |
+
responses:
|
90 |
+
'200':
|
91 |
+
description: Status of the server.
|
92 |
+
content:
|
93 |
+
application/json:
|
94 |
+
schema:
|
95 |
+
$ref: '#/components/schemas/StatusResponse'
|
96 |
+
components:
|
97 |
+
schemas:
|
98 |
+
CommandRequest:
|
99 |
+
type: object
|
100 |
+
required:
|
101 |
+
- session_id
|
102 |
+
- command
|
103 |
+
properties:
|
104 |
+
command:
|
105 |
+
type: string
|
106 |
+
description: Command to execute on the server.
|
107 |
+
session_id:
|
108 |
+
type: string
|
109 |
+
description: Session ID.
|
110 |
+
ReadRequest:
|
111 |
+
type: object
|
112 |
+
required:
|
113 |
+
- session_id
|
114 |
+
- path
|
115 |
+
properties:
|
116 |
+
path:
|
117 |
+
type: string
|
118 |
+
description: Absolute path of the file to be read.
|
119 |
+
session_id:
|
120 |
+
type: string
|
121 |
+
description: Session ID.
|
122 |
+
WriteRequest:
|
123 |
+
type: object
|
124 |
+
required:
|
125 |
+
- session_id
|
126 |
+
- path
|
127 |
+
- content
|
128 |
+
properties:
|
129 |
+
path:
|
130 |
+
type: string
|
131 |
+
description: Absolute path of the file to be written.
|
132 |
+
content:
|
133 |
+
type: string
|
134 |
+
description: New content of the file.
|
135 |
+
session_id:
|
136 |
+
type: string
|
137 |
+
description: Session ID.
|
138 |
+
CommandResponse:
|
139 |
+
type: object
|
140 |
+
required:
|
141 |
+
- return_code
|
142 |
+
- stdout
|
143 |
+
- stderr
|
144 |
+
properties:
|
145 |
+
return_code:
|
146 |
+
type: integer
|
147 |
+
description: Return code of the executed command.
|
148 |
+
stdout:
|
149 |
+
type: string
|
150 |
+
description: Standard output of the executed command.
|
151 |
+
stderr:
|
152 |
+
type: string
|
153 |
+
description: Standard error of the executed command.
|
154 |
+
ReadResponse:
|
155 |
+
type: object
|
156 |
+
required:
|
157 |
+
- content
|
158 |
+
properties:
|
159 |
+
content:
|
160 |
+
type: string
|
161 |
+
description: Content of the file.
|
162 |
+
WriteResponse:
|
163 |
+
type: object
|
164 |
+
required:
|
165 |
+
- size
|
166 |
+
properties:
|
167 |
+
size:
|
168 |
+
type: int
|
169 |
+
description: Number of characters written to the file.
|
170 |
+
ErrorResponse:
|
171 |
+
type: object
|
172 |
+
required:
|
173 |
+
- error
|
174 |
+
properties:
|
175 |
+
error:
|
176 |
+
type: string
|
177 |
+
description: Error message detailing what went wrong.
|
178 |
+
StatusResponse:
|
179 |
+
type: object
|
180 |
+
required:
|
181 |
+
- status
|
182 |
+
- version
|
183 |
+
properties:
|
184 |
+
status:
|
185 |
+
type: string
|
186 |
+
description: Current status of the server.
|
187 |
+
version:
|
188 |
+
type: string
|
189 |
+
description: Version of the server software.
|
poetry.toml
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
[virtualenvs]
|
2 |
+
in-project = true
|
requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
|
server.py
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# server.py
|
2 |
+
import asyncio
|
3 |
+
import uuid
|
4 |
+
from typing import AsyncGenerator, Dict, Tuple, Any, Optional
|
5 |
+
from dataclasses import dataclass
|
6 |
+
from quart import Quart, websocket, request, FileResponse
|
7 |
+
from quart_schema import QuartSchema, validate_request, validate_response
|
8 |
+
import importlib.metadata
|
9 |
+
import secrets
|
10 |
+
import logging
|
11 |
+
|
12 |
+
from broker import SessionBroker, SessionDoesNotExist, ClientRequest, ClientResponse, ClientError
|
13 |
+
|
14 |
+
# Configuraci贸n
|
15 |
+
TIMEOUT: int = 60
|
16 |
+
LOG_LEVEL: int = logging.INFO
|
17 |
+
TRUSTED_HOSTS: list[str] = ["127.0.0.1"]
|
18 |
+
|
19 |
+
# Crear aplicaci贸n
|
20 |
+
app = Quart(__name__)
|
21 |
+
QuartSchema(app)
|
22 |
+
app.logger.setLevel(LOG_LEVEL)
|
23 |
+
|
24 |
+
broker = SessionBroker()
|
25 |
+
|
26 |
+
# Definici贸n de modelos de datos
|
27 |
+
@dataclass
|
28 |
+
class Status:
|
29 |
+
status: str
|
30 |
+
version: str
|
31 |
+
|
32 |
+
@dataclass
|
33 |
+
class Session:
|
34 |
+
session_id: str
|
35 |
+
|
36 |
+
@dataclass
|
37 |
+
class Command:
|
38 |
+
session_id: str
|
39 |
+
command: str
|
40 |
+
|
41 |
+
@dataclass
|
42 |
+
class CommandResponse:
|
43 |
+
return_code: int
|
44 |
+
stdout: str
|
45 |
+
stderr: str
|
46 |
+
|
47 |
+
@dataclass
|
48 |
+
class ErrorResponse:
|
49 |
+
error: str
|
50 |
+
|
51 |
+
# Rutas API
|
52 |
+
@app.get("/status")
|
53 |
+
@validate_response(Status)
|
54 |
+
async def status() -> Status:
|
55 |
+
return Status(status="OK", version=importlib.metadata.version('your-package-name'))
|
56 |
+
|
57 |
+
@app.websocket('/session')
|
58 |
+
async def session_handler():
|
59 |
+
session_id = secrets.token_hex()
|
60 |
+
app.logger.info(f"New session: {session_id}")
|
61 |
+
await websocket.send_as(Session(session_id=session_id), Session)
|
62 |
+
|
63 |
+
task = asyncio.ensure_future(_receive(session_id))
|
64 |
+
try:
|
65 |
+
async for request in broker.subscribe(session_id):
|
66 |
+
app.logger.info(f"Sending request {request.request_id} to client.")
|
67 |
+
await websocket.send_as(request, ClientRequest)
|
68 |
+
finally:
|
69 |
+
task.cancel()
|
70 |
+
|
71 |
+
async def _receive(session_id: str) -> None:
|
72 |
+
while True:
|
73 |
+
response = await websocket.receive_as(ClientResponse)
|
74 |
+
app.logger.info(f"Received response for session {session_id}: {response}")
|
75 |
+
await broker.receive_response(session_id, response)
|
76 |
+
|
77 |
+
@app.post('/command')
|
78 |
+
@validate_request(Command)
|
79 |
+
@validate_response(CommandResponse, 200)
|
80 |
+
@validate_response(ErrorResponse, 500)
|
81 |
+
async def command(data: Command) -> Tuple[CommandResponse | ErrorResponse, int]:
|
82 |
+
try:
|
83 |
+
response_data = await broker.send_request(
|
84 |
+
session_id=data.session_id,
|
85 |
+
data={'action': 'command', 'command': data.command},
|
86 |
+
timeout=TIMEOUT
|
87 |
+
)
|
88 |
+
response = CommandResponse(**response_data)
|
89 |
+
return response, 200
|
90 |
+
except SessionDoesNotExist:
|
91 |
+
app.logger.warning(f"Invalid session ID: {data.session_id}")
|
92 |
+
return ErrorResponse(error='Session does not exist.'), 500
|
93 |
+
except ClientError as e:
|
94 |
+
return ErrorResponse(error=e.message), 500
|
95 |
+
except asyncio.TimeoutError:
|
96 |
+
return ErrorResponse(error='Timeout waiting for client response.'), 500
|
97 |
+
|
98 |
+
# Ejecutar aplicaci贸n
|
99 |
+
def run():
|
100 |
+
app.run(port=7860)
|
101 |
+
|
102 |
+
if __name__ == "__main__":
|
103 |
+
run()
|