Skip to content

Commit 5b15efd

Browse files
committed
Deploy multiplayer backends to VM
1 parent aacec95 commit 5b15efd

13 files changed

Lines changed: 295 additions & 23 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Deploy Multiplayer VM
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- '.github/workflows/deploy-multiplayer-vm.yml'
10+
- 'scripts/deploy_multiplayer_vm.sh'
11+
- 'multiplayer/Tank/server.py'
12+
- 'multiplayer/Tank/requirements.txt'
13+
- 'multiplayer/pacman/server.py'
14+
- 'multiplayer/pacman/requirements.txt'
15+
- 'multiplayer/pong/server.py'
16+
- 'multiplayer/pong/requirements.txt'
17+
- 'multiplayer/spaceshooter/server.py'
18+
- 'multiplayer/spaceshooter/requirements.txt'
19+
- 'multiplayer/arcade_leaderboard/server.py'
20+
21+
concurrency:
22+
group: deploy-multiplayer-vm
23+
cancel-in-progress: true
24+
25+
jobs:
26+
deploy:
27+
runs-on: ubuntu-latest
28+
permissions:
29+
contents: read
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
34+
- name: Setup SSH key
35+
env:
36+
OSDHACK_VM_SSH_KEY: ${{ secrets.OSDHACK_VM_SSH_KEY }}
37+
run: |
38+
test -n "$OSDHACK_VM_SSH_KEY" || { echo "Missing GitHub secret: OSDHACK_VM_SSH_KEY"; exit 1; }
39+
mkdir -p ~/.ssh
40+
printf '%s\n' "$OSDHACK_VM_SSH_KEY" > ~/.ssh/osdhack_vm_key
41+
chmod 600 ~/.ssh/osdhack_vm_key
42+
ssh-keyscan -H 80.225.194.90 >> ~/.ssh/known_hosts
43+
44+
- name: Deploy multiplayer backends
45+
env:
46+
DEPLOY_SSH_HOST: 80.225.194.90
47+
DEPLOY_SSH_USER: ubuntu
48+
DEPLOY_SSH_KEY_PATH: /home/runner/.ssh/osdhack_vm_key
49+
run: |
50+
chmod +x scripts/deploy_multiplayer_vm.sh
51+
./scripts/deploy_multiplayer_vm.sh

