Update Debian packages for embedded templates

- Remove template copying from wsssh-server package since templates are now embedded in wssshd2 binary
- Fix erroneous wssshd.py installation in wssshtools package
- Update both package changelogs to version 1.7.0-1
parent 90cc0568
wsssh-server (1.7.0-1) unstable; urgency=medium
* Version 1.7.0: Templates embedded in server binary
* Removed template copying from Debian package installation
* Templates now embedded in wssshd2 binary, eliminating external dependencies
* Updated package to reflect embedded web interface assets
-- Stefy Lanza <stefy@nexlab.net> Sun, 21 Sep 2025 17:42:00 +0200
wsssh-server (1.6.5-1) unstable; urgency=medium
* Version 1.6.5: Transition to C binary with SQLite database
* Complete rewrite from Python/PyInstaller to native C implementation
* Added SQLite database for persistent user management
* Implemented embedded web interface with user authentication
* Added comprehensive user management (add/edit/delete users)
* Security warning system for default admin credentials
* ASCII art banner on startup
* Improved performance with native C binary (no Python dependencies)
* Updated build system to use GCC instead of PyInstaller
* Added libsqlite3 and libssl runtime dependencies
* Updated man page with C version command line options
* Maintained all existing init scripts, logrotate, and service configurations
* Preserved backward compatibility for service management
* Enhanced security with proper session management
* Added debug options for database operations
-- Stefy Lanza <stefy@nexlab.net> Wed, 18 Sep 2025 08:47:00 +0200
wsssh-server (1.6.1-1) unstable; urgency=medium wsssh-server (1.6.1-1) unstable; urgency=medium
* Version 1.6.1: Major code refactoring and documentation updates * Version 1.6.1: Major code refactoring and documentation updates
......
wsssh-server (1.7.0-1) unstable; urgency=medium
* Version 1.7.0: Templates embedded in server binary
* Removed template copying from Debian package installation
* Templates now embedded in wssshd2 binary, eliminating external dependencies
* Updated package to reflect embedded web interface assets
-- Stefy Lanza <stefy@nexlab.net> Sun, 21 Sep 2025 17:42:00 +0200
wsssh-server (1.6.5-1) unstable; urgency=medium wsssh-server (1.6.5-1) unstable; urgency=medium
* Version 1.6.5: Transition to C binary with SQLite database * Version 1.6.5: Transition to C binary with SQLite database
......
...@@ -30,7 +30,6 @@ override_dh_auto_install: ...@@ -30,7 +30,6 @@ override_dh_auto_install:
mkdir -p debian/wsssh-server/etc/default/ mkdir -p debian/wsssh-server/etc/default/
mkdir -p debian/wsssh-server/usr/share/wsssh/ mkdir -p debian/wsssh-server/usr/share/wsssh/
mkdir -p debian/wsssh-server/usr/share/man/man1/ mkdir -p debian/wsssh-server/usr/share/man/man1/
mkdir -p debian/wsssh-server/usr/share/wsssh/templates
mkdir -p debian/wsssh-server/usr/share/wsssh/logos mkdir -p debian/wsssh-server/usr/share/wsssh/logos
mkdir -p debian/wsssh-server/var/lib/wssshd mkdir -p debian/wsssh-server/var/lib/wssshd
mkdir -p debian/wsssh-server/var/log/wssshd mkdir -p debian/wsssh-server/var/log/wssshd
...@@ -61,8 +60,7 @@ override_dh_auto_install: ...@@ -61,8 +60,7 @@ override_dh_auto_install:
mkdir -p debian/wsssh-server/lib/systemd/system mkdir -p debian/wsssh-server/lib/systemd/system
install -m 644 debian/wssshd.service debian/wsssh-server/lib/systemd/system/ install -m 644 debian/wssshd.service debian/wsssh-server/lib/systemd/system/
# Install web templates and static files # Install static files
cp -r ../templates/* debian/wsssh-server/usr/share/wsssh/templates/
cp -r ../logos/* debian/wsssh-server/usr/share/wsssh/logos/ cp -r ../logos/* debian/wsssh-server/usr/share/wsssh/logos/
override_dh_auto_clean: override_dh_auto_clean:
......
# Makefile for wssshd2 - Generated by configure.sh
# Do not edit manually, run ./configure.sh instead
CC = gcc
CFLAGS = -Wall -Wextra -O2 -I. -pthread
LDFLAGS = -lssl -lcrypto -lm -luuid -lsqlite3
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
CONFIGDIR = /etc
# Source files
SRCS = main.c config.c tunnel.c terminal.c websocket.c websocket_protocol.c web.c assets.c ssl.c
OBJS = $(SRCS:.c=.o)
# Target
TARGET = wssshd
.PHONY: all clean install uninstall
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Dependencies
main.o: main.c config.h websocket.h web.h
config.o: config.c config.h
tunnel.o: tunnel.c tunnel.h websocket.h
terminal.o: terminal.c terminal.h config.h
websocket.o: websocket.c websocket.h websocket_protocol.h config.h
websocket_protocol.o: websocket_protocol.c websocket_protocol.h
web.o: web.c web.h terminal.h assets.h websocket.h html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/users_page.h
assets.o: assets.c assets.h
ssl.o: ssl.c ssl.h
# Asset embedding (run before compilation)
assets.o: image_data.h
image_data.h: embed_assets.sh
./embed_assets.sh
clean:
rm -f $(OBJS) $(TARGET) image_data.h
install: $(TARGET)
install -d $(DESTDIR)$(BINDIR)
install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/
install -d $(DESTDIR)$(CONFIGDIR)
[ -f $(DESTDIR)$(CONFIGDIR)/wssshd.conf ] || install -m 644 wssshd.conf.example $(DESTDIR)$(CONFIGDIR)/wssshd.conf
install -d $(DESTDIR)$(MANDIR)/man8
install -m 644 wssshd.8 $(DESTDIR)$(MANDIR)/man8/
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(TARGET)
rm -f $(DESTDIR)$(MANDIR)/man8/wssshd.8
# Development targets
debug: CFLAGS += -g -DDEBUG
debug: clean all
test: $(TARGET)
@echo "Running basic functionality test..."
./$(TARGET) --help || true
distclean: clean
rm -f Makefile
# Help target
help:
@echo "Available targets:"
@echo " all - Build wssshd2 (default)"
@echo " clean - Remove build artifacts"
@echo " install - Install wssshd2 to system"
@echo " uninstall - Remove wssshd2 from system"
@echo " debug - Build with debug symbols"
@echo " test - Run basic tests"
@echo " distclean - Remove all generated files"
@echo " help - Show this help"
This source diff could not be displayed because it is too large. You can view the blob instead.
File added
File added
<!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="/image.jpg" 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://unpkg.com/xterm@5.3.0/css/xterm.css">
<script src="https://unpkg.com/xterm@5.3.0/lib/xterm.js"></script>
<script src="https://unpkg.com/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></script>
<script>
// Ensure libraries are loaded
function checkLibraries() {
if (typeof Terminal === 'undefined') {
console.error('Terminal not loaded from CDN');
return false;
}
if (typeof FitAddon === 'undefined') {
console.error('FitAddon not loaded from CDN');
return false;
}
console.log('All xterm libraries loaded successfully');
return true;
}
// Check immediately and after a delay
if (!checkLibraries()) {
setTimeout(checkLibraries, 1000);
}
</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>
<button id="fullscreenBtn" class="btn btn-secondary btn-sm" title="Toggle Fullscreen">
<i class="fas fa-expand"></i>
</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>
console.log('Terminal script starting...');
console.log('xterm available:', typeof Terminal);
console.log('xterm-fit available:', typeof FitAddon);
let term = null;
let fitAddon = null;
let connected = false;
let requestId = null;
let pollInterval = null;
console.log('Terminal page loaded, adding event listeners');
document.getElementById('connectBtn').addEventListener('click', connect);
document.getElementById('disconnectBtn').addEventListener('click', disconnect);
document.getElementById('fullscreenBtn').addEventListener('click', toggleFullscreen);
console.log('Event listeners added');
// Fullscreen functionality
function toggleFullscreen() {
const terminalContainer = document.getElementById('terminal');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const icon = fullscreenBtn.querySelector('i');
if (!document.fullscreenElement) {
// Enter fullscreen
if (terminalContainer.requestFullscreen) {
terminalContainer.requestFullscreen();
} else if (terminalContainer.webkitRequestFullscreen) { // Safari
terminalContainer.webkitRequestFullscreen();
} else if (terminalContainer.msRequestFullscreen) { // IE11
terminalContainer.msRequestFullscreen();
}
} else {
// Exit fullscreen
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) { // Safari
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { // IE11
document.msExitFullscreen();
}
}
}
// Update fullscreen button icon based on state
function updateFullscreenButton() {
const fullscreenBtn = document.getElementById('fullscreenBtn');
const icon = fullscreenBtn.querySelector('i');
if (document.fullscreenElement) {
icon.className = 'fas fa-compress';
fullscreenBtn.title = 'Exit Fullscreen';
} else {
icon.className = 'fas fa-expand';
fullscreenBtn.title = 'Enter Fullscreen';
}
}
// Listen for fullscreen changes
function handleFullscreenChange() {
updateFullscreenButton();
// Resize terminal after fullscreen change
setTimeout(() => {
if (window.fitTerminal) {
window.fitTerminal();
}
// Update backend terminal size if connected
if (connected && requestId && fitAddon) {
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 during fullscreen change:', error);
});
}
}, 100); // Small delay to ensure DOM is updated
}
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // Safari
document.addEventListener('msfullscreenchange', handleFullscreenChange); // IE11
function connect() {
console.log('Connect function called');
const username = document.getElementById('sshUsername').value;
console.log('Username value:', username);
if (!username) {
alert('Please enter a username');
return;
}
console.log('Username validation passed');
// 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
try {
if (typeof FitAddon !== 'undefined') {
fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
console.log('FitAddon loaded successfully');
} else {
console.error('FitAddon is not available');
throw new Error('FitAddon not loaded');
}
} catch (e) {
console.error('Failed to load FitAddon:', e);
term.write('Warning: Terminal auto-resizing not available\r\n');
// Continue without fit addon - terminal will still work
}
// Initial fit after a short delay to ensure DOM is ready
setTimeout(() => {
window.fitTerminal();
// Calculate dimensions after initial fit
let initialDimensions = { cols: 80, rows: 24 };
if (fitAddon) {
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', () => {
window.fitTerminal();
// Update terminal size on backend if connected
if (connected && requestId && fitAddon) {
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();
}
// Define fitTerminal function globally for fullscreen handling
window.fitTerminal = function() {
if (fitAddon) {
fitAddon.fit();
}
};
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)
let cols = term._initialCols || 80;
let rows = term._initialRows || 24;
if (fitAddon) {
const dimensions = fitAddon.proposeDimensions();
cols = dimensions.cols || cols;
rows = dimensions.rows || rows;
}
// Send connect request with terminal dimensions
const connectUrl = '/terminal/{{ client_id }}/connect';
console.log('Sending connect request to:', connectUrl);
console.log('Username:', username, 'Cols:', cols, 'Rows:', rows);
fetch(connectUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'username=' + encodeURIComponent(username) +
'&cols=' + encodeURIComponent(cols) +
'&rows=' + encodeURIComponent(rows),
// Add timeout and credentials
credentials: 'same-origin'
})
.then(response => {
console.log('Connect response status:', response.status, 'OK:', response.ok);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
})
.then(data => {
console.log('Connect response data:', 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 || 'Unknown error') + '\r\n');
disconnect();
}
})
.catch(error => {
console.error('Connection failed:', error);
term.write('Connection failed: ' + error.message + '\r\n');
disconnect();
});
// Handle input - send all keystrokes to server, let SSH handle echo
term.onData(data => {
if (!connected || !requestId) return;
console.log('Sending input data:', data.length, 'characters');
// 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)
}).then(response => {
if (response.status !== 200) {
console.log('Input send response status:', response.status);
}
}).catch(error => {
console.error('Input send error:', error);
});
});
}
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 => {
if (response.status !== 200) {
console.log('Poll response status:', response.status);
}
return response.text();
})
.then(data => {
if (data) {
console.log('Received data:', data.length, 'characters');
// 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
File added
File added
wsssh-tools (1.7.0-1) unstable; urgency=medium
* Version 1.7.0: Version synchronization with server component
* No functional changes - client tools remain compatible with embedded server
-- Stefy Lanza <stefy@nexlab.net> Sun, 21 Sep 2025 17:42:00 +0200
wsssh-tools (1.6.5-1) unstable; urgency=medium wsssh-tools (1.6.5-1) unstable; urgency=medium
* Version 1.6.5: Flexible data encoding support for wsssht * Version 1.6.5: Flexible data encoding support for wsssht
......
...@@ -63,9 +63,6 @@ override_dh_auto_install: ...@@ -63,9 +63,6 @@ override_dh_auto_install:
install -m 644 debian/wssshc.service debian/wsssh-tools/lib/systemd/system/ install -m 644 debian/wssshc.service debian/wsssh-tools/lib/systemd/system/
install -m 644 debian/wsssht.service debian/wsssh-tools/lib/systemd/system/ install -m 644 debian/wsssht.service debian/wsssh-tools/lib/systemd/system/
# Install Python scripts
install -m 755 ../wssshd.py debian/wsssh-tools/usr/bin/
override_dh_auto_clean: override_dh_auto_clean:
make clean make clean
rm -f configure.sh.stamp rm -f configure.sh.stamp
\ No newline at end of file
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