I am trying to host a relay server on Fly.io, for my peer-to-peer game. I used a Python script that I found on GitHub with some modifications. It runs well on my machine and gets the job done to some extent. I learned about Fly.io today and wanted to give it a shot, and I am not having the best time. Whenever I start the server, it prints out "Preparing to run:
python ./server.py as root"
, but the script never runs. I know this, because when it runs it should print “Debug print” followed with “Listening on *:8080”. The weird part is that, these messages get printed when I restart the server, just before it gets shutdown. I would be very grateful to anyone who have any suggestions.
Logs:
2024-06-27T15:54:55.860 app[3d8dd629c97338] ams [info] 2024-06-27T15:54:55.860742285 [01J1D571M28ETRA8E7TV94WH5X:main] Running Firecracker v1.7.0
2024-06-27T15:54:56.186 app[3d8dd629c97338] ams [info] [ 0.267681] PCI: Fatal: No config space access function found
2024-06-27T15:54:56.465 app[3d8dd629c97338] ams [info] INFO Starting init (commit: ad092ccf)...
2024-06-27T15:54:56.493 app[3d8dd629c97338] ams [info] INFO Preparing to run: `python ./server.py` as root
2024-06-27T15:54:56.499 app[3d8dd629c97338] ams [info] INFO [fly api proxy] listening at /.fly/api
2024-06-27T15:54:56.506 app[3d8dd629c97338] ams [info] 2024/06/27 15:54:56 INFO SSH listening listen_address=[fdaa:9:78b0:a7b:39:4d7b:d829:2]:22 dns_server=[fdaa::3]:53
2024-06-27T15:54:56.522 runner[3d8dd629c97338] ams [info] Machine started in 751ms
server.py:
#!/usr/bin/python3
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from time import sleep
import sys
def address_to_string(address):
ip, port = address
return ':'.join([ip, str(port)])
class ServerProtocol(DatagramProtocol):
def __init__(self):
print("Debug print")
self.active_sessions = {}
self.registered_clients = {}
def name_is_registered(self, name):
return name in self.registered_clients
def create_session(self, s_id, client_list):
if s_id in self.active_sessions:
print("Tried to create existing session")
return
self.active_sessions[s_id] = Session(s_id, client_list, self)
def remove_session(self, s_id):
try:
del self.active_sessions[s_id]
except KeyError:
print("Tried to terminate non-existing session")
def register_client(self, c_name, c_session, c_ip, c_port):
if self.name_is_registered(c_name):
print("Client %s is already registered." % [c_name])
return
if not c_session in self.active_sessions:
print("Client registered for non-existing session")
else:
new_client = Client(c_name, c_session, c_ip, c_port)
self.registered_clients[c_name] = new_client
self.active_sessions[c_session].client_registered(new_client)
def exchange_info(self, c_session):
if not c_session in self.active_sessions:
return
self.active_sessions[c_session].exchange_peer_info()
def client_checkout(self, name):
try:
del self.registered_clients[name]
except KeyError:
print("Tried to checkout unregistered client")
def datagramReceived(self, datagram, address):
"""Handle incoming datagram messages."""
print(datagram)
data_string = datagram.decode("utf-8")
msg_type = data_string[:2]
if msg_type == "rs":
# register session
c_ip, c_port = address
self.transport.write(bytes('ok:'+str(c_port),"utf-8"), address)
split = data_string.split(":")
session = split[1]
max_clients = split[2]
self.create_session(session, max_clients)
elif msg_type == "rc":
# register client
split = data_string.split(":")
c_name = split[1]
c_session = split[2]
c_ip, c_port = address
self.transport.write(bytes('ok:'+str(c_port),"utf-8"), address)
self.register_client(c_name, c_session, c_ip, c_port)
elif msg_type == "ep":
# exchange peers
split = data_string.split(":")
c_session = split[1]
self.exchange_info(c_session)
elif msg_type == "cc":
# checkout client
split = data_string.split(":")
c_name = split[1]
self.client_checkout(c_name)
class Session:
def __init__(self, session_id, max_clients, server):
self.id = session_id
self.client_max = max_clients
self.server = server
self.registered_clients = []
def client_registered(self, client):
if client in self.registered_clients: return
# print("Client %c registered for Session %s" % client.name, self.id)
self.registered_clients.append(client)
if len(self.registered_clients) == int(self.client_max):
sleep(5)
print("waited for OK message to send, sending out info to peers")
self.exchange_peer_info()
def exchange_peer_info(self):
for addressed_client in self.registered_clients:
address_list = []
for client in self.registered_clients:
if not client.name == addressed_client.name:
address_list.append(client.name + ":" + address_to_string((client.ip, client.port)))
address_string = ",".join(address_list)
message = bytes( "peers:" + address_string, "utf-8")
self.server.transport.write(message, (addressed_client.ip, addressed_client.port))
print("Peer info has been sent. Terminating Session")
for client in self.registered_clients:
self.server.client_checkout(client.name)
self.server.remove_session(self.id)
class Client:
def confirmation_received(self):
self.received_peer_info = True
def __init__(self, c_name, c_session, c_ip, c_port):
self.name = c_name
self.session_id = c_session
self.ip = c_ip
self.port = c_port
self.received_peer_info = False
if __name__ == '__main__':
port = 8080
reactor.listenUDP(port, ServerProtocol())
print('Listening on *:%d' % (port))
reactor.run()
Dockerfile:
FROM python:3.12
COPY server.py .
COPY requirements.txt .
RUN pip install -r requirements.txt
ENTRYPOINT ["python", "./server.py"]
fly.toml:
app = 'nat-server-snowy-wildflower-8750'
primary_region = 'ams'
[build]
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
processes = ['app']
[[vm]]
size = 'shared-cpu-1x'