Add stegasoo info command and update docs for v4.1
- Enhanced `stegasoo info` with fastfetch-style output - Service status, URL, channel key, DCT support - System stats with --full (CPU, temp, uptime, disk) - Updated UNDER_THE_HOOD.md for v4.1 - Added v4.1 changes table (channel keys, docker, Pi wizard) - Updated architecture diagram - Added channel module to responsibilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,14 +9,14 @@
|
|||||||
## Features
|
## Features
|
||||||
- [ ] QR channel key sharing (needs UI thought - avoid crowding encode/decode pages)
|
- [ ] QR channel key sharing (needs UI thought - avoid crowding encode/decode pages)
|
||||||
- [ ] Role-based permissions: admin / mod / user
|
- [ ] Role-based permissions: admin / mod / user
|
||||||
- [ ] `stegasoo info` fastfetch-style command (version, service status, channel, CPU, temp, etc.)
|
- [x] `stegasoo info` fastfetch-style command (version, service status, channel, CPU, temp, etc.)
|
||||||
- [ ] Better capacity estimates / pre-flight check before encode fails
|
- [ ] Better capacity estimates / pre-flight check before encode fails
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
- [ ] Optional encryption for temp file storage (paranoid mode, config toggle)
|
- [ ] Optional encryption for temp file storage (paranoid mode, config toggle)
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
- [ ] Update UNDER_THE_HOOD.md
|
- [x] Update UNDER_THE_HOOD.md (v4.1 changes, channel keys)
|
||||||
- [ ] General docs refresh
|
- [ ] General docs refresh
|
||||||
|
|
||||||
## Ideas (maybe later)
|
## Ideas (maybe later)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A detailed breakdown of how Stegasoo's LSB and DCT steganography modes work under the hood.
|
A detailed breakdown of how Stegasoo's LSB and DCT steganography modes work under the hood.
|
||||||
|
|
||||||
**Version 4.0** - Updated for simplified authentication (no date dependency)
|
**Version 4.1** - Updated for channel keys and deployment isolation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ A detailed breakdown of how Stegasoo's LSB and DCT steganography modes work unde
|
|||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
│ STEGASOO ARCHITECTURE (v4.0) │
|
│ STEGASOO ARCHITECTURE (v4.1) │
|
||||||
├─────────────────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ INPUTS PROCESSING OUTPUT │
|
│ INPUTS PROCESSING OUTPUT │
|
||||||
@@ -30,8 +30,8 @@ A detailed breakdown of how Stegasoo's LSB and DCT steganography modes work unde
|
|||||||
│ │
|
│ │
|
||||||
│ Reference Photo ─┐ │
|
│ Reference Photo ─┐ │
|
||||||
│ Passphrase ──────┼──► Argon2id KDF ──► AES-256 Key │
|
│ Passphrase ──────┼──► Argon2id KDF ──► AES-256 Key │
|
||||||
│ PIN/RSA Key ─────┘ │ │
|
│ PIN/RSA Key ─────┤ │ │
|
||||||
│ ▼ │
|
│ Channel Key ─────┘ (v4.1) ▼ │
|
||||||
│ Message/File ────────────────────────► AES-256-GCM ──► Ciphertext │
|
│ Message/File ────────────────────────► AES-256-GCM ──► Ciphertext │
|
||||||
│ Encryption │ │
|
│ Encryption │ │
|
||||||
│ ▼ │
|
│ ▼ │
|
||||||
@@ -50,11 +50,24 @@ A detailed breakdown of how Stegasoo's LSB and DCT steganography modes work unde
|
|||||||
| Header size | 75 bytes | 65 bytes (no date field) |
|
| Header size | 75 bytes | 65 bytes (no date field) |
|
||||||
| Python support | 3.10+ | 3.10-3.12 only |
|
| Python support | 3.10+ | 3.10-3.12 only |
|
||||||
|
|
||||||
|
### v4.1 Changes
|
||||||
|
|
||||||
|
| Change | v4.0 | v4.1 |
|
||||||
|
|--------|------|------|
|
||||||
|
| Channel keys | None | 32-byte deployment isolation |
|
||||||
|
| Key derivation | passphrase + ref + pin | passphrase + ref + pin + channel |
|
||||||
|
| Web auth | Session-based | Session + admin/user roles |
|
||||||
|
| Raspberry Pi | Manual setup | First-boot wizard with gum |
|
||||||
|
| Docker | Basic | Production-ready compose |
|
||||||
|
|
||||||
|
**Channel Keys** provide deployment isolation - messages encoded on one Stegasoo instance cannot be decoded by another instance with a different channel key, even with the same passphrase/PIN/reference photo.
|
||||||
|
|
||||||
### Module Responsibilities
|
### Module Responsibilities
|
||||||
|
|
||||||
| Module | File | Purpose |
|
| Module | File | Purpose |
|
||||||
|--------|------|---------|
|
|--------|------|---------|
|
||||||
| **Crypto** | `crypto.py` | Key derivation (Argon2id), AES-256-GCM encryption/decryption |
|
| **Crypto** | `crypto.py` | Key derivation (Argon2id), AES-256-GCM encryption/decryption |
|
||||||
|
| **Channel** | `channel.py` | Channel key management, deployment isolation (v4.1) |
|
||||||
| **Steganography** | `steganography.py` | LSB pixel manipulation, capacity calculation |
|
| **Steganography** | `steganography.py` | LSB pixel manipulation, capacity calculation |
|
||||||
| **DCT Steganography** | `dct_steganography.py` | Frequency-domain embedding, jpegio integration |
|
| **DCT Steganography** | `dct_steganography.py` | Frequency-domain embedding, jpegio integration |
|
||||||
| **Compression** | `compression.py` | Optional LZ4 compression of payload |
|
| **Compression** | `compression.py` | Optional LZ4 compression of payload |
|
||||||
|
|||||||
@@ -586,11 +586,113 @@ def generate(ctx, words, pin_length, channel_key):
|
|||||||
|
|
||||||
|
|
||||||
@cli.command()
|
@cli.command()
|
||||||
|
@click.option("--full", is_flag=True, help="Show full system information (Pi stats)")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def info(ctx):
|
def info(ctx, full):
|
||||||
"""Show version and feature information."""
|
"""Show version, features, and system information."""
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
# Check for DCT support
|
||||||
|
try:
|
||||||
|
from .dct_steganography import HAS_SCIPY, HAS_JPEGIO
|
||||||
|
HAS_DCT = HAS_SCIPY and HAS_JPEGIO
|
||||||
|
except ImportError:
|
||||||
|
HAS_DCT = False
|
||||||
|
|
||||||
|
# Check service status
|
||||||
|
service_status = "unknown"
|
||||||
|
service_url = None
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["systemctl", "is-active", "stegasoo"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2,
|
||||||
|
)
|
||||||
|
service_status = result.stdout.strip()
|
||||||
|
if service_status == "active":
|
||||||
|
# Try to get URL from service environment
|
||||||
|
env_result = subprocess.run(
|
||||||
|
["systemctl", "show", "stegasoo", "--property=Environment"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2,
|
||||||
|
)
|
||||||
|
https_enabled = "HTTPS_ENABLED=true" in env_result.stdout
|
||||||
|
protocol = "https" if https_enabled else "http"
|
||||||
|
# Get IP
|
||||||
|
ip_result = subprocess.run(
|
||||||
|
["hostname", "-I"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=2,
|
||||||
|
)
|
||||||
|
ip = ip_result.stdout.strip().split()[0] if ip_result.stdout.strip() else "localhost"
|
||||||
|
service_url = f"{protocol}://{ip}"
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Check channel key
|
||||||
|
channel_fingerprint = None
|
||||||
|
channel_source = None
|
||||||
|
try:
|
||||||
|
from .channel import get_channel_key, get_channel_fingerprint, get_channel_status
|
||||||
|
key = get_channel_key()
|
||||||
|
if key:
|
||||||
|
channel_fingerprint = get_channel_fingerprint(key)
|
||||||
|
status = get_channel_status()
|
||||||
|
channel_source = status.get("source")
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# System info (Pi-specific)
|
||||||
|
cpu_freq = None
|
||||||
|
cpu_temp = None
|
||||||
|
disk_free = None
|
||||||
|
uptime = None
|
||||||
|
|
||||||
|
if full:
|
||||||
|
try:
|
||||||
|
# CPU frequency
|
||||||
|
with open("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq") as f:
|
||||||
|
cpu_freq = int(f.read().strip()) // 1000 # MHz
|
||||||
|
except (FileNotFoundError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# CPU temp
|
||||||
|
with open("/sys/class/thermal/thermal_zone0/temp") as f:
|
||||||
|
cpu_temp = int(f.read().strip()) / 1000 # Celsius
|
||||||
|
except (FileNotFoundError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Disk free
|
||||||
|
st = os.statvfs("/")
|
||||||
|
disk_free = (st.f_bavail * st.f_frsize) / (1024 ** 3) # GB
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Uptime
|
||||||
|
with open("/proc/uptime") as f:
|
||||||
|
uptime_secs = float(f.read().split()[0])
|
||||||
|
days = int(uptime_secs // 86400)
|
||||||
|
hours = int((uptime_secs % 86400) // 3600)
|
||||||
|
uptime = f"{days}d {hours}h" if days else f"{hours}h"
|
||||||
|
except (FileNotFoundError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
info_data = {
|
info_data = {
|
||||||
"version": __version__,
|
"version": __version__,
|
||||||
|
"service": service_status,
|
||||||
|
"url": service_url,
|
||||||
|
"dct_support": HAS_DCT,
|
||||||
|
"channel": {
|
||||||
|
"fingerprint": channel_fingerprint,
|
||||||
|
"source": channel_source,
|
||||||
|
} if channel_fingerprint else None,
|
||||||
"compression": {
|
"compression": {
|
||||||
"available": [algorithm_name(a) for a in get_available_algorithms()],
|
"available": [algorithm_name(a) for a in get_available_algorithms()],
|
||||||
"lz4_installed": HAS_LZ4,
|
"lz4_installed": HAS_LZ4,
|
||||||
@@ -599,20 +701,54 @@ def info(ctx):
|
|||||||
"max_message_bytes": MAX_MESSAGE_SIZE,
|
"max_message_bytes": MAX_MESSAGE_SIZE,
|
||||||
"max_file_payload_bytes": MAX_FILE_PAYLOAD_SIZE,
|
"max_file_payload_bytes": MAX_FILE_PAYLOAD_SIZE,
|
||||||
},
|
},
|
||||||
|
"system": {
|
||||||
|
"cpu_mhz": cpu_freq,
|
||||||
|
"temp_c": cpu_temp,
|
||||||
|
"disk_free_gb": round(disk_free, 1) if disk_free else None,
|
||||||
|
"uptime": uptime,
|
||||||
|
} if full else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.obj.get("json"):
|
if ctx.obj.get("json"):
|
||||||
click.echo(json.dumps(info_data, indent=2))
|
click.echo(json.dumps(info_data, indent=2))
|
||||||
else:
|
else:
|
||||||
click.echo(f"Stegasoo v{__version__}")
|
# Fastfetch-style output
|
||||||
click.echo("\nCompression algorithms:")
|
click.echo(f"\033[1mSTEGASOO\033[0m v{__version__}")
|
||||||
for algo in get_available_algorithms():
|
click.echo("─" * 36)
|
||||||
click.echo(f" • {algorithm_name(algo)}")
|
|
||||||
if not HAS_LZ4:
|
# Service status
|
||||||
click.echo(" (install 'lz4' for LZ4 support)")
|
if service_status == "active":
|
||||||
click.echo("\nLimits:")
|
click.echo(f" Service: \033[32m● running\033[0m")
|
||||||
click.echo(f" • Max message: {MAX_MESSAGE_SIZE:,} bytes")
|
if service_url:
|
||||||
click.echo(f" • Max file payload: {MAX_FILE_PAYLOAD_SIZE:,} bytes")
|
click.echo(f" URL: {service_url}")
|
||||||
|
elif service_status == "inactive":
|
||||||
|
click.echo(f" Service: \033[31m○ stopped\033[0m")
|
||||||
|
else:
|
||||||
|
click.echo(f" Service: \033[33m? {service_status}\033[0m")
|
||||||
|
|
||||||
|
# Channel
|
||||||
|
if channel_fingerprint:
|
||||||
|
masked = f"{channel_fingerprint[:4]}••••••••{channel_fingerprint[-4:]}"
|
||||||
|
click.echo(f" Channel: {masked}")
|
||||||
|
else:
|
||||||
|
click.echo(f" Channel: \033[33mpublic\033[0m")
|
||||||
|
|
||||||
|
# DCT
|
||||||
|
dct_status = "\033[32m✓ enabled\033[0m" if HAS_DCT else "\033[31m✗ disabled\033[0m"
|
||||||
|
click.echo(f" DCT: {dct_status}")
|
||||||
|
|
||||||
|
# System info (if --full)
|
||||||
|
if full and any([cpu_freq, cpu_temp, disk_free, uptime]):
|
||||||
|
click.echo("─" * 36)
|
||||||
|
if cpu_freq:
|
||||||
|
click.echo(f" CPU: {cpu_freq} MHz")
|
||||||
|
if cpu_temp:
|
||||||
|
temp_color = "\033[32m" if cpu_temp < 60 else "\033[33m" if cpu_temp < 75 else "\033[31m"
|
||||||
|
click.echo(f" Temp: {temp_color}{cpu_temp:.1f}°C\033[0m")
|
||||||
|
if uptime:
|
||||||
|
click.echo(f" Uptime: {uptime}")
|
||||||
|
if disk_free:
|
||||||
|
click.echo(f" Disk: {disk_free:.1f} GB free")
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user