Remove artifacts files

parent 17a93753
wsssh-server_1.4.4-1_amd64.buildinfo net optional
wsssh-server_1.4.4-1_amd64.deb net optional
# Automatically added by dh_installsystemd/13.26
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installdebconf/13.26
if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_purge
fi
# End automatically added section
misc:Depends=debconf (>= 0.5) | debconf-2.0
shlibs:Depends=libc6 (>= 2.14), zlib1g (>= 1:1.1.4)
misc:Pre-Depends=
/etc/default/wssshd
/etc/init.d/wssshd
/etc/logrotate.d/wssshd
Package: wsssh-server
Version: 1.4.4-1
Architecture: amd64
Maintainer: Stefy Lanza <stefy@nexlab.net>
Installed-Size: 26704
Depends: libc6 (>= 2.14), zlib1g (>= 1:1.1.4), debconf (>= 0.5) | debconf-2.0, adduser
Section: net
Priority: optional
Homepage: https://git.nexlab.net/nexlab/wsssh
Description: WebSocket SSH Server (wssshd)
A modern SSH tunneling system that provides WebSocket-based SSH/SCP access
to registered client machines. This package contains the server component
that handles WebSocket connections and manages SSH tunnels.
.
This package includes a standalone PyInstaller binary that bundles all
required dependencies, eliminating the need for external Python packages.
.
The wssshd server provides:
- WebSocket SSH tunnel management
- Client registration and authentication
- Web-based management interface
- Secure tunnel establishment between clients and servers
- High availability with watchdog monitoring
.
This is the server component of the WebSocket SSH system.
5b28c390af6ad414bbc6d868f644c127 lib/systemd/system/wssshd.service
d438ff148b99e19c3435b1fe36f24213 usr/bin/wssshd
0ca10988f88229f2118de364159866bd usr/sbin/wssshd-watchdog
81d71d8ec53e6a9ea3daad365eadfb55 usr/share/doc/wsssh-server/changelog.Debian.gz
929c14c9c965f9fca6385eb474c66835 usr/share/doc/wsssh-server/copyright
6771c7ff8f7526f370e92b5234b6611f usr/share/man/man1/wssshd.1.gz
5544f281baa6984b433fe312936524ad usr/share/wsssh/logos/banner-800x200.png
fbae8873c0f4be974ba0710a4431e55c usr/share/wsssh/logos/favicon.ico
52653de9c01c19cd8ebc223a75979736 usr/share/wsssh/logos/icon-128.png
8341d1c90883c14f96fc3c931bff98aa usr/share/wsssh/logos/icon-16.png
0127cf332e8910251c2f8ce1b37af725 usr/share/wsssh/logos/icon-256.png
0985ae2de553ed4628c8a2244922229e usr/share/wsssh/logos/icon-32.png
bcfca95aa4a79b07c1b609d8d91c01f8 usr/share/wsssh/logos/icon-48.png
a6867c9fabaa85db279580b874ba26fb usr/share/wsssh/logos/icon-64.png
52653de9c01c19cd8ebc223a75979736 usr/share/wsssh/logos/logo-128.png
0127cf332e8910251c2f8ce1b37af725 usr/share/wsssh/logos/logo-256.png
283f0c055eff7e299d06120d5913a406 usr/share/wsssh/logos/logo-512.png
a6867c9fabaa85db279580b874ba26fb usr/share/wsssh/logos/logo-64.png
bd58776dc9820c39a46fd51316461fe2 usr/share/wsssh/logos/logo-high-quality.png
07ebae287e8a4882deb22c0223aa6310 usr/share/wsssh/templates/base.html
b98475a970285d499246a8627535a4cf usr/share/wsssh/templates/index.html
dd0dcf5219c6ab94aba6ca0da3186e1e usr/share/wsssh/templates/login.html
f3c419734db44fa03bfaeac866e8287b usr/share/wsssh/templates/terminal.html
42130d3c1a8324daa7cacc004a512650 usr/share/wsssh/templates/users.html
d942cd2a28bf0beee87fe688be0ecc2d usr/share/wsssh/wssshd.conf.example
#!/bin/sh
# postinst script for wsssh-server
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <package-being-installed> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure)
# Create wssshd user and group if they don't exist
if ! getent group wssshd >/dev/null 2>&1; then
addgroup --system wssshd
fi
if ! getent passwd wssshd >/dev/null 2>&1; then
adduser --system --ingroup wssshd --home /var/lib/wssshd \
--no-create-home --shell /bin/false wssshd
fi
# Create wssshd home directory
if [ ! -d /var/lib/wssshd ]; then
mkdir -p /var/lib/wssshd
fi
# Ensure wssshd user owns its home directory and can write to it
chown wssshd:wssshd /var/lib/wssshd
chmod 755 /var/lib/wssshd
# Ensure the user can actually write to its home directory
# Try to create a test file to verify write permissions
if ! su -s /bin/sh wssshd -c "touch /var/lib/wssshd/.test_write 2>/dev/null && rm /var/lib/wssshd/.test_write 2>/dev/null" 2>/dev/null; then
echo "Warning: wssshd user cannot write to /var/lib/wssshd, fixing permissions"
# Try to fix permissions by making directory writable
chmod 775 /var/lib/wssshd
# Also ensure the user is in the right group
usermod -g wssshd wssshd 2>/dev/null || true
fi
# Create log directory
if [ ! -d /var/log/wssshd ]; then
mkdir -p /var/log/wssshd
chown wssshd:wssshd /var/log/wssshd
chmod 755 /var/log/wssshd
fi
# Create configuration directory
if [ ! -d /etc/wssshd ]; then
mkdir -p /etc/wssshd
chown wssshd:wssshd /etc/wssshd
chmod 755 /etc/wssshd
fi
# Create /etc/default/wssshd if it doesn't exist
if [ ! -f /etc/default/wssshd ]; then
cat > /etc/default/wssshd << EOF
# WebSocket SSH Server (wssshd) configuration
# Set to Y, 1, TRUE, true, YES, or yes to enable the service
START=no
# Additional configuration can be done in /etc/wssshd.conf
EOF
chmod 644 /etc/default/wssshd
fi
# Create example configuration file if it doesn't exist
if [ ! -f /etc/wssshd.conf.example ]; then
if [ -f /usr/share/wsssh/wssshd.conf.example ]; then
cp /usr/share/wsssh/wssshd.conf.example /etc/wssshd.conf.example
chmod 644 /etc/wssshd.conf.example
fi
fi
# Set up init script
if [ -x /etc/init.d/wssshd ]; then
update-rc.d wssshd defaults >/dev/null 2>&1 || true
fi
# Enable systemd service if available and sysv init is not active
if [ -f /lib/systemd/system/wssshd.service ] && command -v systemctl >/dev/null 2>&1; then
if ! systemctl is-active wssshd >/dev/null 2>&1 && [ ! -f /var/run/wssshd-watchdog.pid ]; then
systemctl enable wssshd.service >/dev/null 2>&1 || true
fi
fi
# Set proper permissions on binary
if [ -f /usr/bin/wssshd ]; then
chown wssshd:wssshd /usr/bin/wssshd
chmod 755 /usr/bin/wssshd
fi
# Install watchdog script
if [ -f /usr/sbin/wssshd-watchdog ]; then
chown wssshd:wssshd /usr/sbin/wssshd-watchdog
chmod 755 /usr/sbin/wssshd-watchdog
fi
# Create database directory if it doesn't exist
if [ ! -d /var/lib/wssshd/db ]; then
mkdir -p /var/lib/wssshd/db
chown wssshd:wssshd /var/lib/wssshd/db
chmod 755 /var/lib/wssshd/db
fi
# Restart service if it was running before upgrade
if [ -f /tmp/wsssh-server-upgrade-state ]; then
. /tmp/wsssh-server-upgrade-state
rm -f /tmp/wsssh-server-upgrade-state
if [ "$WSSSHD_WAS_RUNNING" = "1" ]; then
echo "Restarting wssshd service after upgrade..."
if [ -x /etc/init.d/wssshd ]; then
/etc/init.d/wssshd start || true
fi
fi
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
# The following line should be removed in trixie or trixie+1
deb-systemd-helper unmask 'wssshd.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled 'wssshd.service'; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper enable 'wssshd.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper update-state 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
exit 0
\ No newline at end of file
#!/bin/sh
# postrm script for wsssh-server
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
# Stop the service if it's running
if [ -x /etc/init.d/wssshd ]; then
invoke-rc.d wssshd stop >/dev/null 2>&1 || true
fi
# Remove init script symlinks
if [ -x /etc/init.d/wssshd ]; then
update-rc.d wssshd remove >/dev/null 2>&1 || true
fi
# Remove user and group on purge
if [ "$1" = "purge" ]; then
# Remove wssshd user and group
if getent passwd wssshd >/dev/null 2>&1; then
deluser wssshd || true
fi
if getent group wssshd >/dev/null 2>&1; then
delgroup wssshd || true
fi
# Remove configuration and data directories
rm -rf /var/lib/wssshd
rm -rf /var/log/wssshd
rm -rf /etc/wssshd
# Remove configuration files
rm -f /etc/default/wssshd
rm -f /etc/wssshd.conf
rm -f /etc/wssshd.conf.example
fi
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
# Automatically added by dh_installsystemd/13.26
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installdebconf/13.26
if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_purge
fi
# End automatically added section
exit 0
\ No newline at end of file
#!/bin/sh
# preinst script for wsssh-server
set -e
# summary of how this script can be called:
# * <new-preinst> `install'
# * <new-preinst> `install' <old-version>
# * <new-preinst> `upgrade' <old-version>
# * <old-preinst> `abort-upgrade' <new-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
WSSSHD_RUNNING=0
case "$1" in
install|upgrade)
# Check if wssshd service is running before upgrade
if [ -x /etc/init.d/wssshd ]; then
if /etc/init.d/wssshd status >/dev/null 2>&1; then
WSSSHD_RUNNING=1
# Check if we're in a non-interactive environment
if [ -n "$DEBIAN_FRONTEND" ] && [ "$DEBIAN_FRONTEND" = "noninteractive" ]; then
echo "Non-interactive environment detected, stopping wssshd service automatically..."
/etc/init.d/wssshd stop || true
sleep 2
else
# Use debconf to ask user for confirmation
. /usr/share/debconf/confmodule
db_input medium wsssh-server/stop-service || true
db_go || true
# Get the user's answer
db_get wsssh-server/stop-service
if [ "$RET" = "true" ] || [ -z "$RET" ]; then
echo "Stopping wssshd service for upgrade..."
/etc/init.d/wssshd stop || true
# Give it a moment to fully stop
sleep 2
else
echo "User declined to stop wssshd service. Upgrade may fail."
echo "You can manually stop the service with: sudo /etc/init.d/wssshd stop"
echo "Then retry the upgrade."
exit 1
fi
fi
fi
fi
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# Store the running state for postinst
if [ "$WSSSHD_RUNNING" = "1" ]; then
echo "WSSSHD_WAS_RUNNING=1" > /tmp/wsssh-server-upgrade-state
else
echo "WSSSHD_WAS_RUNNING=0" > /tmp/wsssh-server-upgrade-state
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
exit 0
\ No newline at end of file
#!/bin/sh
set -e
# Automatically added by dh_installsystemd/13.26
if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
deb-systemd-invoke stop 'wssshd.service' >/dev/null || true
fi
# End automatically added section
Template: wsssh-server/stop-service
Type: boolean
Default: true
Description: Stop wssshd service during upgrade?
The wssshd service is currently running. To upgrade the package safely,
the service needs to be stopped temporarily. It will be restarted
automatically after the upgrade is complete.
.
Should the wssshd service be stopped now?
.
This question will be skipped in non-interactive environments, and the
service will be stopped automatically for safe upgrades.
\ No newline at end of file
# WebSocket SSH Server (wssshd) configuration
# Set to Y, 1, TRUE, true, YES, or yes to enable the service
START=no
# Additional configuration can be done in /etc/wssshd.conf
\ No newline at end of file
#!/bin/bash
#
# wssshd Startup script for WebSocket SSH Daemon
#
# chkconfig: 345 85 15
# description: WebSocket SSH Daemon - Handles WebSocket connections from wsssh/wsscp clients
# processname: wssshd
# pidfile: /var/run/wssshd.pid
# config: /etc/wssshd.conf
### BEGIN INIT INFO
# Provides: wssshd
# Required-Start: $local_fs $network $syslog
# Required-Stop: $local_fs $network $syslog
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: WebSocket SSH Daemon
# Description: WebSocket SSH Daemon handles WebSocket connections from wsssh/wsscp clients
### END INIT INFO
# Source function library (Debian/Ubuntu)
. /lib/lsb/init-functions
# Configuration
NAME="wssshd"
DAEMON="/usr/bin/wssshd"
WATCHDOG="/usr/sbin/wssshd-watchdog"
PIDFILE="/var/run/wssshd.pid"
WATCHDOG_PIDFILE="/var/run/wssshd-watchdog.pid"
CONFIG="/etc/wssshd.conf"
LOG_FACILITY="daemon"
USER="wssshd"
GROUP="wssshd"
# Check if we're running as root
if [ $(id -u) != 0 ]; then
echo "Error: This script must be run as root"
exit 1
fi
# Function to check if process is running
is_running() {
if [ -f "$PIDFILE" ]; then
local pid=$(cat "$PIDFILE")
if [ -d "/proc/$pid" ]; then
return 0
else
# Stale PID file
rm -f "$PIDFILE"
return 1
fi
fi
return 1
}
# Function to start the daemon
start() {
echo -n "Starting $NAME: "
# Check if daemon is already running
if is_running; then
local pid=$(cat "$PIDFILE")
echo "already running (PID: $pid)"
return 0
fi
# Check if config file exists
if [ ! -f "$CONFIG" ]; then
echo "configuration file $CONFIG not found"
echo "Please create $CONFIG with the required settings."
echo "You can use the example configuration as a template:"
echo " cp /usr/share/wsssh/wssshd.conf.example $CONFIG"
return 1
fi
# Check if daemon executable exists
if [ ! -x "$DAEMON" ]; then
echo "daemon executable $DAEMON not found or not executable"
return 1
fi
# Create necessary directories
mkdir -p /var/run /var/log/wssshd
chown $USER:$GROUP /var/run /var/log/wssshd 2>/dev/null || true
# Start the daemon directly
echo "Starting daemon..."
if start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
--chuid $USER:$GROUP --background --make-pidfile \
--exec "$DAEMON" --output /var/log/wssshd/wssshd.log; then
echo "Daemon started successfully"
else
echo "start-stop-daemon failed, trying direct execution..."
if "$DAEMON" >> /var/log/wssshd/wssshd.log 2>&1 &
then
echo $! > "$PIDFILE"
echo "Daemon started successfully (direct execution)"
else
echo "FAILED"
echo "Could not start daemon"
return 1
fi
fi
# Wait for daemon to be fully running
local count=0
while [ $count -lt 10 ] && ! is_running; do
echo "Waiting for daemon to start... ($count/10)"
sleep 1
count=$((count + 1))
done
if is_running; then
local pid=$(cat "$PIDFILE")
echo "Daemon is running (PID: $pid)"
# Stop any existing watchdog before starting a new one
if [ -f "$WATCHDOG_PIDFILE" ] || pgrep -f "wssshd-watchdog" >/dev/null 2>&1; then
echo "Stopping existing watchdog..."
$WATCHDOG stop >/dev/null 2>&1
sleep 2
fi
# Now start the watchdog to monitor the running daemon
echo "Starting watchdog..."
if [ -x "$WATCHDOG" ]; then
$WATCHDOG start >/dev/null 2>&1
# Wait for watchdog PID file to be created (max 5 seconds)
local count=0
while [ $count -lt 10 ] && [ ! -f "$WATCHDOG_PIDFILE" ]; do
sleep 0.5
count=$((count + 1))
done
if [ -f "$WATCHDOG_PIDFILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PIDFILE" 2>/dev/null)
if [ -n "$watchdog_pid" ] && kill -0 "$watchdog_pid" 2>/dev/null; then
echo "OK"
return 0
else
echo "OK (daemon running, watchdog process not responding)"
return 0
fi
else
echo "OK (daemon running, watchdog PID file not found)"
return 0
fi
else
echo "OK (daemon running, no watchdog available)"
return 0
fi
else
echo "FAILED"
echo "Daemon failed to start properly"
return 1
fi
}
# Function to stop the daemon
stop() {
echo -n "Stopping $NAME: "
# First, try to stop the watchdog if it's running
if [ -f "$WATCHDOG_PIDFILE" ]; then
echo "Stopping watchdog..."
$WATCHDOG stop >/dev/null 2>&1
sleep 2
fi
# Check if daemon is running via PID file
if is_running; then
echo "Stopping daemon via PID file..."
local pid=$(cat "$PIDFILE")
# Try to stop gracefully first
kill -TERM "$pid" 2>/dev/null
sleep 2
# Check if it's still running
if kill -0 "$pid" 2>/dev/null; then
# Force kill if still running
kill -KILL "$pid" 2>/dev/null
sleep 1
fi
fi
# Also kill any wssshd processes that might be running (regardless of PID file)
echo "Ensuring all wssshd processes are stopped..."
pkill -TERM -f "^/usr/bin/wssshd" 2>/dev/null || true
sleep 2
pkill -KILL -f "^/usr/bin/wssshd" 2>/dev/null || true
# Clean up PID files
rm -f "$PIDFILE" "$WATCHDOG_PIDFILE"
# Final check
if is_running; then
echo "FAILED"
return 1
else
echo "OK"
return 0
fi
}
# Function to restart the daemon
restart() {
stop
sleep 2
start
}
# Function to check status
status() {
# Check watchdog status
if [ -f "$WATCHDOG_PIDFILE" ]; then
local watchdog_pid=$(cat "$WATCHDOG_PIDFILE")
if kill -0 "$watchdog_pid" 2>/dev/null; then
echo "Watchdog is running (PID: $watchdog_pid)"
else
echo "Watchdog PID file exists but process is not running"
rm -f "$WATCHDOG_PIDFILE"
fi
else
echo "Watchdog is not running"
fi
# Check daemon status
if is_running; then
local pid=$(cat "$PIDFILE")
echo "$NAME daemon is running (PID: $pid)"
return 0
else
echo "$NAME daemon is not running"
return 3
fi
}
# Function to reload configuration
reload() {
echo -n "Reloading $NAME configuration: "
if ! is_running; then
echo "not running"
return 1
fi
local pid=$(cat "$PIDFILE")
kill -HUP $pid 2>/dev/null
if [ $? -eq 0 ]; then
echo "OK"
return 0
else
echo "FAILED"
return 1
fi
}
# Main script logic
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
reload)
reload
;;
status)
status
;;
condrestart|try-restart)
if is_running; then
restart
fi
;;
*)
echo "Usage: $0 {start|stop|restart|reload|status|condrestart}"
exit 2
;;
esac
exit $?
\ No newline at end of file
/var/log/wssshd/wssshd.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 644 wssshd wssshd
postrotate
if [ -f /var/run/wssshd.pid ]; then
kill -HUP $(cat /var/run/wssshd.pid) 2>/dev/null || true
fi
endscript
}
/var/log/wssshd/watchdog.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 644 wssshd wssshd
postrotate
if [ -f /var/run/wssshd-watchdog.pid ]; then
kill -HUP $(cat /var/run/wssshd-watchdog.pid) 2>/dev/null || true
fi
endscript
}
\ No newline at end of file
[Unit]
Description=WebSocket SSH Daemon
After=network.target syslog.target
Requires=network.target
ConditionPathExists=!/etc/init.d/wssshd
ConditionPathExists=!/var/run/wssshd-watchdog.pid
[Service]
Type=forking
User=wssshd
Group=wssshd
EnvironmentFile=-/etc/default/wssshd
ExecStart=/usr/sbin/wssshd-watchdog start
ExecStop=/usr/sbin/wssshd-watchdog stop
ExecReload=/usr/sbin/wssshd-watchdog restart
PIDFile=/var/run/wssshd-watchdog.pid
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
\ No newline at end of file
#!/bin/bash
# WebSocket SSH Daemon Watchdog Script
# Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Configuration
DAEMON_NAME="wssshd"
DAEMON_PATH="/usr/bin/wssshd"
PID_FILE="/var/run/wssshd.pid"
WATCHDOG_PID_FILE="/var/run/wssshd-watchdog.pid"
LOG_FILE="/var/log/wssshd/watchdog.log"
CHECK_INTERVAL=30
MAX_RESTARTS=20
RESTART_WINDOW=60 # 1 minute
# Default configuration values (can be overridden by /etc/default/wssshd)
START=yes
DAEMON_ARGS=""
# Load configuration if available
if [ -f /etc/default/wssshd ]; then
. /etc/default/wssshd
echo "Loaded configuration from /etc/default/wssshd"
else
echo "No configuration file found at /etc/default/wssshd, using defaults"
fi
echo "START value after configuration loading: '$START'"
# Function to log messages
log_message() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $*" >> "$LOG_FILE"
logger -t "$DAEMON_NAME-watchdog" "$*"
}
# Function to check if daemon is running (double-check process)
is_daemon_running() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# First check if process exists
if kill -0 "$pid" 2>/dev/null; then
# Double-check: verify the process is actually our daemon
if ps -p "$pid" -o comm= 2>/dev/null | grep -q "^wssshd$"; then
return 0 # Running and correct process
else
log_message "PID file exists but process $pid is not wssshd"
rm -f "$PID_FILE"
fi
else
log_message "PID file exists but process $pid is not running"
rm -f "$PID_FILE"
fi
fi
return 1 # Not running
}
# Function to start daemon
start_daemon() {
log_message "Starting $DAEMON_NAME daemon..."
# Create necessary directories
mkdir -p /var/log/wssshd 2>/dev/null || log_message "Warning: Could not create /var/log/wssshd"
chown wssshd:wssshd /var/log/wssshd 2>/dev/null || log_message "Warning: Could not chown /var/log/wssshd"
# Check if daemon binary exists
if [ ! -x "$DAEMON_PATH" ]; then
log_message "Error: Daemon binary $DAEMON_PATH not found or not executable"
return 1
fi
# Try to start daemon as wssshd user, fallback to current user if that fails
log_message "Attempting to start daemon with start-stop-daemon..."
if [ -n "$DAEMON_ARGS" ]; then
start-stop-daemon --start --quiet --pidfile "$PID_FILE" \
--chuid wssshd:wssshd --background --make-pidfile \
--exec "$DAEMON_PATH" -- $DAEMON_ARGS 2>/dev/null
local result=$?
else
start-stop-daemon --start --quiet --pidfile "$PID_FILE" \
--chuid wssshd:wssshd --background --make-pidfile \
--exec "$DAEMON_PATH" 2>/dev/null
local result=$?
fi
# If start-stop-daemon failed, try running directly
if [ $result -ne 0 ]; then
log_message "start-stop-daemon failed (exit code: $result), trying direct execution..."
if [ -n "$DAEMON_ARGS" ]; then
"$DAEMON_PATH" $DAEMON_ARGS &
echo $! > "$PID_FILE"
else
"$DAEMON_PATH" &
echo $! > "$PID_FILE"
fi
result=$?
fi
if [ $result -eq 0 ]; then
log_message "$DAEMON_NAME started successfully"
return 0
else
log_message "Failed to start $DAEMON_NAME (exit code: $result)"
return 1
fi
}
# Function to check restart limits
check_restart_limits() {
local current_time=$(date +%s)
local restart_count=0
local window_start=$((current_time - RESTART_WINDOW))
# Count successful starts within the restart window
if [ -f "$LOG_FILE" ]; then
# Count starts in the last RESTART_WINDOW seconds
restart_count=$(awk -v window_start="$window_start" '
BEGIN { count = 0 }
{
# Extract timestamp from log line [YYYY-MM-DD HH:MM:SS]
if (match($0, /\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})\]/, arr)) {
timestamp = arr[1]
# Convert to epoch time
cmd = "date -d \"" timestamp "\" +%s 2>/dev/null"
cmd | getline epoch_time
close(cmd)
if (epoch_time >= window_start && $0 ~ /started successfully/) {
count++
}
}
}
END { print count }
' "$LOG_FILE" 2>/dev/null || echo "0")
fi
if [ "$restart_count" -ge "$MAX_RESTARTS" ]; then
log_message "Too many restarts ($restart_count) in $RESTART_WINDOW seconds. Watchdog will exit."
return 1
fi
return 0
}
# Function to stop daemon (double-check process)
stop_daemon() {
log_message "Stopping $DAEMON_NAME daemon..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# Double-check: verify the process is actually our daemon
if ps -p "$pid" -o comm= 2>/dev/null | grep -q "^wssshd$"; then
start-stop-daemon --stop --quiet --pidfile "$PID_FILE" --retry=TERM/30/KILL/5
local result=$?
rm -f "$PID_FILE"
return $result
else
log_message "PID file exists but process $pid is not wssshd"
rm -f "$PID_FILE"
return 1
fi
else
log_message "PID file not found, daemon may not be running"
return 1
fi
}
# Function to cleanup on exit
cleanup() {
log_message "Watchdog shutting down..."
if [ -f "$WATCHDOG_PID_FILE" ]; then
rm -f "$WATCHDOG_PID_FILE"
fi
exit 0
}
# Trap signals
trap cleanup SIGTERM SIGINT
# Main watchdog function (continuous monitoring)
main() {
# Debug: Log the START value
log_message "START configuration value: '$START'"
# Check if START is enabled (accept various forms: yes, YES, Y, 1, true, TRUE)
START_LOWER=$(echo "$START" | tr '[:upper:]' '[:lower:]')
log_message "START_LOWER: '$START_LOWER'"
if [ "$START_LOWER" != "yes" ] && [ "$START_LOWER" != "y" ] && [ "$START_LOWER" != "1" ] && [ "$START_LOWER" != "true" ]; then
log_message "START is not set to a valid enabled value in /etc/default/wssshd. Exiting."
exit 0
fi
log_message "START validation passed, proceeding with watchdog initialization"
log_message "Watchdog started for $DAEMON_NAME"
log_message "Check interval: $CHECK_INTERVAL seconds"
log_message "Max restarts: $MAX_RESTARTS per $RESTART_WINDOW seconds"
log_message "Entering monitoring loop"
local loop_count=0
while true; do
loop_count=$((loop_count + 1))
log_message "Monitoring loop iteration $loop_count"
if ! is_daemon_running; then
log_message "$DAEMON_NAME is not running"
# Check restart limits before attempting to start
if ! check_restart_limits; then
log_message "Restart limits exceeded, watchdog will exit"
break
fi
# Attempt to start daemon
if start_daemon; then
log_message "$DAEMON_NAME restarted successfully"
# Give daemon time to fully start before checking
sleep 3
else
log_message "Failed to restart $DAEMON_NAME"
# If daemon fails to start, exit watchdog
log_message "Watchdog exiting due to daemon restart failure"
break
fi
else
log_message "$DAEMON_NAME is running, monitoring continues"
fi
log_message "Sleeping for $CHECK_INTERVAL seconds"
sleep "$CHECK_INTERVAL"
done
log_message "Watchdog exiting"
cleanup
}
# Handle command line arguments
case "$1" in
start)
echo "Starting watchdog..."
if [ -f "$WATCHDOG_PID_FILE" ]; then
echo "Watchdog is already running"
exit 1
fi
echo "Calling main() function..."
# Run main() function in background and capture its PID
main &
MAIN_PID=$!
# Create PID file with the background process PID
echo $MAIN_PID > "$WATCHDOG_PID_FILE"
# Give it a moment to start
sleep 1
# Check if the process is still running
if kill -0 $MAIN_PID 2>/dev/null; then
echo "Watchdog started successfully (PID: $MAIN_PID)"
exit 0 # Exit immediately to avoid killing background main process
else
echo "Watchdog failed to start"
rm -f "$WATCHDOG_PID_FILE"
exit 1
fi
;;
stop)
# First, try to stop the process from PID file
if [ -f "$WATCHDOG_PID_FILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PID_FILE")
# Double-check: verify the process is actually our watchdog
if ps -p "$watchdog_pid" -o comm= 2>/dev/null | grep -q "wssshd-watchdog"; then
kill "$watchdog_pid" 2>/dev/null
rm -f "$WATCHDOG_PID_FILE"
echo "Watchdog stopped (from PID file)"
else
echo "PID file exists but process is not wssshd-watchdog"
rm -f "$WATCHDOG_PID_FILE"
fi
fi
# Also kill any other watchdog processes that might be running
# Find all bash processes running our watchdog script
watchdog_processes=$(ps aux | grep "/usr/sbin/wssshd-watchdog start" | grep -v grep | awk '{print $2}')
if [ -n "$watchdog_processes" ]; then
echo "Found additional watchdog processes: $watchdog_processes"
for pid in $watchdog_processes; do
if [ "$pid" != "$$" ]; then # Don't kill ourselves
kill "$pid" 2>/dev/null && echo "Killed watchdog process $pid"
fi
done
fi
# Clean up any remaining PID files
rm -f "$WATCHDOG_PID_FILE" 2>/dev/null
echo "Watchdog stop process completed"
;;
status)
if [ -f "$WATCHDOG_PID_FILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PID_FILE")
if kill -0 "$watchdog_pid" 2>/dev/null; then
# Double-check: verify the process is actually our watchdog
if ps -p "$watchdog_pid" -o comm= 2>/dev/null | grep -q "wssshd-watchdog"; then
echo "Watchdog is running (PID: $watchdog_pid)"
else
echo "Watchdog PID file exists but process is not wssshd-watchdog"
rm -f "$WATCHDOG_PID_FILE"
fi
else
echo "Watchdog PID file exists but process is not running"
rm -f "$WATCHDOG_PID_FILE"
fi
else
echo "Watchdog is not running"
fi
# Also check daemon status
if is_daemon_running; then
daemon_pid=$(cat "$PID_FILE")
echo "Daemon is running (PID: $daemon_pid)"
else
echo "Daemon is not running"
fi
;;
restart)
$0 stop
sleep 2
$0 start
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
;;
esac
exit 0
\ No newline at end of file
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: wsssh-server
Upstream-Contact: Stefy Lanza <stefy@nexlab.net>
Source: https://github.com/stefy/wsssh
Files: *
Copyright: 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
License: GPL-3.0+
Files: debian/*
Copyright: 2024 Stefy Lanza <stefy@nexlab.net>
License: GPL-3.0+
License: GPL-3.0+
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}WebSocket SSH Daemon{% endblock %}</title>
<link rel="icon" href="/logos/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
<style>
.navbar-brand {
font-weight: bold;
}
.client-card {
transition: transform 0.2s;
}
.client-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.terminal-container {
background-color: #1e1e1e;
color: #f8f8f2;
font-family: 'Courier New', monospace;
border-radius: 8px;
height: calc(100vh - 200px);
min-height: 400px;
overflow: hidden;
position: relative;
}
.terminal-input {
background: transparent;
border: none;
color: #f8f8f2;
font-family: 'Courier New', monospace;
width: 100%;
outline: none;
}
.terminal-input:focus {
box-shadow: none;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index') }}">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon
</a>
<div class="navbar-nav ms-auto">
{% if current_user.is_authenticated %}
<span class="navbar-text me-3">
Welcome, {{ current_user.username }}!
</span>
<button class="btn btn-outline-warning btn-sm me-2" data-bs-toggle="modal" data-bs-target="#donationModal">
<i class="fas fa-heart"></i> Donate
</button>
<a class="nav-link" href="{{ url_for('logout') }}">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<!-- Donation Modal -->
<div class="modal fade" id="donationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-heart text-danger"></i> Support WebSocket SSH Development
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted">Your support helps us continue developing and maintaining this open-source project!</p>
<div class="row">
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-paypal text-primary"></i> PayPal</h6>
<a href="https://www.paypal.com/paypalme/nexlab" target="_blank" class="btn btn-primary btn-sm">
<i class="fab fa-paypal"></i> Donate via PayPal
</a>
<small class="d-block text-muted mt-1">info@nexlab.net</small>
</div>
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-bitcoin text-warning"></i> Bitcoin</h6>
<div class="mb-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=bitcoin:bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx" alt="BTC QR Code" class="img-fluid rounded">
</div>
<div class="input-group input-group-sm">
<input type="text" class="form-control form-control-sm font-monospace" value="bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx" readonly style="font-size: 0.75rem;">
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="copyToClipboard('bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-ethereum text-secondary"></i> Ethereum</h6>
<div class="mb-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=ethereum:0xdA6dAb526515b5cb556d20269207D43fcc760E51" alt="ETH QR Code" class="img-fluid rounded">
</div>
<div class="input-group input-group-sm">
<input type="text" class="form-control form-control-sm font-monospace" value="0xdA6dAb526515b5cb556d20269207D43fcc760E51" readonly style="font-size: 0.75rem;">
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="copyToClipboard('0xdA6dAb526515b5cb556d20269207D43fcc760E51')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
<hr>
<p class="text-center mb-0">
<small class="text-muted">
Thank you for your support! ❤️
</small>
</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
// Show a temporary success message
const btn = event.target.closest('button');
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i>';
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalHtml;
btn.classList.remove('btn-success');
btn.classList.add('btn-outline-secondary');
}, 1000);
});
}
</script>
{% block scripts %}{% endblock %}
</body>
</html>
\ No newline at end of file
{% extends "base.html" %}
{% block title %}Dashboard - WebSocket SSH Daemon{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title mb-0">
<i class="fas fa-server"></i> Connected Clients
</h3>
</div>
<div class="card-body">
{% if clients %}
<div class="row">
{% for client in clients %}
<div class="col-md-4 mb-3">
<div class="card client-card h-100">
<div class="card-body text-center">
<i class="fas fa-desktop fa-3x text-success mb-3"></i>
<h5 class="card-title">{{ client }}</h5>
<p class="card-text text-muted">Connected</p>
<a href="{{ url_for('terminal', client_id=client) }}" class="btn btn-primary">
<i class="fas fa-terminal"></i> Connect
</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-server fa-4x text-muted mb-3"></i>
<h4 class="text-muted">No clients connected</h4>
<p class="text-muted">Clients will appear here when they connect to the daemon.</p>
</div>
{% endif %}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3 class="card-title mb-0">
<i class="fas fa-cogs"></i> Quick Actions
</h3>
</div>
<div class="card-body">
{% if current_user.is_admin %}
<a href="{{ url_for('users') }}" class="btn btn-outline-primary btn-sm mb-2 w-100">
<i class="fas fa-users"></i> Manage Users
</a>
{% endif %}
<button class="btn btn-outline-secondary btn-sm w-100" onclick="location.reload()">
<i class="fas fa-sync"></i> Refresh Status
</button>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h3 class="card-title mb-0">
<i class="fas fa-info-circle"></i> System Info
</h3>
</div>
<div class="card-body">
<p class="mb-1"><strong>WebSocket Port:</strong> <span id="websocket-port">{{ websocket_port or 'N/A' }}</span></p>
<p class="mb-1"><strong>Domain:</strong> <span id="domain">{{ domain or 'N/A' }}</span></p>
<p class="mb-0"><strong>Connected Clients:</strong> <span id="client-count">{{ clients|length }}</span></p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentClients = {{ clients|tojson }};
function updateClients() {
fetch('/api/clients')
.then(response => response.json())
.then(data => {
// Update client count
document.getElementById('client-count').textContent = data.count;
// Check if client list changed
if (JSON.stringify(data.clients.sort()) !== JSON.stringify(currentClients.sort())) {
// Reload the page to show updated client list
location.reload();
}
})
.catch(error => {
console.log('Error fetching client data:', error);
});
}
// Update every 5 seconds
setInterval(updateClients, 5000);
// Initial update after 1 second
setTimeout(updateClients, 1000);
</script>
{% endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% block title %}Login - WebSocket SSH Daemon{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h3 class="card-title mb-0"><i class="fas fa-sign-in-alt"></i> Login</h3>
</div>
<div class="card-body">
<form method="post">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-sign-in-alt"></i> Login
</button>
</form>
<div class="mt-3">
<small class="text-muted">
Default credentials: admin / admin123
</small>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% block title %}Terminal - {{ client_id }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center">
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary btn-sm me-3">
<i class="fas fa-arrow-left"></i> Back to Dashboard
</a>
<h3 class="card-title mb-0">
<i class="fas fa-terminal"></i> SSH Terminal - {{ client_id }}
</h3>
</div>
<div>
<input type="text" id="sshUsername" class="form-control form-control-sm d-inline-block w-auto me-2" placeholder="Username" value="root">
<button id="connectBtn" class="btn btn-success btn-sm">
<i class="fas fa-play"></i> Connect
</button>
<button id="disconnectBtn" class="btn btn-danger btn-sm" disabled>
<i class="fas fa-stop"></i> Disconnect
</button>
</div>
</div>
<div class="card-body p-2">
<div id="terminal" class="terminal-container w-100"></div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let term = null;
let connected = false;
let requestId = null;
let pollInterval = null;
document.getElementById('connectBtn').addEventListener('click', connect);
document.getElementById('disconnectBtn').addEventListener('click', disconnect);
function connect() {
const username = document.getElementById('sshUsername').value;
if (!username) {
alert('Please enter a username');
return;
}
// Initialize xterm with proper configuration
if (!term) {
term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
fontSize: 14,
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
theme: {
background: '#1e1e1e',
foreground: '#f8f8f2',
cursor: '#f8f8f2',
cursorAccent: '#1e1e1e',
selection: 'rgba(248, 248, 242, 0.3)'
},
allowTransparency: true,
scrollback: 1000,
tabStopWidth: 4,
convertEol: true,
disableStdin: false,
cursorWidth: 2,
bellStyle: 'none',
rightClickSelectsWord: true,
fastScrollModifier: 'alt',
fastScrollSensitivity: 5,
screenReaderMode: false,
macOptionIsMeta: false,
macOptionClickForcesSelection: false,
minimumContrastRatio: 1
});
term.open(document.getElementById('terminal'));
// Load fit addon
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
// Fit terminal to container
function fitTerminal() {
fitAddon.fit();
}
// Initial fit after a short delay to ensure DOM is ready
setTimeout(() => {
fitTerminal();
// Calculate dimensions after initial fit
const initialDimensions = fitAddon.proposeDimensions();
term._initialCols = initialDimensions.cols || 80;
term._initialRows = initialDimensions.rows || 24;
}, 100);
// Fit on window resize and update backend terminal size
window.addEventListener('resize', () => {
fitTerminal();
// Update terminal size on backend if connected
if (connected && requestId) {
const newDimensions = fitAddon.proposeDimensions();
const newCols = newDimensions.cols || 80;
const newRows = newDimensions.rows || 24;
fetch('/terminal/{{ client_id }}/resize', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'request_id=' + encodeURIComponent(requestId) +
'&cols=' + encodeURIComponent(newCols) +
'&rows=' + encodeURIComponent(newRows)
}).catch(error => {
console.error('Resize error:', error);
});
}
});
term.focus();
}
term.write('Connecting to ' + username + '@{{ client_id }}...\r\n');
connected = true;
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
document.getElementById('sshUsername').disabled = true;
// Use calculated dimensions (either from initial fit or current)
const cols = term._initialCols || fitAddon.proposeDimensions().cols || 80;
const rows = term._initialRows || fitAddon.proposeDimensions().rows || 24;
// Send connect request with terminal dimensions
fetch('/terminal/{{ client_id }}/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'username=' + encodeURIComponent(username) +
'&cols=' + encodeURIComponent(cols) +
'&rows=' + encodeURIComponent(rows)
})
.then(response => response.json())
.then(data => {
if (data.request_id) {
requestId = data.request_id;
if (data.command) {
term.write('Launching: ' + data.command + '\r\n');
}
term.write('Connected successfully!\r\n$ ');
// Start polling for data with shorter interval for better responsiveness
pollInterval = setInterval(pollData, 100);
} else {
term.write('Error: ' + data.error + '\r\n');
disconnect();
}
})
.catch(error => {
term.write('Connection failed: ' + error + '\r\n');
disconnect();
});
// Handle input - send all keystrokes to server, let SSH handle echo
term.onData(data => {
if (!connected || !requestId) return;
// Send all input to server, let SSH handle echo and display
fetch('/terminal/{{ client_id }}/data', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'request_id=' + encodeURIComponent(requestId) + '&data=' + encodeURIComponent(data)
});
});
}
function disconnect() {
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
document.getElementById('sshUsername').disabled = false;
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
if (requestId) {
fetch('/terminal/{{ client_id }}/disconnect', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'request_id=' + encodeURIComponent(requestId)
});
requestId = null;
}
if (term) {
term.write('\r\nDisconnected.\r\n');
}
}
function pollData() {
if (!requestId) return;
fetch('/terminal/{{ client_id }}/data?request_id=' + encodeURIComponent(requestId))
.then(response => response.text())
.then(data => {
if (data) {
// Let the server handle all echo and display logic
term.write(data.replace(/\n/g, '\r\n'));
}
})
.catch(error => {
console.error('Polling error:', error);
});
}
// Focus on terminal when connected
document.addEventListener('keydown', function(e) {
if (connected && term) {
term.focus();
}
});
</script>
{% endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% block title %}User Management - WebSocket SSH Daemon{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h3 class="card-title mb-0">
<i class="fas fa-users"></i> User Management
</h3>
<div>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary btn-sm me-2">
<i class="fas fa-home"></i> Back to Home
</a>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="fas fa-plus"></i> Add User
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Username</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>
{% if user.is_admin %}
<span class="badge bg-danger">Admin</span>
{% else %}
<span class="badge bg-secondary">User</span>
{% endif %}
</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="editUser({{ user.id }}, '{{ user.username }}', {{ user.is_admin|lower }})">
<i class="fas fa-edit"></i> Edit
</button>
{% if user.username != current_user.username %}
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser({{ user.id }}, '{{ user.username }}')">
<i class="fas fa-trash"></i> Delete
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Add User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add New User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="addUserForm">
<div class="modal-body">
<div class="mb-3">
<label for="addUsername" class="form-label">Username</label>
<input type="text" class="form-control" id="addUsername" name="username" required>
</div>
<div class="mb-3">
<label for="addPassword" class="form-label">Password</label>
<input type="password" class="form-control" id="addPassword" name="password" required>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="addIsAdmin" name="is_admin">
<label class="form-check-label" for="addIsAdmin">Administrator</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Add User</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit User Modal -->
<div class="modal fade" id="editUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editUserForm">
<input type="hidden" id="editUserId" name="user_id">
<div class="modal-body">
<div class="mb-3">
<label for="editUsername" class="form-label">Username</label>
<input type="text" class="form-control" id="editUsername" name="username" required>
</div>
<div class="mb-3">
<label for="editPassword" class="form-label">New Password (leave empty to keep current)</label>
<input type="password" class="form-control" id="editPassword" name="password">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="editIsAdmin" name="is_admin">
<label class="form-check-label" for="editIsAdmin">Administrator</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Update User</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function editUser(userId, username, isAdmin) {
document.getElementById('editUserId').value = userId;
document.getElementById('editUsername').value = username;
document.getElementById('editPassword').value = '';
document.getElementById('editIsAdmin').checked = isAdmin;
new bootstrap.Modal(document.getElementById('editUserModal')).show();
}
function deleteUser(userId, username) {
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
fetch(`/delete_user/${userId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error: ' + data.error);
}
});
}
}
document.getElementById('addUserForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('/add_user', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();
location.reload();
} else {
alert('Error: ' + data.error);
}
});
});
document.getElementById('editUserForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const userId = document.getElementById('editUserId').value;
fetch(`/edit_user/${userId}`, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();
location.reload();
} else {
alert('Error: ' + data.error);
}
});
});
</script>
{% endblock %}
\ No newline at end of file
[wssshd]
host = 0.0.0.0
port = 9898
password = mysecret
domain = example.com
web-host = 0.0.0.0
web-port = 8080
web-https = false
\ No newline at end of file
Format: 1.0
Source: wsssh-server
Binary: wsssh-server
Architecture: amd64 source
Version: 1.4.4-1
Checksums-Md5:
56d229ac0c75d4d3989fa31efba60bfc 730 wsssh-server_1.4.4-1.dsc
09acdffcb0f2ce1964960c5a00c191ea 26629888 wsssh-server_1.4.4-1_amd64.deb
Checksums-Sha1:
2572508a507c5c51e6771c757880856ee541572c 730 wsssh-server_1.4.4-1.dsc
cacc524558df8a861453333cab40abddc292e1fe 26629888 wsssh-server_1.4.4-1_amd64.deb
Checksums-Sha256:
03e9a4d1e4dd2388c7d6a4254247ea949d79ba1d364e93fd36f993b890ebeefa 730 wsssh-server_1.4.4-1.dsc
e9391f5160b8dd0e5b6cc89a00b0bd99d889467ba78fb9af72d931daf0401b44 26629888 wsssh-server_1.4.4-1_amd64.deb
Build-Origin: Devuan
Build-Architecture: amd64
Build-Date: Tue, 16 Sep 2025 18:34:04 +0200
Build-Tainted-By:
usr-local-has-configs
usr-local-has-libraries
usr-local-has-programs
Installed-Build-Depends:
autoconf (= 2.72-3.1),
automake (= 1:1.17-4),
autopoint (= 0.22.5-4),
autotools-dev (= 20220109.1),
base-files (= 13.6devuan1),
base-passwd (= 3.6.6),
bash (= 5.2.37-2+b5),
binutils (= 2.43.50.20250108-1),
binutils-common (= 2.43.50.20250108-1),
binutils-x86-64-linux-gnu (= 2.43.50.20250108-1),
bsdextrautils (= 2.40.2-14devuan1),
bsdutils (= 1:2.40.2-14devuan1),
build-essential (= 12.12),
bzip2 (= 1.0.8-6),
ca-certificates (= 20241223),
clang-13 (= 1:13.0.0-9+b2),
clang-16 (= 1:16.0.6-27),
clang-19 (= 1:19.1.7-3+b1),
coreutils (= 9.5-1+b1),
cpp (= 4:14.2.0-1),
cpp-10 (= 10.5.0-4),
cpp-11 (= 11.5.0-2),
cpp-13 (= 13.3.0-12),
cpp-13-x86-64-linux-gnu (= 13.3.0-12),
cpp-14 (= 14.2.0-12),
cpp-14-x86-64-linux-gnu (= 14.2.0-12),
cpp-x86-64-linux-gnu (= 4:14.2.0-1),
dash (= 0.5.12-11),
debconf (= 1.5.89),
debhelper (= 13.26),
debianutils (= 5.21),
dh-autoreconf (= 20),
dh-strip-nondeterminism (= 1.14.2-1),
diffutils (= 1:3.10-2),
dpkg (= 1.22.13),
dpkg-dev (= 1.22.13),
dwz (= 0.15-1+b1),
file (= 1:5.45-3+b1),
findutils (= 4.10.0-3),
g++ (= 4:14.2.0-1),
g++-14 (= 14.2.0-12),
g++-14-x86-64-linux-gnu (= 14.2.0-12),
g++-x86-64-linux-gnu (= 4:14.2.0-1),
gawk (= 1:5.2.1-2+b1),
gcc (= 4:14.2.0-1),
gcc-10 (= 10.5.0-4),
gcc-10-base (= 10.5.0-4),
gcc-11 (= 11.5.0-2),
gcc-11-base (= 11.5.0-2),
gcc-13 (= 13.3.0-12),
gcc-13-base (= 13.3.0-12),
gcc-13-x86-64-linux-gnu (= 13.3.0-12),
gcc-14 (= 14.2.0-12),
gcc-14-base (= 14.2.0-12),
gcc-14-x86-64-linux-gnu (= 14.2.0-12),
gcc-x86-64-linux-gnu (= 4:14.2.0-1),
gettext (= 0.22.5-4),
gettext-base (= 0.22.5-4),
grep (= 3.11-4),
groff-base (= 1.23.0-7),
gzip (= 1.12-1.2),
hostname (= 3.25),
init-system-helpers (= 1.68devuan1),
intltool-debian (= 0.35.0+20060710.6),
lib32gcc-s1 (= 14.2.0-12),
lib32stdc++6 (= 14.2.0-12),
libacl1 (= 2.3.2-2+b1),
libarchive-zip-perl (= 1.68-1),
libasan6 (= 11.5.0-2),
libasan8 (= 14.2.0-12),
libatomic1 (= 14.2.0-12),
libattr1 (= 1:2.5.2-2),
libaudit-common (= 1:4.0.2-2),
libaudit1 (= 1:4.0.2-2),
libbinutils (= 2.43.50.20250108-1),
libblkid1 (= 2.40.2-14devuan1),
libbsd0 (= 0.12.2-2),
libbz2-1.0 (= 1.0.8-6),
libc-bin (= 2.40-5),
libc-dev-bin (= 2.40-5),
libc6 (= 2.40-5),
libc6-dev (= 2.40-5),
libc6-i386 (= 2.40-5),
libcap-ng0 (= 0.8.5-4),
libcap2 (= 1:2.66-5+b1),
libcc1-0 (= 14.2.0-12),
libclang-common-13-dev (= 1:13.0.0-9+b2),
libclang-common-16-dev (= 1:16.0.6-27),
libclang-common-19-dev (= 1:19.1.7-3+b1),
libclang-cpp13 (= 1:13.0.0-9+b2),
libclang-cpp16t64 (= 1:16.0.6-27),
libclang-cpp19 (= 1:19.1.7-3+b1),
libclang1-13 (= 1:13.0.0-9+b2),
libclang1-16t64 (= 1:16.0.6-27),
libclang1-19 (= 1:19.1.7-3+b1),
libcrypt-dev (= 1:4.4.36-5),
libcrypt1 (= 1:4.4.36-5),
libctf-nobfd0 (= 2.43.50.20250108-1),
libctf0 (= 2.43.50.20250108-1),
libdb5.3t64 (= 5.3.28+dfsg2-9),
libdebconfclient0 (= 0.277),
libdebhelper-perl (= 13.26),
libdpkg-perl (= 1.22.13),
libedit2 (= 3.1-20240808-1),
libelf1t64 (= 0.192-4),
libelogind-compat (= 255.17-2),
libelogind0 (= 255.17-2),
libeudev1 (= 3.2.14-2),
libexpat1 (= 2.6.4-1),
libffi8 (= 3.4.6-1),
libfile-stripnondeterminism-perl (= 1.14.2-1),
libgc1 (= 1:8.2.8-1),
libgcc-10-dev (= 10.5.0-4),
libgcc-11-dev (= 11.5.0-2),
libgcc-13-dev (= 13.3.0-12),
libgcc-14-dev (= 14.2.0-12),
libgcc-s1 (= 14.2.0-12),
libgdbm-compat4t64 (= 1.24-2),
libgdbm6t64 (= 1.24-2),
libgmp10 (= 2:6.3.0+dfsg-3),
libgomp1 (= 14.2.0-12),
libgprofng0 (= 2.43.50.20250108-1),
libhwasan0 (= 14.2.0-12),
libicu72 (= 72.1-6),
libisl23 (= 0.27-1),
libitm1 (= 14.2.0-12),
libjansson4 (= 2.14-2+b3),
libllvm13 (= 1:13.0.0-9+b2),
libllvm16t64 (= 1:16.0.6-27),
libllvm19 (= 1:19.1.7-3+b1),
liblsan0 (= 14.2.0-12),
liblzma5 (= 5.6.3-1+b1),
libmagic-mgc (= 1:5.45-3+b1),
libmagic1t64 (= 1:5.45-3+b1),
libmd0 (= 1.1.0-2+b1),
libmount1 (= 2.40.2-14devuan1),
libmpc3 (= 1.3.1-1+b3),
libmpfr6 (= 4.2.1-1+b2),
libncursesw6 (= 6.5-2+b1),
libobjc-11-dev (= 11.5.0-2),
libobjc-13-dev (= 13.3.0-12),
libobjc-14-dev (= 14.2.0-12),
libobjc4 (= 14.2.0-12),
libpam-modules (= 1.5.3-7+b1),
libpam-modules-bin (= 1.5.3-7+b1),
libpam-runtime (= 1.5.3-7),
libpam0g (= 1.5.3-7+b1),
libpcre2-8-0 (= 10.44-5),
libperl5.40 (= 5.40.1-5),
libpipeline1 (= 1.5.8-1),
libpython3-stdlib (= 3.13.7-1),
libpython3.13-minimal (= 3.13.7-1),
libpython3.13-stdlib (= 3.13.7-1),
libquadmath0 (= 14.2.0-12),
libreadline8t64 (= 8.2-6),
libseccomp2 (= 2.5.5-2),
libselinux1 (= 3.7-3+b1),
libsframe1 (= 2.43.50.20250108-1),
libsigsegv2 (= 2.14-1+b2),
libsmartcols1 (= 2.40.2-14devuan1),
libsqlite3-0 (= 3.46.1-6),
libssl3t64 (= 3.4.0-2),
libstdc++-11-dev (= 11.5.0-2),
libstdc++-13-dev (= 13.3.0-12),
libstdc++-14-dev (= 14.2.0-12),
libstdc++6 (= 14.2.0-12),
libtinfo6 (= 6.5-2+b1),
libtool (= 2.5.4-2),
libtsan0 (= 11.5.0-2),
libtsan2 (= 14.2.0-12),
libubsan1 (= 14.2.0-12),
libuchardet0 (= 0.0.8-1+b2),
libunistring5 (= 1.3-1),
libuuid1 (= 2.40.2-14devuan1),
libxml2 (= 2.12.7+dfsg+really2.9.14-0.2+b1),
libz3-4 (= 4.13.3-1),
libzstd1 (= 1.5.6+dfsg-2),
linux-libc-dev (= 6.12.15-1),
llvm-16-linker-tools (= 1:16.0.6-27),
llvm-19-linker-tools (= 1:19.1.7-3+b1),
m4 (= 1.4.19-5),
make (= 4.4.1-1),
man-db (= 2.13.0-1),
mawk (= 1.3.4.20240905-1),
media-types (= 10.1.0),
ncurses-base (= 6.5-2),
ncurses-bin (= 6.5-2+b1),
netbase (= 6.4),
openssl (= 3.4.0-2),
openssl-provider-legacy (= 3.4.0-2),
patch (= 2.7.6-7),
perl (= 5.40.1-5),
perl-base (= 5.40.1-5),
perl-modules-5.40 (= 5.40.1-5),
po-debconf (= 1.0.21+nmu1),
python3 (= 3.13.7-1),
python3-autocommand (= 2.2.2-3),
python3-importlib-metadata (= 8.5.0-1),
python3-inflect (= 7.3.1-2),
python3-jaraco.context (= 6.0.0-1),
python3-jaraco.functools (= 4.1.0-1),
python3-jaraco.text (= 4.0.0-1),
python3-minimal (= 3.13.7-1),
python3-more-itertools (= 10.5.0-1),
python3-packaging (= 24.2-1),
python3-pip (= 25.1.1+dfsg-1),
python3-pkg-resources (= 78.1.1-0.1),
python3-setuptools (= 78.1.1-0.1),
python3-typeguard (= 4.4.1-1),
python3-typing-extensions (= 4.12.2-2),
python3-wheel (= 0.46.1-2),
python3-zipp (= 3.21.0-1),
python3.13 (= 3.13.7-1),
python3.13-minimal (= 3.13.7-1),
readline-common (= 8.2-6),
rpcsvc-proto (= 1.4.3-1),
sed (= 4.9-2),
sensible-utils (= 0.0.24),
sysvinit-utils (= 3.13-1devuan1),
tar (= 1.35+dfsg-3.1),
tzdata (= 2024b-5),
util-linux (= 2.40.2-14devuan1),
xz-utils (= 5.6.3-1+b1),
zlib1g (= 1:1.3.dfsg+really1.3.1-1+b1)
Environment:
DEB_BUILD_OPTIONS="parallel=8"
LANG="en_ZA.UTF-8"
SOURCE_DATE_EPOCH="1757827219"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment