Skip to content

Commit

Permalink
Merge pull request #26 from SimerusM/productionize
Browse files Browse the repository at this point in the history
Productionize
  • Loading branch information
16BitNarwhal authored Sep 2, 2024
2 parents 73c06b1 + 42cee87 commit ab38ba6
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
*.env
12 changes: 12 additions & 0 deletions api-server/Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.10.10

WORKDIR /app/server

# install packages
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn==23.0.0 gevent==24.2.1 gevent-websocket==0.10.1

COPY . .

CMD ["gunicorn", "-k", "geventwebsocket.gunicorn.workers.GeventWebSocketWorker", "-w", "1", "--threads", "100", "-b", "0.0.0.0:8080", "app:app"]
12 changes: 9 additions & 3 deletions api-server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
from chat import setup_chat
from webrtc import setup_webrtc
import uuid
import os
from utils.Debugger import Debugger

app = Flask(__name__)
CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*")
CORS(app, supports_credentials=True)
socketio = SocketIO(app, cors_allowed_origins="*", manage_session=False)

# In-memory storage for each session
session_storage = {}
Expand All @@ -35,18 +36,21 @@ def create_meeting():

@app.route('/api/session/<meeting_id>', methods=['GET'])
def get_session(meeting_id):
Debugger.log_message('DEBUG', f'{session_storage}')
if meeting_id not in session_storage:
return jsonify({'error': 'Meeting ID not found'}), 404
return jsonify(session_storage[meeting_id])

@app.route('/api/users/<meeting_id>', methods=['GET'])
def get_users(meeting_id):
Debugger.log_message('DEBUG', f'{session_storage}')
if meeting_id not in session_storage:
return jsonify({'error': 'Meeting ID not found'}), 404
return jsonify(list(session_storage[meeting_id]['users'].keys()))

@socketio.on('join')
def handle_join(data):
Debugger.log_message('DEBUG', f'{session_storage}')
if 'username' not in data or 'meeting_id' not in data:
Debugger.log_message('ERROR', f'Join request missing username or meeting ID: {data}')
emit('error', {'message': 'Missing username or meeting ID'}, to=request.sid)
Expand All @@ -59,6 +63,7 @@ def handle_join(data):
session['users'][username] = request.sid

Debugger.log_message('INFO', f'User {username} joined the meeting', meeting_id)
Debugger.log_message('DEBUG', f'{session_storage}')
emit('user_joined', {'username': username, 'meeting_id': meeting_id}, room=meeting_id)

@socketio.on('disconnect')
Expand All @@ -80,8 +85,9 @@ def handle_disconnect():
setup_webrtc(app, socketio, session_storage, Debugger.log_message)

if __name__ == '__main__':
import os
if os.getenv('TESTING', True):
socketio.run(app, debug=True, allow_unsafe_werkzeug=True)
elif os.getenv('PRODUCTION', True):
socketio.run(app, async_mode='gevent')
else:
socketio.run(app, debug=True)
3 changes: 3 additions & 0 deletions api-server/chat.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from flask import jsonify, request
from flask_socketio import emit
from utils.Debugger import Debugger

def setup_chat(app, socketio, session_storage, log_message):
@app.route('/api/chat_history/<meeting_id>', methods=['GET'])
def get_chat_history(meeting_id):
Debugger.log_message('DEBUG', f'{session_storage}')
if meeting_id not in session_storage:
return jsonify({'error': 'Meeting ID not found'}), 404
return jsonify(session_storage[meeting_id]['chat_history'])

