telemux 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- telemux/__init__.py +24 -0
- telemux/cleanup.py +185 -0
- telemux/cli.py +82 -0
- telemux/config.py +77 -0
- telemux/control.py +302 -0
- telemux/installer.py +421 -0
- telemux/listener.py +345 -0
- telemux/shell_functions.sh +117 -0
- telemux-1.0.5.dist-info/METADATA +478 -0
- telemux-1.0.5.dist-info/RECORD +14 -0
- telemux-1.0.5.dist-info/WHEEL +5 -0
- telemux-1.0.5.dist-info/entry_points.txt +15 -0
- telemux-1.0.5.dist-info/licenses/LICENSE +21 -0
- telemux-1.0.5.dist-info/top_level.txt +1 -0
telemux/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TeleMux - Bidirectional Telegram integration for tmux sessions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.5"
|
|
6
|
+
__author__ = "Marco Almazan"
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
# Package-level constants
|
|
11
|
+
TELEMUX_DIR = Path.home() / ".telemux"
|
|
12
|
+
MESSAGE_QUEUE_DIR = TELEMUX_DIR / "message_queue"
|
|
13
|
+
CONFIG_FILE = TELEMUX_DIR / "telegram_config"
|
|
14
|
+
LOG_FILE = TELEMUX_DIR / "telegram_listener.log"
|
|
15
|
+
TMUX_SESSION = "telegram-listener"
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"__version__",
|
|
19
|
+
"TELEMUX_DIR",
|
|
20
|
+
"MESSAGE_QUEUE_DIR",
|
|
21
|
+
"CONFIG_FILE",
|
|
22
|
+
"LOG_FILE",
|
|
23
|
+
"TMUX_SESSION",
|
|
24
|
+
]
|
telemux/cleanup.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TeleMux Log Rotation and Cleanup
|
|
3
|
+
Automatically rotates large log files and archives old data
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import sys
|
|
7
|
+
import gzip
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from datetime import datetime, timedelta
|
|
12
|
+
|
|
13
|
+
from . import MESSAGE_QUEUE_DIR, LOG_FILE
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Configuration
|
|
17
|
+
MAX_SIZE_MB = 10
|
|
18
|
+
MAX_SIZE_BYTES = MAX_SIZE_MB * 1024 * 1024
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def log_info(message: str):
|
|
22
|
+
"""Print info message."""
|
|
23
|
+
print(f"✓ {message}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def log_warning(message: str):
|
|
27
|
+
"""Print warning message."""
|
|
28
|
+
print(f"⚠ {message}")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def rotate_log(log_file: Path):
|
|
32
|
+
"""Rotate a log file if it exceeds size limit."""
|
|
33
|
+
if not log_file.exists():
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
file_size = log_file.stat().st_size
|
|
37
|
+
|
|
38
|
+
if file_size > MAX_SIZE_BYTES:
|
|
39
|
+
# Create archive directory
|
|
40
|
+
archive_month = datetime.now().strftime("%Y-%m")
|
|
41
|
+
archive_dir = MESSAGE_QUEUE_DIR / "archive" / archive_month
|
|
42
|
+
archive_dir.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
|
|
44
|
+
# Create archive filename
|
|
45
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
46
|
+
archive_file = archive_dir / f"{log_file.name}.{timestamp}"
|
|
47
|
+
|
|
48
|
+
size_mb = file_size / (1024 * 1024)
|
|
49
|
+
log_warning(f"Rotating {log_file.name} ({size_mb:.2f}MB > {MAX_SIZE_MB}MB)")
|
|
50
|
+
|
|
51
|
+
# Move to archive
|
|
52
|
+
shutil.move(str(log_file), str(archive_file))
|
|
53
|
+
|
|
54
|
+
# Compress archive
|
|
55
|
+
with open(archive_file, 'rb') as f_in:
|
|
56
|
+
with gzip.open(str(archive_file) + '.gz', 'wb') as f_out:
|
|
57
|
+
shutil.copyfileobj(f_in, f_out)
|
|
58
|
+
|
|
59
|
+
# Remove uncompressed archive
|
|
60
|
+
archive_file.unlink()
|
|
61
|
+
|
|
62
|
+
# Create new empty log file
|
|
63
|
+
log_file.touch()
|
|
64
|
+
|
|
65
|
+
log_info(f"Archived to {archive_file}.gz")
|
|
66
|
+
else:
|
|
67
|
+
size_mb = file_size / (1024 * 1024)
|
|
68
|
+
log_info(f"{log_file.name} is {size_mb:.2f}MB (under {MAX_SIZE_MB}MB limit)")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cleanup_old_archives():
|
|
72
|
+
"""Remove archives older than 6 months."""
|
|
73
|
+
archive_base = MESSAGE_QUEUE_DIR / "archive"
|
|
74
|
+
if not archive_base.exists():
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Calculate cutoff date (6 months ago)
|
|
78
|
+
cutoff_date = datetime.now() - timedelta(days=180)
|
|
79
|
+
cutoff_str = cutoff_date.strftime("%Y-%m")
|
|
80
|
+
|
|
81
|
+
# Find and remove old archives
|
|
82
|
+
removed_count = 0
|
|
83
|
+
for archive_dir in archive_base.iterdir():
|
|
84
|
+
if archive_dir.is_dir() and archive_dir.name < cutoff_str:
|
|
85
|
+
log_warning(f"Removing {archive_dir.name}")
|
|
86
|
+
shutil.rmtree(archive_dir)
|
|
87
|
+
removed_count += 1
|
|
88
|
+
|
|
89
|
+
if removed_count > 0:
|
|
90
|
+
print("")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def install_cron():
|
|
94
|
+
"""Install cron job for monthly log rotation."""
|
|
95
|
+
print("")
|
|
96
|
+
print("Installing cron job for monthly log rotation...")
|
|
97
|
+
|
|
98
|
+
cron_cmd = "0 0 1 * * python3 -m telemux.cleanup"
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Get current crontab
|
|
102
|
+
result = subprocess.run(
|
|
103
|
+
['crontab', '-l'],
|
|
104
|
+
capture_output=True,
|
|
105
|
+
text=True,
|
|
106
|
+
check=False
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Filter out existing cleanup entries
|
|
110
|
+
existing_lines = []
|
|
111
|
+
if result.returncode == 0:
|
|
112
|
+
existing_lines = [
|
|
113
|
+
line for line in result.stdout.splitlines()
|
|
114
|
+
if 'telemux' not in line.lower() and 'cleanup' not in line.lower()
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
# Add new cron job
|
|
118
|
+
new_crontab = '\n'.join(existing_lines + [cron_cmd]) + '\n'
|
|
119
|
+
|
|
120
|
+
# Install new crontab
|
|
121
|
+
subprocess.run(
|
|
122
|
+
['crontab', '-'],
|
|
123
|
+
input=new_crontab,
|
|
124
|
+
text=True,
|
|
125
|
+
check=True
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
log_info("Cron job installed (runs 1st of each month at midnight)")
|
|
129
|
+
print("To remove: crontab -e")
|
|
130
|
+
|
|
131
|
+
except subprocess.CalledProcessError as e:
|
|
132
|
+
print(f"Failed to install cron job: {e}")
|
|
133
|
+
except FileNotFoundError:
|
|
134
|
+
print("crontab command not found (cron may not be available)")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def main():
|
|
138
|
+
"""Main cleanup entry point."""
|
|
139
|
+
print("TeleMux Log Rotation")
|
|
140
|
+
print("=" * 60)
|
|
141
|
+
print("")
|
|
142
|
+
|
|
143
|
+
# Log files to rotate
|
|
144
|
+
outgoing_log = MESSAGE_QUEUE_DIR / "outgoing.log"
|
|
145
|
+
incoming_log = MESSAGE_QUEUE_DIR / "incoming.log"
|
|
146
|
+
|
|
147
|
+
# Rotate logs if they exceed size limit
|
|
148
|
+
rotate_log(outgoing_log)
|
|
149
|
+
rotate_log(incoming_log)
|
|
150
|
+
rotate_log(LOG_FILE)
|
|
151
|
+
|
|
152
|
+
print("")
|
|
153
|
+
|
|
154
|
+
# Clean up old archives
|
|
155
|
+
cleanup_old_archives()
|
|
156
|
+
|
|
157
|
+
# Summary
|
|
158
|
+
print("Summary")
|
|
159
|
+
print("-" * 60)
|
|
160
|
+
|
|
161
|
+
archive_dir = MESSAGE_QUEUE_DIR / "archive"
|
|
162
|
+
if archive_dir.exists():
|
|
163
|
+
# Count archived files
|
|
164
|
+
archive_count = sum(1 for _ in archive_dir.rglob("*.gz"))
|
|
165
|
+
|
|
166
|
+
# Calculate total size
|
|
167
|
+
total_size = sum(f.stat().st_size for f in archive_dir.rglob("*.gz"))
|
|
168
|
+
size_mb = total_size / (1024 * 1024)
|
|
169
|
+
|
|
170
|
+
print(f"Archive directory: {archive_dir}")
|
|
171
|
+
print(f"Archived files: {archive_count}")
|
|
172
|
+
print(f"Total archive size: {size_mb:.2f} MB")
|
|
173
|
+
else:
|
|
174
|
+
print("No archives yet")
|
|
175
|
+
|
|
176
|
+
print("")
|
|
177
|
+
log_info("Log rotation complete!")
|
|
178
|
+
|
|
179
|
+
# Optional: Install cron job
|
|
180
|
+
if len(sys.argv) > 1 and sys.argv[1] == "--install-cron":
|
|
181
|
+
install_cron()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == "__main__":
|
|
185
|
+
main()
|
telemux/cli.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Main CLI entry point for TeleMux
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from . import control, __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main():
|
|
10
|
+
"""Main CLI dispatcher."""
|
|
11
|
+
if len(sys.argv) < 2:
|
|
12
|
+
print("TeleMux - Bidirectional Telegram Integration for tmux")
|
|
13
|
+
print("")
|
|
14
|
+
print("Usage: telemux <command>")
|
|
15
|
+
print("")
|
|
16
|
+
print("Commands:")
|
|
17
|
+
print(" install - Run interactive installer")
|
|
18
|
+
print(" start - Start the listener daemon")
|
|
19
|
+
print(" stop - Stop the listener daemon")
|
|
20
|
+
print(" restart - Restart the listener daemon")
|
|
21
|
+
print(" status - Check if listener is running")
|
|
22
|
+
print(" logs - Tail the log file")
|
|
23
|
+
print(" attach - Attach to the listener tmux session")
|
|
24
|
+
print(" cleanup - Rotate and clean up log files")
|
|
25
|
+
print(" doctor - Run health check and diagnose issues")
|
|
26
|
+
print(" version - Show version information")
|
|
27
|
+
print("")
|
|
28
|
+
print("Shell Functions (available after installation):")
|
|
29
|
+
print(" tg_alert \"message\" - Send notification to Telegram")
|
|
30
|
+
print(" tg_agent \"name\" \"message\" - Send message and receive replies")
|
|
31
|
+
print(" tg_done - Alert when previous command completes")
|
|
32
|
+
print("")
|
|
33
|
+
print("Shortcuts:")
|
|
34
|
+
print(" tg-start, tg-stop, tg-status, tg-logs")
|
|
35
|
+
print("")
|
|
36
|
+
print("Examples:")
|
|
37
|
+
print(" telemux install # Run installer")
|
|
38
|
+
print(" telemux start # Start listener")
|
|
39
|
+
print(" telemux --version # Show version")
|
|
40
|
+
print(" tg_alert \"Build complete\" # Send notification")
|
|
41
|
+
print("")
|
|
42
|
+
print("Documentation: https://github.com/malmazan/telemux")
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
|
|
45
|
+
command = sys.argv[1]
|
|
46
|
+
|
|
47
|
+
# Handle version flags
|
|
48
|
+
if command in ["--version", "-v", "version"]:
|
|
49
|
+
print(f"telemux {__version__}")
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
elif command == "install":
|
|
52
|
+
from .installer import main as installer_main
|
|
53
|
+
installer_main()
|
|
54
|
+
elif command == "start":
|
|
55
|
+
control.start()
|
|
56
|
+
elif command == "stop":
|
|
57
|
+
control.stop()
|
|
58
|
+
elif command == "restart":
|
|
59
|
+
control.restart()
|
|
60
|
+
elif command == "status":
|
|
61
|
+
control.status()
|
|
62
|
+
elif command == "logs":
|
|
63
|
+
control.logs()
|
|
64
|
+
elif command == "attach":
|
|
65
|
+
control.attach()
|
|
66
|
+
elif command == "cleanup":
|
|
67
|
+
from .cleanup import main as cleanup_main
|
|
68
|
+
cleanup_main()
|
|
69
|
+
elif command == "doctor":
|
|
70
|
+
control.doctor()
|
|
71
|
+
elif command in ["-h", "--help", "help"]:
|
|
72
|
+
# Remove command argument and show help
|
|
73
|
+
sys.argv.pop(1)
|
|
74
|
+
main()
|
|
75
|
+
else:
|
|
76
|
+
print(f"Unknown command: {command}")
|
|
77
|
+
print("Run 'telemux' with no arguments for usage information")
|
|
78
|
+
sys.exit(1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
main()
|
telemux/config.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for TeleMux
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from . import TELEMUX_DIR, MESSAGE_QUEUE_DIR, CONFIG_FILE
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ensure_directories() -> None:
|
|
12
|
+
"""Create TeleMux directories if they don't exist."""
|
|
13
|
+
TELEMUX_DIR.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
MESSAGE_QUEUE_DIR.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config() -> Tuple[Optional[str], Optional[str]]:
|
|
18
|
+
"""
|
|
19
|
+
Load Telegram configuration from config file.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Tuple of (bot_token, chat_id) or (None, None) if not configured
|
|
23
|
+
"""
|
|
24
|
+
if not CONFIG_FILE.exists():
|
|
25
|
+
return None, None
|
|
26
|
+
|
|
27
|
+
# Source the bash config file to extract env vars
|
|
28
|
+
bot_token = None
|
|
29
|
+
chat_id = None
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
with open(CONFIG_FILE, 'r') as f:
|
|
33
|
+
for line in f:
|
|
34
|
+
line = line.strip()
|
|
35
|
+
if line.startswith('export TELEMUX_TG_BOT_TOKEN='):
|
|
36
|
+
bot_token = line.split('=', 1)[1].strip('"').strip("'")
|
|
37
|
+
elif line.startswith('export TELEMUX_TG_CHAT_ID='):
|
|
38
|
+
chat_id = line.split('=', 1)[1].strip('"').strip("'")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Also check environment variables (they take precedence)
|
|
43
|
+
bot_token = os.environ.get('TELEMUX_TG_BOT_TOKEN', bot_token)
|
|
44
|
+
chat_id = os.environ.get('TELEMUX_TG_CHAT_ID', chat_id)
|
|
45
|
+
|
|
46
|
+
return bot_token, chat_id
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def save_config(bot_token: str, chat_id: str) -> None:
|
|
50
|
+
"""
|
|
51
|
+
Save Telegram configuration to config file.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
bot_token: Telegram bot token
|
|
55
|
+
chat_id: Telegram chat ID
|
|
56
|
+
"""
|
|
57
|
+
ensure_directories()
|
|
58
|
+
|
|
59
|
+
config_content = f"""#!/bin/bash
|
|
60
|
+
# TeleMux Telegram Bot Configuration
|
|
61
|
+
# Keep this file secure! (chmod 600)
|
|
62
|
+
|
|
63
|
+
export TELEMUX_TG_BOT_TOKEN="{bot_token}"
|
|
64
|
+
export TELEMUX_TG_CHAT_ID="{chat_id}"
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
with open(CONFIG_FILE, 'w') as f:
|
|
68
|
+
f.write(config_content)
|
|
69
|
+
|
|
70
|
+
# Secure the config file
|
|
71
|
+
os.chmod(CONFIG_FILE, 0o600)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def is_configured() -> bool:
|
|
75
|
+
"""Check if TeleMux is configured."""
|
|
76
|
+
bot_token, chat_id = load_config()
|
|
77
|
+
return bot_token is not None and chat_id is not None
|
telemux/control.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Control commands for TeleMux listener daemon
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from . import LOG_FILE, TMUX_SESSION
|
|
10
|
+
from .config import load_config
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def is_listener_running() -> bool:
|
|
14
|
+
"""Check if the listener tmux session is running."""
|
|
15
|
+
try:
|
|
16
|
+
result = subprocess.run(
|
|
17
|
+
['tmux', 'has-session', '-t', TMUX_SESSION],
|
|
18
|
+
capture_output=True,
|
|
19
|
+
check=False
|
|
20
|
+
)
|
|
21
|
+
return result.returncode == 0
|
|
22
|
+
except FileNotFoundError:
|
|
23
|
+
print("Error: tmux is not installed")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def start():
|
|
28
|
+
"""Start the Telegram listener daemon."""
|
|
29
|
+
if is_listener_running():
|
|
30
|
+
print("Telegram listener is already running")
|
|
31
|
+
print(" Use: telemux-status")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
print("Starting Telegram listener...")
|
|
35
|
+
|
|
36
|
+
# Start tmux session with the listener module
|
|
37
|
+
# Use -m flag to run as module, which handles imports correctly
|
|
38
|
+
subprocess.run(
|
|
39
|
+
['tmux', 'new-session', '-d', '-s', TMUX_SESSION, 'python3', '-m', 'telemux.listener'],
|
|
40
|
+
check=False
|
|
41
|
+
)
|
|
42
|
+
time.sleep(1)
|
|
43
|
+
|
|
44
|
+
if is_listener_running():
|
|
45
|
+
print("Telegram listener started successfully")
|
|
46
|
+
print(f" Session: {TMUX_SESSION}")
|
|
47
|
+
print(f" Log: {LOG_FILE}")
|
|
48
|
+
print("")
|
|
49
|
+
print("Commands:")
|
|
50
|
+
print(" telemux-status - Check status")
|
|
51
|
+
print(" telemux-logs - View logs")
|
|
52
|
+
print(" telemux-attach - Attach to session")
|
|
53
|
+
print(" telemux-stop - Stop listener")
|
|
54
|
+
else:
|
|
55
|
+
print("Failed to start listener")
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def stop():
|
|
60
|
+
"""Stop the Telegram listener daemon."""
|
|
61
|
+
if not is_listener_running():
|
|
62
|
+
print("Telegram listener is not running")
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
65
|
+
print("Stopping Telegram listener...")
|
|
66
|
+
subprocess.run(['tmux', 'kill-session', '-t', TMUX_SESSION], check=False)
|
|
67
|
+
print("Telegram listener stopped")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def restart():
|
|
71
|
+
"""Restart the Telegram listener daemon."""
|
|
72
|
+
stop()
|
|
73
|
+
time.sleep(2)
|
|
74
|
+
start()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def status():
|
|
78
|
+
"""Check the status of the listener daemon."""
|
|
79
|
+
if is_listener_running():
|
|
80
|
+
print("Telegram listener is RUNNING")
|
|
81
|
+
print(f" Session: {TMUX_SESSION}")
|
|
82
|
+
print(f" Log: {LOG_FILE}")
|
|
83
|
+
print("")
|
|
84
|
+
print("Recent activity:")
|
|
85
|
+
|
|
86
|
+
if LOG_FILE.exists():
|
|
87
|
+
try:
|
|
88
|
+
with open(LOG_FILE, 'r') as f:
|
|
89
|
+
lines = f.readlines()
|
|
90
|
+
recent = lines[-10:] if len(lines) >= 10 else lines
|
|
91
|
+
for line in recent:
|
|
92
|
+
print(line.rstrip())
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Error reading log: {e}")
|
|
95
|
+
else:
|
|
96
|
+
print("No logs yet")
|
|
97
|
+
else:
|
|
98
|
+
print("Telegram listener is NOT running")
|
|
99
|
+
print(" Start with: telemux-start")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def logs():
|
|
103
|
+
"""Tail the log file."""
|
|
104
|
+
if LOG_FILE.exists():
|
|
105
|
+
try:
|
|
106
|
+
subprocess.run(['tail', '-f', str(LOG_FILE)])
|
|
107
|
+
except KeyboardInterrupt:
|
|
108
|
+
print("\nLog streaming stopped")
|
|
109
|
+
else:
|
|
110
|
+
print(f"No log file found at {LOG_FILE}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def attach():
|
|
114
|
+
"""Attach to the listener tmux session."""
|
|
115
|
+
if is_listener_running():
|
|
116
|
+
subprocess.run(['tmux', 'attach-session', '-t', TMUX_SESSION])
|
|
117
|
+
else:
|
|
118
|
+
print("Telegram listener is not running")
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def doctor():
|
|
123
|
+
"""Run health check and diagnose issues."""
|
|
124
|
+
print("TeleMux Health Check")
|
|
125
|
+
print("=" * 60)
|
|
126
|
+
print("")
|
|
127
|
+
|
|
128
|
+
# Check tmux
|
|
129
|
+
print("Checking tmux...")
|
|
130
|
+
try:
|
|
131
|
+
result = subprocess.run(['tmux', '-V'], capture_output=True, text=True, check=False)
|
|
132
|
+
if result.returncode == 0:
|
|
133
|
+
print(f" tmux is installed ({result.stdout.strip()})")
|
|
134
|
+
else:
|
|
135
|
+
print(" tmux is NOT installed")
|
|
136
|
+
except FileNotFoundError:
|
|
137
|
+
print(" tmux is NOT installed")
|
|
138
|
+
print("")
|
|
139
|
+
|
|
140
|
+
# Check Python
|
|
141
|
+
print("Checking Python...")
|
|
142
|
+
result = subprocess.run(['python3', '--version'], capture_output=True, text=True, check=False)
|
|
143
|
+
if result.returncode == 0:
|
|
144
|
+
print(f" Python is installed ({result.stdout.strip()})")
|
|
145
|
+
else:
|
|
146
|
+
print(" Python3 is NOT installed")
|
|
147
|
+
print("")
|
|
148
|
+
|
|
149
|
+
# Check dependencies
|
|
150
|
+
print("Checking Python dependencies...")
|
|
151
|
+
try:
|
|
152
|
+
import requests
|
|
153
|
+
print(f" requests library is installed (v{requests.__version__})")
|
|
154
|
+
except ImportError:
|
|
155
|
+
print(" requests library is NOT installed")
|
|
156
|
+
print(" Install with: pip install telemux")
|
|
157
|
+
print("")
|
|
158
|
+
|
|
159
|
+
# Check config file
|
|
160
|
+
print("Checking configuration...")
|
|
161
|
+
from . import CONFIG_FILE
|
|
162
|
+
if CONFIG_FILE.exists():
|
|
163
|
+
print(f" Config file exists: {CONFIG_FILE}")
|
|
164
|
+
|
|
165
|
+
# Check permissions
|
|
166
|
+
perms = oct(CONFIG_FILE.stat().st_mode)[-3:]
|
|
167
|
+
if perms == "600":
|
|
168
|
+
print(" Config file permissions are secure (600)")
|
|
169
|
+
else:
|
|
170
|
+
print(f" Config file permissions: {perms} (should be 600)")
|
|
171
|
+
print(f" Fix with: chmod 600 {CONFIG_FILE}")
|
|
172
|
+
|
|
173
|
+
# Check if credentials are set
|
|
174
|
+
bot_token, chat_id = load_config()
|
|
175
|
+
if bot_token:
|
|
176
|
+
print(" Bot token is set")
|
|
177
|
+
else:
|
|
178
|
+
print(" Bot token is NOT set")
|
|
179
|
+
|
|
180
|
+
if chat_id:
|
|
181
|
+
print(" Chat ID is set")
|
|
182
|
+
# Validate format
|
|
183
|
+
if chat_id.lstrip('-').isdigit():
|
|
184
|
+
if chat_id.startswith('-'):
|
|
185
|
+
print(f" (Group chat: {chat_id})")
|
|
186
|
+
else:
|
|
187
|
+
print(f" (Personal chat: {chat_id})")
|
|
188
|
+
else:
|
|
189
|
+
print(" Chat ID format may be invalid")
|
|
190
|
+
else:
|
|
191
|
+
print(" Chat ID is NOT set")
|
|
192
|
+
else:
|
|
193
|
+
print(f" Config file NOT found: {CONFIG_FILE}")
|
|
194
|
+
print(" Run: telemux-install")
|
|
195
|
+
print("")
|
|
196
|
+
|
|
197
|
+
# Test bot connection
|
|
198
|
+
print("Testing Telegram bot connection...")
|
|
199
|
+
bot_token, chat_id = load_config()
|
|
200
|
+
if bot_token:
|
|
201
|
+
try:
|
|
202
|
+
response = requests.get(f"https://api.telegram.org/bot{bot_token}/getMe", timeout=10)
|
|
203
|
+
data = response.json()
|
|
204
|
+
if data.get("ok"):
|
|
205
|
+
bot_name = data["result"].get("first_name", "")
|
|
206
|
+
bot_username = data["result"].get("username", "")
|
|
207
|
+
print(" Bot connection successful!")
|
|
208
|
+
print(f" Bot name: {bot_name}")
|
|
209
|
+
print(f" Username: @{bot_username}")
|
|
210
|
+
else:
|
|
211
|
+
print(" Bot connection failed")
|
|
212
|
+
print(f" Response: {data}")
|
|
213
|
+
print(" Check your bot token")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print(f" Connection failed: {e}")
|
|
216
|
+
else:
|
|
217
|
+
print(" Skipping (no bot token configured)")
|
|
218
|
+
print("")
|
|
219
|
+
|
|
220
|
+
# Check listener process
|
|
221
|
+
print("Checking listener daemon...")
|
|
222
|
+
if is_listener_running():
|
|
223
|
+
print(f" Listener is RUNNING (session: {TMUX_SESSION})")
|
|
224
|
+
else:
|
|
225
|
+
print(" Listener is NOT running")
|
|
226
|
+
print(" Start with: telemux-start")
|
|
227
|
+
print("")
|
|
228
|
+
|
|
229
|
+
# Check log files
|
|
230
|
+
print("Checking log files...")
|
|
231
|
+
if LOG_FILE.exists():
|
|
232
|
+
size = LOG_FILE.stat().st_size
|
|
233
|
+
size_mb = size / (1024 * 1024)
|
|
234
|
+
with open(LOG_FILE, 'r') as f:
|
|
235
|
+
line_count = sum(1 for _ in f)
|
|
236
|
+
print(f" Listener log exists: {LOG_FILE}")
|
|
237
|
+
print(f" Size: {size_mb:.2f} MB ({line_count} lines)")
|
|
238
|
+
else:
|
|
239
|
+
print(" No listener log file yet")
|
|
240
|
+
|
|
241
|
+
from . import MESSAGE_QUEUE_DIR
|
|
242
|
+
outgoing_log = MESSAGE_QUEUE_DIR / "outgoing.log"
|
|
243
|
+
if outgoing_log.exists():
|
|
244
|
+
with open(outgoing_log, 'r') as f:
|
|
245
|
+
count = sum(1 for _ in f)
|
|
246
|
+
print(f" Outgoing message log exists ({count} messages)")
|
|
247
|
+
else:
|
|
248
|
+
print(" No outgoing messages yet")
|
|
249
|
+
|
|
250
|
+
incoming_log = MESSAGE_QUEUE_DIR / "incoming.log"
|
|
251
|
+
if incoming_log.exists():
|
|
252
|
+
with open(incoming_log, 'r') as f:
|
|
253
|
+
count = sum(1 for _ in f)
|
|
254
|
+
print(f" Incoming message log exists ({count} messages)")
|
|
255
|
+
else:
|
|
256
|
+
print(" No incoming messages yet")
|
|
257
|
+
print("")
|
|
258
|
+
|
|
259
|
+
# Summary
|
|
260
|
+
print("=" * 60)
|
|
261
|
+
print("Health Check Complete")
|
|
262
|
+
print("=" * 60)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def main():
|
|
266
|
+
"""Main control CLI entry point."""
|
|
267
|
+
if len(sys.argv) < 2:
|
|
268
|
+
print("TeleMux Control")
|
|
269
|
+
print("")
|
|
270
|
+
print("Usage: telemux <command>")
|
|
271
|
+
print("")
|
|
272
|
+
print("Commands:")
|
|
273
|
+
print(" start - Start the listener daemon")
|
|
274
|
+
print(" stop - Stop the listener daemon")
|
|
275
|
+
print(" restart - Restart the listener daemon")
|
|
276
|
+
print(" status - Check if listener is running")
|
|
277
|
+
print(" logs - Tail the log file")
|
|
278
|
+
print(" attach - Attach to the tmux session")
|
|
279
|
+
print(" doctor - Run health check and diagnose issues")
|
|
280
|
+
print("")
|
|
281
|
+
print("You can also use:")
|
|
282
|
+
print(" telemux-start, telemux-stop, telemux-status, telemux-logs, etc.")
|
|
283
|
+
sys.exit(1)
|
|
284
|
+
|
|
285
|
+
command = sys.argv[1]
|
|
286
|
+
if command == "start":
|
|
287
|
+
start()
|
|
288
|
+
elif command == "stop":
|
|
289
|
+
stop()
|
|
290
|
+
elif command == "restart":
|
|
291
|
+
restart()
|
|
292
|
+
elif command == "status":
|
|
293
|
+
status()
|
|
294
|
+
elif command == "logs":
|
|
295
|
+
logs()
|
|
296
|
+
elif command == "attach":
|
|
297
|
+
attach()
|
|
298
|
+
elif command == "doctor":
|
|
299
|
+
doctor()
|
|
300
|
+
else:
|
|
301
|
+
print(f"Unknown command: {command}")
|
|
302
|
+
sys.exit(1)
|