Update.
This commit is contained in:
		
							parent
							
								
									8e9ee4bff0
								
							
						
					
					
						commit
						f182c2209e
					
				| @ -8,6 +8,15 @@ import pathlib | |||||||
| class ChannelService(BaseService): | class ChannelService(BaseService): | ||||||
|     mapper_name = "channel" |     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): |     async def get_attachment_folder(self, channel_uid,ensure=False): | ||||||
|         path = pathlib.Path(f"./drive/{channel_uid}/attachments") |         path = pathlib.Path(f"./drive/{channel_uid}/attachments") | ||||||
|         if ensure: |         if ensure: | ||||||
| @ -56,6 +65,7 @@ class ChannelService(BaseService): | |||||||
|         model["is_private"] = is_private |         model["is_private"] = is_private | ||||||
|         model["is_listed"] = is_listed |         model["is_listed"] = is_listed | ||||||
|         if await self.save(model): |         if await self.save(model): | ||||||
|  |             await self.services.container.create(model["uid"]) | ||||||
|             return model |             return model | ||||||
|         raise Exception(f"Failed to create channel: {model.errors}.") |         raise Exception(f"Failed to create channel: {model.errors}.") | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,9 +1,36 @@ | |||||||
| from snek.system.service import BaseService | from snek.system.service import BaseService | ||||||
|  | from snek.system.docker import ComposeFileManager  | ||||||
| 
 | 
 | ||||||
| class ContainerService(BaseService): | class ContainerService(BaseService): | ||||||
|     mapper_name = "container" |     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 = await self.new() | ||||||
|         model["id"] = id |         model["id"] = id | ||||||
|         model["name"] = name |         model["name"] = name | ||||||
|  | |||||||
							
								
								
									
										75
									
								
								src/snek/system/docker.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/snek/system/docker.py
									
									
									
									
									
										Normal file
									
								
							| @ -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') | ||||||
| @ -6,6 +6,20 @@ | |||||||
| 
 | 
 | ||||||
| <section class="chat-area"> | <section class="chat-area"> | ||||||
|         <message-list class="chat-messages"> |         <message-list class="chat-messages"> | ||||||
|  |         {% if not messages %} | ||||||
|  |          | ||||||
|  |         <div> | ||||||
|  |           <h1>Welcome to your new channel!</h1> | ||||||
|  |           <p>This is the start of something great. Use the commands below to get started:</p> | ||||||
|  |           <ul> | ||||||
|  |             <li>Press <code>/invite</code> to invite someone.</li> | ||||||
|  |             <li>Press <code>/online</code> to see who's currently online.</li> | ||||||
|  |             <li>Press <code>/help</code> to view all available commands.</li> | ||||||
|  |           </ul> | ||||||
|  |           <p>Enjoy chatting!</p> | ||||||
|  |         </div> | ||||||
|  |         {% endif %} | ||||||
|  | 
 | ||||||
|         {% for message in messages %} |         {% for message in messages %} | ||||||
|             {% autoescape false %} |             {% autoescape false %} | ||||||
|             {{ message.html }} |             {{ message.html }} | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user