Windows friendly solution.

This commit is contained in:
retoor 2025-05-13 18:32:59 +02:00
parent ac2f68f93f
commit a4bea94495
9 changed files with 66 additions and 43 deletions

View File

@ -20,7 +20,6 @@ serve: run
run: run:
.venv/bin/snek serve .venv/bin/snek serve
#$(GUNICORN) -w $(GUNICORN_WORKERS) -k aiohttp.worker.GunicornWebWorker snek.gunicorn:app --bind 0.0.0.0:$(PORT) --reload
install: ubuntu install: ubuntu
python3.12 -m venv .venv python3.12 -m venv .venv

View File

@ -18,7 +18,8 @@ dependencies = [
"lxml", "lxml",
"IPython", "IPython",
"shed", "shed",
"app @ git+https://retoor.molodetz.nl/retoor/app", "app @ git+https://retoor.molodetz.nl/retoor/app.git",
"zhurnal @git+https://retoor.molodetz.nl/retoor/zhurnal.git",
"beautifulsoup4", "beautifulsoup4",
"gunicorn", "gunicorn",
"imgkit", "imgkit",

View File

@ -1,24 +1,45 @@
import click import click
import uvloop
from aiohttp import web from aiohttp import web
import asyncio import asyncio
from snek.app import Application from snek.app import Application
from IPython import start_ipython from IPython import start_ipython
import sqlite3
import pathlib
import shutil
@click.group() @click.group()
def cli(): def cli():
pass pass
@cli.command()
@click.option('--db_path',default="snek.db", help='Database to initialize if not exists.')
@click.option('--source',default=None, help='Database to initialize if not exists.')
def init(db_path,source):
if source and pathlib.Path(source).exists():
print(f"Copying {source} to {db_path}")
shutil.copy2(source,db_path)
print("Database initialized.")
return
if pathlib.Path(db_path).exists():
return
print(f"Initializing database at {db_path}")
db = sqlite3.connect(db_path)
db.cursor().executescript(
pathlib.Path(__file__).parent.joinpath("schema.sql").read_text()
)
db.commit()
db.close()
print("Database initialized.")
@cli.command() @cli.command()
@click.option('--port', default=8081, show_default=True, help='Port to run the application on') @click.option('--port', default=8081, show_default=True, help='Port to run the application on')
@click.option('--host', default='0.0.0.0', show_default=True, help='Host to run the application on') @click.option('--host', default='0.0.0.0', show_default=True, help='Host to run the application on')
@click.option('--db_path', default='snek.db', show_default=True, help='Database path for the application') @click.option('--db_path', default='snek.db', show_default=True, help='Database path for the application')
def serve(port, host, db_path): def serve(port, host, db_path):
try: #init(db_path)
import uvloop #asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
print("uvloop not installed, using default event loop.")
web.run_app( web.run_app(
Application(db_path=f"sqlite:///{db_path}"), port=port, host=host Application(db_path=f"sqlite:///{db_path}"), port=port, host=host
) )

View File

@ -3,9 +3,9 @@ import logging
import pathlib import pathlib
import time import time
import uuid import uuid
from snek import snode
from snek.view.threads import ThreadsView from snek.view.threads import ThreadsView
import json
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
@ -57,7 +57,6 @@ from snek.view.web import WebView
from snek.view.channel import ChannelAttachmentView from snek.view.channel import ChannelAttachmentView
from snek.webdav import WebdavApplication from snek.webdav import WebdavApplication
from snek.sgit import GitApplication from snek.sgit import GitApplication
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34" SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
@ -100,17 +99,25 @@ class Application(BaseApplication):
self.ssh_port = 2242 self.ssh_port = 2242
self.setup_router() self.setup_router()
self.ssh_server = None self.ssh_server = None
self.sync_service = None
self.executor = None self.executor = None
self.cache = Cache(self) self.cache = Cache(self)
self.services = get_services(app=self) self.services = get_services(app=self)
self.mappers = get_mappers(app=self) self.mappers = get_mappers(app=self)
self.broadcast_service = None
self.on_startup.append(self.start_ssh_server) self.on_startup.append(self.start_ssh_server)
self.on_startup.append(self.prepare_asyncio) self.on_startup.append(self.prepare_asyncio)
self.on_startup.append(self.prepare_database) self.on_startup.append(self.prepare_database)
async def snode_sync(self, app):
self.sync_service = asyncio.create_task(snode.sync_service(app))
async def start_ssh_server(self, app): async def start_ssh_server(self, app):
app.ssh_server = await start_ssh_server(app,app.ssh_host,app.ssh_port) app.ssh_server = await start_ssh_server(app,app.ssh_host,app.ssh_port)
asyncio.create_task(app.ssh_server.wait_closed()) if app.ssh_server:
asyncio.create_task(app.ssh_server.wait_closed())
async def prepare_asyncio(self, app): async def prepare_asyncio(self, app):
# app.loop = asyncio.get_running_loop() # app.loop = asyncio.get_running_loop()

View File

@ -16,4 +16,5 @@ class DriveItemService(BaseService):
if await self.save(model): if await self.save(model):
return model return model
errors = await model.errors errors = await model.errors
print("XXXXXXXXXX")
raise Exception(f"Failed to create drive item: {errors}.") raise Exception(f"Failed to create drive item: {errors}.")

View File

@ -1,6 +1,7 @@
from snek.model.user import UserModel from snek.model.user import UserModel
from snek.system.service import BaseService from snek.system.service import BaseService
from datetime import datetime
import json
class SocketService(BaseService): class SocketService(BaseService):
@ -10,6 +11,7 @@ class SocketService(BaseService):
self.is_connected = True self.is_connected = True
self.user = user self.user = user
async def send_json(self, data): async def send_json(self, data):
if not self.is_connected: if not self.is_connected:
return False return False
@ -33,6 +35,7 @@ class SocketService(BaseService):
self.sockets = set() self.sockets = set()
self.users = {} self.users = {}
self.subscriptions = {} self.subscriptions = {}
self.last_update = str(datetime.now())
async def add(self, ws, user_uid): async def add(self, ws, user_uid):
s = self.Socket(ws, await self.app.services.user.get(uid=user_uid)) s = self.Socket(ws, await self.app.services.user.get(uid=user_uid))
@ -54,7 +57,11 @@ class SocketService(BaseService):
count += 1 count += 1
return count return count
async def broadcast(self, channel_uid, message): async def broadcast(self, channel_uid, message):
await self._broadcast(channel_uid, message)
async def _broadcast(self, channel_uid, message):
try: try:
async for user_uid in self.services.channel_member.get_user_uids( async for user_uid in self.services.channel_member.get_user_uids(
channel_uid channel_uid

View File

@ -1,7 +1,7 @@
import os import os
import aiohttp import aiohttp
from aiohttp import web from aiohttp import web
import git
import shutil import shutil
import json import json
import tempfile import tempfile
@ -14,6 +14,8 @@ logger = logging.getLogger('git_server')
class GitApplication(web.Application): class GitApplication(web.Application):
def __init__(self, parent=None): def __init__(self, parent=None):
#import git
#globals()['git'] = git
self.parent = parent self.parent = parent
super().__init__(client_max_size=1024*1024*1024*5) super().__init__(client_max_size=1024*1024*1024*5)
self.add_routes([ self.add_routes([

View File

@ -4,7 +4,6 @@ from pathlib import Path
global _app global _app
def set_app(app): def set_app(app):
global _app global _app
_app = app _app = app
@ -12,19 +11,11 @@ def set_app(app):
def get_app(): def get_app():
return _app return _app
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("sftp_server.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
roots = {} roots = {}
class MySFTPServer(asyncssh.SFTPServer): class SFTPServer(asyncssh.SFTPServer):
def __init__(self, chan: asyncssh.SSHServerChannel): def __init__(self, chan: asyncssh.SSHServerChannel):
self.root = get_app().services.user.get_home_folder_by_username( self.root = get_app().services.user.get_home_folder_by_username(
@ -35,29 +26,24 @@ class MySFTPServer(asyncssh.SFTPServer):
super().__init__(chan, chroot=self.root) super().__init__(chan, chroot=self.root)
def map_path(self, path): def map_path(self, path):
# Map client path to local filesystem path
mapped_path = Path(self.root).joinpath(path.lstrip(b"/").decode()) mapped_path = Path(self.root).joinpath(path.lstrip(b"/").decode())
#Path(mapped_path).mkdir(exist_ok=True)
print(mapped_path)
logger.debug(f"Mapping client path {path} to {mapped_path}") logger.debug(f"Mapping client path {path} to {mapped_path}")
return str(mapped_path).encode() return str(mapped_path).encode()
class MySSHServer(asyncssh.SSHServer): class SSHServer(asyncssh.SSHServer):
def password_auth_supported(self): def password_auth_supported(self):
return True return True
def validate_password(self, username, password): def validate_password(self, username, password):
# Simple in-memory user database
logger.debug(f"Validating credentials for user {username}") logger.debug(f"Validating credentials for user {username}")
return get_app().services.user.authenticate_sync(username,password) result = get_app().services.user.authenticate_sync(username,password)
logger.info(f"Validating credentials for user {username}: {result}")
return result
async def start_ssh_server(app,host,port): async def start_ssh_server(app,host,port):
set_app(app) set_app(app)
logger.info("Starting SFTP server setup") logger.info("Starting SFTP server setup")
# Server host key (generate or load one)
host_key_path = Path("drive") / ".ssh" / "sftp_server_key" host_key_path = Path("drive") / ".ssh" / "sftp_server_key"
host_key_path.parent.mkdir(exist_ok=True) host_key_path.parent.mkdir(exist_ok=True)
try: try:
@ -72,20 +58,19 @@ async def start_ssh_server(app,host,port):
logger.error(f"Failed to generate or load host key: {e}") logger.error(f"Failed to generate or load host key: {e}")
raise raise
# Start the SSH server with SFTP support logger.info(f"Starting SFTP server on 127.0.0.1:{port}")
logger.info("Starting SFTP server on localhost:8022")
try: try:
x = await asyncssh.listen( x = await asyncssh.listen(
host=host, host=host,
port=port, port=port,
#process_factory=handle_client, #process_factory=handle_client,
server_host_keys=[key], server_host_keys=[key],
server_factory=MySSHServer, server_factory=SSHServer,
sftp_factory=MySFTPServer sftp_factory=SFTPServer
) )
return x return x
except Exception as e: except Exception as e:
logger.error(f"Failed to start SFTP server: {e}") logger.warning(f"Failed to start SFTP server. Already running.")
raise pass

View File

@ -6,7 +6,7 @@ import humanize
from aiohttp import web from aiohttp import web
from snek.system.view import BaseView from snek.system.view import BaseView
import asyncio import asyncio
from git import Repo