Drive access.

This commit is contained in:
retoor 2025-03-22 19:57:39 +01:00
parent 7dcabde2ed
commit 013d4adce5
10 changed files with 329 additions and 51 deletions

View File

@ -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"]

View File

@ -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
View 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);
}
}
});

View 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
View File

@ -0,0 +1 @@
../static/

View 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
View 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
View 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
View 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

Binary file not shown.