@socketio.on('chat_message')
def handle_chat_message(data):
Debugger.log_message('DEBUG', f'{session_storage}')
meeting_id = data['meeting_id']
sender = data['sender']
message = data['text']
Expand Down
17 changes: 8 additions & 9 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "client",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:5000",
"dependencies": {
"@headlessui/react": "^2.1.3",
"@heroicons/react": "^2.1.5",
Expand Down
27 changes: 26 additions & 1 deletion client/src/services/rtcHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,32 @@ class RTCHandler {
}

const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
iceServers: [
{
urls: "stun:stun.relay.metered.ca:80",
},
{
urls: "turn:global.relay.metered.ca:80",
username: process.env.REACT_APP_TURN_SERVER_USERNAME,
credential: process.env.REACT_APP_TURN_SERVER_CREDENTIALS,
},
{
urls: "turn:global.relay.metered.ca:80?transport=tcp",
username: process.env.REACT_APP_TURN_SERVER_USERNAME,
credential: process.env.REACT_APP_TURN_SERVER_CREDENTIALS,
},
{
urls: "turn:global.relay.metered.ca:443",
username: process.env.REACT_APP_TURN_SERVER_USERNAME,
credential: process.env.REACT_APP_TURN_SERVER_CREDENTIALS,
},
{
urls: "turns:global.relay.metered.ca:443?transport=tcp",
username: process.env.REACT_APP_TURN_SERVER_USERNAME,
credential: process.env.REACT_APP_TURN_SERVER_CREDENTIALS,
},
{ urls: 'stun:stun.l.google.com:19302' },
]
});

pc.onicecandidate = (event) => this.handleICECandidateEvent(event, peerUsername);
Expand Down
36 changes: 36 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
services:
api_server:
build:
context: ./api-server
dockerfile: Dockerfile.prod
container_name: api_server
environment:
- PRODUCTION=true
networks:
- app_network

nginx:
container_name: nginx
image: jonasal/nginx-certbot
restart: always
environment:
- [email protected]
- CERTBOT_DOMAINS=vmeet.duckdns.org
ports:
- 80:80
- 443:443
volumes:
- nginx_secrets:/etc/letsencrypt
- ./user_conf.d:/etc/nginx/user_conf.d
- ./client/build:/usr/share/nginx/html
depends_on:
- api_server
networks:
- app_network

volumes:
nginx_secrets:

networks:
app_network:
driver: bridge
27 changes: 27 additions & 0 deletions prod.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/bin/bash

export REACT_APP_API_URL="https://vmeet.duckdns.org"
export PRODUCTION="true"

(cd client && rm -rf build && npm run build)

DOWN_COMMAND="down --remove-orphans"
if command -v docker-compose &> /dev/null; then
docker-compose $DOWN_COMMAND
elif command -v docker compose &> /dev/null; then
docker compose $DOWN_COMMAND
else
echo "Error: Neither 'docker-compose' nor 'docker compose' is installed."
exit 1
fi

COMPOSE_COMMAND="-f docker-compose.prod.yml up --build -d"

if command -v docker-compose &> /dev/null; then
docker-compose $COMPOSE_COMMAND "$@"
elif command -v docker compose &> /dev/null; then
docker compose $COMPOSE_COMMAND "$@"
else
echo "Error: Neither 'docker-compose' nor 'docker compose' is installed."
exit 1
fi
63 changes: 63 additions & 0 deletions user_conf.d/vmeet.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
server {
listen 80;
server_name vmeet.duckdns.org;

# Redirect all HTTP traffic to HTTPS
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
server_name vmeet.duckdns.org;

# SSL certificates (use Let's Encrypt certificates or any other trusted provider)
ssl_certificate /etc/letsencrypt/live/vmeet.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vmeet.duckdns.org/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;

# Serve React app (single-page application)
location / {
root /usr/share/nginx/html;
try_files $uri /index.html;
}

# Proxy settings for API requests
location /api/ {
proxy_pass http://api_server:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

# Proxy settings for WebSocket connections
location /socket.io/ {
proxy_pass http://api_server:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_buffering off;
}

# Additional settings for better security (optional)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

# Increase buffer and timeout settings if needed
client_max_body_size 100M;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}

0 comments on commit ab38ba6

Please sign in to comment.