Drive access.
This commit is contained in:
		
							parent
							
								
									7dcabde2ed
								
							
						
					
					
						commit
						013d4adce5
					
				
							
								
								
									
										39
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,41 +1,10 @@ | ||||
| FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf  | ||||
| FROM python:3.12.8-alpine3.21 | ||||
| FROM python:3.14.0a6-bookworm | ||||
| RUN mkdir -p /code  | ||||
| WORKDIR /code | ||||
| ENV FLASK_APP=app.py | ||||
| ENV FLASK_RUN_HOST=0.0.0.0 | ||||
| RUN apk add --no-cache gcc musl-dev linux-headers git | ||||
| 
 | ||||
| #WKHTMLTOPDFNEEDS | ||||
| RUN apk add --no-cache \ | ||||
|     libstdc++ \ | ||||
|     libx11 \ | ||||
|     libxrender \ | ||||
|     libxext \ | ||||
|     libssl3 \ | ||||
|     ca-certificates \ | ||||
|     docker \ | ||||
|     fontconfig \ | ||||
|     freetype \ | ||||
|     ttf-dejavu \ | ||||
|     ttf-droid \ | ||||
|     ttf-freefont \ | ||||
|     ttf-liberation \ | ||||
|     # more fonts | ||||
|   && apk add --no-cache --virtual .build-deps \ | ||||
|     msttcorefonts-installer \ | ||||
|   # Install microsoft fonts | ||||
|   && update-ms-fonts \ | ||||
|   && fc-cache -f \ | ||||
|   # Clean up when done | ||||
|   && rm -rf /tmp/* \ | ||||
|   && apk del .build-deps | ||||
| COPY --from=wkhtmltopdf /bin/wkhtmltopdf /bin/wkhtmltopdf | ||||
| COPY --from=wkhtmltopdf /bin/wkhtmltoimage /bin/wkhtmltoimage | ||||
| RUN apt update && apt install build-essential docker -y      | ||||
| COPY pyproject.toml pyproject.toml  | ||||
| COPY src src | ||||
| RUN mkdir /drive | ||||
| RUN pip install --upgrade pip | ||||
| RUN pip install -e . | ||||
| EXPOSE 8081 | ||||
| 
 | ||||
| CMD ["python","-m","snek.app"] | ||||
| #CMD ["gunicorn", "-w", "10", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"] | ||||
|  | ||||
							
								
								
									
										18
									
								
								compose.yml
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								compose.yml
									
									
									
									
									
								
							| @ -7,26 +7,12 @@ services: | ||||
|       - "8081:8081" | ||||
|     volumes: | ||||
|       - ./:/code | ||||
|       - /media/storage/snek/molodetz.nl/drive:/code/drive | ||||
|       - /media/storage/snek.molodetz.nl/drive:/code/drive | ||||
|       - /media/storage/snek.molodetz.nl/drive:/drive | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|       - /media/storage/snek/molodetz.nl/drive:/drive | ||||
|     environment: | ||||
|      - PYTHONDONTWRITEBYTECODE=1 | ||||
|      - PYTHONUNBUFFERED=1 | ||||
|     entrypoint: ["gunicorn", "-w", "1", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"] | ||||
|     #entrypoint: ["python","-m","snek.app"] | ||||
|   snecssh: | ||||
|     build: | ||||
|       context: . | ||||
|       dockerfile: DockerfileDrive | ||||
|     restart: always | ||||
|     ports: | ||||
|       - "2225:2225" | ||||
|     volumes: | ||||
|       - ./:/code | ||||
|     environment: | ||||
|      - PYTHONDONTWRITEBYTECODE="1" | ||||
|      - PYTHONUNBUFFERED="1" | ||||
|     entrypoint: ["python","-m","snekssh.app2"] | ||||
|     #["python","-m","snek.app"] | ||||
|     | ||||
|  | ||||
							
								
								
									
										54
									
								
								src/snek/scripts/chat.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/snek/scripts/chat.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| const channelUid = "{{ channel.uid.value }}"; | ||||
| 
 | ||||
| function initInputField(textBox) { | ||||
|     textBox.addEventListener('change', (e) => { | ||||
|         e.preventDefault(); | ||||
|         this.dispatchEvent(new CustomEvent('change', { detail: e.target.value, bubbles: true })); | ||||
|     }); | ||||
| 
 | ||||
|     textBox.addEventListener('keydown', (e) => { | ||||
|         if (e.key === 'Enter' && !e.shiftKey) { | ||||
|             e.preventDefault(); | ||||
|             const message = e.target.value.trim(); | ||||
|             if (message) { | ||||
|                 app.rpc.sendMessage(channelUid, message); | ||||
|                 e.target.value = ''; | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     textBox.focus(); | ||||
| } | ||||
| 
 | ||||
| function updateTimes() { | ||||
|     document.querySelectorAll(".time").forEach((time) => { | ||||
|         time.innerText = app.timeDescription(time.dataset.created_at); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function isElementVisible(element) { | ||||
|     const rect = element.getBoundingClientRect(); | ||||
|     return ( | ||||
|         rect.top >= 0 && | ||||
|         rect.left >= 0 && | ||||
|         rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | ||||
|         rect.right <= (window.innerWidth || document.documentElement.clientWidth) | ||||
|     ); | ||||
| } | ||||
| 
 | ||||
| const messagesContainer = document.querySelector(".chat-messages"); | ||||
| 
 | ||||
| let isLoadingExtra = false; | ||||
| 
 | ||||
| messagesContainer.addEventListener("scroll", () => { | ||||
|     loadExtra(); | ||||
| }); | ||||
| 
 | ||||
| setInterval(updateTimes, 1000); | ||||
| 
 | ||||
| app.addEventListener("channel-message", (data) => { | ||||
|     if (data.channel_uid !== channelUid) { | ||||
|         if(!isMentionForSomeoneElse(data.message)){ | ||||
|             channelSidebar.notify(data); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
							
								
								
									
										48
									
								
								src/snek/system/terminal.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/snek/system/terminal.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| import asyncio | ||||
| import aiohttp | ||||
| import aiohttp.web | ||||
| import os | ||||
| import pty | ||||
| import shlex | ||||
| import subprocess | ||||
| import pathlib | ||||
| 
 | ||||
| commands = { | ||||
|     'alpine': 'docker run -it alpine /bin/sh', | ||||
|     'r': 'docker run -v /usr/local/bin:/usr/local/bin -it ubuntu:latest run.sh', | ||||
| } | ||||
| 
 | ||||
| class TerminalSession: | ||||
|     def __init__(self,command): | ||||
|         self.master, self.slave = pty.openpty() | ||||
|         self.sockets =[] | ||||
|         self.process = subprocess.Popen( | ||||
|             command.split(" "), | ||||
|             stdin=self.slave, | ||||
|             stdout=self.slave, | ||||
|             stderr=self.slave, | ||||
|             bufsize=0, | ||||
|             universal_newlines=True | ||||
|         ) | ||||
| 
 | ||||
|     async def read_output(self, ws): | ||||
|         loop = asyncio.get_event_loop() | ||||
|         self.sockets.append(ws) | ||||
|         if len(self.sockets) > 1: | ||||
|             return  | ||||
|         while True: | ||||
|             try: | ||||
|                 data = await loop.run_in_executor(None, os.read, self.master, 1024) | ||||
|                 if not data: | ||||
|                     break | ||||
|                 try: | ||||
|                     for ws in self.sockets: await ws.send_bytes(data)  # Send raw bytes for ANSI support | ||||
|                 except: | ||||
|                     self.sockets.remove(ws) | ||||
|             except Exception: | ||||
|                 break | ||||
| 
 | ||||
|     async def write_input(self, data): | ||||
|         os.write(self.master, data.encode()) | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										1
									
								
								src/snek/templates/static
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								src/snek/templates/static
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../static/ | ||||
							
								
								
									
										47
									
								
								src/snek/templates/terminal.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/snek/templates/terminal.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| {% extends "app.html" %} | ||||
| 
 | ||||
| {% block sidebar %} | ||||
| Reboot | ||||
| {% endblock %} | ||||
| 
 | ||||
| {% block main %} | ||||
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css"> | ||||
|     <script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script> | ||||
|     <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit/lib/xterm-addon-fit.js"></script> | ||||
|     <style> | ||||
|        #terminal { width: 100%; height: 480px; overflow-y: none; } | ||||
|     </style> | ||||
| 
 | ||||
|   <div class="container" id="terminal"></div> | ||||
| 
 | ||||
|     <script> | ||||
|         const term = new Terminal({ cursorBlink: true }); | ||||
|         const fitAddon = new FitAddon.FitAddon(); | ||||
|         term.loadAddon(fitAddon); | ||||
|         term.open(document.getElementById("terminal")); | ||||
|         fitAddon.fit(); | ||||
| 
 | ||||
|         window.addEventListener("resize", () => fitAddon.fit()); | ||||
|          | ||||
|         const schema = window.location.protocol === "https:" ? "wss" : "ws"; | ||||
|         const hostname = window.location.host; | ||||
|         const url = `${schema}://${hostname}/terminal.ws`; | ||||
| 
 | ||||
|         const socket = new WebSocket(url); | ||||
|         socket.binaryType = "arraybuffer";  // Support binary data | ||||
| 
 | ||||
|         socket.onopen = () => term.write("\x1b[32mConnected to Molodetz\x1b[0m\r\n"); | ||||
| 
 | ||||
|         socket.onmessage = (event) => { | ||||
|             const data = new Uint8Array(event.data); | ||||
|             term.write(new TextDecoder().decode(data)); | ||||
|         }; | ||||
| 
 | ||||
|         term.onData(data => socket.send(new TextEncoder().encode(data))); | ||||
| 
 | ||||
|         socket.onclose = () => term.write("\r\n\x1b[31mConnection closed\x1b[0m\r\n"); | ||||
|     </script> | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| {% endblock main %} | ||||
							
								
								
									
										56
									
								
								src/snek/view/terminal.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/snek/view/terminal.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| from snek.system.view import BaseView  | ||||
| import aiohttp  | ||||
| import asyncio | ||||
| from snek.system.terminal import TerminalSession | ||||
| import pathlib | ||||
| 
 | ||||
| class TerminalSocketView(BaseView): | ||||
|      | ||||
|     login_required = True | ||||
| 
 | ||||
|     user_sessions = {} | ||||
|      | ||||
|     async def prepare_drive(self): | ||||
|         user = await self.services.user.get(uid=self.session.get("uid")) | ||||
|         root = pathlib.Path("drive").joinpath(user["uid"]) | ||||
|         root.mkdir(parents=True, exist_ok=True) | ||||
|         terminal_folder = pathlib.Path("terminal") | ||||
|         for path in terminal_folder.iterdir(): | ||||
|             destination_path = root.joinpath(path.name) | ||||
|             if not destination_path.exists(): | ||||
|                 if not path.is_dir(): | ||||
|                     destination_path.write_bytes(path.read_bytes()) | ||||
|         return root  | ||||
|          | ||||
|     async def get(self): | ||||
|         | ||||
| 
 | ||||
| 
 | ||||
|         ws = aiohttp.web.WebSocketResponse() | ||||
|         await ws.prepare(self.request) | ||||
|         user = await self.services.user.get(uid=self.session.get("uid")) | ||||
|         root = await self.prepare_drive() | ||||
|          | ||||
|         command = f"docker run -v ./{root}/:/root --rm -it --memory 512M --cpus=0.5 -w /root ubuntu:latest /bin/bash" | ||||
|         print(command) | ||||
| 
 | ||||
|         session = self.user_sessions.get(user["uid"]) | ||||
|         if not session: | ||||
|             self.user_sessions[user["uid"]] = TerminalSession(command=command) | ||||
|         session = self.user_sessions[user["uid"]]    | ||||
|         asyncio.create_task(session.read_output(ws))   | ||||
| 
 | ||||
|         async for msg in ws: | ||||
|             if msg.type == aiohttp.WSMsgType.BINARY: | ||||
|                 await session.write_input(msg.data.decode()) | ||||
| 
 | ||||
|          | ||||
|         return ws | ||||
| 
 | ||||
| class TerminalView(BaseView): | ||||
| 
 | ||||
|     login_required = True | ||||
| 
 | ||||
|     async def get(self): | ||||
|         request = self.request | ||||
|         return await self.request.app.render_template('terminal.html',self.request) | ||||
							
								
								
									
										108
									
								
								terminal/.bashrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								terminal/.bashrc
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,108 @@ | ||||
| # ~/.bashrc: executed by bash(1) for non-login shells. | ||||
| # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc) | ||||
| # for examples | ||||
| 
 | ||||
| # If not running interactively, don't do anything | ||||
| [ -z "$PS1" ] && return | ||||
| 
 | ||||
| # don't put duplicate lines in the history. See bash(1) for more options | ||||
| # ... or force ignoredups and ignorespace | ||||
| HISTCONTROL=ignoredups:ignorespace | ||||
| 
 | ||||
| # append to the history file, don't overwrite it | ||||
| shopt -s histappend | ||||
| 
 | ||||
| # for setting history length see HISTSIZE and HISTFILESIZE in bash(1) | ||||
| HISTSIZE=1000 | ||||
| HISTFILESIZE=2000 | ||||
| 
 | ||||
| # check the window size after each command and, if necessary, | ||||
| # update the values of LINES and COLUMNS. | ||||
| shopt -s checkwinsize | ||||
| 
 | ||||
| # make less more friendly for non-text input files, see lesspipe(1) | ||||
| [ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)" | ||||
| 
 | ||||
| # set variable identifying the chroot you work in (used in the prompt below) | ||||
| if [ -z "$debian_chroot" ] && [ -r /etc/debian_chroot ]; then | ||||
|     debian_chroot=$(cat /etc/debian_chroot) | ||||
| fi | ||||
| 
 | ||||
| # set a fancy prompt (non-color, unless we know we "want" color) | ||||
| case "$TERM" in | ||||
|     xterm-color) color_prompt=yes;; | ||||
| esac | ||||
| 
 | ||||
| # uncomment for a colored prompt, if the terminal has the capability; turned | ||||
| # off by default to not distract the user: the focus in a terminal window | ||||
| # should be on the output of commands, not on the prompt | ||||
| #force_color_prompt=yes | ||||
| 
 | ||||
| if [ -n "$force_color_prompt" ]; then | ||||
|     if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then | ||||
| 	# We have color support; assume it's compliant with Ecma-48 | ||||
| 	# (ISO/IEC-6429). (Lack of such support is extremely rare, and such | ||||
| 	# a case would tend to support setf rather than setaf.) | ||||
| 	color_prompt=yes | ||||
|     else | ||||
| 	color_prompt= | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| if [ "$color_prompt" = yes ]; then | ||||
|     PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' | ||||
| else | ||||
|     PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' | ||||
| fi | ||||
| unset color_prompt force_color_prompt | ||||
| 
 | ||||
| # If this is an xterm set the title to user@host:dir | ||||
| case "$TERM" in | ||||
| xterm*|rxvt*) | ||||
|     PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1" | ||||
|     ;; | ||||
| *) | ||||
|     ;; | ||||
| esac | ||||
| 
 | ||||
| # enable color support of ls and also add handy aliases | ||||
| if [ -x /usr/bin/dircolors ]; then | ||||
|     test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)" | ||||
|     alias ls='ls --color=auto' | ||||
|     #alias dir='dir --color=auto' | ||||
|     #alias vdir='vdir --color=auto' | ||||
| 
 | ||||
|     alias grep='grep --color=auto' | ||||
|     alias fgrep='fgrep --color=auto' | ||||
|     alias egrep='egrep --color=auto' | ||||
| fi | ||||
| 
 | ||||
| # some more ls aliases | ||||
| alias ll='ls -alF' | ||||
| alias la='ls -A' | ||||
| alias l='ls -CF' | ||||
| 
 | ||||
| # Alias definitions. | ||||
| # You may want to put all your additions into a separate file like | ||||
| # ~/.bash_aliases, instead of adding them here directly. | ||||
| # See /usr/share/doc/bash-doc/examples in the bash-doc package. | ||||
| 
 | ||||
| if [ -f ~/.bash_aliases ]; then | ||||
|     . ~/.bash_aliases | ||||
| fi | ||||
| 
 | ||||
| 
 | ||||
| cp ~/r /usr/local/bin  | ||||
| 
 | ||||
| chmod -x /usr/local/bin/* | ||||
| 
 | ||||
| apt update && apt install libreadline-dev libcurl4-openssl-dev libssl-dev libncurses5-dev libncursesw5-dev libsqlite3-dev libreadline6-dev zlib1g-dev libbz2-dev libffi-dev liblzma-dev python3 python3-pip python3-venv -y | ||||
| 
 | ||||
| echo "r is installed." | ||||
| 
 | ||||
| # enable programmable completion features (you don't need to enable | ||||
| # this, if it's already enabled in /etc/bash.bashrc and /etc/profile | ||||
| # sources /etc/bash.bashrc). | ||||
| #if [ -f /etc/bash_completion ] && ! shopt -oq posix; then | ||||
| #    . /etc/bash_completion | ||||
| #fi | ||||
							
								
								
									
										9
									
								
								terminal/.profile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								terminal/.profile
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| # ~/.profile: executed by Bourne-compatible login shells. | ||||
| 
 | ||||
| if [ "$BASH" ]; then | ||||
|   if [ -f ~/.bashrc ]; then | ||||
|     . ~/.bashrc | ||||
|   fi | ||||
| fi | ||||
| 
 | ||||
| mesg n 2> /dev/null || true | ||||
							
								
								
									
										
											BIN
										
									
								
								terminal/r
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								terminal/r
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user