Compare commits
No commits in common. "6e68408ddfd8be2376f7453deac8f63a6bfb93e4" and "3960390ec45f427979ebd2b81c1a21666a47e71d" have entirely different histories.
6e68408ddf
...
3960390ec4
38
Dockerfile
38
Dockerfile
@ -1,10 +1,40 @@
|
|||||||
FROM python:3.14.0a6-bookworm
|
FROM surnet/alpine-wkhtmltopdf:3.21.2-0.12.6-full as wkhtmltopdf
|
||||||
RUN mkdir -p /code
|
FROM python:3.12.8-alpine3.21
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
RUN apt update && apt install build-essential docker -y
|
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 \
|
||||||
|
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,17 +2,27 @@ 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,8 +115,6 @@ 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",
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
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,11 +4,6 @@
|
|||||||
}
|
}
|
||||||
</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'] %}
|
||||||
@ -21,7 +16,6 @@
|
|||||||
<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 +0,0 @@
|
|||||||
../static/
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
{% 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,10 +24,8 @@ 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.update({
|
self.request.session["uid"] = result["uid"]
|
||||||
"uid": result["uid"],
|
self.request.session["username"] = result["username"]
|
||||||
"username": result["username"],
|
self.request.session["logged_in"] = True
|
||||||
"logged_in": True,
|
self.request.session["color"] = result["color"]
|
||||||
"color": result["color"]
|
|
||||||
})
|
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
||||||
@ -14,20 +14,21 @@
|
|||||||
# 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 all
|
# The above copyright notice and this permission notice shall be included in
|
||||||
# copies or substantial portions of the Software.
|
# all 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 THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# SOFTWARE.
|
# THE 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
|
||||||
|
|
||||||
@ -35,10 +36,8 @@ 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.update({
|
self.request.session["uid"] = result["uid"]
|
||||||
"uid": result["uid"],
|
self.request.session["username"] = result["username"]
|
||||||
"username": result["username"],
|
self.request.session["logged_in"] = True
|
||||||
"logged_in": True,
|
|
||||||
"color": result["color"]
|
|
||||||
})
|
|
||||||
return {"redirect_url": "/web.html"}
|
return {"redirect_url": "/web.html"}
|
||||||
@ -19,10 +19,11 @@
|
|||||||
# 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):
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
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
108
terminal/.bashrc
@ -1,108 +0,0 @@
|
|||||||
# ~/.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
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# ~/.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
BIN
terminal/r
Binary file not shown.
Loading…
Reference in New Issue
Block a user