Autoformat
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
from dragonion_server import main
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from .common import cli
|
||||
|
||||
|
||||
__all__ = [
|
||||
'cli'
|
||||
]
|
||||
__all__ = ["cli"]
|
||||
|
||||
@@ -2,34 +2,34 @@ import os
|
||||
|
||||
import click
|
||||
|
||||
from dragonion_server.utils.config import db
|
||||
from dragonion_server.common import console
|
||||
from dragonion_server.utils.config import db
|
||||
|
||||
|
||||
class ServiceRemoveCommand(click.Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='remove',
|
||||
name="remove",
|
||||
callback=self.callback,
|
||||
params=[
|
||||
click.Option(
|
||||
('--name', '-n'),
|
||||
("--name", "-n"),
|
||||
required=True,
|
||||
prompt=True,
|
||||
type=str,
|
||||
help='Name of service to write to'
|
||||
help="Name of service to write to",
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def callback(name: str):
|
||||
try:
|
||||
del db.services[name]
|
||||
if os.path.isfile(f'{name}.auth'):
|
||||
os.remove(f'{name}.auth')
|
||||
|
||||
print(f'Removed service {name}')
|
||||
if os.path.isfile(f"{name}.auth"):
|
||||
os.remove(f"{name}.auth")
|
||||
|
||||
print(f"Removed service {name}")
|
||||
except KeyError:
|
||||
print(f'Service "{name}" does not exist in this storage')
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,60 +1,63 @@
|
||||
import sys
|
||||
|
||||
import click
|
||||
|
||||
from dragonion_server.modules.server import run, run_without_onion, integrate_onion
|
||||
from dragonion_server.common import console
|
||||
from dragonion_server.modules.server import integrate_onion, run, run_without_onion
|
||||
|
||||
|
||||
class ServiceRunCommand(click.Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='run',
|
||||
name="run",
|
||||
callback=self.callback,
|
||||
params=[
|
||||
click.Option(
|
||||
('--name', '-n'),
|
||||
("--name", "-n"),
|
||||
required=True,
|
||||
prompt=True,
|
||||
type=str,
|
||||
help='Name of service to write to'
|
||||
help="Name of service to write to",
|
||||
),
|
||||
click.Option(
|
||||
('--port', '-p'),
|
||||
("--port", "-p"),
|
||||
required=False,
|
||||
prompt=True,
|
||||
prompt_required=False,
|
||||
type=int,
|
||||
help='Port to start service on'
|
||||
help="Port to start service on",
|
||||
),
|
||||
click.Option(
|
||||
('--without-tor', '-wt'),
|
||||
("--without-tor", "-wt"),
|
||||
is_flag=True,
|
||||
help='Run service without tor'
|
||||
help="Run service without tor",
|
||||
),
|
||||
click.Option(
|
||||
('--only-tor', '-ot'),
|
||||
("--only-tor", "-ot"),
|
||||
is_flag=True,
|
||||
help='Run only tor proxy to service'
|
||||
)
|
||||
]
|
||||
help="Run only tor proxy to service",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def callback(name: str, port: int | None, without_tor: bool, only_tor: bool):
|
||||
try:
|
||||
if without_tor and only_tor:
|
||||
print('Cannot run only tor without tor, exiting')
|
||||
print("Cannot run only tor without tor, exiting")
|
||||
sys.exit(1)
|
||||
elif without_tor:
|
||||
run_without_onion(name, port)
|
||||
elif only_tor:
|
||||
if port is None:
|
||||
print('For this mode, you need to specify port, '
|
||||
'to which requests will be redirected. Cannot start '
|
||||
'tor service, exiting')
|
||||
print(
|
||||
"For this mode, you need to specify port, "
|
||||
"to which requests will be redirected. Cannot start "
|
||||
"tor service, exiting"
|
||||
)
|
||||
sys.exit(1)
|
||||
onion = integrate_onion(port, name)
|
||||
input('Press Enter to stop onion and service...')
|
||||
input("Press Enter to stop onion and service...")
|
||||
onion.cleanup()
|
||||
else:
|
||||
run(name, port)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
from ...utils import ModuleGroup
|
||||
from .write import ServiceWriteCommand
|
||||
from .run import ServiceRunCommand
|
||||
from .remove import ServiceRemoveCommand
|
||||
|
||||
from .run import ServiceRunCommand
|
||||
from .write import ServiceWriteCommand
|
||||
|
||||
service_group = ModuleGroup(
|
||||
name='service',
|
||||
name="service",
|
||||
commands={
|
||||
'write': ServiceWriteCommand(),
|
||||
'run': ServiceRunCommand(),
|
||||
'remove': ServiceRemoveCommand()
|
||||
}
|
||||
"write": ServiceWriteCommand(),
|
||||
"run": ServiceRunCommand(),
|
||||
"remove": ServiceRemoveCommand(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import click
|
||||
|
||||
from dragonion_server.utils.onion import Onion
|
||||
from dragonion_server.common import console
|
||||
from dragonion_server.utils.onion import Onion
|
||||
|
||||
|
||||
class ServiceWriteCommand(click.Command):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
name='write',
|
||||
name="write",
|
||||
callback=self.callback,
|
||||
params=[
|
||||
click.Option(
|
||||
('--name', '-n'),
|
||||
("--name", "-n"),
|
||||
required=True,
|
||||
prompt=True,
|
||||
type=str,
|
||||
help='Name of service to write to'
|
||||
help="Name of service to write to",
|
||||
),
|
||||
click.Option(
|
||||
('--port', '-p'),
|
||||
("--port", "-p"),
|
||||
required=True,
|
||||
prompt=True,
|
||||
type=int,
|
||||
help='Port to start service on'
|
||||
)
|
||||
]
|
||||
help="Port to start service on",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import click
|
||||
|
||||
from .cmd.service.service import service_group
|
||||
|
||||
|
||||
cli = click.CommandCollection(
|
||||
name='dragonion-server',
|
||||
sources=[
|
||||
service_group()
|
||||
]
|
||||
)
|
||||
cli = click.CommandCollection(name="dragonion-server", sources=[service_group()])
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from .groups import ModuleGroup
|
||||
|
||||
|
||||
__all__ = [
|
||||
'ModuleGroup'
|
||||
]
|
||||
__all__ = ["ModuleGroup"]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import click
|
||||
import typing as t
|
||||
|
||||
import click
|
||||
|
||||
|
||||
class ModuleGroup(click.Group):
|
||||
def __init__(
|
||||
@@ -13,6 +14,6 @@ class ModuleGroup(click.Group):
|
||||
) -> None:
|
||||
new_commands = dict()
|
||||
for command_key in commands.keys():
|
||||
new_commands[f'{name}-{command_key}'] = commands[command_key]
|
||||
|
||||
new_commands[f"{name}-{command_key}"] = commands[command_key]
|
||||
|
||||
super().__init__(name, new_commands, **attrs)
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
from .server import run, run_without_onion
|
||||
from .integration import integrate_onion
|
||||
from .server import run, run_without_onion
|
||||
|
||||
|
||||
__all__ = [
|
||||
'run',
|
||||
'run_without_onion',
|
||||
'integrate_onion'
|
||||
]
|
||||
__all__ = ["run", "run_without_onion", "integrate_onion"]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from attrs import define
|
||||
from fastapi import WebSocket
|
||||
from dragonion_core.proto.web.webmessage import (
|
||||
WebErrorMessage,
|
||||
set_time,
|
||||
webmessages_union,
|
||||
webmessage_error_message_literal,
|
||||
WebErrorMessage
|
||||
webmessages_union,
|
||||
)
|
||||
from fastapi import WebSocket
|
||||
|
||||
|
||||
@define
|
||||
@@ -19,21 +19,14 @@ class Connection(object):
|
||||
"""
|
||||
Sends WebMessage object to this connection
|
||||
:param obj: Should be some type of WebMessage
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
await self.ws.send_text(set_time(obj).to_json())
|
||||
|
||||
async def send_error(
|
||||
self,
|
||||
error_message: webmessage_error_message_literal
|
||||
):
|
||||
async def send_error(self, error_message: webmessage_error_message_literal):
|
||||
"""
|
||||
Sends error with specified messages
|
||||
:param error_message: See webmessage_error_message_literal for available
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
await self.send_webmessage(
|
||||
WebErrorMessage(
|
||||
error_message=error_message
|
||||
)
|
||||
)
|
||||
await self.send_webmessage(WebErrorMessage(error_message=error_message))
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
from attrs import define
|
||||
from .connection import Connection
|
||||
from .exceptions import GotInvalidWebmessage
|
||||
from datetime import datetime
|
||||
from json.decoder import JSONDecodeError
|
||||
from typing import Dict
|
||||
|
||||
from attrs import define
|
||||
from dragonion_core.proto.web.webmessage import (
|
||||
WebBroadcastableMessage,
|
||||
WebConnectionMessage,
|
||||
WebConnectionResultMessage,
|
||||
WebDisconnectMessage,
|
||||
WebErrorMessage,
|
||||
WebMessageMessage,
|
||||
WebNotificationMessage,
|
||||
set_time,
|
||||
webmessage_error_message_literal,
|
||||
webmessages_union,
|
||||
)
|
||||
from fastapi import WebSocket
|
||||
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
from dragonion_core.proto.web.webmessage import (
|
||||
webmessages_union,
|
||||
set_time,
|
||||
WebMessageMessage,
|
||||
WebBroadcastableMessage,
|
||||
WebNotificationMessage,
|
||||
webmessage_error_message_literal,
|
||||
WebErrorMessage,
|
||||
WebConnectionMessage,
|
||||
WebDisconnectMessage,
|
||||
WebConnectionResultMessage
|
||||
)
|
||||
|
||||
from datetime import datetime
|
||||
from .connection import Connection
|
||||
from .exceptions import GotInvalidWebmessage
|
||||
|
||||
|
||||
@define
|
||||
@@ -28,62 +27,65 @@ class Room(object):
|
||||
|
||||
async def accept_connection(self, ws: WebSocket) -> Connection | None:
|
||||
"""
|
||||
Accepts connection, checks username availability and adds it to dict of
|
||||
connections
|
||||
:param ws: Websocket of connection
|
||||
:return:
|
||||
Accepts connection, checks username availability and adds it to dict of
|
||||
connections
|
||||
:param ws: Websocket of connection
|
||||
:return:
|
||||
"""
|
||||
print('Incoming connection')
|
||||
print("Incoming connection")
|
||||
await ws.accept()
|
||||
try:
|
||||
connection_message = WebConnectionMessage.from_json(
|
||||
await ws.receive_text()
|
||||
)
|
||||
connection_message = WebConnectionMessage.from_json(await ws.receive_text())
|
||||
except JSONDecodeError:
|
||||
await ws.send_text(set_time(WebErrorMessage(
|
||||
'invalid_webmessage'
|
||||
)).to_json())
|
||||
await ws.close(reason='invalid_webmessage')
|
||||
return
|
||||
|
||||
await ws.send_text(
|
||||
set_time(WebErrorMessage("invalid_webmessage")).to_json()
|
||||
)
|
||||
await ws.close(reason="invalid_webmessage")
|
||||
return
|
||||
|
||||
connection = Connection(
|
||||
username=connection_message.username,
|
||||
ws=ws,
|
||||
public_key=connection_message.public_key,
|
||||
password=connection_message.password
|
||||
password=connection_message.password,
|
||||
)
|
||||
|
||||
|
||||
if connection_message.username in self.connections.keys():
|
||||
await connection.send_error(
|
||||
'username_exists'
|
||||
)
|
||||
await ws.close(reason='username_exists')
|
||||
return
|
||||
await connection.send_error("username_exists")
|
||||
await ws.close(reason="username_exists")
|
||||
return
|
||||
|
||||
self.connections[connection_message.username] = connection
|
||||
await connection.send_webmessage(WebConnectionResultMessage(
|
||||
connected_users=dict(
|
||||
map(
|
||||
lambda i, j: (i, j),
|
||||
[_username for _username in list(self.connections.keys())
|
||||
if self.connections[_username].password ==
|
||||
connection_message.password],
|
||||
[_connection.public_key for _connection
|
||||
in self.connections.values() if _connection.password ==
|
||||
connection_message.password]
|
||||
await connection.send_webmessage(
|
||||
WebConnectionResultMessage(
|
||||
connected_users=dict(
|
||||
map(
|
||||
lambda i, j: (i, j),
|
||||
[
|
||||
_username
|
||||
for _username in list(self.connections.keys())
|
||||
if self.connections[_username].password
|
||||
== connection_message.password
|
||||
],
|
||||
[
|
||||
_connection.public_key
|
||||
for _connection in self.connections.values()
|
||||
if _connection.password == connection_message.password
|
||||
],
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
|
||||
await self.broadcast_webmessage(connection_message)
|
||||
print(f'[{datetime.now().time()}] Accepted {connection_message.username}')
|
||||
print(f"[{datetime.now().time()}] Accepted {connection_message.username}")
|
||||
return connection
|
||||
|
||||
async def broadcast_webmessage(self, obj: webmessages_union):
|
||||
"""
|
||||
Broadcasts WebMessages to all connections in room
|
||||
:param obj:
|
||||
:return:
|
||||
:param obj:
|
||||
:return:
|
||||
"""
|
||||
for connection in self.connections.values():
|
||||
await connection.send_webmessage(obj)
|
||||
@@ -91,11 +93,11 @@ class Room(object):
|
||||
async def broadcast_message(self, broadcastable: WebBroadcastableMessage):
|
||||
"""
|
||||
Broadcasts message to every user in room
|
||||
:param broadcastable: String object with json representation of
|
||||
:param broadcastable: String object with json representation of
|
||||
WebBroadcastableMessage
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
try:
|
||||
for to_username in broadcastable.messages.keys():
|
||||
try:
|
||||
await self.connections[to_username].send_webmessage(
|
||||
@@ -110,28 +112,17 @@ class Room(object):
|
||||
"""
|
||||
Broadcasts notification from server
|
||||
:param message: Content
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
await self.broadcast_webmessage(
|
||||
WebNotificationMessage(
|
||||
message=message
|
||||
)
|
||||
)
|
||||
await self.broadcast_webmessage(WebNotificationMessage(message=message))
|
||||
|
||||
async def broadcast_error(
|
||||
self,
|
||||
error_message: webmessage_error_message_literal
|
||||
):
|
||||
async def broadcast_error(self, error_message: webmessage_error_message_literal):
|
||||
"""
|
||||
Broadcasts server error
|
||||
:param error_message: See webmessage_error_message_literal
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
await self.broadcast_webmessage(
|
||||
WebErrorMessage(
|
||||
error_message=error_message
|
||||
)
|
||||
)
|
||||
await self.broadcast_webmessage(WebErrorMessage(error_message=error_message))
|
||||
|
||||
async def broadcast_user_disconnected(self, username: str):
|
||||
"""
|
||||
@@ -139,18 +130,14 @@ class Room(object):
|
||||
:param username: Username of user that disconnected
|
||||
:return:
|
||||
"""
|
||||
await self.broadcast_webmessage(
|
||||
WebDisconnectMessage(
|
||||
username=username
|
||||
)
|
||||
)
|
||||
await self.broadcast_webmessage(WebDisconnectMessage(username=username))
|
||||
|
||||
async def get_connection_by(self, attribute: str, value: str) -> Connection | None:
|
||||
"""
|
||||
Search for connection by attribute and value in it
|
||||
:param attribute:
|
||||
:param value:
|
||||
:return:
|
||||
:param attribute:
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
for connection in self.connections.values():
|
||||
if getattr(connection, attribute) == value:
|
||||
@@ -158,11 +145,11 @@ class Room(object):
|
||||
|
||||
async def disconnect(self, connection: Connection, close_reason: str | None = None):
|
||||
"""
|
||||
Disconnects by connection object.
|
||||
:param connection: Object of connection.
|
||||
Disconnects by connection object.
|
||||
:param connection: Object of connection.
|
||||
It can be obtained using get_connection_by
|
||||
:param close_reason: Reason if exists
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
if connection not in self.connections.values():
|
||||
return
|
||||
@@ -170,9 +157,7 @@ class Room(object):
|
||||
del self.connections[connection.username]
|
||||
|
||||
try:
|
||||
await connection.ws.close(
|
||||
reason=close_reason
|
||||
)
|
||||
await connection.ws.close(reason=close_reason)
|
||||
except Exception as e:
|
||||
assert e
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from .connection import Connection
|
||||
from .room import Room
|
||||
from typing import Dict
|
||||
|
||||
from dragonion_core.proto.web.webmessage import (
|
||||
webmessage_error_message_literal
|
||||
)
|
||||
from dragonion_core.proto.web.webmessage import webmessage_error_message_literal
|
||||
|
||||
from .connection import Connection
|
||||
from .room import Room
|
||||
|
||||
|
||||
class Service(object):
|
||||
@@ -21,10 +20,7 @@ class Service(object):
|
||||
for room in self.rooms.values():
|
||||
await room.broadcast_notification(message)
|
||||
|
||||
async def broadcast_error(
|
||||
self,
|
||||
error_message: webmessage_error_message_literal
|
||||
):
|
||||
async def broadcast_error(self, error_message: webmessage_error_message_literal):
|
||||
for room in self.rooms.values():
|
||||
await room.broadcast_error(error_message)
|
||||
|
||||
@@ -32,31 +28,31 @@ class Service(object):
|
||||
"""
|
||||
Searches for room by valid connection object in it
|
||||
:param connection: Connection in unknown room to search
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
for room in self.rooms.values():
|
||||
if connection in room.connections.values():
|
||||
return room
|
||||
|
||||
async def get_connection_by_attribute(
|
||||
self, attribute: str, value: str
|
||||
self, attribute: str, value: str
|
||||
) -> Connection:
|
||||
"""
|
||||
Gets connection in some room by attribute and value in it
|
||||
:param attribute:
|
||||
:param value:
|
||||
:return:
|
||||
:param attribute:
|
||||
:param value:
|
||||
:return:
|
||||
"""
|
||||
for room in self.rooms.values():
|
||||
if connection := await room.get_connection_by(attribute, value):
|
||||
return connection
|
||||
|
||||
async def close_room(self, room_name: str, reason: str = 'Unknown reason'):
|
||||
async def close_room(self, room_name: str, reason: str = "Unknown reason"):
|
||||
"""
|
||||
Closes all connections in room
|
||||
:param room_name: Close name
|
||||
:param reason: Reason to close room, default is Unknown reason
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
room = self.rooms.get(room_name)
|
||||
if room is None:
|
||||
@@ -64,6 +60,5 @@ class Service(object):
|
||||
|
||||
for connection in room.connections.values():
|
||||
await room.disconnect(
|
||||
connection=connection,
|
||||
close_reason=f'Room is closed: {reason}'
|
||||
connection=connection, close_reason=f"Room is closed: {reason}"
|
||||
)
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
from .managers.service import Service
|
||||
from dragonion_core.proto.web.webmessage import (
|
||||
webmessages_union,
|
||||
WebMessage
|
||||
)
|
||||
from .managers.exceptions import GotInvalidWebmessage
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from dragonion_core.proto.web.webmessage import WebMessage, webmessages_union
|
||||
from fastapi import WebSocket, WebSocketDisconnect
|
||||
|
||||
from .managers.exceptions import GotInvalidWebmessage
|
||||
from .managers.service import Service
|
||||
|
||||
service = Service()
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import sys
|
||||
|
||||
from dragonion_server.utils.onion import Onion
|
||||
from dragonion_core.proto.file import AuthFile
|
||||
from dragonion_server.utils.config.db import services
|
||||
|
||||
from rich import print
|
||||
|
||||
from dragonion_server.utils.config.db import services
|
||||
from dragonion_server.utils.onion import Onion
|
||||
|
||||
|
||||
def integrate_onion(port: int, name: str) -> Onion:
|
||||
"""
|
||||
Starts onion service, writes it to config
|
||||
Starts onion service, writes it to config
|
||||
:param port: Port, where local service is started
|
||||
:param name: Name of service to get or write to config
|
||||
:return: Onion object, that is connected and service is started
|
||||
@@ -23,16 +23,20 @@ def integrate_onion(port: int, name: str) -> Onion:
|
||||
if not onion.connected_to_tor:
|
||||
onion.cleanup()
|
||||
sys.exit(1)
|
||||
|
||||
print(f'[green]Available on[/] '
|
||||
f'{(onion_host := onion.start_onion_service(name))}.onion')
|
||||
|
||||
|
||||
print(
|
||||
f"[green]Available on[/] "
|
||||
f"{(onion_host := onion.start_onion_service(name))}.onion"
|
||||
)
|
||||
|
||||
auth = AuthFile(name)
|
||||
auth['host'] = f'{onion_host}.onion'
|
||||
auth['auth'] = onion.auth_string
|
||||
print(f'To connect to server just share [green]{auth.filename}[/] file')
|
||||
print(f'Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n'
|
||||
f'[#ff901b]{onion_host}[/] \n'
|
||||
f'[#564ec3]{services[name].client_auth_priv_key}[/]')
|
||||
|
||||
auth["host"] = f"{onion_host}.onion"
|
||||
auth["auth"] = onion.auth_string
|
||||
print(f"To connect to server just share [green]{auth.filename}[/] file")
|
||||
print(
|
||||
f"Or use [#ff901b]service id[/] and [#564ec3]auth string[/]: \n"
|
||||
f"[#ff901b]{onion_host}[/] \n"
|
||||
f"[#564ec3]{services[name].client_auth_priv_key}[/]"
|
||||
)
|
||||
|
||||
return onion
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi import APIRouter, WebSocket
|
||||
from .handlers.websocket_server import serve_websocket
|
||||
|
||||
from .handlers.websocket_server import serve_websocket
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
|
||||
from dragonion_server.utils.onion import get_available_port
|
||||
|
||||
from .integration import integrate_onion
|
||||
from .routes import router
|
||||
|
||||
@@ -8,15 +10,15 @@ from .routes import router
|
||||
def get_app(port: int, name: str) -> FastAPI:
|
||||
"""
|
||||
Creates FastAPI object and runs integrate_onion
|
||||
:param port: Must be same with port on which uvicorn is running
|
||||
:param port: Must be same with port on which uvicorn is running
|
||||
:param name: Name of service
|
||||
:return: FastAPI object with onion.cleanup function on shutdown
|
||||
"""
|
||||
onion = integrate_onion(port, name)
|
||||
return FastAPI(
|
||||
title=f'dragonion-server: {name}',
|
||||
description=f'Secure dragonion chat endpoint server - service {name}',
|
||||
on_shutdown=[onion.cleanup]
|
||||
title=f"dragonion-server: {name}",
|
||||
description=f"Secure dragonion chat endpoint server - service {name}",
|
||||
on_shutdown=[onion.cleanup],
|
||||
)
|
||||
|
||||
|
||||
@@ -25,22 +27,22 @@ def run(name: str, port: int | None = get_available_port()):
|
||||
Runs service with specified name and starts onion
|
||||
:param name: Name of service
|
||||
:param port: Port where to start service, if not specified - gets random available
|
||||
:return:
|
||||
:return:
|
||||
"""
|
||||
if port is None:
|
||||
port = get_available_port()
|
||||
app = get_app(port, name)
|
||||
app.include_router(router)
|
||||
uvicorn.run(app, host='0.0.0.0', port=port)
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
|
||||
|
||||
def run_without_onion(name: str, port: int | None = get_available_port()):
|
||||
if port is None:
|
||||
port = get_available_port()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title=f'dragonion-server: {name}',
|
||||
description=f'Secure dragonion chat endpoint server - service {name}'
|
||||
title=f"dragonion-server: {name}",
|
||||
description=f"Secure dragonion chat endpoint server - service {name}",
|
||||
)
|
||||
app.include_router(router)
|
||||
uvicorn.run(app, host='0.0.0.0', port=port)
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
from . import db, models
|
||||
|
||||
__all__ = [
|
||||
'db',
|
||||
'models'
|
||||
]
|
||||
__all__ = ["db", "models"]
|
||||
|
||||
@@ -3,12 +3,8 @@ import sqlitedict
|
||||
|
||||
class ConfigDatabase(sqlitedict.SqliteDict):
|
||||
def __init__(self, tablename):
|
||||
super().__init__(
|
||||
filename='data.storage',
|
||||
tablename=tablename,
|
||||
autocommit=True
|
||||
)
|
||||
super().__init__(filename="data.storage", tablename=tablename, autocommit=True)
|
||||
|
||||
|
||||
config = ConfigDatabase('config')
|
||||
services = ConfigDatabase('services')
|
||||
config = ConfigDatabase("config")
|
||||
services = ConfigDatabase("services")
|
||||
|
||||
@@ -13,5 +13,5 @@ class ServiceModel:
|
||||
client_auth_pub_key: str
|
||||
|
||||
service_id: str = None
|
||||
key_content: str = 'ED25519-V3'
|
||||
key_type: str = 'NEW'
|
||||
key_content: str = "ED25519-V3"
|
||||
key_type: str = "NEW"
|
||||
|
||||
@@ -5,28 +5,33 @@ import sys
|
||||
|
||||
|
||||
def get_resource_path(filename):
|
||||
application_path = 'resources'
|
||||
application_path = "resources"
|
||||
|
||||
return os.path.join(application_path, filename)
|
||||
|
||||
|
||||
def get_tor_paths():
|
||||
if (platform.system() != "Darwin" and
|
||||
platform.machine().lower() in ['aarch64', 'arm64']):
|
||||
if shutil.which('tor'):
|
||||
return 'tor'
|
||||
if platform.system() != "Darwin" and platform.machine().lower() in [
|
||||
"aarch64",
|
||||
"arm64",
|
||||
]:
|
||||
if shutil.which("tor"):
|
||||
return "tor"
|
||||
else:
|
||||
print('Detected ARM system and tor is not installed or added to PATH. '
|
||||
'Please, consider reading documentation and installing application '
|
||||
'properly')
|
||||
print(
|
||||
"Detected ARM system and tor is not installed or added to PATH. "
|
||||
"Please, consider reading documentation and installing application "
|
||||
"properly"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
else:
|
||||
from ..onion.tor_downloader import download_tor
|
||||
|
||||
if platform.system() in ["Linux", "Darwin"]:
|
||||
tor_path = os.path.join(build_data_dir(), 'tor/tor')
|
||||
tor_path = os.path.join(build_data_dir(), "tor/tor")
|
||||
elif platform.system() == "Windows":
|
||||
tor_path = os.path.join(build_data_dir(), 'tor/tor.exe')
|
||||
tor_path = os.path.join(build_data_dir(), "tor/tor.exe")
|
||||
else:
|
||||
raise Exception("Platform not supported")
|
||||
|
||||
@@ -37,7 +42,7 @@ def get_tor_paths():
|
||||
|
||||
|
||||
def build_data_dir():
|
||||
dragonion_data_dir = 'data'
|
||||
dragonion_data_dir = "data"
|
||||
|
||||
os.makedirs(dragonion_data_dir, exist_ok=True)
|
||||
return dragonion_data_dir
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
from .onion import Onion
|
||||
from .onion import get_available_port
|
||||
from .onion import Onion, get_available_port
|
||||
|
||||
__all__ = [
|
||||
'Onion',
|
||||
'get_available_port'
|
||||
]
|
||||
__all__ = ["Onion", "get_available_port"]
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
from stem.control import Controller
|
||||
from .stem_process import launch_tor_with_config
|
||||
from stem import ProtocolError
|
||||
|
||||
import socket
|
||||
import random
|
||||
import base64
|
||||
import os
|
||||
import psutil
|
||||
import platform
|
||||
import random
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import platform
|
||||
import time
|
||||
import base64
|
||||
|
||||
import nacl.public
|
||||
|
||||
import psutil
|
||||
from rich import print
|
||||
from stem import ProtocolError
|
||||
from stem.control import Controller
|
||||
|
||||
from dragonion_server.utils.core import dirs
|
||||
from dragonion_server.utils import config
|
||||
from dragonion_server.utils.config.db import services
|
||||
from dragonion_server.utils.core import dirs
|
||||
|
||||
from .stem_process import launch_tor_with_config
|
||||
|
||||
|
||||
def get_available_port(min_port: int = 1000, max_port: int = 65535):
|
||||
@@ -61,9 +61,9 @@ class Onion(object):
|
||||
try:
|
||||
cmdline = proc.cmdline()
|
||||
if (
|
||||
cmdline[0] == self.tor_path
|
||||
and cmdline[1] == "-f"
|
||||
and cmdline[2] == self.tor_torrc
|
||||
cmdline[0] == self.tor_path
|
||||
and cmdline[1] == "-f"
|
||||
and cmdline[2] == self.tor_torrc
|
||||
):
|
||||
proc.terminate()
|
||||
proc.wait()
|
||||
@@ -82,18 +82,18 @@ class Onion(object):
|
||||
self.kill_same_tor()
|
||||
|
||||
tor_config = {
|
||||
'DataDirectory': tor_data_directory_name,
|
||||
'SocksPort': str(self.tor_socks_port),
|
||||
'CookieAuthentication': '1',
|
||||
'CookieAuthFile': self.tor_cookie_auth_file,
|
||||
'AvoidDiskWrites': '1',
|
||||
'Log': [
|
||||
'NOTICE stdout'
|
||||
]
|
||||
"DataDirectory": tor_data_directory_name,
|
||||
"SocksPort": str(self.tor_socks_port),
|
||||
"CookieAuthentication": "1",
|
||||
"CookieAuthFile": self.tor_cookie_auth_file,
|
||||
"AvoidDiskWrites": "1",
|
||||
"Log": ["NOTICE stdout"],
|
||||
}
|
||||
|
||||
if platform.system() in ["Windows", "Darwin"] or \
|
||||
len(tor_data_directory_name) > 90:
|
||||
if (
|
||||
platform.system() in ["Windows", "Darwin"]
|
||||
or len(tor_data_directory_name) > 90
|
||||
):
|
||||
try:
|
||||
self.tor_control_port = get_available_port(1000, 65535)
|
||||
tor_config = tor_config | {"ControlPort": str(self.tor_control_port)}
|
||||
@@ -102,24 +102,22 @@ class Onion(object):
|
||||
self.tor_control_socket = None
|
||||
else:
|
||||
self.tor_control_port = None
|
||||
self.tor_control_socket = os.path.abspath(os.path.join(
|
||||
tor_data_directory_name, "control_socket"
|
||||
))
|
||||
self.tor_control_socket = os.path.abspath(
|
||||
os.path.join(tor_data_directory_name, "control_socket")
|
||||
)
|
||||
tor_config = tor_config | {"ControlSocket": str(self.tor_control_socket)}
|
||||
|
||||
return tor_config
|
||||
|
||||
|
||||
def connect(self):
|
||||
self.tor_data_directory = tempfile.TemporaryDirectory(
|
||||
dir=dirs.build_tmp_dir()
|
||||
)
|
||||
self.tor_data_directory = tempfile.TemporaryDirectory(dir=dirs.build_tmp_dir())
|
||||
self.tor_data_directory_name = self.tor_data_directory.name
|
||||
|
||||
self.tor_proc = launch_tor_with_config(
|
||||
config=self.get_config(self.tor_data_directory_name),
|
||||
tor_cmd=self.tor_path,
|
||||
take_ownership=True,
|
||||
init_msg_handler=print
|
||||
init_msg_handler=print,
|
||||
)
|
||||
|
||||
time.sleep(2)
|
||||
@@ -149,14 +147,12 @@ class Onion(object):
|
||||
|
||||
client_auth_priv_key_raw = nacl.public.PrivateKey.generate()
|
||||
client_auth_priv_key = key_str(client_auth_priv_key_raw)
|
||||
client_auth_pub_key = key_str(
|
||||
client_auth_priv_key_raw.public_key
|
||||
)
|
||||
client_auth_pub_key = key_str(client_auth_priv_key_raw.public_key)
|
||||
|
||||
services[name] = config.models.ServiceModel(
|
||||
port=port,
|
||||
client_auth_priv_key=client_auth_priv_key,
|
||||
client_auth_pub_key=client_auth_pub_key
|
||||
client_auth_pub_key=client_auth_pub_key,
|
||||
)
|
||||
return services[name]
|
||||
|
||||
@@ -167,7 +163,7 @@ class Onion(object):
|
||||
:return: .onion url
|
||||
"""
|
||||
if name not in services.keys():
|
||||
raise 'Service not created'
|
||||
raise "Service not created"
|
||||
|
||||
service: config.models.ServiceModel = services[name]
|
||||
|
||||
@@ -193,8 +189,9 @@ class Onion(object):
|
||||
service.key_type = "ED25519-V3"
|
||||
service.key_content = res.private_key
|
||||
|
||||
self.auth_string = f'{res.service_id}:descriptor:' \
|
||||
f'x25519:{service.client_auth_priv_key}'
|
||||
self.auth_string = (
|
||||
f"{res.service_id}:descriptor:" f"x25519:{service.client_auth_priv_key}"
|
||||
)
|
||||
|
||||
services[name] = service
|
||||
|
||||
@@ -204,9 +201,7 @@ class Onion(object):
|
||||
service: config.models.ServiceModel = services[name]
|
||||
if service.service_id:
|
||||
try:
|
||||
self.c.remove_ephemeral_hidden_service(
|
||||
service.service_id
|
||||
)
|
||||
self.c.remove_ephemeral_hidden_service(service.service_id)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
@@ -222,8 +217,8 @@ class Onion(object):
|
||||
rendezvous_circuit_ids = []
|
||||
for c in self.c.get_circuits():
|
||||
if (
|
||||
c.purpose == "HS_SERVICE_REND"
|
||||
and c.rend_query in self.graceful_close_onions
|
||||
c.purpose == "HS_SERVICE_REND"
|
||||
and c.rend_query in self.graceful_close_onions
|
||||
):
|
||||
rendezvous_circuit_ids.append(c.id)
|
||||
|
||||
@@ -237,9 +232,7 @@ class Onion(object):
|
||||
num_rend_circuits += 1
|
||||
|
||||
if num_rend_circuits == 0:
|
||||
print(
|
||||
"\rTor rendezvous circuits have closed" + " " * 20
|
||||
)
|
||||
print("\rTor rendezvous circuits have closed" + " " * 20)
|
||||
break
|
||||
|
||||
if num_rend_circuits == 1:
|
||||
@@ -271,7 +264,7 @@ class Onion(object):
|
||||
try:
|
||||
self.tor_data_directory.cleanup()
|
||||
except Exception as e:
|
||||
print(f'Cannot cleanup temporary directory: {e}')
|
||||
print(f"Cannot cleanup temporary directory: {e}")
|
||||
|
||||
@property
|
||||
def get_tor_socks_port(self):
|
||||
|
||||
@@ -15,13 +15,21 @@ import stem.util.str_tools
|
||||
import stem.util.system
|
||||
import stem.version
|
||||
|
||||
NO_TORRC = '<no torrc>'
|
||||
NO_TORRC = "<no torrc>"
|
||||
DEFAULT_INIT_TIMEOUT = 90
|
||||
|
||||
|
||||
def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100,
|
||||
init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT,
|
||||
take_ownership=False, close_output=True, stdin=None):
|
||||
def launch_tor(
|
||||
tor_cmd="tor",
|
||||
args=None,
|
||||
torrc_path=None,
|
||||
completion_percent=100,
|
||||
init_msg_handler=None,
|
||||
timeout=DEFAULT_INIT_TIMEOUT,
|
||||
take_ownership=False,
|
||||
close_output=True,
|
||||
stdin=None,
|
||||
):
|
||||
"""
|
||||
Initializes a tor process. This blocks until initialization completes or we
|
||||
error out.
|
||||
@@ -68,13 +76,14 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
|
||||
if stem.util.system.is_windows():
|
||||
if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT:
|
||||
raise OSError('You cannot launch tor with a timeout on Windows')
|
||||
raise OSError("You cannot launch tor with a timeout on Windows")
|
||||
|
||||
timeout = None
|
||||
elif threading.current_thread().__class__.__name__ != '_MainThread':
|
||||
elif threading.current_thread().__class__.__name__ != "_MainThread":
|
||||
if timeout is not None and timeout != DEFAULT_INIT_TIMEOUT:
|
||||
raise OSError(
|
||||
'Launching tor with a timeout can only be done in the main thread')
|
||||
"Launching tor with a timeout can only be done in the main thread"
|
||||
)
|
||||
|
||||
timeout = None
|
||||
|
||||
@@ -88,8 +97,10 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
elif not os.path.isfile(tor_cmd):
|
||||
raise OSError("'%s' doesn't exist" % tor_cmd)
|
||||
elif not stem.util.system.is_available(tor_cmd):
|
||||
raise OSError(f"{tor_cmd} isn't available on your system. "
|
||||
f"Maybe it's not in your PATH?")
|
||||
raise OSError(
|
||||
f"{tor_cmd} isn't available on your system. "
|
||||
f"Maybe it's not in your PATH?"
|
||||
)
|
||||
|
||||
# double check that we have a torrc to work with
|
||||
if torrc_path not in (None, NO_TORRC) and not os.path.exists(torrc_path):
|
||||
@@ -103,13 +114,13 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
|
||||
if torrc_path:
|
||||
if torrc_path == NO_TORRC:
|
||||
temp_file = tempfile.mkstemp(prefix='empty-torrc-', text=True)[1]
|
||||
runtime_args += ['-f', temp_file]
|
||||
temp_file = tempfile.mkstemp(prefix="empty-torrc-", text=True)[1]
|
||||
runtime_args += ["-f", temp_file]
|
||||
else:
|
||||
runtime_args += ['-f', torrc_path]
|
||||
runtime_args += ["-f", torrc_path]
|
||||
|
||||
if take_ownership:
|
||||
runtime_args += ['__OwningControllerProcess', str(os.getpid())]
|
||||
runtime_args += ["__OwningControllerProcess", str(os.getpid())]
|
||||
|
||||
tor_process = None
|
||||
|
||||
@@ -119,8 +130,9 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=None if platform.system() == 'Windows' else
|
||||
{"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)}
|
||||
env=None
|
||||
if platform.system() == "Windows"
|
||||
else {"LD_LIBRARY_PATH": os.path.dirname(tor_cmd)},
|
||||
)
|
||||
|
||||
if stdin:
|
||||
@@ -129,15 +141,16 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
tor_process.stdin.close()
|
||||
|
||||
if timeout:
|
||||
|
||||
def timeout_handler(*_):
|
||||
raise OSError('reached a %i second timeout without success' % timeout)
|
||||
raise OSError("reached a %i second timeout without success" % timeout)
|
||||
|
||||
signal.signal(signal.SIGALRM, timeout_handler)
|
||||
signal.setitimer(signal.ITIMER_REAL, timeout)
|
||||
|
||||
bootstrap_line = re.compile('Bootstrapped ([0-9]+)%')
|
||||
problem_line = re.compile('\\[(warn|err)] (.*)$')
|
||||
last_problem = 'Timed out'
|
||||
bootstrap_line = re.compile("Bootstrapped ([0-9]+)%")
|
||||
problem_line = re.compile("\\[(warn|err)] (.*)$")
|
||||
last_problem = "Timed out"
|
||||
|
||||
while True:
|
||||
# Tor's stdout will be read as ASCII bytes. This is fine for python 2, but
|
||||
@@ -147,12 +160,12 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
# It seems like python 2.x is perfectly happy for this to be unicode, so
|
||||
# normalizing to that.
|
||||
|
||||
init_line = tor_process.stdout.readline().decode('utf-8', 'replace').strip()
|
||||
init_line = tor_process.stdout.readline().decode("utf-8", "replace").strip()
|
||||
|
||||
# this will provide empty results if the process is terminated
|
||||
|
||||
if not init_line:
|
||||
raise OSError('Process terminated: %s' % last_problem)
|
||||
raise OSError("Process terminated: %s" % last_problem)
|
||||
|
||||
# provide the caller with the initialization message if they want it
|
||||
|
||||
@@ -169,9 +182,9 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
elif problem_match:
|
||||
runlevel, msg = problem_match.groups()
|
||||
|
||||
if 'see warnings above' not in msg:
|
||||
if ': ' in msg:
|
||||
msg = msg.split(': ')[-1].strip()
|
||||
if "see warnings above" not in msg:
|
||||
if ": " in msg:
|
||||
msg = msg.split(": ")[-1].strip()
|
||||
|
||||
last_problem = msg
|
||||
except Exception as e:
|
||||
@@ -199,9 +212,15 @@ def launch_tor(tor_cmd='tor', args=None, torrc_path=None, completion_percent=100
|
||||
assert e
|
||||
|
||||
|
||||
def launch_tor_with_config(config, tor_cmd='tor', completion_percent=100,
|
||||
init_msg_handler=None, timeout=DEFAULT_INIT_TIMEOUT,
|
||||
take_ownership=False, close_output=True):
|
||||
def launch_tor_with_config(
|
||||
config,
|
||||
tor_cmd="tor",
|
||||
completion_percent=100,
|
||||
init_msg_handler=None,
|
||||
timeout=DEFAULT_INIT_TIMEOUT,
|
||||
take_ownership=False,
|
||||
close_output=True,
|
||||
):
|
||||
"""
|
||||
Initializes a tor process, like :func:`~stem.process.launch_tor`, but with a
|
||||
customized configuration. This writes a temporary torrc to disk, launches
|
||||
@@ -246,62 +265,71 @@ def launch_tor_with_config(config, tor_cmd='tor', completion_percent=100,
|
||||
"""
|
||||
|
||||
try:
|
||||
use_stdin = stem.version.get_system_tor_version(
|
||||
tor_cmd) >= stem.version.Requirement.TORRC_VIA_STDIN
|
||||
use_stdin = (
|
||||
stem.version.get_system_tor_version(tor_cmd)
|
||||
>= stem.version.Requirement.TORRC_VIA_STDIN
|
||||
)
|
||||
except IOError:
|
||||
use_stdin = False
|
||||
|
||||
# we need to be sure that we're logging to stdout to figure out when we're
|
||||
# done bootstrapping
|
||||
|
||||
if 'Log' in config:
|
||||
stdout_options = ['DEBUG stdout', 'INFO stdout', 'NOTICE stdout']
|
||||
if "Log" in config:
|
||||
stdout_options = ["DEBUG stdout", "INFO stdout", "NOTICE stdout"]
|
||||
|
||||
if isinstance(config['Log'], str):
|
||||
config['Log'] = [config['Log']]
|
||||
if isinstance(config["Log"], str):
|
||||
config["Log"] = [config["Log"]]
|
||||
|
||||
has_stdout = False
|
||||
|
||||
for log_config in config['Log']:
|
||||
for log_config in config["Log"]:
|
||||
if log_config in stdout_options:
|
||||
has_stdout = True
|
||||
break
|
||||
|
||||
if not has_stdout:
|
||||
config['Log'].append('NOTICE stdout')
|
||||
config["Log"].append("NOTICE stdout")
|
||||
|
||||
config_str = ''
|
||||
config_str = ""
|
||||
|
||||
for key, values in list(config.items()):
|
||||
if isinstance(values, str):
|
||||
config_str += '%s %s\n' % (key, values)
|
||||
config_str += "%s %s\n" % (key, values)
|
||||
else:
|
||||
for value in values:
|
||||
config_str += '%s %s\n' % (key, value)
|
||||
config_str += "%s %s\n" % (key, value)
|
||||
|
||||
if use_stdin:
|
||||
return launch_tor(
|
||||
tor_cmd=tor_cmd,
|
||||
args=['-f', '-'],
|
||||
args=["-f", "-"],
|
||||
completion_percent=completion_percent,
|
||||
init_msg_handler=init_msg_handler,
|
||||
timeout=timeout,
|
||||
take_ownership=take_ownership,
|
||||
close_output=close_output,
|
||||
stdin=config_str
|
||||
stdin=config_str,
|
||||
)
|
||||
else:
|
||||
torrc_descriptor, torrc_path = tempfile.mkstemp(prefix='torrc-', text=True)
|
||||
torrc_descriptor, torrc_path = tempfile.mkstemp(prefix="torrc-", text=True)
|
||||
|
||||
try:
|
||||
with open(torrc_path, 'w') as torrc_file:
|
||||
with open(torrc_path, "w") as torrc_file:
|
||||
torrc_file.write(config_str)
|
||||
|
||||
# prevents tor from error-ing out due to a missing torrc if it gets a sighup
|
||||
args = ['__ReloadTorrcOnSIGHUP', '0']
|
||||
args = ["__ReloadTorrcOnSIGHUP", "0"]
|
||||
|
||||
return launch_tor(tor_cmd, args, torrc_path, completion_percent,
|
||||
init_msg_handler, timeout, take_ownership)
|
||||
return launch_tor(
|
||||
tor_cmd,
|
||||
args,
|
||||
torrc_path,
|
||||
completion_percent,
|
||||
init_msg_handler,
|
||||
timeout,
|
||||
take_ownership,
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
os.close(torrc_descriptor)
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
import os
|
||||
import io
|
||||
import tarfile
|
||||
import requests
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tarfile
|
||||
from typing import Literal
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
def get_latest_version() -> str:
|
||||
"""
|
||||
Gets latest non-alfa version name from dist.torproject.org
|
||||
:return:
|
||||
"""
|
||||
r = requests.get('https://dist.torproject.org/torbrowser/').text
|
||||
r = requests.get("https://dist.torproject.org/torbrowser/").text
|
||||
|
||||
results = re.findall(r'<a href=".+/">(.+)/</a>', r)
|
||||
for res in results:
|
||||
if 'a' not in res:
|
||||
if "a" not in res:
|
||||
return res
|
||||
|
||||
|
||||
def get_build() -> Literal[
|
||||
'windows-x86_64',
|
||||
'linux-x86_64',
|
||||
'macos-x86_64',
|
||||
'macos-aarch64'
|
||||
"windows-x86_64", "linux-x86_64", "macos-x86_64", "macos-aarch64"
|
||||
]:
|
||||
"""
|
||||
Gets proper build name for your system
|
||||
:return:
|
||||
"""
|
||||
if sys.platform == 'win32':
|
||||
return 'windows-x86_64'
|
||||
elif sys.platform == 'linux':
|
||||
return 'linux-x86_64'
|
||||
elif sys.platform == 'darwin':
|
||||
if sys.platform == "win32":
|
||||
return "windows-x86_64"
|
||||
elif sys.platform == "linux":
|
||||
return "linux-x86_64"
|
||||
elif sys.platform == "darwin":
|
||||
import platform
|
||||
if platform.uname().machine == 'arm64':
|
||||
return 'macos-aarch64'
|
||||
|
||||
if platform.uname().machine == "arm64":
|
||||
return "macos-aarch64"
|
||||
else:
|
||||
return 'macos-x86_64'
|
||||
return "macos-x86_64"
|
||||
else:
|
||||
raise 'System not supported'
|
||||
raise "System not supported"
|
||||
|
||||
|
||||
def get_tor_expert_bundles(version: str = get_latest_version(),
|
||||
platform: str = get_build()):
|
||||
def get_tor_expert_bundles(
|
||||
version: str = get_latest_version(), platform: str = get_build()
|
||||
):
|
||||
"""
|
||||
Returns a link for downloading tor expert bundle by version and platform
|
||||
:param version: Tor expert bundle version that exists in dist.torproject.org
|
||||
@@ -53,11 +53,13 @@ def get_tor_expert_bundles(version: str = get_latest_version(),
|
||||
get_build()
|
||||
:return:
|
||||
"""
|
||||
return f'https://dist.torproject.org/torbrowser/{version}/tor-expert-bundle-' \
|
||||
f'{version}-{platform}.tar.gz'
|
||||
return (
|
||||
f"https://dist.torproject.org/torbrowser/{version}/tor-expert-bundle-"
|
||||
f"{version}-{platform}.tar.gz"
|
||||
)
|
||||
|
||||
|
||||
def download_tor(url: str = get_tor_expert_bundles(), dist: str = 'tor'):
|
||||
def download_tor(url: str = get_tor_expert_bundles(), dist: str = "tor"):
|
||||
"""
|
||||
Downloads tor from url and unpacks it to specified directory. Note, that
|
||||
it doesn't unpack only tor executable to dist folder, but creates there
|
||||
@@ -69,15 +71,15 @@ def download_tor(url: str = get_tor_expert_bundles(), dist: str = 'tor'):
|
||||
if not os.path.exists(dist):
|
||||
os.makedirs(dist)
|
||||
|
||||
(tar := tarfile.open(fileobj=io.BytesIO(requests.get(url).content),
|
||||
mode='r:gz')).extractall(
|
||||
(
|
||||
tar := tarfile.open(fileobj=io.BytesIO(requests.get(url).content), mode="r:gz")
|
||||
).extractall(
|
||||
members=[
|
||||
tarinfo
|
||||
for tarinfo
|
||||
in tar.getmembers()
|
||||
if tarinfo.name.startswith("tor/")
|
||||
], path=dist)
|
||||
tarinfo for tarinfo in tar.getmembers() if tarinfo.name.startswith("tor/")
|
||||
],
|
||||
path=dist,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
download_tor()
|
||||
|
||||
Reference in New Issue
Block a user