kctl-rustdesk 0.6.3__tar.gz
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.
- kctl_rustdesk-0.6.3/.gitignore +33 -0
- kctl_rustdesk-0.6.3/PKG-INFO +16 -0
- kctl_rustdesk-0.6.3/README.md +72 -0
- kctl_rustdesk-0.6.3/pyproject.toml +44 -0
- kctl_rustdesk-0.6.3/skills/rustdesk-admin/SKILL.md +160 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/__init__.py +3 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/__main__.py +3 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/cli.py +133 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/__init__.py +0 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/audit.py +143 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/backup.py +151 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/config_cmd.py +173 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/dashboard.py +98 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/doctor_cmd.py +57 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/health.py +125 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/maintenance.py +170 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/peers.py +112 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/setup.py +114 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/skill_cmd.py +76 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/commands/users.py +115 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/core/__init__.py +0 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/core/callbacks.py +29 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/core/config.py +69 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/core/executor.py +162 -0
- kctl_rustdesk-0.6.3/src/kctl_rustdesk/core/plugins.py +39 -0
- kctl_rustdesk-0.6.3/tests/conftest.py +77 -0
- kctl_rustdesk-0.6.3/tests/test_core.py +318 -0
- kctl_rustdesk-0.6.3/tests/test_peers.py +122 -0
- kctl_rustdesk-0.6.3/tests/test_smoke.py +149 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.idea/
|
|
16
|
+
.vscode/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Environment
|
|
32
|
+
.env
|
|
33
|
+
.env.local
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kctl-rustdesk
|
|
3
|
+
Version: 0.6.3
|
|
4
|
+
Summary: Kodemeio RustDesk CLI — manage RustDesk server infrastructure
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: kctl-lib>=0.8.0
|
|
7
|
+
Requires-Dist: pydantic>=2.10.0
|
|
8
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
9
|
+
Requires-Dist: rich>=13.9.0
|
|
10
|
+
Requires-Dist: typer>=0.15.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: mypy>=1.14.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# kctl-rustdesk
|
|
2
|
+
|
|
3
|
+
Kodemeio RustDesk CLI — manage RustDesk server infrastructure.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv tool install .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
kctl-rustdesk config init
|
|
15
|
+
kctl-rustdesk health
|
|
16
|
+
kctl-rustdesk dashboard
|
|
17
|
+
kctl-rustdesk peers list
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Command Groups
|
|
21
|
+
|
|
22
|
+
| Group | Description |
|
|
23
|
+
|-------|-------------|
|
|
24
|
+
| `config` | Manage CLI configuration and profiles |
|
|
25
|
+
| `health` | Health checks for RustDesk server |
|
|
26
|
+
| `dashboard` | System overview dashboard |
|
|
27
|
+
| `peers` | Manage RustDesk peers (devices) |
|
|
28
|
+
| `users` | Manage RustDesk users |
|
|
29
|
+
| `audit` | Audit logs and connection history |
|
|
30
|
+
| `backup` | Backup and restore RustDesk server data |
|
|
31
|
+
| `setup` | Server setup and configuration |
|
|
32
|
+
| `maintenance` | Maintenance and operational tasks |
|
|
33
|
+
|
|
34
|
+
## Global Options
|
|
35
|
+
|
|
36
|
+
| Option | Short | Description |
|
|
37
|
+
|--------|-------|-------------|
|
|
38
|
+
| `--json` | | Output as JSON |
|
|
39
|
+
| `--quiet` | `-q` | Suppress info messages |
|
|
40
|
+
| `--format` | `-f` | Output format: pretty, json, csv, yaml |
|
|
41
|
+
| `--no-header` | | Omit column headers |
|
|
42
|
+
| `--profile` | `-p` | Config profile |
|
|
43
|
+
| `--host` | | Server host override |
|
|
44
|
+
| `--version` | `-V` | Show version and exit |
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Config lives in `~/.config/kodemeio/config.yaml` under the `rustdesk` service key.
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# Initialize default profile
|
|
52
|
+
kctl-rustdesk config init
|
|
53
|
+
|
|
54
|
+
# Add a named profile
|
|
55
|
+
kctl-rustdesk config add prod \
|
|
56
|
+
--host https://rustdesk.example.com \
|
|
57
|
+
--api-key $RUSTDESK_API_KEY
|
|
58
|
+
|
|
59
|
+
# Switch active profile
|
|
60
|
+
kctl-rustdesk config use prod
|
|
61
|
+
|
|
62
|
+
# Show current profile (key masked)
|
|
63
|
+
kctl-rustdesk config show
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Development
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv run pytest tests/ -v
|
|
70
|
+
uv run ruff check src/
|
|
71
|
+
uv run mypy src/
|
|
72
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kctl-rustdesk"
|
|
7
|
+
version = "0.6.3"
|
|
8
|
+
description = "Kodemeio RustDesk CLI — manage RustDesk server infrastructure"
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"kctl-lib>=0.8.0",
|
|
12
|
+
"typer>=0.15.0",
|
|
13
|
+
"rich>=13.9.0",
|
|
14
|
+
"pydantic>=2.10.0",
|
|
15
|
+
"pyyaml>=6.0.2",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=8.3.0",
|
|
21
|
+
"pytest-httpx>=0.35.0",
|
|
22
|
+
"ruff>=0.9.0",
|
|
23
|
+
"mypy>=1.14.0",
|
|
24
|
+
"types-PyYAML>=6.0.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
kctl-rustdesk = "kctl_rustdesk.cli:_run"
|
|
29
|
+
|
|
30
|
+
[tool.uv.sources]
|
|
31
|
+
kctl-lib = { workspace = true }
|
|
32
|
+
|
|
33
|
+
[project.entry-points."kctl_rustdesk.plugins"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
packages = ["src/kctl_rustdesk"]
|
|
37
|
+
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
target-version = "py312"
|
|
40
|
+
line-length = 120
|
|
41
|
+
|
|
42
|
+
[tool.mypy]
|
|
43
|
+
python_version = "3.12"
|
|
44
|
+
strict = true
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rustdesk-admin
|
|
3
|
+
description: >
|
|
4
|
+
RustDesk remote desktop administration via kctl-rustdesk CLI (10 groups, ~36 commands).
|
|
5
|
+
MUST use for ANY kctl-rustdesk operation.
|
|
6
|
+
Triggers on: "active", "audit", "backup", "check", "clean", "cleanup", "client-config", "config", "connections", "count", "dashboard", "db-optimize", "db-stats", "export", "firewall", "generate", "get-key", "groups", "health", "init", "kctl-rustdesk", "logins", "logs", "maintenance", "peers", "profile", "profiles", "restore", "search", "setup", "skill", "stats", "test", "users", "version".
|
|
7
|
+
Auto-generated: 2026-04-05
|
|
8
|
+
registry_hash: 74352b3f8a29
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# rustdesk-admin — kctl-rustdesk CLI Reference
|
|
12
|
+
|
|
13
|
+
> Auto-generated from `kctl-rustdesk` command registry. Do not edit manually.
|
|
14
|
+
> To regenerate: `kctl-rustdesk skill generate`
|
|
15
|
+
> To add custom content: edit `SKILL.extra.md` in the same directory.
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
**CLI:** `kctl-rustdesk`
|
|
20
|
+
**Command groups:** 10
|
|
21
|
+
**Total commands:** ~36
|
|
22
|
+
**Install:** `cd cli && uv tool install --editable .`
|
|
23
|
+
|
|
24
|
+
## Global Options
|
|
25
|
+
|
|
26
|
+
| Flag | Description |
|
|
27
|
+
|------|-------------|
|
|
28
|
+
| `--json` | JSON output |
|
|
29
|
+
| `--quiet`, `-q` | Suppress info messages |
|
|
30
|
+
| `--format`, `-f` | Output format: pretty/json/csv/yaml |
|
|
31
|
+
| `--no-header` | Omit CSV header row |
|
|
32
|
+
| `--profile`, `-p` | Config profile name |
|
|
33
|
+
| `--version`, `-V` | Show version |
|
|
34
|
+
|
|
35
|
+
## Command Reference
|
|
36
|
+
|
|
37
|
+
### `kctl-rustdesk audit`
|
|
38
|
+
|
|
39
|
+
Audit logs and connection history.
|
|
40
|
+
|
|
41
|
+
| Command | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| `audit active` | Show currently active sessions. |
|
|
44
|
+
| `audit connections [--today] [--limit]` | Show connection history. |
|
|
45
|
+
| `audit logins [--failed] [--limit]` | Show login history. |
|
|
46
|
+
| `audit stats` | Show connection statistics. |
|
|
47
|
+
|
|
48
|
+
### `kctl-rustdesk backup`
|
|
49
|
+
|
|
50
|
+
Backup and restore RustDesk server data.
|
|
51
|
+
|
|
52
|
+
| Command | Description |
|
|
53
|
+
|---------|-------------|
|
|
54
|
+
| `backup clean [--days]` | Remove old backups. |
|
|
55
|
+
| `backup create` | Create a backup of keys and database. |
|
|
56
|
+
| `backup list` | List available backups. |
|
|
57
|
+
| `backup restore <backup_file>` | Restore from a backup file. |
|
|
58
|
+
|
|
59
|
+
### `kctl-rustdesk config`
|
|
60
|
+
|
|
61
|
+
Manage CLI configuration and profiles.
|
|
62
|
+
|
|
63
|
+
| Command | Description |
|
|
64
|
+
|---------|-------------|
|
|
65
|
+
| `config init [--host] [--ssh_user] [--compose_file] [--env_file] [--project_name] [--domain] [--name]` | Initialize CLI configuration with a new profile. |
|
|
66
|
+
| `config profiles` | List all profiles. |
|
|
67
|
+
| `config show` | Show current configuration. |
|
|
68
|
+
| `config test` | Test connection to the RustDesk server. |
|
|
69
|
+
| `config use <name>` | Set the default profile. |
|
|
70
|
+
|
|
71
|
+
### `kctl-rustdesk dashboard`
|
|
72
|
+
|
|
73
|
+
System overview dashboard.
|
|
74
|
+
|
|
75
|
+
| Command | Description |
|
|
76
|
+
|---------|-------------|
|
|
77
|
+
| `dashboard show [--compact]` | Show system overview dashboard. |
|
|
78
|
+
|
|
79
|
+
### `kctl-rustdesk health`
|
|
80
|
+
|
|
81
|
+
Health checks for RustDesk server.
|
|
82
|
+
|
|
83
|
+
| Command | Description |
|
|
84
|
+
|---------|-------------|
|
|
85
|
+
| `health check [--as_json]` | Run health checks on RustDesk server. |
|
|
86
|
+
|
|
87
|
+
### `kctl-rustdesk maintenance`
|
|
88
|
+
|
|
89
|
+
Maintenance and operational tasks.
|
|
90
|
+
|
|
91
|
+
| Command | Description |
|
|
92
|
+
|---------|-------------|
|
|
93
|
+
| `maintenance cleanup` | Clean up unused Docker resources. |
|
|
94
|
+
| `maintenance db-optimize` | Optimize the SQLite database (VACUUM + ANALYZE). |
|
|
95
|
+
| `maintenance db-stats` | Show database statistics. |
|
|
96
|
+
| `maintenance logs [--service] [--lines]` | View container logs. |
|
|
97
|
+
| `maintenance status` | Show container status and resource usage. |
|
|
98
|
+
| `maintenance version` | Show version information. |
|
|
99
|
+
|
|
100
|
+
### `kctl-rustdesk peers`
|
|
101
|
+
|
|
102
|
+
Manage RustDesk peers (devices).
|
|
103
|
+
|
|
104
|
+
| Command | Description |
|
|
105
|
+
|---------|-------------|
|
|
106
|
+
| `peers count` | Count total peers. |
|
|
107
|
+
| `peers export` | Export all peers as JSON. |
|
|
108
|
+
| `peers get <peer_id>` | Get details for a specific peer. |
|
|
109
|
+
| `peers list [--online]` | List all registered peers. |
|
|
110
|
+
| `peers search <term>` | Search peers by ID, UUID, or note. |
|
|
111
|
+
|
|
112
|
+
### `kctl-rustdesk setup`
|
|
113
|
+
|
|
114
|
+
Server setup and configuration.
|
|
115
|
+
|
|
116
|
+
| Command | Description |
|
|
117
|
+
|---------|-------------|
|
|
118
|
+
| `setup client-config` | Generate RustDesk client configuration string. |
|
|
119
|
+
| `setup firewall` | Show required firewall rules. |
|
|
120
|
+
| `setup get-key` | Display the server's public key. |
|
|
121
|
+
| `setup status` | Show setup status checklist. |
|
|
122
|
+
|
|
123
|
+
### `kctl-rustdesk skill`
|
|
124
|
+
|
|
125
|
+
Claude Code skill management.
|
|
126
|
+
|
|
127
|
+
| Command | Description |
|
|
128
|
+
|---------|-------------|
|
|
129
|
+
| `skill generate [--output] [--install] [--check]` | Auto-generate SKILL.md from CLI command registry. |
|
|
130
|
+
|
|
131
|
+
**Examples:**
|
|
132
|
+
```bash
|
|
133
|
+
kctl-rustdesk skill generate
|
|
134
|
+
kctl-rustdesk skill generate --install
|
|
135
|
+
kctl-rustdesk skill generate --check
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `kctl-rustdesk users`
|
|
139
|
+
|
|
140
|
+
Manage RustDesk users.
|
|
141
|
+
|
|
142
|
+
| Command | Description |
|
|
143
|
+
|---------|-------------|
|
|
144
|
+
| `users count` | Count users. |
|
|
145
|
+
| `users export` | Export all users as JSON. |
|
|
146
|
+
| `users get <username>` | Get details for a specific user. |
|
|
147
|
+
| `users groups` | List user groups. |
|
|
148
|
+
| `users list [--active]` | List all users. |
|
|
149
|
+
|
|
150
|
+
## Configuration
|
|
151
|
+
|
|
152
|
+
Shared config: `~/.config/kodemeio/config.yaml`
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
kctl-rustdesk config init # Interactive setup
|
|
156
|
+
kctl-rustdesk config show # Show current config
|
|
157
|
+
kctl-rustdesk config profiles # List profiles
|
|
158
|
+
kctl-rustdesk config current # Show active profile
|
|
159
|
+
kctl-rustdesk config validate # Verify config
|
|
160
|
+
```
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""kctl-rustdesk CLI entry point."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from kctl_lib import handle_cli_error
|
|
11
|
+
from kctl_lib.exceptions import KctlError
|
|
12
|
+
|
|
13
|
+
from kctl_rustdesk import __version__
|
|
14
|
+
from kctl_rustdesk.commands.audit import app as audit_app
|
|
15
|
+
from kctl_rustdesk.commands.backup import app as backup_app
|
|
16
|
+
from kctl_rustdesk.commands.config_cmd import app as config_app
|
|
17
|
+
from kctl_rustdesk.commands.dashboard import app as dashboard_app
|
|
18
|
+
from kctl_rustdesk.commands.health import app as health_app
|
|
19
|
+
from kctl_rustdesk.commands.maintenance import app as maintenance_app
|
|
20
|
+
from kctl_rustdesk.commands.peers import app as peers_app
|
|
21
|
+
from kctl_rustdesk.commands.setup import app as setup_app
|
|
22
|
+
from kctl_rustdesk.commands.users import app as users_app
|
|
23
|
+
from kctl_rustdesk.core.plugins import discover_and_load_plugins
|
|
24
|
+
from kctl_rustdesk.commands.skill_cmd import app as skill_app
|
|
25
|
+
from kctl_rustdesk.commands.doctor_cmd import app as doctor_app
|
|
26
|
+
from kctl_lib.self_update import notify_if_outdated
|
|
27
|
+
from kctl_lib.tui import add_tui_command
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def version_callback(value: bool) -> None:
|
|
31
|
+
if value:
|
|
32
|
+
typer.echo(f"kctl-rustdesk {__version__}")
|
|
33
|
+
raise typer.Exit()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
app = typer.Typer(
|
|
37
|
+
name="kctl-rustdesk",
|
|
38
|
+
help="Kodemeio RustDesk CLI — manage RustDesk server infrastructure.",
|
|
39
|
+
no_args_is_help=True,
|
|
40
|
+
rich_markup_mode="rich",
|
|
41
|
+
pretty_exceptions_enable=False,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.callback()
|
|
46
|
+
def main(
|
|
47
|
+
ctx: typer.Context,
|
|
48
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
49
|
+
quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
|
|
50
|
+
profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile")] = None,
|
|
51
|
+
format: Annotated[str, typer.Option("--format", "-f", help="Output format")] = "pretty",
|
|
52
|
+
no_header: Annotated[bool, typer.Option("--no-header", help="Omit column headers")] = False,
|
|
53
|
+
host: Annotated[str | None, typer.Option("--host", help="Server host override")] = None,
|
|
54
|
+
version: Annotated[bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True)] = False,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Manage RustDesk server infrastructure."""
|
|
57
|
+
from kctl_rustdesk.core.callbacks import AppContext
|
|
58
|
+
|
|
59
|
+
ctx.ensure_object(dict)
|
|
60
|
+
ctx.obj = AppContext(
|
|
61
|
+
json_mode=json_output,
|
|
62
|
+
quiet=quiet,
|
|
63
|
+
profile=profile,
|
|
64
|
+
format=format,
|
|
65
|
+
no_header=no_header,
|
|
66
|
+
host_override=host,
|
|
67
|
+
)
|
|
68
|
+
notify_if_outdated(ctx.obj.output, "kctl-rustdesk", __version__)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
app.add_typer(config_app, name="config")
|
|
72
|
+
app.add_typer(health_app, name="health")
|
|
73
|
+
app.add_typer(dashboard_app, name="dashboard")
|
|
74
|
+
app.add_typer(peers_app, name="peers")
|
|
75
|
+
app.add_typer(users_app, name="users")
|
|
76
|
+
app.add_typer(audit_app, name="audit")
|
|
77
|
+
app.add_typer(backup_app, name="backup")
|
|
78
|
+
app.add_typer(setup_app, name="setup")
|
|
79
|
+
app.add_typer(maintenance_app, name="maintenance")
|
|
80
|
+
app.add_typer(skill_app, name="skill", hidden=True)
|
|
81
|
+
app.add_typer(doctor_app, name="doctor")
|
|
82
|
+
|
|
83
|
+
discover_and_load_plugins(app)
|
|
84
|
+
add_tui_command(app, service_key="rustdesk", version=__version__)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@app.command("self-update")
|
|
88
|
+
def self_update_cmd(ctx: typer.Context) -> None:
|
|
89
|
+
"""Check for updates and upgrade kctl-rustdesk."""
|
|
90
|
+
actx = ctx.obj
|
|
91
|
+
out = actx.output
|
|
92
|
+
|
|
93
|
+
from kctl_lib.self_update import check_update
|
|
94
|
+
from kctl_lib.self_update import update as do_update
|
|
95
|
+
|
|
96
|
+
latest = check_update("kctl-rustdesk", __version__)
|
|
97
|
+
if latest:
|
|
98
|
+
out.info(f"Updating to {latest}...")
|
|
99
|
+
do_update("kctl-rustdesk")
|
|
100
|
+
out.success(f"Updated to {latest}")
|
|
101
|
+
else:
|
|
102
|
+
out.success("Already up to date")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.command()
|
|
106
|
+
def completions(
|
|
107
|
+
shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
|
|
108
|
+
install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Generate or install shell completions."""
|
|
111
|
+
from kctl_lib.completions import get_completion_script, install_completions
|
|
112
|
+
|
|
113
|
+
if install:
|
|
114
|
+
path = install_completions("kctl-rustdesk", shell)
|
|
115
|
+
if path:
|
|
116
|
+
typer.echo(f"Completions installed to {path}")
|
|
117
|
+
else:
|
|
118
|
+
typer.echo(f"Could not install completions for {shell}", err=True)
|
|
119
|
+
raise typer.Exit(code=1)
|
|
120
|
+
else:
|
|
121
|
+
script = get_completion_script("kctl-rustdesk", shell)
|
|
122
|
+
typer.echo(script)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _run() -> None:
|
|
126
|
+
try:
|
|
127
|
+
app()
|
|
128
|
+
except KctlError as e:
|
|
129
|
+
handle_cli_error(e)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
if __name__ == "__main__":
|
|
133
|
+
_run()
|
|
File without changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Audit log and connection history commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from kctl_rustdesk.core.callbacks import AppContext
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(help="Audit logs and connection history.")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command()
|
|
15
|
+
def connections(
|
|
16
|
+
ctx: typer.Context,
|
|
17
|
+
today: Annotated[bool, typer.Option("--today", help="Only today")] = False,
|
|
18
|
+
limit: Annotated[int, typer.Option("--limit", "-n", help="Max rows")] = 50,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Show connection history."""
|
|
21
|
+
c: AppContext = ctx.obj
|
|
22
|
+
ex = c.executor
|
|
23
|
+
|
|
24
|
+
where = "WHERE date(created_at) = date('now')" if today else ""
|
|
25
|
+
sql = f"SELECT peer_id, ip, created_at FROM conn_log {where} ORDER BY created_at DESC LIMIT {limit};"
|
|
26
|
+
|
|
27
|
+
rows_data = ex.query_db(sql)
|
|
28
|
+
rows = [[r.get("peer_id", ""), r.get("ip", ""), r.get("created_at", "")] for r in rows_data]
|
|
29
|
+
|
|
30
|
+
title = "Connections (today)" if today else f"Connections (last {limit})"
|
|
31
|
+
c.output.table(
|
|
32
|
+
title,
|
|
33
|
+
[("Peer ID", "cyan"), ("IP", ""), ("Time", "dim")],
|
|
34
|
+
rows,
|
|
35
|
+
data_for_json=rows_data,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command()
|
|
40
|
+
def logins(
|
|
41
|
+
ctx: typer.Context,
|
|
42
|
+
failed: Annotated[bool, typer.Option("--failed", help="Only failed logins")] = False,
|
|
43
|
+
limit: Annotated[int, typer.Option("--limit", "-n", help="Max rows")] = 50,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Show login history."""
|
|
46
|
+
c: AppContext = ctx.obj
|
|
47
|
+
ex = c.executor
|
|
48
|
+
|
|
49
|
+
where = "WHERE type != 0" if failed else ""
|
|
50
|
+
sql = f"SELECT user_id, ip, type, created_at FROM login_log {where} ORDER BY created_at DESC LIMIT {limit};"
|
|
51
|
+
|
|
52
|
+
rows_data = ex.query_db(sql)
|
|
53
|
+
rows = [
|
|
54
|
+
[
|
|
55
|
+
r.get("user_id", ""),
|
|
56
|
+
r.get("ip", ""),
|
|
57
|
+
"failed" if r.get("type", "0") != "0" else "ok",
|
|
58
|
+
r.get("created_at", ""),
|
|
59
|
+
]
|
|
60
|
+
for r in rows_data
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
title = "Failed Logins" if failed else f"Logins (last {limit})"
|
|
64
|
+
c.output.table(
|
|
65
|
+
title,
|
|
66
|
+
[("User", "cyan"), ("IP", ""), ("Status", ""), ("Time", "dim")],
|
|
67
|
+
rows,
|
|
68
|
+
data_for_json=rows_data,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@app.command()
|
|
73
|
+
def stats(ctx: typer.Context) -> None:
|
|
74
|
+
"""Show connection statistics."""
|
|
75
|
+
c: AppContext = ctx.obj
|
|
76
|
+
ex = c.executor
|
|
77
|
+
|
|
78
|
+
total_conns = ex.query_db_scalar("SELECT count(*) FROM conn_log;")
|
|
79
|
+
today_conns = ex.query_db_scalar("SELECT count(*) FROM conn_log WHERE date(created_at) = date('now');")
|
|
80
|
+
unique_peers = ex.query_db_scalar("SELECT count(DISTINCT peer_id) FROM conn_log;")
|
|
81
|
+
unique_ips = ex.query_db_scalar("SELECT count(DISTINCT ip) FROM conn_log;")
|
|
82
|
+
total_logins = ex.query_db_scalar("SELECT count(*) FROM login_log;")
|
|
83
|
+
failed_logins = ex.query_db_scalar("SELECT count(*) FROM login_log WHERE type != 0;")
|
|
84
|
+
|
|
85
|
+
top_peers = ex.query_db("SELECT peer_id, count(*) as cnt FROM conn_log GROUP BY peer_id ORDER BY cnt DESC LIMIT 5;")
|
|
86
|
+
|
|
87
|
+
sections = [
|
|
88
|
+
(
|
|
89
|
+
"Connection Stats",
|
|
90
|
+
[
|
|
91
|
+
("Total connections", total_conns),
|
|
92
|
+
("Today", today_conns),
|
|
93
|
+
("Unique peers", unique_peers),
|
|
94
|
+
("Unique IPs", unique_ips),
|
|
95
|
+
],
|
|
96
|
+
),
|
|
97
|
+
(
|
|
98
|
+
"Login Stats",
|
|
99
|
+
[
|
|
100
|
+
("Total logins", total_logins),
|
|
101
|
+
("Failed logins", failed_logins),
|
|
102
|
+
],
|
|
103
|
+
),
|
|
104
|
+
("Top Peers", [(p.get("peer_id", ""), f"{p.get('cnt', 0)} connections") for p in top_peers]),
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
c.output.detail(
|
|
108
|
+
"Audit Statistics",
|
|
109
|
+
sections,
|
|
110
|
+
data_for_json={
|
|
111
|
+
"connections": {
|
|
112
|
+
"total": int(total_conns),
|
|
113
|
+
"today": int(today_conns),
|
|
114
|
+
"unique_peers": int(unique_peers),
|
|
115
|
+
"unique_ips": int(unique_ips),
|
|
116
|
+
},
|
|
117
|
+
"logins": {"total": int(total_logins), "failed": int(failed_logins)},
|
|
118
|
+
"top_peers": top_peers,
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@app.command()
|
|
124
|
+
def active(ctx: typer.Context) -> None:
|
|
125
|
+
"""Show currently active sessions."""
|
|
126
|
+
c: AppContext = ctx.obj
|
|
127
|
+
ex = c.executor
|
|
128
|
+
|
|
129
|
+
sql = (
|
|
130
|
+
"SELECT peer_id, ip, created_at FROM conn_log "
|
|
131
|
+
"WHERE created_at > datetime('now', '-5 minutes') "
|
|
132
|
+
"ORDER BY created_at DESC;"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
rows_data = ex.query_db(sql)
|
|
136
|
+
rows = [[r.get("peer_id", ""), r.get("ip", ""), r.get("created_at", "")] for r in rows_data]
|
|
137
|
+
|
|
138
|
+
c.output.table(
|
|
139
|
+
"Active Sessions (last 5m)",
|
|
140
|
+
[("Peer ID", "cyan"), ("IP", ""), ("Connected", "dim")],
|
|
141
|
+
rows,
|
|
142
|
+
data_for_json=rows_data,
|
|
143
|
+
)
|