Compare commits
No commits in common. "master" and "e0e69f8c06bd1782307d33c1f081535192163271" have entirely different histories.
master
...
e0e69f8c06
|
@ -4,8 +4,7 @@ __pycache__/
|
|||
*.py[cod]
|
||||
*$py.class
|
||||
requirements.txt
|
||||
users.json
|
||||
app/users.json
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
|
|
16
Dockerfile
16
Dockerfile
|
@ -1,14 +1,6 @@
|
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.9-slim
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8-slim
|
||||
ENV DOCKER=1
|
||||
|
||||
RUN pip install \
|
||||
python-jose \
|
||||
passlib \
|
||||
python-multipart \
|
||||
docker \
|
||||
aiodocker \
|
||||
sse-starlette \
|
||||
anyio
|
||||
COPY ./app /app/app
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--proxy-headers", "--host", "0.0.0.0", "--port", "80"]
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install -r /app/requirements.txt
|
||||
COPY ./app /app/app
|
7
Pipfile
7
Pipfile
|
@ -10,9 +10,6 @@ uvicorn = {extras = ["standard"], version = "*"}
|
|||
python-jose = {extras = ["cryptography"], version = "*"}
|
||||
passlib = "*"
|
||||
python-multipart = "*"
|
||||
docker = "*"
|
||||
aiodocker = "*"
|
||||
sse-starlette = "*"
|
||||
|
||||
[dev-packages]
|
||||
pytest = "*"
|
||||
|
@ -22,8 +19,8 @@ black = "*"
|
|||
mypy = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.9"
|
||||
python_version = "3.8"
|
||||
|
||||
[scripts]
|
||||
test = "pytest app/test/test_auth.py app/test/test_server.py -W ignore::DeprecationWarning -s"
|
||||
test = "pytest app/test/test_main.py -s"
|
||||
dev = "uvicorn app.main:app --reload"
|
||||
|
|
32
app/auth.py
32
app/auth.py
|
@ -3,14 +3,11 @@ import json
|
|||
from datetime import timedelta, datetime
|
||||
from typing import Optional
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi import Depends, HTTPException, status, APIRouter
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from app.models import User, UserInDB, TokenData, Token
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from app.models import User, UserInDB, TokenData
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# to get a string like this run:
|
||||
# openssl rand -hex 32
|
||||
|
@ -81,27 +78,4 @@ async def get_current_user(token: str = Depends(oauth2_scheme)):
|
|||
async def get_current_active_user(current_user: User = Depends(get_current_user)):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
def authorise(server, current_user: User = Depends(get_current_active_user)):
|
||||
print(server, current_user.servers)
|
||||
if server not in current_user.servers:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
|
||||
|
||||
|
||||
|
||||
@router.post("/token", response_model=Token)
|
||||
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.username}, expires_delta=access_token_expires
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
return current_user
|
51
app/main.py
51
app/main.py
|
@ -1,20 +1,35 @@
|
|||
from fastapi import FastAPI, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from app import auth, user, server
|
||||
from os import getenv
|
||||
app = FastAPI(docs_url="/")
|
||||
from datetime import timedelta
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
dependencies = list()
|
||||
if not getenv('DISABLE_AUTH'):
|
||||
dependencies.append(Depends(auth.authorise))
|
||||
app.include_router(auth.router)
|
||||
app.include_router(user.router)
|
||||
app.include_router(server.router, dependencies=dependencies)
|
||||
from fastapi import Depends, FastAPI, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from app.models import Token, User
|
||||
from app.auth import get_current_active_user, create_access_token, authenticate_user, fake_users_db, ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/token", response_model=Token)
|
||||
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
user = authenticate_user(fake_users_db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token = create_access_token(
|
||||
data={"sub": user.username}, expires_delta=access_token_expires
|
||||
)
|
||||
return {"access_token": access_token, "token_type": "bearer"}
|
||||
|
||||
|
||||
@app.get("/users/me/", response_model=User)
|
||||
async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
||||
return current_user
|
||||
|
||||
|
||||
@app.get("/users/me/items/")
|
||||
async def read_own_items(current_user: User = Depends(get_current_active_user)):
|
||||
return [{"item_id": "Foo", "owner": current_user.username}]
|
||||
|
|
|
@ -12,9 +12,9 @@ class TokenData(BaseModel):
|
|||
|
||||
class User(BaseModel):
|
||||
username: str
|
||||
email: Optional[str] = None
|
||||
full_name: Optional[str] = None
|
||||
disabled: Optional[bool] = None
|
||||
servers: list
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
hashed_password: str
|
|
@ -1,76 +0,0 @@
|
|||
from fastapi import APIRouter, HTTPException, status, WebSocket, Request
|
||||
from sse_starlette.sse import EventSourceResponse
|
||||
import base64
|
||||
import docker
|
||||
import aiodocker
|
||||
from asyncio import sleep
|
||||
|
||||
router = APIRouter()
|
||||
client = docker.from_env()
|
||||
docker = aiodocker.Docker()
|
||||
|
||||
@router.post("/server/{server}/start")
|
||||
async def start(server):
|
||||
try:
|
||||
container = await getContainer(server)
|
||||
await container.start()
|
||||
except:
|
||||
raise HTTPException(status_code=500)
|
||||
return server
|
||||
|
||||
@router.post("/server/{server}/stop")
|
||||
async def stop(server):
|
||||
try:
|
||||
container = await getContainer(server)
|
||||
await container.stop()
|
||||
except:
|
||||
raise HTTPException(status_code=500)
|
||||
return server
|
||||
|
||||
@router.post("/server/{server}/command/{command}",
|
||||
description="Take server and base64 encoded command")
|
||||
async def commnd(server, command):
|
||||
try:
|
||||
container = client.containers.get(server)
|
||||
print(command)
|
||||
cmd = base64.urlsafe_b64decode(command).decode('utf_8')
|
||||
print(cmd)
|
||||
container.exec_run(cmd="/usr/local/bin/cmd " + str(cmd))
|
||||
s= 200
|
||||
running="Success"
|
||||
except:
|
||||
raise HTTPException(status_code=500)
|
||||
|
||||
return f"{server} {base64.urlsafe_b64decode(command).decode('utf_8')}"
|
||||
|
||||
|
||||
@router.get("/server/{server}/logs")
|
||||
async def logs(server: str, request: Request, follow: bool = True, tail: int = 1000):
|
||||
container = await getContainer(server)
|
||||
#event_generator = logGenerator(request, server)
|
||||
if follow:
|
||||
return EventSourceResponse(logStream(container, tail))
|
||||
else:
|
||||
return await container.log(stdout=True, follow=False, tail=tail)
|
||||
|
||||
async def logStream(container, tail):
|
||||
async for line in container.log(stdout=True, follow=True, tail=tail):
|
||||
yield line
|
||||
yield '\n'
|
||||
|
||||
@router.get("/server/{server}/stats")
|
||||
async def stats(server: str, request: Request, stream: bool = True, delay: int = 2):
|
||||
container = await getContainer(server)
|
||||
if stream:
|
||||
return EventSourceResponse(statStream(request, container, delay))
|
||||
return await container.stats(stream=False)
|
||||
|
||||
async def statStream(request, container, delay):
|
||||
while True:
|
||||
yield await container.stats(stream=False)
|
||||
_delay = delay - 1 if delay else 0
|
||||
print(_delay)
|
||||
await sleep(_delay)
|
||||
|
||||
async def getContainer(server):
|
||||
return await docker.containers.get(server)
|
|
@ -8,8 +8,7 @@
|
|||
from fastapi.testclient import TestClient
|
||||
from datetime import timedelta
|
||||
|
||||
from app.main import app
|
||||
from app.auth import create_access_token
|
||||
from app.main import app, create_access_token
|
||||
|
||||
client = TestClient(app)
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
#curl -i -X POST http://localhost:8000/token -H "Content-Type: application/x-www-form-urlencoded" -d "username=johndoe&password=secret"
|
||||
|
||||
# curl -X 'GET' \
|
||||
# 'http://localhost:8000/users/me/' \
|
||||
# -H 'accept: application/json' \
|
||||
# -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJqb2huZG9lIiwiZXhwIjoxNjMxNDQ4MjQ1fQ.DrM92jgRiry0uXBXn-61rRehATW4zDhHUWoGR6lv6Us'
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
import docker
|
||||
|
||||
from server import *
|
||||
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
testclient = TestClient(app)
|
||||
|
||||
def test_start():
|
||||
#response = testclient.post("/server/minecraft/start")
|
||||
#assert response.status_code == 200
|
||||
pass
|
||||
|
11
app/user.py
11
app/user.py
|
@ -1,11 +0,0 @@
|
|||
from fastapi import Depends, APIRouter
|
||||
from app.models import User
|
||||
from app.auth import get_current_active_user
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/users/me/", response_model=User)
|
||||
async def read_users_me(current_user: User = Depends(get_current_active_user)):
|
||||
return current_user
|
||||
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
from distutils.fancy_getopt import fancy_getopt
|
||||
from site import USER_BASE
|
||||
from passlib.context import CryptContext
|
||||
from json import load, dump
|
||||
from sys import argv
|
||||
|
||||
with open("app/users.json", 'r+') as f:
|
||||
fake_users_db = load(f)
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
fake_users_db[argv[1]] = {"username": argv[1], "hashed_password": pwd_context.hash(argv[2]),
|
||||
"disabled": False, "servers": argv[3:]}
|
||||
f.seek(0)
|
||||
dump(fake_users_db, f, indent=2)
|
||||
print(fake_users_db)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"johndoe": {
|
||||
"username": "johndoe",
|
||||
"full_name": "John Doe",
|
||||
"email": "johndoe@example.com",
|
||||
"hashed_password": "$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36WQoeG6Lruj3vjPGga31lW",
|
||||
"disabled": "False"
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
api:
|
||||
app:
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 8000:8000
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- ./app:/app/app
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./users.json:/app/app/users.json
|
||||
command: uvicorn app.main:app --host 0.0.0.0 --reload
|
|
@ -5,7 +5,10 @@ services:
|
|||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 8000:80
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- ./app:/app/app
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: pytest app/test/test_main.py -s
|
|
@ -1,33 +1,13 @@
|
|||
version: '3.7'
|
||||
|
||||
services:
|
||||
api:
|
||||
build: ./
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./users.json:/app/app/users.json
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- caddy
|
||||
labels:
|
||||
caddy: console
|
||||
caddy.reverse_proxy: "{{upstreams 80}}"
|
||||
#caddy.tls: "admin@localhost"
|
||||
caddy.tls: "internal"
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
- .env
|
||||
ports:
|
||||
- 8000:80
|
||||
tty: true
|
||||
stdin_open: true
|
||||
|
||||
|
||||
caddy:
|
||||
image: lucaslorentz/caddy-docker-proxy:ci-alpine
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
networks:
|
||||
- caddy
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
name: caddy
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"test": {
|
||||
"username": "test",
|
||||
"hashed_password": "$2b$12$VS1k1fdA4x2EeF1a/LMIyex.evEQGF5EsviIcG22S55YO8uUQCT7q",
|
||||
"disabled": false,
|
||||
"servers": [
|
||||
"test"
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue