diff --git a/deployment-service/.env b/deployment-service/.env deleted file mode 100644 index 3a47558..0000000 --- a/deployment-service/.env +++ /dev/null @@ -1,15 +0,0 @@ -# Fly API token for Management Service -FLY_API_TOKEN=FlyV1 fm2_lJPECAAAAAAACJJHxBBwbuk1FNDzBQl/dWFv18aswrVodHRwczovL2FwaS5mbHkuaW8vdjGUAJLOAA//nh8Lk7lodHRwczovL2FwaS5mbHkuaW8vYWFhL3YxxDy2Mf28JyaaU50pzS95pUibO6PCjFa7jJ6l+HdBg1NN3XaM4fRmQX5UnSgoKGTY+q+K+9LEdgeKZ6/jM5PETm7WQZcDR+6sy+8HcD0D+cO9zFyJ1/SnS9hrdSTilS++SrvxdeKswcBZbJs1hDLZ28BNXAtf86/urf+qVJiet4RVZhwmm/+AkgsPV29N98Qg8sBjIe2AJxoGDkUFFMW+uBBabRn+yOiIFmqRpSxuUbs=,fm2_lJPETm7WQZcDR+6sy+8HcD0D+cO9zFyJ1/SnS9hrdSTilS++SrvxdeKswcBZbJs1hDLZ28BNXAtf86/urf+qVJiet4RVZhwmm/+AkgsPV29N98QQwSdewQC1cShau0sg8G0QJcO5aHR0cHM6Ly9hcGkuZmx5LmlvL2FhYS92MZgEks5oC3chzwAAAAEkA5U/F84AD2FZCpHOAA9hWQzEEFT5imWgo8DLSNPluL+eVJfEIMFKlsEhtF+4ZGupW3XW9Yyr8hUFSQW0x4BfcB6zxHYU -FLY_ORG=personal - -# Docker image for each Notebook API server -CONTAINER_IMAGE=python:3.11 - -# Common Tigris bucket name shared by all instances -COMMON_BUCKET=snakeapi-deployment-test-bucket - -# Tigris S3-compatible credentials (injected into A by Fly extension) -AWS_ACCESS_KEY_ID=tid__NSmOVaGknqitaCySppZjqVTgJSdDFnFbWcQllkC_juHwkbQZO -AWS_SECRET_ACCESS_KEY=tsec_6Bz1aMbfYQftuq5WfIVEDZkHwskU4MMjVywdtxSP6uxetEBvkSC2VHI9HfTeDgHr4D6kiz -AWS_ENDPOINT_URL_S3=https://fly.storage.tigris.dev -AWS_REGION=auto diff --git a/deployment-service/dockerfile b/deployment-service/dockerfile index 1955d67..efd442f 100644 --- a/deployment-service/dockerfile +++ b/deployment-service/dockerfile @@ -1,16 +1,11 @@ FROM node:18-alpine WORKDIR /app -# Install dependencies -COPY package.json ./ -RUN npm install --production +COPY package.json package-lock.json ./ +RUN npm ci --only=production -# Copy source and example env COPY src ./src -COPY .env.example .env +COPY snakeapi_service ./snakeapi_service -# Expose port EXPOSE 3006 - -# Start management service -CMD ["npm", "start"] +CMD ["node", "src/index.js"] diff --git a/deployment-service/fly-org.token b/deployment-service/fly-org.token deleted file mode 100644 index 3413ae0..0000000 Binary files a/deployment-service/fly-org.token and /dev/null differ diff --git a/deployment-service/fly.toml b/deployment-service/fly.toml index d265157..7938a3b 100644 --- a/deployment-service/fly.toml +++ b/deployment-service/fly.toml @@ -1,19 +1,15 @@ -app = "deployment-service" -primary_region = "sea" +app = 'deployment-service-test' +primary_region = 'sea' [build] +dockerfile = "Dockerfile" + +[env] + FLY_ORG = "personal" + COMMON_BUCKET = "snakeapi-deployment-test-bucket" + AWS_ENDPOINT_URL_S3 = "https://fly.storage.tigris.dev" + AWS_REGION = "auto" [http_service] - internal_port = 3006 - -[[services]] - protocol = "tcp" - internal_port = 3006 - - [[services.ports]] - port = 443 - handlers = ["tls","http"] - - [[services.ports]] - port = 80 - handlers = ["http"] +internal_port = 3006 +force_https = true diff --git a/deployment-service/snakeapi_service/dockerfile b/deployment-service/snakeapi_service/dockerfile index 9ba254e..80c866a 100644 --- a/deployment-service/snakeapi_service/dockerfile +++ b/deployment-service/snakeapi_service/dockerfile @@ -4,7 +4,6 @@ WORKDIR /app RUN pip install --no-cache-dir jupyter flask awscli flask_cors nbconvert nbformat COPY entrypoint.sh . -COPY battlesnake_server.py . COPY notebook.ipynb . RUN chmod +x entrypoint.sh diff --git a/deployment-service/snakeapi_service/snakeapi_server.py b/deployment-service/snakeapi_service/snakeapi_server.py deleted file mode 100644 index 6a10bd9..0000000 --- a/deployment-service/snakeapi_service/snakeapi_server.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import logging -import typing -import json -import nbformat -from nbconvert import PythonExporter -import subprocess -from flask import Flask, request, jsonify -from flask_cors import CORS - -def run_server(handlers: typing.Dict): - app = Flask(__name__) - CORS(app) - - @app.get("/") - def on_info(): - return handlers["info"]() - - @app.post("/start") - def on_start(): - handlers["start"](request.get_json()) - return "ok" - - @app.post("/move") - def on_move(): - return handlers["move"](request.get_json()) - - @app.post("/end") - def on_end(): - handlers["end"](request.get_json()) - return "ok" - - @app.get("/notebook") - def get_notebook(): - with open("notebook.ipynb", "r", encoding="utf-8") as f: - content = f.read() - return content, 200, {"Content-Type": "application/json"} - - @app.post("/notebook") - def update_notebook(): - notebook_json = request.get_json() - with open("notebook.ipynb", "w", encoding="utf-8") as f: - json.dump(notebook_json, f) - return {"status": "saved"}, 200 - - port = int(os.environ.get("PORT", "3006")) - logging.getLogger("werkzeug").setLevel(logging.ERROR) - app.run(host="0.0.0.0", port=port) diff --git a/deployment-service/src/index.js b/deployment-service/src/index.js index 93af12c..5e08139 100644 --- a/deployment-service/src/index.js +++ b/deployment-service/src/index.js @@ -1,48 +1,19 @@ -require('dotenv').config(); const express = require('express'); -const axios = require('axios'); +const fs = require('fs'); +const path = require('path'); const AWS = require('aws-sdk'); +const axios = require('axios'); const { - FLY_API_TOKEN, FLY_ORG, - CONTAINER_IMAGE, COMMON_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ENDPOINT_URL_S3, - AWS_REGION + AWS_REGION, + FLY_ACCESS_TOKEN } = process.env; -if ( - !FLY_API_TOKEN || - !FLY_ORG || - !CONTAINER_IMAGE || - !COMMON_BUCKET || - !AWS_ACCESS_KEY_ID || - !AWS_SECRET_ACCESS_KEY || - !AWS_ENDPOINT_URL_S3 || - !AWS_REGION -) { - console.error('Missing required environment variables.'); - process.exit(1); -} - -async function createFlyClient() { - const response = await axios.post( - 'https://api.fly.io/api/v1/cli_sessions', - null, - { headers: { Authorization: `Bearer ${FLY_API_TOKEN}` } } - ); - return axios.create({ - baseURL: 'https://api.machines.dev/v1', - headers: { - Authorization: `Bearer ${response.data.token}`, - 'Content-Type': 'application/json' - } - }); -} - const s3 = new AWS.S3({ endpoint: AWS_ENDPOINT_URL_S3, region: AWS_REGION, @@ -50,56 +21,52 @@ const s3 = new AWS.S3({ s3ForcePathStyle: true }); -(async () => { - const fly = await createFlyClient(); - const app = express(); - app.use(express.json()); - - app.post('/servers', async (req, res) => { - const { appName, region = 'sea' } = req.body; - try { - await fly.post('/apps', { name: appName, org_slug: FLY_ORG, primary_region: region }); - await fly.post( - `/apps/${appName}/extensions`, - { name: 'tigris_object_storage', settings: { bucket_name: COMMON_BUCKET } } - ); - await fly.post( - `/apps/${appName}/secrets`, - { secrets: { INSTANCE_PREFIX: appName, BUCKET_NAME: COMMON_BUCKET } } - ); - await fly.post(`/apps/${appName}/deploys`, { image: CONTAINER_IMAGE }); - res.json({ status: 'created', app: appName, url: `https://${appName}.fly.dev` }); - } catch (error) { - res.status(500).json({ error: error.response?.data || error.message }); +function createFlyClient() { + return axios.create({ + baseURL: 'https://api.fly.io', + headers: { + Authorization: `Bearer ${FLY_ACCESS_TOKEN}`, + 'Content-Type': 'application/json' } }); +} - app.post('/servers/:name/notebook', async (req, res) => { - const name = req.params.name; - try { - await s3.putObject({ - Bucket: COMMON_BUCKET, - Key: `${name}/notebook.ipynb`, - Body: JSON.stringify(req.body), - ContentType: 'application/json' - }).promise(); - await fly.post(`/apps/${name}/deploys`, { image: CONTAINER_IMAGE }); - res.json({ status: 'updated', app: name }); - } catch (error) { - res.status(500).json({ error: error.message }); - } - }); +const app = express(); +app.use(express.json()); - app.delete('/servers/:name', async (req, res) => { - const name = req.params.name; - try { - await fly.delete(`/apps/${name}`); - res.json({ status: 'deleted', app: name }); - } catch (error) { - res.status(500).json({ error: error.response?.data || error.message }); - } - }); +app.post('/deploy', async (req, res) => { + const { appName, region = 'sea', notebookName } = req.body; + if (!appName || !notebookName) return res.status(400).json({ error: 'appName and notebookName are required.' }); - const port = process.env.PORT || 3006; - app.listen(port, () => console.log(`Listening on port ${port}`)); -})(); + try { + const fly = createFlyClient(); + await fly.post('/apps', { name: appName, org_slug: FLY_ORG, primary_region: region }); + await fly.post(`/apps/${appName}/secrets`, { + secrets: { + INSTANCE_PREFIX: appName, + BUCKET_NAME: COMMON_BUCKET, + AWS_ACCESS_KEY_ID, + AWS_SECRET_ACCESS_KEY, + AWS_ENDPOINT_URL_S3, + AWS_REGION + } + }); + + const tpl = fs.readFileSync(path.join(__dirname, '../snakeapi_service', notebookName)); + await s3.putObject({ + Bucket: COMMON_BUCKET, + Key: `${appName}/${notebookName}`, + Body: tpl, + ContentType: 'application/json' + }).promise(); + + await fly.post(`/apps/${appName}/deploys`); + res.json({ status: 'created', app: appName, url: `https://${appName}.fly.dev` }); + } catch (error) { + console.error('Deploy endpoint failed:', error); + res.status(500).json({ error: error.response?.data || error.message }); + } +}); + +const port = process.env.PORT || 3006; +app.listen(port, '0.0.0.0', () => console.log(`Listening on port ${port}`)); diff --git a/deployment-service/user.token b/deployment-service/user.token deleted file mode 100644 index 3413ae0..0000000 Binary files a/deployment-service/user.token and /dev/null differ