ndev-stack 0.3.15__tar.gz → 0.3.17__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.
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/PKG-INFO +1 -1
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/__init__.py +1 -1
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/db.py +196 -10
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/nginx_helper.py +1 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/PKG-INFO +1 -1
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/pyproject.toml +1 -1
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/tests/test_opensuse_compat.py +28 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/README.md +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/__main__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/cli.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/config.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/constants.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/distro.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/github.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/logger.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/manifest.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/base.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/mailpit/module.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/mongodb/module.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/postgres/module.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/redis/manifest.json +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/builtin/redis/module.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/modules/manager.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/common/utils.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/chroot/manager.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/chroot/packages.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/chroot/shell.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/cli.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/available.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/clean.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/ctl.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/current.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/doctor.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/grok.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/install.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/list.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/logs.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/mailpit.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/modules.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/mongodb.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/postgres.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/reload.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/restart.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/setup.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/start.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/status.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/stop.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/uninstall.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/update.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/upgrade.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/use.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/commands/vhost.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/builder.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/downloader.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/extensions.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/installer.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/resolver.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/php/templates.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/fpm.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/mailpit.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/pma.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/process.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/services.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/sockets.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/runtime/upgrade.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/linux/tui.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/main.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/tui.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/bin/shim.exe +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/cli.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/commands/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/__init__.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/db.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/elevate.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/ext.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/fcgi.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/grok.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/logs.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/mailpit.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/mkcert.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/paths.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/php.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/pma.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/redis_core.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/services.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/setup.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/shim.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/upgrade.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/core/vhost.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/templates/vhost.conf.tmpl +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/templates/vhost_ssl.conf.tmpl +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev/win/tui.py +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/SOURCES.txt +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/dependency_links.txt +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/entry_points.txt +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/requires.txt +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/ndev_stack.egg-info/top_level.txt +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/setup.cfg +0 -0
- {ndev_stack-0.3.15 → ndev_stack-0.3.17}/tests/test_shim.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndev-stack
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: Cross-platform developer stack manager for PHP, FastCGI/PHP-FPM, MariaDB, Redis, PostgreSQL, MongoDB, Nginx, SSL, and Mailpit on Linux and Windows
|
|
5
5
|
Author-email: Ankit Karan <ankit.karan99@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ankitkaran99/ndev
|
|
@@ -3,9 +3,11 @@ import sys
|
|
|
3
3
|
import shutil
|
|
4
4
|
import subprocess
|
|
5
5
|
import typer
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
7
8
|
from rich.console import Console
|
|
8
9
|
from ndev.common.logger import logger
|
|
10
|
+
from ndev.common.utils import which_binary
|
|
9
11
|
|
|
10
12
|
console = Console()
|
|
11
13
|
|
|
@@ -13,7 +15,8 @@ db_app = typer.Typer(help="Manage MySQL databases and users", invoke_without_com
|
|
|
13
15
|
|
|
14
16
|
# Shared options for commands
|
|
15
17
|
def execute_sql_mysql(host: str, port: int, user: str, password: Optional[str], sql: str) -> subprocess.CompletedProcess:
|
|
16
|
-
|
|
18
|
+
client = which_binary("mariadb") or which_binary("mysql") or "mysql"
|
|
19
|
+
cmd = [client, "-h", host, "-P", str(port), "-u", user, "--batch", "--skip-column-names"]
|
|
17
20
|
if password:
|
|
18
21
|
cmd.append(f"--password={password}")
|
|
19
22
|
return subprocess.run(cmd, input=sql, capture_output=True, text=True)
|
|
@@ -44,13 +47,20 @@ def run_wizard():
|
|
|
44
47
|
console.print(" 1) Create Database")
|
|
45
48
|
console.print(" 2) Drop Database")
|
|
46
49
|
console.print(" 3) Export Database (mysqldump)")
|
|
47
|
-
console.print(" 4)
|
|
48
|
-
console.print(" 5)
|
|
49
|
-
|
|
50
|
+
console.print(" 4) Import Database (.sql file)")
|
|
51
|
+
console.print(" 5) Create User")
|
|
52
|
+
console.print(" 6) Drop User")
|
|
53
|
+
op_choice = typer.prompt("Choice [1-6]", default=1, type=int)
|
|
50
54
|
|
|
51
|
-
action_map = {1: "create-db", 2: "drop-db", 3: "export-db", 4: "create-user",
|
|
55
|
+
action_map = {1: "create-db", 2: "drop-db", 3: "export-db", 4: "import-db", 5: "create-user", 6: "drop-user"}
|
|
52
56
|
action = action_map.get(op_choice, "create-db")
|
|
53
57
|
|
|
58
|
+
if action == "import-db":
|
|
59
|
+
sql_file = typer.prompt("Path to .sql file to import").strip()
|
|
60
|
+
target_db = typer.prompt("Database name (optional, press Enter to skip)", default="")
|
|
61
|
+
execute_import_db(host, port, admin_user, admin_password, sql_file, target_db or None)
|
|
62
|
+
return
|
|
63
|
+
|
|
54
64
|
target_name = typer.prompt("Database/Username")
|
|
55
65
|
if not target_name:
|
|
56
66
|
logger.error("Target name is required.")
|
|
@@ -160,6 +170,66 @@ def execute_export_db(
|
|
|
160
170
|
raise typer.Exit(code=1)
|
|
161
171
|
sys.stdout.write(res.stdout)
|
|
162
172
|
|
|
173
|
+
def execute_import_db(
|
|
174
|
+
host: str,
|
|
175
|
+
port: int,
|
|
176
|
+
user: str,
|
|
177
|
+
password: Optional[str],
|
|
178
|
+
sql_file: str,
|
|
179
|
+
database: Optional[str] = None,
|
|
180
|
+
):
|
|
181
|
+
sql_path = Path(sql_file).expanduser().resolve()
|
|
182
|
+
if not sql_path.exists():
|
|
183
|
+
logger.error(f"SQL file not found: {sql_path}")
|
|
184
|
+
raise typer.Exit(code=1)
|
|
185
|
+
|
|
186
|
+
client = which_binary("mariadb") or which_binary("mysql") or "mysql"
|
|
187
|
+
if not shutil.which(client):
|
|
188
|
+
logger.error(f"MySQL / MariaDB client ({client}) not found in PATH.")
|
|
189
|
+
raise typer.Exit(code=1)
|
|
190
|
+
|
|
191
|
+
# Resolve password if not provided
|
|
192
|
+
if password is None and user == "root":
|
|
193
|
+
res = subprocess.run([client, "-h", host, "-P", str(port), "-u", "root", "--password=root", "-e", "SELECT 1;"], capture_output=True)
|
|
194
|
+
if res.returncode == 0:
|
|
195
|
+
password = "root"
|
|
196
|
+
else:
|
|
197
|
+
res_blank = subprocess.run([client, "-h", host, "-P", str(port), "-u", "root", "-e", "SELECT 1;"], capture_output=True)
|
|
198
|
+
if res_blank.returncode == 0:
|
|
199
|
+
password = ""
|
|
200
|
+
else:
|
|
201
|
+
password = "root"
|
|
202
|
+
|
|
203
|
+
# If target database specified, ensure it exists
|
|
204
|
+
if database:
|
|
205
|
+
create_sql = f"CREATE DATABASE IF NOT EXISTS `{database}`;"
|
|
206
|
+
res_create = execute_sql_mysql(host, port, user, password, create_sql)
|
|
207
|
+
if res_create.returncode != 0:
|
|
208
|
+
logger.error(f"Failed to ensure database '{database}' exists: {res_create.stderr}")
|
|
209
|
+
raise typer.Exit(code=1)
|
|
210
|
+
|
|
211
|
+
cmd = [client, "-h", host, "-P", str(port), "-u", user]
|
|
212
|
+
if password:
|
|
213
|
+
cmd.append(f"--password={password}")
|
|
214
|
+
if database:
|
|
215
|
+
cmd.append(database)
|
|
216
|
+
|
|
217
|
+
target_desc = f"database '{database}'" if database else "MariaDB / MySQL"
|
|
218
|
+
console.print(f"[bold yellow]Importing '{sql_path.name}' ({sql_path.stat().st_size:,} bytes) into {target_desc}...[/bold yellow]")
|
|
219
|
+
|
|
220
|
+
try:
|
|
221
|
+
with open(sql_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
222
|
+
res = subprocess.run(cmd, stdin=f, capture_output=True, text=True)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"Failed to read SQL file '{sql_path}': {e}")
|
|
225
|
+
raise typer.Exit(code=1)
|
|
226
|
+
|
|
227
|
+
if res.returncode != 0:
|
|
228
|
+
logger.error(f"MySQL Import Error:\n{res.stderr.strip()}")
|
|
229
|
+
raise typer.Exit(code=1)
|
|
230
|
+
|
|
231
|
+
console.print(f"[bold green]✓ Successfully imported '{sql_path.name}' into {target_desc}![/bold green]")
|
|
232
|
+
|
|
163
233
|
def execute_create_user(host: str, port: int, user: str, password: Optional[str], new_user: str, new_pass: str, grant_db: Optional[str], user_host: str):
|
|
164
234
|
logger.info(f"Creating user '{new_user}' on MySQL...")
|
|
165
235
|
if not shutil.which("mysql"):
|
|
@@ -273,6 +343,49 @@ def export_db_cmd(
|
|
|
273
343
|
triggers=triggers
|
|
274
344
|
)
|
|
275
345
|
|
|
346
|
+
@db_app.command("import")
|
|
347
|
+
@db_app.command("import-db")
|
|
348
|
+
def import_db_cmd(
|
|
349
|
+
target: Optional[str] = typer.Argument(None, help="Path to .sql file OR database name"),
|
|
350
|
+
file_or_db: Optional[str] = typer.Argument(None, help="Path to .sql file if database name was specified first, or database name"),
|
|
351
|
+
database: Optional[str] = typer.Option(None, "--database", "-d", "--name", "-n", help="Target database name (optional if SQL dump contains CREATE DATABASE / USE)"),
|
|
352
|
+
host: str = typer.Option("localhost", "--host", "-h", help="Database host"),
|
|
353
|
+
port: int = typer.Option(3306, "--port", "-P", help="Database port"),
|
|
354
|
+
user: str = typer.Option("root", "--user", "-u", help="Admin username"),
|
|
355
|
+
password: Optional[str] = typer.Option(None, "--password", "-p", help="Admin password (default: root)"),
|
|
356
|
+
):
|
|
357
|
+
"""Import a SQL dump file into MariaDB/MySQL."""
|
|
358
|
+
sql_file = None
|
|
359
|
+
target_db = database
|
|
360
|
+
|
|
361
|
+
if target:
|
|
362
|
+
p_target = Path(target).expanduser()
|
|
363
|
+
if p_target.exists() or target.endswith(".sql"):
|
|
364
|
+
sql_file = str(p_target)
|
|
365
|
+
if file_or_db and not target_db:
|
|
366
|
+
target_db = file_or_db
|
|
367
|
+
else:
|
|
368
|
+
target_db = target
|
|
369
|
+
if file_or_db:
|
|
370
|
+
sql_file = str(Path(file_or_db).expanduser())
|
|
371
|
+
elif file_or_db:
|
|
372
|
+
sql_file = str(Path(file_or_db).expanduser())
|
|
373
|
+
|
|
374
|
+
if not sql_file:
|
|
375
|
+
sql_file = typer.prompt("Path to .sql file").strip()
|
|
376
|
+
if not sql_file:
|
|
377
|
+
logger.error("SQL file path is required.")
|
|
378
|
+
raise typer.Exit(code=1)
|
|
379
|
+
|
|
380
|
+
execute_import_db(
|
|
381
|
+
host=host,
|
|
382
|
+
port=port,
|
|
383
|
+
user=user,
|
|
384
|
+
password=password,
|
|
385
|
+
sql_file=sql_file,
|
|
386
|
+
database=target_db,
|
|
387
|
+
)
|
|
388
|
+
|
|
276
389
|
@db_app.command("create-user")
|
|
277
390
|
def create_user_cmd(
|
|
278
391
|
username: str = typer.Argument(None, help="Username"),
|
|
@@ -328,9 +441,65 @@ def configure_mariadb_root_password(new_password: str = "root", sudo_password: O
|
|
|
328
441
|
from ndev.linux.runtime.nginx_helper import run_privileged_command
|
|
329
442
|
from ndev.common.utils import which_binary
|
|
330
443
|
|
|
444
|
+
client_bin = which_binary("mariadb") or which_binary("mysql") or "mariadb"
|
|
445
|
+
|
|
446
|
+
# 1. Ensure MariaDB service is active
|
|
447
|
+
try:
|
|
448
|
+
run_privileged_command(["systemctl", "start", "mariadb"], password=sudo_password, check=False)
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|
|
451
|
+
|
|
452
|
+
# 2. Detect MariaDB unix socket path
|
|
453
|
+
socket_candidates = [
|
|
454
|
+
Path("/run/mysql/mysql.sock"),
|
|
455
|
+
Path("/run/mysqld/mysqld.sock"),
|
|
456
|
+
Path("/var/run/mysqld/mysqld.sock"),
|
|
457
|
+
Path("/var/lib/mysql/mysql.sock"),
|
|
458
|
+
]
|
|
459
|
+
socket_args: list[str] = []
|
|
460
|
+
for s in socket_candidates:
|
|
461
|
+
if s.exists():
|
|
462
|
+
socket_args = ["-S", str(s)]
|
|
463
|
+
break
|
|
464
|
+
|
|
465
|
+
# 3. Dual auth (unix_socket + password) attempt for localhost
|
|
466
|
+
dual_auth_sql = (
|
|
467
|
+
f"ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('{new_password}') OR unix_socket; "
|
|
468
|
+
"FLUSH PRIVILEGES;"
|
|
469
|
+
)
|
|
470
|
+
try:
|
|
471
|
+
run_privileged_command([client_bin] + socket_args + ["-u", "root", "-e", dual_auth_sql], password=sudo_password, check=False)
|
|
472
|
+
except Exception as e:
|
|
473
|
+
logger.debug(f"Dual auth attempt: {e}")
|
|
474
|
+
|
|
475
|
+
# 4. Standard root password & network user creation
|
|
476
|
+
batch_sql = (
|
|
477
|
+
f"ALTER USER 'root'@'localhost' IDENTIFIED BY '{new_password}'; "
|
|
478
|
+
f"CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}'; "
|
|
479
|
+
f"ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}'; "
|
|
480
|
+
f"CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{new_password}'; "
|
|
481
|
+
f"ALTER USER 'root'@'%' IDENTIFIED BY '{new_password}'; "
|
|
482
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION; "
|
|
483
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION; "
|
|
484
|
+
"GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION; "
|
|
485
|
+
"FLUSH PRIVILEGES;"
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
try:
|
|
489
|
+
run_privileged_command([client_bin] + socket_args + ["-u", "root", "-f", "-e", batch_sql], password=sudo_password, check=False)
|
|
490
|
+
except Exception as e:
|
|
491
|
+
logger.debug(f"Batch SQL config: {e}")
|
|
492
|
+
|
|
493
|
+
# Also try if root already has password set
|
|
494
|
+
try:
|
|
495
|
+
run_privileged_command([client_bin] + socket_args + ["-u", "root", f"--password={new_password}", "-f", "-e", batch_sql], password=sudo_password, check=False)
|
|
496
|
+
except Exception:
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
# 5. Fallback individual statements
|
|
331
500
|
sql_statements = [
|
|
332
|
-
f"ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('{new_password}') OR unix_socket;",
|
|
333
501
|
f"ALTER USER 'root'@'localhost' IDENTIFIED BY '{new_password}';",
|
|
502
|
+
f"SET PASSWORD FOR 'root'@'localhost' = PASSWORD('{new_password}');",
|
|
334
503
|
f"CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}';",
|
|
335
504
|
f"ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}';",
|
|
336
505
|
f"CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{new_password}';",
|
|
@@ -341,16 +510,14 @@ def configure_mariadb_root_password(new_password: str = "root", sudo_password: O
|
|
|
341
510
|
"FLUSH PRIVILEGES;",
|
|
342
511
|
]
|
|
343
512
|
|
|
344
|
-
client_bin = which_binary("mariadb") or which_binary("mysql") or "mariadb"
|
|
345
|
-
|
|
346
513
|
for sql in sql_statements:
|
|
347
514
|
try:
|
|
348
|
-
cmd = [client_bin
|
|
515
|
+
cmd = [client_bin] + socket_args + ["-u", "root", "-e", sql]
|
|
349
516
|
run_privileged_command(cmd, password=sudo_password, check=False)
|
|
350
517
|
except Exception:
|
|
351
518
|
pass
|
|
352
519
|
|
|
353
|
-
# Verify if connection succeeds via TCP
|
|
520
|
+
# 6. Verify if connection succeeds via TCP
|
|
354
521
|
for h in ["127.0.0.1", "localhost"]:
|
|
355
522
|
try:
|
|
356
523
|
test_cmd = [client_bin, "-h", h, "-P", "3306", "-u", "root", f"--password={new_password}", "-e", "SELECT 1;"]
|
|
@@ -369,6 +536,25 @@ def set_root_password_cmd(
|
|
|
369
536
|
password: str = typer.Option("root", "--password", "-p", help="New root password (default: root)"),
|
|
370
537
|
):
|
|
371
538
|
"""Set or reset MariaDB root password and enable password login across localhost and 127.0.0.1."""
|
|
539
|
+
if os.geteuid() != 0:
|
|
540
|
+
console.print("[bold yellow]MariaDB root configuration requires administrator privileges. Elevating via sudo...[/bold yellow]")
|
|
541
|
+
cmd = [
|
|
542
|
+
"sudo",
|
|
543
|
+
sys.executable,
|
|
544
|
+
"-m",
|
|
545
|
+
"ndev",
|
|
546
|
+
"db",
|
|
547
|
+
"set-root-password",
|
|
548
|
+
"--password",
|
|
549
|
+
password,
|
|
550
|
+
]
|
|
551
|
+
try:
|
|
552
|
+
res = subprocess.run(cmd)
|
|
553
|
+
raise typer.Exit(code=res.returncode)
|
|
554
|
+
except KeyboardInterrupt:
|
|
555
|
+
console.print("\n[red]Cancelled by user.[/red]")
|
|
556
|
+
raise typer.Exit(code=130)
|
|
557
|
+
|
|
372
558
|
console.print(f"[bold yellow]Configuring MariaDB root access with password '{password}'...[/bold yellow]")
|
|
373
559
|
ok = configure_mariadb_root_password(new_password=password)
|
|
374
560
|
if ok:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndev-stack
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.17
|
|
4
4
|
Summary: Cross-platform developer stack manager for PHP, FastCGI/PHP-FPM, MariaDB, Redis, PostgreSQL, MongoDB, Nginx, SSL, and Mailpit on Linux and Windows
|
|
5
5
|
Author-email: Ankit Karan <ankit.karan99@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ankitkaran99/ndev
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ndev-stack"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.17"
|
|
8
8
|
description = "Cross-platform developer stack manager for PHP, FastCGI/PHP-FPM, MariaDB, Redis, PostgreSQL, MongoDB, Nginx, SSL, and Mailpit on Linux and Windows"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -559,6 +559,34 @@ class TestDatabaseCommands(unittest.TestCase):
|
|
|
559
559
|
self.assertTrue(ok)
|
|
560
560
|
mock_priv.assert_called()
|
|
561
561
|
|
|
562
|
+
def test_set_root_password_cmd_elevates_when_non_root(self):
|
|
563
|
+
import typer
|
|
564
|
+
from ndev.linux.commands.db import set_root_password_cmd
|
|
565
|
+
with patch("os.geteuid", return_value=1000):
|
|
566
|
+
with patch("subprocess.run") as mock_run:
|
|
567
|
+
mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0)
|
|
568
|
+
with self.assertRaises((SystemExit, typer.Exit)) as cm:
|
|
569
|
+
set_root_password_cmd(password="root")
|
|
570
|
+
exit_code = getattr(cm.exception, "exit_code", getattr(cm.exception, "code", 0))
|
|
571
|
+
self.assertEqual(exit_code, 0)
|
|
572
|
+
mock_run.assert_called_once()
|
|
573
|
+
self.assertIn("sudo", mock_run.call_args[0][0])
|
|
574
|
+
self.assertIn("set-root-password", mock_run.call_args[0][0])
|
|
575
|
+
|
|
576
|
+
def test_import_db_cmd(self):
|
|
577
|
+
import tempfile
|
|
578
|
+
from ndev.linux.commands.db import import_db_cmd
|
|
579
|
+
with tempfile.NamedTemporaryFile(suffix=".sql") as tf:
|
|
580
|
+
tf.write(b"SELECT 1;\n")
|
|
581
|
+
tf.flush()
|
|
582
|
+
with patch("subprocess.run") as mock_run:
|
|
583
|
+
mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0)
|
|
584
|
+
import_db_cmd(target=tf.name, database="testdb", user="root", password="root")
|
|
585
|
+
# Ensure subprocess.run was invoked with mysql/mariadb and the target database
|
|
586
|
+
cmd_args = mock_run.call_args[0][0]
|
|
587
|
+
self.assertIn("testdb", cmd_args)
|
|
588
|
+
self.assertIn("--password=root", cmd_args)
|
|
589
|
+
|
|
562
590
|
|
|
563
591
|
class TestTuiEnsureSudo(unittest.TestCase):
|
|
564
592
|
def test_ensure_sudo_when_not_needed(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|