redundanet 2.0.0__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.
redundanet/cli/main.py ADDED
@@ -0,0 +1,247 @@
1
+ """Main CLI application for RedundaNet."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Annotated, Optional
7
+
8
+ import typer
9
+ from rich import print as rprint
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.table import Table
13
+
14
+ from redundanet import __version__
15
+ from redundanet.cli.network import app as network_app
16
+ from redundanet.cli.node import app as node_app
17
+ from redundanet.cli.storage import app as storage_app
18
+ from redundanet.core.config import load_settings
19
+ from redundanet.core.manifest import Manifest
20
+ from redundanet.utils.logging import setup_logging
21
+
22
+ # Create the main app
23
+ app = typer.Typer(
24
+ name="redundanet",
25
+ help="RedundaNet - Distributed encrypted storage on a mesh VPN network",
26
+ no_args_is_help=True,
27
+ rich_markup_mode="rich",
28
+ )
29
+
30
+ # Add subcommands
31
+ app.add_typer(node_app, name="node", help="Node management commands")
32
+ app.add_typer(network_app, name="network", help="Network management commands")
33
+ app.add_typer(storage_app, name="storage", help="Storage management commands")
34
+
35
+ console = Console()
36
+
37
+
38
+ def version_callback(value: bool) -> None:
39
+ """Print version and exit."""
40
+ if value:
41
+ rprint(f"[bold blue]RedundaNet[/bold blue] version [green]{__version__}[/green]")
42
+ raise typer.Exit()
43
+
44
+
45
+ @app.callback()
46
+ def main(
47
+ version: Annotated[
48
+ Optional[bool],
49
+ typer.Option(
50
+ "--version",
51
+ "-v",
52
+ callback=version_callback,
53
+ is_eager=True,
54
+ help="Show version and exit",
55
+ ),
56
+ ] = None,
57
+ debug: Annotated[
58
+ bool,
59
+ typer.Option("--debug", "-d", help="Enable debug logging"),
60
+ ] = False,
61
+ config_dir: Annotated[
62
+ Optional[Path],
63
+ typer.Option("--config-dir", "-c", help="Configuration directory"),
64
+ ] = None,
65
+ ) -> None:
66
+ """RedundaNet - Distributed encrypted storage on a mesh VPN network."""
67
+ log_level = "DEBUG" if debug else "INFO"
68
+ setup_logging(level=log_level)
69
+
70
+
71
+ @app.command()
72
+ def init(
73
+ node_name: Annotated[
74
+ str,
75
+ typer.Option("--name", "-n", prompt="Enter node name", help="Name for this node"),
76
+ ],
77
+ network_name: Annotated[
78
+ str,
79
+ typer.Option(
80
+ "--network",
81
+ prompt="Enter network name",
82
+ help="Name of the network to join or create",
83
+ ),
84
+ ] = "redundanet",
85
+ storage_contribution: Annotated[
86
+ str,
87
+ typer.Option(
88
+ "--storage",
89
+ prompt="Storage contribution (e.g., 1TB)",
90
+ help="Amount of storage to contribute",
91
+ ),
92
+ ] = "1TB",
93
+ manifest_repo: Annotated[
94
+ Optional[str],
95
+ typer.Option(
96
+ "--manifest-repo",
97
+ prompt="Manifest repository URL (or press Enter to skip)",
98
+ help="Git repository URL for the network manifest",
99
+ ),
100
+ ] = None,
101
+ docker: Annotated[
102
+ bool,
103
+ typer.Option("--docker", help="Initialize for Docker deployment"),
104
+ ] = True,
105
+ ) -> None:
106
+ """Initialize a new RedundaNet node with interactive setup."""
107
+ console.print(
108
+ Panel(
109
+ "[bold blue]Welcome to RedundaNet![/bold blue]\n\n"
110
+ "This wizard will help you set up a new node on the distributed storage network.",
111
+ title="RedundaNet Setup",
112
+ )
113
+ )
114
+
115
+ with console.status("[bold green]Initializing node..."):
116
+ # Create configuration directories
117
+ settings = load_settings()
118
+ config_dir = settings.config_dir
119
+ data_dir = settings.data_dir
120
+
121
+ config_dir.mkdir(parents=True, exist_ok=True)
122
+ data_dir.mkdir(parents=True, exist_ok=True)
123
+ (data_dir / "manifest").mkdir(exist_ok=True)
124
+ (data_dir / "tinc").mkdir(exist_ok=True)
125
+ (data_dir / "tahoe").mkdir(exist_ok=True)
126
+
127
+ console.print(f"[green]Created configuration directory:[/green] {config_dir}")
128
+ console.print(f"[green]Created data directory:[/green] {data_dir}")
129
+
130
+ # Generate node configuration
131
+ console.print("\n[bold]Node Configuration:[/bold]")
132
+ table = Table(show_header=False, box=None)
133
+ table.add_column("Property", style="cyan")
134
+ table.add_column("Value", style="green")
135
+ table.add_row("Node Name", node_name)
136
+ table.add_row("Network", network_name)
137
+ table.add_row("Storage Contribution", storage_contribution)
138
+ table.add_row("Deployment Mode", "Docker" if docker else "Native")
139
+ console.print(table)
140
+
141
+ console.print("\n[bold green]Node initialized successfully![/bold green]")
142
+ console.print("\n[bold]Next steps:[/bold]")
143
+ console.print("1. Generate GPG keys: [cyan]redundanet node keys generate[/cyan]")
144
+ console.print("2. Join the network: [cyan]redundanet network join[/cyan]")
145
+ console.print("3. Start services: [cyan]docker compose up -d[/cyan]" if docker else "")
146
+
147
+
148
+ @app.command()
149
+ def status(
150
+ verbose: Annotated[
151
+ bool,
152
+ typer.Option("--verbose", "-v", help="Show detailed status"),
153
+ ] = False,
154
+ ) -> None:
155
+ """Show the current status of the local node and network."""
156
+ console.print(Panel("[bold]RedundaNet Status[/bold]", expand=False))
157
+
158
+ # Node status table
159
+ table = Table(title="Local Node", show_header=True)
160
+ table.add_column("Property", style="cyan")
161
+ table.add_column("Value")
162
+
163
+ settings = load_settings()
164
+
165
+ table.add_row("Node Name", settings.node_name or "[dim]Not configured[/dim]")
166
+ table.add_row("Config Dir", str(settings.config_dir))
167
+ table.add_row("Data Dir", str(settings.data_dir))
168
+ table.add_row("Debug Mode", "Yes" if settings.debug else "No")
169
+
170
+ console.print(table)
171
+
172
+ # Check VPN status
173
+ console.print("\n[bold]Service Status:[/bold]")
174
+
175
+ services = [
176
+ ("Tinc VPN", "tinc"),
177
+ ("Tahoe Introducer", "tahoe-introducer"),
178
+ ("Tahoe Storage", "tahoe-storage"),
179
+ ("Tahoe Client", "tahoe-client"),
180
+ ]
181
+
182
+ status_table = Table(show_header=True)
183
+ status_table.add_column("Service")
184
+ status_table.add_column("Status")
185
+
186
+ for name, _ in services:
187
+ # In a real implementation, we'd check the actual service status
188
+ status_table.add_row(name, "[yellow]Unknown[/yellow]")
189
+
190
+ console.print(status_table)
191
+
192
+
193
+ @app.command()
194
+ def sync(
195
+ force: Annotated[
196
+ bool,
197
+ typer.Option("--force", "-f", help="Force sync even if up to date"),
198
+ ] = False,
199
+ ) -> None:
200
+ """Sync the network manifest from the repository."""
201
+ settings = load_settings()
202
+
203
+ if not settings.manifest_repo:
204
+ console.print("[red]Error:[/red] No manifest repository configured.")
205
+ console.print("Set REDUNDANET_MANIFEST_REPO environment variable or run init.")
206
+ raise typer.Exit(1)
207
+
208
+ with console.status("[bold green]Syncing manifest..."):
209
+ # In a real implementation, we'd clone/pull the manifest repo
210
+ console.print(f"[green]Syncing from:[/green] {settings.manifest_repo}")
211
+ console.print(f"[green]Branch:[/green] {settings.manifest_branch}")
212
+
213
+ console.print("[bold green]Manifest synced successfully![/bold green]")
214
+
215
+
216
+ @app.command("validate")
217
+ def validate_manifest(
218
+ manifest_path: Annotated[
219
+ Path,
220
+ typer.Argument(help="Path to the manifest file"),
221
+ ],
222
+ ) -> None:
223
+ """Validate a network manifest file."""
224
+ try:
225
+ manifest = Manifest.from_file(manifest_path)
226
+ errors = manifest.validate()
227
+
228
+ if errors:
229
+ console.print("[yellow]Validation warnings:[/yellow]")
230
+ for error in errors:
231
+ console.print(f" [yellow]![/yellow] {error}")
232
+ else:
233
+ console.print("[green]Manifest is valid![/green]")
234
+
235
+ # Print summary
236
+ console.print(f"\n[bold]Network:[/bold] {manifest.network.name}")
237
+ console.print(f"[bold]Nodes:[/bold] {len(manifest.nodes)}")
238
+ console.print(f"[bold]Introducers:[/bold] {len(manifest.get_introducers())}")
239
+ console.print(f"[bold]Storage Nodes:[/bold] {len(manifest.get_storage_nodes())}")
240
+
241
+ except Exception as e:
242
+ console.print(f"[red]Validation failed:[/red] {e}")
243
+ raise typer.Exit(1) from None
244
+
245
+
246
+ if __name__ == "__main__":
247
+ app()
@@ -0,0 +1,194 @@
1
+ """Network management CLI commands for RedundaNet."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Optional
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.table import Table
11
+
12
+ app = typer.Typer(help="Network management commands")
13
+ console = Console()
14
+
15
+
16
+ @app.command("join")
17
+ def join_network(
18
+ manifest_repo: Annotated[
19
+ Optional[str],
20
+ typer.Option("--repo", "-r", help="Git repository URL for the manifest"),
21
+ ] = None,
22
+ branch: Annotated[
23
+ str,
24
+ typer.Option("--branch", "-b", help="Git branch"),
25
+ ] = "main",
26
+ ) -> None:
27
+ """Join an existing RedundaNet network."""
28
+ from redundanet.core.config import load_settings
29
+
30
+ settings = load_settings()
31
+ repo = manifest_repo or settings.manifest_repo
32
+
33
+ if not repo:
34
+ console.print("[red]Error:[/red] No manifest repository specified")
35
+ console.print("Use --repo or set REDUNDANET_MANIFEST_REPO")
36
+ raise typer.Exit(1)
37
+
38
+ console.print(Panel(f"[bold]Joining RedundaNet Network[/bold]\nRepository: {repo}"))
39
+
40
+ with console.status("[bold green]Cloning manifest repository..."):
41
+ # In a real implementation, we'd clone the repo
42
+ console.print(f"[green]Repository:[/green] {repo}")
43
+ console.print(f"[green]Branch:[/green] {branch}")
44
+
45
+ console.print("\n[bold green]Successfully joined the network![/bold green]")
46
+ console.print("\n[bold]Next steps:[/bold]")
47
+ console.print("1. Start the VPN: [cyan]redundanet network vpn start[/cyan]")
48
+ console.print("2. Start storage: [cyan]redundanet storage start[/cyan]")
49
+
50
+
51
+ @app.command("leave")
52
+ def leave_network(
53
+ force: Annotated[
54
+ bool,
55
+ typer.Option("--force", "-f", help="Skip confirmation"),
56
+ ] = False,
57
+ ) -> None:
58
+ """Leave the current RedundaNet network."""
59
+ if not force:
60
+ confirm = typer.confirm("Are you sure you want to leave the network?")
61
+ if not confirm:
62
+ console.print("Aborted.")
63
+ raise typer.Exit(0)
64
+
65
+ with console.status("[bold yellow]Leaving network..."):
66
+ # In a real implementation, we'd stop services and cleanup
67
+ pass
68
+
69
+ console.print("[yellow]Left the RedundaNet network[/yellow]")
70
+
71
+
72
+ @app.command("status")
73
+ def network_status(
74
+ verbose: Annotated[
75
+ bool,
76
+ typer.Option("--verbose", "-v", help="Show detailed status"),
77
+ ] = False,
78
+ ) -> None:
79
+ """Show the status of the network connection."""
80
+ console.print(Panel("[bold]Network Status[/bold]", expand=False))
81
+
82
+ # VPN Status
83
+ table = Table(title="VPN Connection", show_header=True)
84
+ table.add_column("Property", style="cyan")
85
+ table.add_column("Value")
86
+
87
+ table.add_row("Interface", "tinc0")
88
+ table.add_row("Status", "[yellow]Unknown[/yellow]")
89
+ table.add_row("Connected Peers", "[dim]--[/dim]")
90
+ table.add_row("Local IP", "[dim]--[/dim]")
91
+
92
+ console.print(table)
93
+
94
+ if verbose:
95
+ # Peer list
96
+ console.print("\n[bold]Connected Peers:[/bold]")
97
+ console.print("[dim]No peer information available[/dim]")
98
+
99
+
100
+ @app.command("peers")
101
+ def list_peers(
102
+ online_only: Annotated[
103
+ bool,
104
+ typer.Option("--online", "-o", help="Show only online peers"),
105
+ ] = False,
106
+ ) -> None:
107
+ """List all peers in the network."""
108
+ table = Table(title="Network Peers")
109
+ table.add_column("Node", style="cyan")
110
+ table.add_column("VPN IP", style="green")
111
+ table.add_column("Status")
112
+ table.add_column("Latency")
113
+
114
+ # In a real implementation, we'd query the VPN for peer info
115
+ console.print("[dim]Peer discovery not yet implemented[/dim]")
116
+ console.print(table)
117
+
118
+
119
+ @app.command("ping")
120
+ def ping_node(
121
+ node_name: Annotated[str, typer.Argument(help="Name of the node to ping")],
122
+ count: Annotated[
123
+ int,
124
+ typer.Option("--count", "-c", help="Number of ping packets"),
125
+ ] = 4,
126
+ ) -> None:
127
+ """Ping a node in the network."""
128
+ console.print(f"[bold]Pinging node: {node_name}[/bold]")
129
+
130
+ # In a real implementation, we'd resolve the node IP and ping
131
+
132
+ # For now, just show a placeholder
133
+ console.print(f"[dim]Would ping {node_name} ({count} packets)[/dim]")
134
+
135
+
136
+ # VPN subcommands
137
+ vpn_app = typer.Typer(help="VPN management commands")
138
+ app.add_typer(vpn_app, name="vpn")
139
+
140
+
141
+ @vpn_app.command("start")
142
+ def vpn_start() -> None:
143
+ """Start the Tinc VPN connection."""
144
+ with console.status("[bold green]Starting VPN..."):
145
+ # In a real implementation, we'd start tinc
146
+ pass
147
+ console.print("[green]VPN started[/green]")
148
+
149
+
150
+ @vpn_app.command("stop")
151
+ def vpn_stop() -> None:
152
+ """Stop the Tinc VPN connection."""
153
+ with console.status("[bold yellow]Stopping VPN..."):
154
+ # In a real implementation, we'd stop tinc
155
+ pass
156
+ console.print("[yellow]VPN stopped[/yellow]")
157
+
158
+
159
+ @vpn_app.command("restart")
160
+ def vpn_restart() -> None:
161
+ """Restart the Tinc VPN connection."""
162
+ vpn_stop()
163
+ vpn_start()
164
+
165
+
166
+ @vpn_app.command("status")
167
+ def vpn_status() -> None:
168
+ """Show VPN status."""
169
+ table = Table(title="VPN Status", show_header=False)
170
+ table.add_column("Property", style="cyan")
171
+ table.add_column("Value")
172
+
173
+ table.add_row("Service", "[yellow]Unknown[/yellow]")
174
+ table.add_row("Interface", "tinc0")
175
+ table.add_row("Network", "redundanet")
176
+
177
+ console.print(table)
178
+
179
+
180
+ @vpn_app.command("logs")
181
+ def vpn_logs(
182
+ follow: Annotated[
183
+ bool,
184
+ typer.Option("--follow", "-f", help="Follow log output"),
185
+ ] = False,
186
+ lines: Annotated[
187
+ int,
188
+ typer.Option("--lines", "-n", help="Number of lines to show"),
189
+ ] = 50,
190
+ ) -> None:
191
+ """Show VPN logs."""
192
+ console.print(f"[dim]Would show last {lines} lines of VPN logs[/dim]")
193
+ if follow:
194
+ console.print("[dim]Following logs... (Ctrl+C to exit)[/dim]")