Compare commits
4 Commits
3960390ec4
...
6e68408ddf
Author | SHA1 | Date | |
---|---|---|---|
6e68408ddf | |||
013d4adce5 | |||
7dcabde2ed | |||
5ba239caa8 |
38
Dockerfile
38
Dockerfile
@ -1,40 +1,10 @@
|
|||||||
FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf
|
FROM python:3.14.0a6-bookworm
|
||||||
FROM python:3.12.8-alpine3.21
|
RUN mkdir -p /code
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
ENV FLASK_APP=app.py
|
RUN apt update && apt install build-essential docker -y
|
||||||
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 \
|
|
||||||
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
|
|
||||||
COPY pyproject.toml pyproject.toml
|
COPY pyproject.toml pyproject.toml
|
||||||
COPY src src
|
COPY src src
|
||||||
|
RUN mkdir /drive
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install -e .
|
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
@ -2,27 +2,17 @@ services:
|
|||||||
snek:
|
snek:
|
||||||
build: .
|
build: .
|
||||||
restart: always
|
restart: always
|
||||||
|
privileged: true
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/code
|
- ./:/code
|
||||||
|
- /media/storage/snek.molodetz.nl/drive:/code/drive
|
||||||
|
- /media/storage/snek.molodetz.nl/drive:/drive
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
environment:
|
environment:
|
||||||
- PYTHONDONTWRITEBYTECODE=1
|
- PYTHONDONTWRITEBYTECODE=1
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
entrypoint: ["gunicorn", "-w", "1", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
|
entrypoint: ["gunicorn", "-w", "1", "-k", "aiohttp.worker.GunicornWebWorker", "snek.gunicorn:app","--bind","0.0.0.0:8081"]
|
||||||
#entrypoint: ["python","-m","snek.app"]
|
#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"]
|
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ from snek.view.upload import UploadView
|
|||||||
from snek.view.search_user import SearchUserView
|
from snek.view.search_user import SearchUserView
|
||||||
from snek.view.avatar import AvatarView
|
from snek.view.avatar import AvatarView
|
||||||
from snek.system.profiler import profiler_handler
|
from snek.system.profiler import profiler_handler
|
||||||
|
from snek.view.terminal import TerminalView, TerminalSocketView
|
||||||
|
|
||||||
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"
|
||||||
|
|
||||||
@ -115,6 +115,8 @@ class Application(BaseApplication):
|
|||||||
self.router.add_get("/rpc.ws", RPCView)
|
self.router.add_get("/rpc.ws", RPCView)
|
||||||
self.router.add_view("/channel/{channel}.html", WebView)
|
self.router.add_view("/channel/{channel}.html", WebView)
|
||||||
self.router.add_view("/threads.html", ThreadsView)
|
self.router.add_view("/threads.html", ThreadsView)
|
||||||
|
self.router.add_view("/terminal.ws", TerminalSocketView)
|
||||||
|
self.router.add_view("/terminal.html", TerminalView)
|
||||||
|
|
||||||
self.add_subapp(
|
self.add_subapp(
|
||||||
"/docs",
|
"/docs",
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
@ -4,6 +4,11 @@
|
|||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<aside class="sidebar" id="channelSidebar">
|
<aside class="sidebar" id="channelSidebar">
|
||||||
|
<h2 class="no-select">Terminals</h2>
|
||||||
|
<ul>
|
||||||
|
<li><a class="no-select" href="/terminal.html">Ubuntu</a></li>
|
||||||
|
</ul>
|
||||||
|
{% if channels %}
|
||||||
<h2 class="no-select">Channels</h2>
|
<h2 class="no-select">Channels</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{% for channel in channels if not channel['is_private'] %}
|
{% for channel in channels if not channel['is_private'] %}
|
||||||
@ -16,6 +21,7 @@
|
|||||||
<li id="channel-list-item-{{channel['uid']}}"><a class="no-select" href="/channel/{{channel['uid']}}.html">{{channel['name']}} <span class="message-count"></span></a></li>
|
<li id="channel-list-item-{{channel['uid']}}"><a class="no-select" href="/channel/{{channel['uid']}}.html">{{channel['name']}} <span class="message-count"></span></a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</aside>
|
</aside>
|
||||||
<script>
|
<script>
|
||||||
class ChannelSidebar {
|
class ChannelSidebar {
|
||||||
|
1
src/snek/templates/static
Symbolic link
1
src/snek/templates/static
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../static/
|
49
src/snek/templates/terminal.html
Normal file
49
src/snek/templates/terminal.html
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{% extends "app.html" %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
|
||||||
|
{% include "sidebar_channels.html" %}
|
||||||
|
|
||||||
|
{% 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 %}
|
@ -31,4 +31,4 @@ class LoginView(BaseFormView):
|
|||||||
"color": user["color"]
|
"color": user["color"]
|
||||||
})
|
})
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
||||||
return {"is_valid": False}
|
return {"is_valid": False}
|
@ -24,8 +24,10 @@ class RegisterView(BaseFormView):
|
|||||||
result = await self.app.services.user.register(
|
result = await self.app.services.user.register(
|
||||||
form.email.value, form.username.value, form.password.value
|
form.email.value, form.username.value, form.password.value
|
||||||
)
|
)
|
||||||
self.request.session["uid"] = result["uid"]
|
self.request.session.update({
|
||||||
self.request.session["username"] = result["username"]
|
"uid": result["uid"],
|
||||||
self.request.session["logged_in"] = True
|
"username": result["username"],
|
||||||
self.request.session["color"] = result["color"]
|
"logged_in": True,
|
||||||
|
"color": result["color"]
|
||||||
|
})
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
@ -14,21 +14,20 @@
|
|||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# all copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM,
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# THE SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
from snek.form.register import RegisterForm
|
from snek.form.register import RegisterForm
|
||||||
from snek.system.view import BaseFormView
|
from snek.system.view import BaseFormView
|
||||||
|
|
||||||
|
|
||||||
class RegisterFormView(BaseFormView):
|
class RegisterFormView(BaseFormView):
|
||||||
form = RegisterForm
|
form = RegisterForm
|
||||||
|
|
||||||
@ -36,8 +35,10 @@ class RegisterFormView(BaseFormView):
|
|||||||
result = await self.app.services.user.register(
|
result = await self.app.services.user.register(
|
||||||
form.email.value, form.username.value, form.password.value
|
form.email.value, form.username.value, form.password.value
|
||||||
)
|
)
|
||||||
self.request.session["uid"] = result["uid"]
|
self.request.session.update({
|
||||||
self.request.session["username"] = result["username"]
|
"uid": result["uid"],
|
||||||
self.request.session["logged_in"] = True
|
"username": result["username"],
|
||||||
|
"logged_in": True,
|
||||||
|
"color": result["color"]
|
||||||
|
})
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
@ -19,11 +19,10 @@
|
|||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
# CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||||
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
from snek.system.view import BaseView
|
from snek.system.view import BaseView
|
||||||
|
|
||||||
class StatusView(BaseView):
|
class StatusView(BaseView):
|
||||||
|
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