partial deployment
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*/__pycache__/
|
*/__pycache__/
|
||||||
|
data/
|
||||||
@@ -5,4 +5,9 @@ COPY requirements.txt requirements.txt
|
|||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
CMD ["fastapi", "run", "main.py"]
|
EXPOSE 80
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=10s \
|
||||||
|
CMD python healthcheck.py http://localhost/api/ || exit 1
|
||||||
|
|
||||||
|
CMD ["fastapi", "run", "main.py", "--port", "80", "--proxy-headers"]
|
||||||
24
api/healthcheck.py
Executable file
24
api/healthcheck.py
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/env python3
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from sys import argv
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(argv) > 1:
|
||||||
|
try_addr = argv[1]
|
||||||
|
else:
|
||||||
|
try_addr = "http://localhost/api/"
|
||||||
|
try:
|
||||||
|
r = httpx.get(try_addr)
|
||||||
|
except:
|
||||||
|
print("No server at address or port.")
|
||||||
|
quit(1)
|
||||||
|
if r.is_success:
|
||||||
|
print(f"Success. {r.status_code}")
|
||||||
|
quit(0)
|
||||||
|
else:
|
||||||
|
print(f"Fail!!! {r.status_code}")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sys import argv
|
from sys import argv
|
||||||
|
from exceptions import bad_key
|
||||||
|
|
||||||
|
from repos.api_keys import ApiKeyRepo
|
||||||
|
|
||||||
from routers.prefixes import prefix_router
|
from routers.prefixes import prefix_router
|
||||||
from routers.tickets import ticket_router
|
from routers.tickets import ticket_router
|
||||||
@@ -16,6 +19,12 @@ if argv[1] == "run":
|
|||||||
else:
|
else:
|
||||||
app = FastAPI(title="TAM3 API Server")
|
app = FastAPI(title="TAM3 API Server")
|
||||||
|
|
||||||
|
@app.get("/api/")
|
||||||
|
def remote_check(api_key: str = ""):
|
||||||
|
if not ApiKeyRepo().check_api(api_key):
|
||||||
|
return {"status": "healthy", "auth": False, "whoami": "TAM3 Server"}
|
||||||
|
return {"status": "healthy", "auth": True, "whoami": "TAM3 Server"}
|
||||||
|
|
||||||
app.include_router(prefix_router)
|
app.include_router(prefix_router)
|
||||||
app.include_router(ticket_router)
|
app.include_router(ticket_router)
|
||||||
app.include_router(basket_router)
|
app.include_router(basket_router)
|
||||||
|
|||||||
@@ -21,10 +21,13 @@ def get_one_prefix(api_key: str, prefix_name: str):
|
|||||||
|
|
||||||
|
|
||||||
@prefix_router.post("/")
|
@prefix_router.post("/")
|
||||||
def post_one_prefix(api_key: str, p: Prefix):
|
def post_one_prefix(api_key: str, p: Prefix | list[Prefix]):
|
||||||
if not ApiKeyRepo().check_api(api_key):
|
if not ApiKeyRepo().check_api(api_key):
|
||||||
raise bad_key
|
raise bad_key
|
||||||
rep_detail = PrefixRepo().add_one(p)
|
if type(p) is list:
|
||||||
|
rep_detail = PrefixRepo().add_list(p)
|
||||||
|
elif type(p) is Prefix:
|
||||||
|
rep_detail = PrefixRepo().add_one(p)
|
||||||
return {"detail": rep_detail}
|
return {"detail": rep_detail}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: dbob16/tam3-db:0.0.1
|
image: dbob16/tam3-db:0.1.0
|
||||||
restart: always
|
restart: always
|
||||||
hostname: mariadb
|
hostname: mariadb
|
||||||
container_name: mariadb
|
container_name: mariadb
|
||||||
|
|||||||
39
deployment/remote_server/compose.yml
Normal file
39
deployment/remote_server/compose.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
services:
|
||||||
|
tam3-db:
|
||||||
|
image: dbob16/tam3-db:0.1.0
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MARIADB_RANDOM_ROOT_PASSWORD: 1
|
||||||
|
MARIADB_DATABASE: tam3
|
||||||
|
MARIADB_USER: tam3
|
||||||
|
MARIADB_PASSWORD: ${DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- "${DB_LOCATION}:/var/lib/mysql"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
start_period: 10s
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
tam3-api:
|
||||||
|
image: dbob16/tam3-api:0.1.0
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
TAM3_DATA_PATH: /data
|
||||||
|
TAM3_DB_HOST: tam3-db
|
||||||
|
TAM3_DB_DATABASE: tam3
|
||||||
|
TAM3_DB_USER: tam3
|
||||||
|
TAM3_DB_PASSWD: ${DB_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "8000:80"
|
||||||
|
autoheal:
|
||||||
|
deploy:
|
||||||
|
replicas: 1
|
||||||
|
environment:
|
||||||
|
AUTOHEAL_CONTAINER_LABEL: autoheal-app
|
||||||
|
image: willfarrell/autoheal:latest
|
||||||
|
network_mode: none
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
export async function load({ fetch }) {
|
export async function load({ fetch }) {
|
||||||
const res = await fetch('/api/prefixes');
|
const res = await fetch('/api/prefixes');
|
||||||
|
const check_res = await fetch('/api');
|
||||||
|
const check_json = await check_res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return { prefixes: data, status: "Prefixes fetched successfully." }
|
return { prefixes: data, status: check_json.detail }
|
||||||
} else {
|
} else {
|
||||||
return { prefixes: [], status: "Error fetching prefixes."}
|
return { prefixes: [], status: check_json.detail }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,10 +64,15 @@
|
|||||||
<div><h2>Admin Mode:</h2></div>
|
<div><h2>Admin Mode:</h2></div>
|
||||||
<div class="flex-row {current_prefix.color}">
|
<div class="flex-row {current_prefix.color}">
|
||||||
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
|
<a href="/prefixes" target="_blank" class="styled">Prefix Editor</a>
|
||||||
|
<a href="/backuprestore" target="_blank" class="styled">Backup/Restore</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="status tb-margin">
|
||||||
|
{data.status}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="annotation">
|
<div class="annotation">
|
||||||
<p>Ticket Auction Manager 3 by Dilan Gilluly</p>
|
<p>Ticket Auction Manager 3 by Dilan Gilluly</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
18
webapp/src/routes/api/+server.js
Normal file
18
webapp/src/routes/api/+server.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { env } from "$env/dynamic/private";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
if (env.TAM3_REMOTE) {
|
||||||
|
const res = await fetch(`${env.TAM3_REMOTE}/api/?api_key=${env.TAM3_REMOTE_KEY}`);
|
||||||
|
if (!res.ok) {
|
||||||
|
return new Response(JSON.stringify({detail: "Server setup but is getting an error. Check TAM3_REMOTE and TAM3_REMOTE_KEY env variables."}))
|
||||||
|
};
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.auth) {
|
||||||
|
return new Response(JSON.stringify({detail: "Remote connection is successful."}))
|
||||||
|
} else {
|
||||||
|
return new Response(JSON.stringify({detail: "Server is connecting but the api key isn't checking out."}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Response(JSON.stringify({detail: "Operating in offline mode."}))
|
||||||
|
}
|
||||||
|
}
|
||||||
9
webapp/src/routes/api/backuprestore/local/+server.js
Normal file
9
webapp/src/routes/api/backuprestore/local/+server.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { db } from "$lib/server/db";
|
||||||
|
import { prefixes, tickets, baskets } from "$lib/server/db/schema";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const p_data = await db.select().from(prefixes);
|
||||||
|
const t_data = await db.select().from(tickets);
|
||||||
|
const b_data = await db.select().from(baskets);
|
||||||
|
return new Response(JSON.stringify({prefixes: p_data, tickets: t_data, baskets: b_data}), {status: 200, statusText: "Loaded backup file successfully."})
|
||||||
|
}
|
||||||
78
webapp/src/routes/backuprestore/+page.svelte
Normal file
78
webapp/src/routes/backuprestore/+page.svelte
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<script>
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
let upload_file = $state();
|
||||||
|
let contents = $state("");
|
||||||
|
let results = $state("");
|
||||||
|
|
||||||
|
function setResult(new_result) {
|
||||||
|
results = new_result;
|
||||||
|
setTimeout(() => results = "", 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadBackupFile(target) {
|
||||||
|
let fetch_url;
|
||||||
|
if (target === "local") {
|
||||||
|
fetch_url = "/api/backuprestore/local";
|
||||||
|
} else if (target === "remote") {
|
||||||
|
fetch_url = "/api/backuprestore"
|
||||||
|
};
|
||||||
|
const now = new Date()
|
||||||
|
const date = {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: String(now.getMonth()+1).padStart(2, '0'),
|
||||||
|
day: String(now.getDate()).padStart(2, '0'),
|
||||||
|
hour: String(now.getHours()).padStart(2, '0'),
|
||||||
|
minutes: String(now.getMinutes()).padStart(2, '0')
|
||||||
|
}
|
||||||
|
const res = await fetch(fetch_url);
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
const jsonString = JSON.stringify(data); // Pretty print JSON
|
||||||
|
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `TAM3_${date.year}-${date.month}-${date.day} ${date.hour}-${date.minutes}.json`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function fileUpload() {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsText(upload_file[0], 'utf-8');
|
||||||
|
reader.onload = function(e) {
|
||||||
|
contents = String(e.target.result)
|
||||||
|
};
|
||||||
|
setTimeout(() => {
|
||||||
|
fetch('/api/backuprestore', {body: contents, method: 'POST', headers: {'Content-Type': 'application/json'}})
|
||||||
|
.then(() => {setResult("File was sent. Check to see if your data exists now.")})
|
||||||
|
.catch(() => {setResult("Error sending file.")})
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
if (browser) {
|
||||||
|
document.title = "TAM3 - Backup and Restore"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>TAM3 Backup and Restore</h1>
|
||||||
|
<div>
|
||||||
|
<h2>Backup</h2>
|
||||||
|
<div class="flex-row">
|
||||||
|
<button class="styled" onclick={() => downloadBackupFile("remote")}>Download Backup from Remote (if setup)</button>
|
||||||
|
<button class="styled" onclick={() => downloadBackupFile("local")}>Download Backup from Local Only</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Restore</h2>
|
||||||
|
<div class="flex-row">
|
||||||
|
<input type="file" accept=".json" bind:files={upload_file}>
|
||||||
|
<button class="styled" onclick={fileUpload}>Upload</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{results}
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user