From f182c2209e0bea66bb9a64580994bde75c738d4f Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 7 Jun 2025 05:46:30 +0200 Subject: [PATCH] Update. --- src/snek/service/channel.py | 10 +++++ src/snek/service/container.py | 29 +++++++++++++- src/snek/system/docker.py | 75 +++++++++++++++++++++++++++++++++++ src/snek/templates/web.html | 16 +++++++- 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/snek/system/docker.py diff --git a/src/snek/service/channel.py b/src/snek/service/channel.py index 238867d..a5c166d 100644 --- a/src/snek/service/channel.py +++ b/src/snek/service/channel.py @@ -8,6 +8,15 @@ import pathlib class ChannelService(BaseService): mapper_name = "channel" + async def get_home_folder(self, channel_uid): + folder = pathlib.Path(f"./drive/{channel_uid}/container/home") + if not folder.exists(): + try: + folder.mkdir(parents=True, exist_ok=True) + except: + pass + return folder + async def get_attachment_folder(self, channel_uid,ensure=False): path = pathlib.Path(f"./drive/{channel_uid}/attachments") if ensure: @@ -56,6 +65,7 @@ class ChannelService(BaseService): model["is_private"] = is_private model["is_listed"] = is_listed if await self.save(model): + await self.services.container.create(model["uid"]) return model raise Exception(f"Failed to create channel: {model.errors}.") diff --git a/src/snek/service/container.py b/src/snek/service/container.py index 4025e9e..03c63aa 100644 --- a/src/snek/service/container.py +++ b/src/snek/service/container.py @@ -1,9 +1,36 @@ from snek.system.service import BaseService +from snek.system.docker import ComposeFileManager class ContainerService(BaseService): mapper_name = "container" - async def create(self, id, name, status, resources=None, user_uid=None, path=None, readonly=False): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.compose_path = "snek-container-compose.yml" + self.compose = ComposeFileManager(self.compose_path) + + + + async def get_instances(self): + return list(self.compose.list_instances()) + + async def get_container_name(self, channel_uid): + return f"channel-{channel_uid}" + + async def create(self, channel_uid, image="ubuntu:latest", command=None, cpus=1, memory='1024m', ports=None, volumes=None): + name = await self.get_container_name(channel_uid) + self.compose.create_instance( + name, + image, + command, + cpus, + memory, + ports, + ["./"+str((await self.services.channel.get_home_folder(channel_uid))) +":"+ "/home/ubuntu"] + ) + + async def create2(self, id, name, status, resources=None, user_uid=None, path=None, readonly=False): model = await self.new() model["id"] = id model["name"] = name diff --git a/src/snek/system/docker.py b/src/snek/system/docker.py new file mode 100644 index 0000000..d651950 --- /dev/null +++ b/src/snek/system/docker.py @@ -0,0 +1,75 @@ +import yaml +import copy + +class ComposeFileManager: + def __init__(self, compose_path='docker-compose.yml'): + self.compose_path = compose_path + self._load() + + def _load(self): + try: + with open(self.compose_path, 'r') as f: + self.compose = yaml.safe_load(f) or {} + except FileNotFoundError: + self.compose = {'version': '3', 'services': {}} + + def _save(self): + with open(self.compose_path, 'w') as f: + yaml.dump(self.compose, f, default_flow_style=False) + + def list_instances(self): + return list(self.compose.get('services', {}).keys()) + + def create_instance(self, name, image, command=None, cpus=None, memory=None, ports=None, volumes=None): + service = { + 'image': image, + } + if command: + service['command'] = command + if cpus or memory: + service['deploy'] = {'resources': {'limits': {}}} + if cpus: + service['deploy']['resources']['limits']['cpus'] = str(cpus) + if memory: + service['deploy']['resources']['limits']['memory'] = str(memory) + if ports: + service['ports'] = [f"{host}:{container}" for container, host in ports.items()] + if volumes: + service['volumes'] = volumes + + self.compose.setdefault('services', {})[name] = service + self._save() + + def remove_instance(self, name): + if name in self.compose.get('services', {}): + del self.compose['services'][name] + self._save() + + def get_instance(self, name): + return self.compose.get('services', {}).get(name) + + def duplicate_instance(self, name, new_name): + orig = self.get_instance(name) + if not orig: + raise ValueError(f"No such instance: {name}") + self.compose['services'][new_name] = copy.deepcopy(orig) + self._save() + + def update_instance(self, name, **kwargs): + service = self.get_instance(name) + if not service: + raise ValueError(f"No such instance: {name}") + for k, v in kwargs.items(): + if v is not None: + service[k] = v + self.compose['services'][name] = service + self._save() + + # Storage size is not tracked in compose files; would need Docker API for that. + +# Example usage: +# mgr = ComposeFileManager() +# mgr.create_instance('web', 'nginx:latest', cpus=1, memory='512m', ports={80:8080}, volumes=['./data:/data']) +# print(mgr.list_instances()) +# mgr.duplicate_instance('web', 'web_copy') +# mgr.remove_instance('web_copy') diff --git a/src/snek/templates/web.html b/src/snek/templates/web.html index 3f301d9..5160b00 100644 --- a/src/snek/templates/web.html +++ b/src/snek/templates/web.html @@ -5,7 +5,21 @@ {% block main %}
- + + {% if not messages %} + +
+

Welcome to your new channel!

+

This is the start of something great. Use the commands below to get started:

+
    +
  • Press /invite to invite someone.
  • +
  • Press /online to see who's currently online.
  • +
  • Press /help to view all available commands.
  • +
+

Enjoy chatting!

+
+ {% endif %} + {% for message in messages %} {% autoescape false %} {{ message.html }}