Kaynağa Gözat

Big swath of reorganizing now that the deployment path is clear

Kirk Trombley 5 yıl önce
ebeveyn
işleme
34eb2cd10b

+ 2 - 1
.dockerignore

@@ -1,2 +1,3 @@
 **/.venv/
-**/*.pyc
+**/*.pyc
+**/secrets.sh

+ 3 - 1
.gitignore

@@ -7,4 +7,6 @@ __pycache__/
 dist/
 node_modules/
 
-secrets.toml
+secrets.sh
+
+ggsh.tgz

+ 10 - 0
README.md

@@ -1,5 +1,11 @@
 # Geoguessr Self-Hosted Reimplementation
 
+## Build Docker Image Tarball
+
+```bash
+./docker/build.sh
+```
+
 ## Back End
 
 In the future, names should be tracked via actual tokens
@@ -11,6 +17,10 @@ GET /
         "version": string,
         "status": string
     }
+GET /googleApiKey
+    Returns {
+        "googleApiKey": string
+    }
 PUT /game
     Header Authorization: Name string
     Accepts {

+ 2 - 2
Dockerfile → docker/Dockerfile

@@ -14,11 +14,11 @@ COPY server/* ./
 
 RUN pip3 install -r requirements.txt
 
-COPY nginx.conf /etc/nginx/nginx.conf
+COPY docker/nginx.conf /etc/nginx/nginx.conf
 
 COPY ui/build /usr/share/nginx/html
 
-COPY start-gunicorn-and-nginx.sh .
+COPY docker/start-gunicorn-and-nginx.sh .
 
 EXPOSE 80
 

+ 7 - 0
docker/build.sh

@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+pushd ui
+yarn build
+popd
+sudo docker build -t ggsh:latest -f docker/Dockerfile . 
+sudo docker save ggsh:latest | gzip > docker/ggsh.tgz

+ 0 - 0
nginx.conf → docker/nginx.conf


+ 0 - 0
start-gunicorn-and-nginx.sh → docker/start-gunicorn-and-nginx.sh


+ 0 - 93
run-docker.sh

@@ -1,93 +0,0 @@
-#!/usr/bin/env sh
-
-if [ $# -lt 1 ] || [ "$1" == "help" ] || [ "$1" == "h" ]
-then
-    echo "usage: $0 (help|status|clean|run|logs) [container-name]"
-    echo "  help   - print this message"
-    echo "  status - report whether a container named container-name can be found, and whether or not it is running"
-    echo "  clean  - kill and remove any existing containers matching container-name"
-    echo "  run    - kill and remove any existing containers matching container-name, build the current directory into a new image, and deploy a new container container-name"
-    echo "  logs   - tail the logs of the container named container-name, equivalent to docker logs -f container-name"
-    echo "  container-name defaults to ggsh-instance"
-    echo "  Run as root to ensure docker can be used, or set FORCE_NOROOT=* to force the script to run anyway."
-    exit 1
-fi
-
-if [ "$EUID" -ne 0 ] && [ -z "$FORCE_NOROOT" ]
-then
-    echo "$0 must be run as root to use docker. Set FORCE_NOROOT=* to force the script to run anyway."
-    exit 1
-fi
-
-if [ $# -gt 1 ]
-then
-    CONTAINER_NAME=$2
-else
-    CONTAINER_NAME="ggsh-instance"
-fi
-
-if [ -z "$PORT" ]
-then
-    echo "Defaulting to PORT=8080"
-    PORT=8080
-fi
-
-status_check() {
-    docker inspect -f '{{.State.Running}}' $CONTAINER_NAME 2> /dev/null
-}
-
-clean_container() {
-    STATUS=$(status_check)
-    if [ "$STATUS" = "true" ]
-    then
-        echo "Container $CONTAINER_NAME is running and will be stopped and removed."
-        docker kill $CONTAINER_NAME
-        docker rm $CONTAINER_NAME
-    elif [ "$STATUS" = "false" ]
-    then
-        echo "Container $CONTAINER_NAME is stopped and will be removed."
-        docker rm $CONTAINER_NAME
-    else
-        echo "No existing container $CONTAINER_NAME could be found, no cleanup will be performed."
-    fi
-}
-
-case $1 in
-    "l"|"logs")
-        STATUS=$(status_check)
-        if [ "$STATUS" = "true" ] || [ "$STATUS" = "false" ]
-        then
-            docker logs -f $CONTAINER_NAME
-        else
-            echo "No existing container $CONTAINER_NAME could be found."
-        fi
-    ;;
-    "r"|"run")
-        clean_container
-        echo "Building GeoGuessr Self Host image as ggsh:latest"
-        docker build -t ggsh:latest .
-        echo "Executing new container $CONTAINER_NAME using ggsh:latest"
-        docker run -p$PORT:80 --name $CONTAINER_NAME -d ggsh
-        echo "Geoguessr Self Host UI accessible at http://localhost:$PORT/"
-        echo "Geoguessr Self Host API accessible at http://localhost:$PORT/api/"
-    ;;
-    "c"|"clean")
-        clean_container
-    ;;
-    "s"|"status")
-        STATUS=$(status_check)
-        if [ "$STATUS" = "true" ]
-        then
-            echo "Existing container $CONTAINER_NAME is running."
-        elif [ "$STATUS" = "false" ]
-        then
-            echo "Existing container $CONTAINER_NAME is stopped."
-        else
-            echo "No existing container $CONTAINER_NAME could be found."
-        fi
-    ;;
-    *)
-        echo "Unknown option $1, must be one of help, status, clean, run, logs."
-        exit 1
-    ;;
-esac

+ 9 - 7
server/app.py

@@ -1,6 +1,7 @@
+import os
+
 from flask import Flask, jsonify
 from flask_cors import CORS
-import toml
 
 from db import db
 from game_api import game
@@ -8,12 +9,8 @@ from game_api import game
 app = Flask(__name__)
 CORS(app)
 
-with open("secrets.toml") as infile:
-    secrets = toml.load(infile)
-app.secret_key = secrets["secret_key"]
-app.config["GROUP_PASS"] = secrets["group_pass"]
-app.config["GOOGLE_API_KEY"] = secrets["google_api_key"]
-app.config["SQLALCHEMY_DATABASE_URI"] = secrets["db_uri"]
+app.config["GOOGLE_API_KEY"] = os.environ["GOOGLE_API_KEY"]
+app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URI"]
 app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
 
 app.register_blueprint(game, url_prefix="/game")
@@ -27,5 +24,10 @@ def version():
     return jsonify({"version": "1", "status": "healthy"})
 
 
+@app.route("/googleApiKey")
+def google_api_key():
+    return jsonify({"googleApiKey": app.config["GOOGLE_API_KEY"]})
+
+
 if __name__ == "__main__":
     app.run("0.0.0.0", 5000, debug=True)

+ 0 - 1
server/requirements.txt

@@ -12,6 +12,5 @@ MarkupSafe==1.1.1
 requests==2.22.0
 six==1.12.0
 SQLAlchemy==1.3.7
-toml==0.10.0
 urllib3==1.25.3
 Werkzeug==0.15.5

+ 1 - 0
ui/package.json

@@ -2,6 +2,7 @@
   "name": "geoguessr-selfhost",
   "version": "0.1.0",
   "private": true,
+  "homepage": "https://kirkleon.ddns.net/terrassumptions/",
   "dependencies": {
     "pretty-ms": "^5.0.0",
     "react": "^16.9.0",

+ 18 - 9
ui/src/App.js

@@ -1,12 +1,12 @@
 import React from 'react';
-import { createGame, gameInfo, joinGame, getGuesses, sendGuess } from "./services/ggsh.service";
+import { getGoogleApiKey, createGame, gameInfo, joinGame, getGuesses, sendGuess } from "./services/ggsh.service";
 import GamePanel from "./components/game-panel.component";
 import ApiInfo from "./components/api-info.component";
 import PlayerScores from "./components/player-scores.component";
 import RoundSummary from './components/round-summary.component';
-import './App.css';
 import PreGame from './components/pre-game.component';
 import PreRound from './components/pre-round.component';
+import './App.css';
 
 const LOADING    = "LOADING";   // Application is loading
 const PRE_GAME   = "PREGAME";   // Game is not yet started
@@ -20,7 +20,8 @@ class Game extends React.Component {
   constructor(props) {
     super(props);
     this.state = {
-      gameState: PRE_GAME,
+      googleApiKey: null,
+      gameState: LOADING,
       playerName: null,
       gameId: null,
       currentRound: null,
@@ -35,6 +36,11 @@ class Game extends React.Component {
 
   // TODO error handling throughout - at the moment it assumes all calls always succeed
 
+  async componentDidMount() {
+    const googleApiKey = await getGoogleApiKey();
+    this.setState({ googleApiKey, gameState: PRE_GAME });
+  }
+
   async handleCreateGame() {
     this.setState({ gameState: LOADING });
     const { playerName } = this.state;
@@ -95,11 +101,12 @@ class Game extends React.Component {
   }
 
   render() {
-    const { 
-      gameState, 
-      gameId, 
-      playerName, 
-      targetPoint, 
+    const {
+      googleApiKey,
+      gameState,
+      gameId,
+      playerName,
+      targetPoint,
       selectedPoint,
     } = this.state;
 
@@ -124,6 +131,7 @@ class Game extends React.Component {
       case IN_ROUND:
         const { roundTimer } = this.state;
         return <GamePanel
+          googleApiKey={googleApiKey}
           onSelectPoint={latLng => this.setState({selectedPoint: latLng})}
           onSubmitGuess={() => this.handleSubmitGuess()}
           streetViewPoint={targetPoint}
@@ -134,6 +142,7 @@ class Game extends React.Component {
       case POST_ROUND:
         const { currentRound, lastScore, totalScore } = this.state;
         return <RoundSummary
+          googleApiKey={googleApiKey}
           roundNum={currentRound}
           score={lastScore}
           totalScore={totalScore}
@@ -153,7 +162,7 @@ class Game extends React.Component {
       default:
         this.setState({ gameState: ERROR });
         return <p>Application state is inconsistent, please refresh and rejoin your previous game.</p>
-    } 
+    }
   }
 }
 

+ 5 - 3
ui/src/components/game-panel.component.jsx

@@ -1,14 +1,14 @@
 import React from 'react';
 import { compose, withProps } from "recompose";
 import { withScriptjs, withGoogleMap, StreetViewPanorama, GoogleMap, Marker } from "react-google-maps";
-import { GOOGLE_API_KEY } from "../config";
 import RoundTimer from "./round-timer.component";
 
 // TODO want a button for moving the pano back to start somehow
 
+const getMapUrl = googleApiKey => `https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&v=3.exp&libraries=geometry,drawing,places`
+
 const PositionedStreetView = compose(
   withProps({
-    googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`,
     loadingElement: <div style={{ height: `100%` }} />,
     containerElement: <div style={{ height: `70vh` }} />,
     mapElement: <div style={{ height: `100%` }} />,
@@ -29,7 +29,6 @@ const PositionedStreetView = compose(
 
 const SelectionMap = compose(
   withProps({
-    googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}&v=3.exp&libraries=geometry,drawing,places`,
     loadingElement: <div style={{ height: `100%` }} />,
     containerElement: <div style={{ height: `250px` }} />,
     mapElement: <div style={{ height: `100%` }} />,
@@ -55,6 +54,7 @@ const SelectionMap = compose(
 );
 
 const GamePanel = ({
+  googleApiKey,
   onSelectPoint,
   onSubmitGuess,
   streetViewPoint,
@@ -69,6 +69,7 @@ const GamePanel = ({
   }}>
     <div style={{ flexGrow: 3 }}>
       <PositionedStreetView
+        googleMapURL={getMapUrl(googleApiKey)}
         point={streetViewPoint}
       />
     </div>
@@ -80,6 +81,7 @@ const GamePanel = ({
     }}>
       <RoundTimer seconds={roundSeconds} onTimeout={onTimeout} />
       <SelectionMap
+        googleMapURL={getMapUrl(googleApiKey)}
         onClick={({ latLng }) => onSelectPoint({ lat: latLng.lat(), lng: latLng.lng() })}
         markerPosition={selectedPoint}
       />

+ 2 - 2
ui/src/components/round-summary.component.jsx

@@ -1,12 +1,11 @@
 import React from "react";
-import { GOOGLE_API_KEY } from "../config";
 
 // TODO eventually we want this to query and show other players
 // TODO also should show the actual location of the last round
 
-const mapUrl = `https://maps.googleapis.com/maps/api/staticmap?key=${GOOGLE_API_KEY}`;
 
 const RoundSummary = ({
+  googleApiKey,
   roundNum, 
   score, 
   totalScore,
@@ -16,6 +15,7 @@ const RoundSummary = ({
   targetPoint,
 }) => {
   // TODO determine zoom and center dynamically
+  const mapUrl = `https://maps.googleapis.com/maps/api/staticmap?key=${googleApiKey}`;
   const size = `&size=600x400`
   const zoom = `&zoom=5`;
   const centerOnTarget = `&center=${targetPoint.lat},${targetPoint.lng}`;

+ 0 - 1
ui/src/config.js

@@ -1 +0,0 @@
-export const API_BASE = "http://localhost:5000";

+ 10 - 1
ui/src/services/ggsh.service.js

@@ -1,4 +1,4 @@
-import { API_BASE } from "../config";
+const API_BASE = "https://kirkleon.ddns.net/terrassumptions/api";
 
 export const getStatus = async () => {
     try {
@@ -12,6 +12,15 @@ export const getStatus = async () => {
     }
 }
 
+export const getGoogleApiKey = async () => {
+    const res = await fetch(`${API_BASE}/googleApiKey`);
+    if (!res.ok) {
+        throw Error(res.statusText);
+    }
+    const { googleApiKey } = await res.json();
+    return googleApiKey;
+}
+
 export const createGame = async (name, timer) => {
     const res = await fetch(`${API_BASE}/game`, {
         method: "PUT",