js/menu.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const MenuScreen = (() => {
2323
const REGISTER_URL = 'https://unstop.com/hackathons/osdhack-2026-open-source-developers-communityosdc-1693803?lb=';
2424
const ARCADE_LEADERBOARD_URL = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
2525
? 'http://localhost:8787'
26-
: 'http://jha.jpoop.in:8787';
26+
: 'https://jha.jpoop.in:6950';
2727

2828
function isModalOpen() {
2929
const overlay = $('arcadeModalOverlay');

multiplayer/Tank/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const canvas = document.getElementById('gameCanvas');
22
const ctx = canvas.getContext('2d');
33
const SERVER_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
44
? 'ws://localhost:8765'
5-
: 'wss://jha.jpoop.in:6900';
5+
: 'wss://jha.jpoop.in:6940';
66

77
let gameState = {
88
tanks: {},

multiplayer/Tank/server.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import json
33
import math
4+
import os
45
import random
56
import re
67
import time
@@ -738,6 +739,9 @@ async def start_server(self, host='localhost', port=8765):
738739
if __name__ == "__main__":
739740
server = GameServer()
740741
try:
741-
asyncio.run(server.start_server())
742+
asyncio.run(server.start_server(
743+
host=os.environ.get("HOST", "localhost"),
744+
port=int(os.environ.get("PORT", "8765"))
745+
))
742746
except KeyboardInterrupt:
743747
pass

multiplayer/arcade_leaderboard/server.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import json
2+
import os
23
from datetime import datetime, timezone
34
from pathlib import Path
45

56
from aiohttp import web
67

78
ROOT = Path(__file__).resolve().parent
8-
DATA_FILE = ROOT / "leaderboards.json"
9+
DATA_FILE = Path(os.environ.get("ARCADE_LEADERBOARD_DATA_PATH", ROOT / "leaderboards.json"))
910
MAX_ENTRIES_PER_GAME = 10
1011

1112

@@ -76,5 +77,7 @@ async def submit_score(request):
7677

7778

7879
if __name__ == "__main__":
79-
print("Arcade leaderboard server running on http://localhost:8787")
80-
web.run_app(app, host="0.0.0.0", port=8787)
80+
host = os.environ.get("HOST", "0.0.0.0")
81+
port = int(os.environ.get("PORT", "8787"))
82+
print(f"Arcade leaderboard server running on http://{host}:{port}")
83+
web.run_app(app, host=host, port=port)

multiplayer/gee-bee/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const paddleWidth = 75;
1818
const maxBallSpeed = 7;
1919
const LEADERBOARD_URL = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
2020
? 'http://localhost:8787'
21-
: 'http://jha.jpoop.in:8787';
21+
: 'https://jha.jpoop.in:6950';
2222

2323
let animationFrameId = null;
2424
let timerId = null;

multiplayer/pacman/script.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2682,6 +2682,9 @@ class Timer {
26822682
const leaderboardList = document.getElementById('leaderboard-list');
26832683
const playerNameDisplay = document.getElementById('leaderboard-player-name');
26842684
const bestScoreDisplay = document.getElementById('leaderboard-best-score');
2685+
const API_BASE = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
2686+
? ''
2687+
: 'https://jha.jpoop.in:6910';
26852688
const browserId = loadBrowserId();
26862689
const playerName = loadPlayerName();
26872690
let browserBest = parseInt(localStorage.getItem(LOCAL_BEST_KEY) || '0', 10) || 0;
@@ -2775,7 +2778,7 @@ class Timer {
27752778

27762779
async function refreshLeaderboard() {
27772780
try {
2778-
const data = await fetchJson('/api/leaderboard');
2781+
const data = await fetchJson(`${API_BASE}/api/leaderboard`);
27792782
serverAvailable = true;
27802783
renderLeaderboard(data.leaders || []);
27812784
} catch (_error) {
@@ -2785,7 +2788,7 @@ class Timer {
27852788

27862789
async function refreshPlayer() {
27872790
try {
2788-
const data = await fetchJson(`/api/player/${encodeURIComponent(browserId)}`);
2791+
const data = await fetchJson(`${API_BASE}/api/player/${encodeURIComponent(browserId)}`);
27892792
serverAvailable = true;
27902793
applyPlayerRecord(data.player || data);
27912794
} catch (_error) {
@@ -2813,7 +2816,7 @@ class Timer {
28132816
const remaining = [];
28142817
for (const payload of pending) {
28152818
try {
2816-
const data = await fetchJson('/api/score', {
2819+
const data = await fetchJson(`${API_BASE}/api/score`, {
28172820
method: 'POST',
28182821
headers: { 'Content-Type': 'application/json' },
28192822
body: JSON.stringify(payload),
@@ -2831,7 +2834,7 @@ class Timer {
28312834

28322835
async function submitScore(payload) {
28332836
try {
2834-
const data = await fetchJson('/api/score', {
2837+
const data = await fetchJson(`${API_BASE}/api/score`, {
28352838
method: 'POST',
28362839
headers: { 'Content-Type': 'application/json' },
28372840
body: JSON.stringify(payload),

multiplayer/pacman/server.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import asyncio
22
import json
3+
import os
34
from datetime import datetime, timezone
45
from pathlib import Path
56

67
from aiohttp import web
78

89

910
ROOT = Path(__file__).resolve().parent
10-
SCORES_PATH = ROOT / "scores.json"
11+
SCORES_PATH = Path(os.environ.get("PACMAN_SCORES_PATH", ROOT / "scores.json"))
1112
STATIC_FILES = {
1213
"/": ROOT / "index.html",
1314
"/index.html": ROOT / "index.html",
@@ -169,11 +170,26 @@ async def post_score(request):
169170
return web.json_response({"player": player, "leaders": rank_leaders(data.get("players", {}))})
170171

171172

173+
@web.middleware
174+
async def cors_middleware(request, handler):
175+
if request.method == "OPTIONS":
176+
response = web.Response(status=204)
177+
else:
178+
response = await handler(request)
179+
response.headers["Access-Control-Allow-Origin"] = "*"
180+
response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
181+
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
182+
return response
183+
184+
172185
async def create_app():
173-
app = web.Application()
186+
app = web.Application(middlewares=[cors_middleware])
174187
store = ScoreStore(SCORES_PATH)
175188
await store.ensure_file()
176189
app["store"] = store
190+
app.router.add_options("/api/leaderboard", lambda _request: web.Response(status=204))
191+
app.router.add_options("/api/player/{browserId}", lambda _request: web.Response(status=204))
192+
app.router.add_options("/api/score", lambda _request: web.Response(status=204))
177193
app.router.add_get("/", serve_static)
178194
app.router.add_get("/index.html", serve_static)
179195
app.router.add_get("/style.css", serve_static)
@@ -187,5 +203,7 @@ async def create_app():
187203

188204
if __name__ == "__main__":
189205
application = asyncio.run(create_app())
190-
print("http://localhost:8080")
191-
web.run_app(application, host="localhost", port=8080)
206+
host = os.environ.get("HOST", "localhost")
207+
port = int(os.environ.get("PORT", "8080"))
208+
print(f"http://{host}:{port}")
209+
web.run_app(application, host=host, port=port)

multiplayer/pong/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const canvas = document.getElementById('pong');
22
const ctx = canvas.getContext('2d');
33
const WS_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
44
? 'ws://localhost:8765'
5-
: 'wss://jha.jpoop.in:4200';
5+
: 'wss://jha.jpoop.in:6920';
66

77
let gameState = {
88
ballX: 400,

multiplayer/pong/server.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import json
33
import logging
4+
import os
45
import random
56
import time
67
import uuid
@@ -380,18 +381,20 @@ async def game_loop(self):
380381
await asyncio.sleep(1 / 60)
381382

382383
async def start_server(self):
384+
host = os.environ.get("HOST", "localhost")
385+
port = int(os.environ.get("PORT", "8765"))
383386
asyncio.create_task(self.game_loop())
384387
server = await websockets.serve(
385388
self.handle_client,
386-
"localhost",
387-
8765,
389+
host,
390+
port,
388391
ping_interval=20,
389392
ping_timeout=10
390393
)
391394
print("=" * 60)
392395
print("🏓 MULTIPLAYER PONG SERVER STARTED 🏓")
393396
print("=" * 60)
394-
print("Server: ws://localhost:8765")
397+
print(f"Server: ws://{host}:{port}")
395398
print("• Room joining and spectators enabled")
396399
print("• First to 10 wins")
397400
print("=" * 60)

0 commit comments

Comments
 (0)