ndev-stack 0.3.13__tar.gz → 0.3.15__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.
Files changed (106) hide show
  1. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/PKG-INFO +1 -1
  2. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/__init__.py +1 -1
  3. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/db.py +61 -0
  4. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/doctor.py +19 -1
  5. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/setup.py +80 -22
  6. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/nginx_helper.py +75 -2
  7. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/PKG-INFO +1 -1
  8. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/pyproject.toml +1 -1
  9. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/tests/test_opensuse_compat.py +11 -0
  10. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/README.md +0 -0
  11. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/__main__.py +0 -0
  12. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/cli.py +0 -0
  13. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/__init__.py +0 -0
  14. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/config.py +0 -0
  15. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/constants.py +0 -0
  16. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/distro.py +0 -0
  17. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/github.py +0 -0
  18. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/logger.py +0 -0
  19. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/manifest.py +0 -0
  20. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/__init__.py +0 -0
  21. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/base.py +0 -0
  22. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
  23. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/mailpit/module.py +0 -0
  24. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
  25. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/mongodb/module.py +0 -0
  26. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
  27. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/postgres/module.py +0 -0
  28. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/redis/manifest.json +0 -0
  29. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/builtin/redis/module.py +0 -0
  30. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/modules/manager.py +0 -0
  31. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/common/utils.py +0 -0
  32. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/__init__.py +0 -0
  33. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/chroot/manager.py +0 -0
  34. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/chroot/packages.py +0 -0
  35. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/chroot/shell.py +0 -0
  36. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/cli.py +0 -0
  37. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/available.py +0 -0
  38. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/clean.py +0 -0
  39. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/ctl.py +0 -0
  40. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/current.py +0 -0
  41. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/grok.py +0 -0
  42. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/install.py +0 -0
  43. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/list.py +0 -0
  44. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/logs.py +0 -0
  45. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/mailpit.py +0 -0
  46. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/modules.py +0 -0
  47. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/mongodb.py +0 -0
  48. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/postgres.py +0 -0
  49. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/reload.py +0 -0
  50. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/restart.py +0 -0
  51. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/start.py +0 -0
  52. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/status.py +0 -0
  53. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/stop.py +0 -0
  54. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/uninstall.py +0 -0
  55. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/update.py +0 -0
  56. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/upgrade.py +0 -0
  57. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/use.py +0 -0
  58. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/commands/vhost.py +0 -0
  59. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/builder.py +0 -0
  60. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/downloader.py +0 -0
  61. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/extensions.py +0 -0
  62. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/installer.py +0 -0
  63. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/resolver.py +0 -0
  64. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/php/templates.py +0 -0
  65. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/fpm.py +0 -0
  66. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/mailpit.py +0 -0
  67. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/pma.py +0 -0
  68. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/process.py +0 -0
  69. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/services.py +0 -0
  70. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/sockets.py +0 -0
  71. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/runtime/upgrade.py +0 -0
  72. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/linux/tui.py +0 -0
  73. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/main.py +0 -0
  74. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/tui.py +0 -0
  75. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/__init__.py +0 -0
  76. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/bin/shim.exe +0 -0
  77. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/cli.py +0 -0
  78. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/commands/__init__.py +0 -0
  79. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/__init__.py +0 -0
  80. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/db.py +0 -0
  81. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/elevate.py +0 -0
  82. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/ext.py +0 -0
  83. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/fcgi.py +0 -0
  84. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/grok.py +0 -0
  85. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/logs.py +0 -0
  86. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/mailpit.py +0 -0
  87. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/mkcert.py +0 -0
  88. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/paths.py +0 -0
  89. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/php.py +0 -0
  90. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/pma.py +0 -0
  91. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/redis_core.py +0 -0
  92. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/services.py +0 -0
  93. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/setup.py +0 -0
  94. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/shim.py +0 -0
  95. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/upgrade.py +0 -0
  96. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/core/vhost.py +0 -0
  97. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/templates/vhost.conf.tmpl +0 -0
  98. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/templates/vhost_ssl.conf.tmpl +0 -0
  99. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev/win/tui.py +0 -0
  100. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/SOURCES.txt +0 -0
  101. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/dependency_links.txt +0 -0
  102. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/entry_points.txt +0 -0
  103. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/requires.txt +0 -0
  104. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/ndev_stack.egg-info/top_level.txt +0 -0
  105. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/setup.cfg +0 -0
  106. {ndev_stack-0.3.13 → ndev_stack-0.3.15}/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.13
3
+ Version: 0.3.15
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
@@ -1,7 +1,7 @@
1
1
  """
2
2
  ndev: Fast, cross-platform PHP developer environment for Windows and Linux.
3
3
  """
4
- __version__ = "0.3.13"
4
+ __version__ = "0.3.15"
5
5
 
6
6
  from . import common as common
7
7
  from .common.logger import logger as logger
@@ -317,3 +317,64 @@ def drop_user_cmd(
317
317
  confirm_destructive(force, username, f"You are about to DROP user '{username}' permanently.")
318
318
  execute_drop_user(host, port, user, password, username, user_host)
319
319
 
320
+
321
+ def configure_mariadb_root_password(new_password: str = "root", sudo_password: Optional[str] = None) -> bool:
322
+ """
323
+ Configure MariaDB root account to allow password authentication (e.g. root/root)
324
+ across localhost, 127.0.0.1, and %.
325
+ Fixes ERROR 1698 (28000): Access denied for user 'root'@'localhost'
326
+ caused by default unix_socket authentication on Linux.
327
+ """
328
+ from ndev.linux.runtime.nginx_helper import run_privileged_command
329
+ from ndev.common.utils import which_binary
330
+
331
+ sql_statements = [
332
+ f"ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('{new_password}') OR unix_socket;",
333
+ f"ALTER USER 'root'@'localhost' IDENTIFIED BY '{new_password}';",
334
+ f"CREATE USER IF NOT EXISTS 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}';",
335
+ f"ALTER USER 'root'@'127.0.0.1' IDENTIFIED BY '{new_password}';",
336
+ f"CREATE USER IF NOT EXISTS 'root'@'%' IDENTIFIED BY '{new_password}';",
337
+ f"ALTER USER 'root'@'%' IDENTIFIED BY '{new_password}';",
338
+ "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION;",
339
+ "GRANT ALL PRIVILEGES ON *.* TO 'root'@'127.0.0.1' WITH GRANT OPTION;",
340
+ "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;",
341
+ "FLUSH PRIVILEGES;",
342
+ ]
343
+
344
+ client_bin = which_binary("mariadb") or which_binary("mysql") or "mariadb"
345
+
346
+ for sql in sql_statements:
347
+ try:
348
+ cmd = [client_bin, "-u", "root", "-e", sql]
349
+ run_privileged_command(cmd, password=sudo_password, check=False)
350
+ except Exception:
351
+ pass
352
+
353
+ # Verify if connection succeeds via TCP
354
+ for h in ["127.0.0.1", "localhost"]:
355
+ try:
356
+ test_cmd = [client_bin, "-h", h, "-P", "3306", "-u", "root", f"--password={new_password}", "-e", "SELECT 1;"]
357
+ res = subprocess.run(test_cmd, capture_output=True, text=True, timeout=5)
358
+ if res.returncode == 0:
359
+ return True
360
+ except Exception:
361
+ pass
362
+
363
+ return False
364
+
365
+
366
+ @db_app.command("set-root-password")
367
+ @db_app.command("reset-root")
368
+ def set_root_password_cmd(
369
+ password: str = typer.Option("root", "--password", "-p", help="New root password (default: root)"),
370
+ ):
371
+ """Set or reset MariaDB root password and enable password login across localhost and 127.0.0.1."""
372
+ console.print(f"[bold yellow]Configuring MariaDB root access with password '{password}'...[/bold yellow]")
373
+ ok = configure_mariadb_root_password(new_password=password)
374
+ if ok:
375
+ console.print(f"[bold green]✓ MariaDB root password configured successfully! (user: root, password: {password})[/bold green]")
376
+ console.print(f"[dim]You can now connect via phpMyAdmin, Laravel, or CLI using user 'root' and password '{password}'.[/dim]")
377
+ else:
378
+ logger.error("Failed to configure MariaDB root password. Ensure MariaDB is running and you have administrator privileges.")
379
+ raise typer.Exit(code=1)
380
+
@@ -64,7 +64,25 @@ def doctor_cmd():
64
64
 
65
65
  db_path = which_binary("mariadb") or which_binary("mysql") or which_binary("mysqld")
66
66
  if db_path:
67
- table.add_row("MariaDB / MySQL", "[green]OK[/green]", f"Found at {db_path}")
67
+ db_details = f"Found at {db_path}"
68
+ connected = False
69
+ client_bin = which_binary("mariadb") or which_binary("mysql") or db_path
70
+ for test_pwd in ["root", ""]:
71
+ try:
72
+ cmd = [client_bin, "-h", "127.0.0.1", "-P", "3306", "-u", "root", f"--password={test_pwd}", "-e", "SELECT 1;"]
73
+ r = subprocess.run(cmd, capture_output=True, timeout=2)
74
+ if r.returncode == 0:
75
+ connected = True
76
+ pwd_label = f"password: '{test_pwd}'" if test_pwd else "no password"
77
+ db_details += f" (127.0.0.1 root access: {pwd_label})"
78
+ break
79
+ except Exception:
80
+ pass
81
+ if not connected:
82
+ db_details += " (127.0.0.1 root access restricted: run 'ndev db set-root-password')"
83
+ table.add_row("MariaDB / MySQL", "[yellow]RESTRICTED[/yellow]", db_details)
84
+ else:
85
+ table.add_row("MariaDB / MySQL", "[green]OK[/green]", db_details)
68
86
  else:
69
87
  table.add_row("MariaDB / MySQL", "[yellow]NOT INSTALLED[/yellow]", f"Run 'ndev setup' or: {stack_install_cmd}")
70
88
 
@@ -51,9 +51,9 @@ def setup_cmd():
51
51
  console.print("[bold yellow]Refreshing openSUSE repositories (sudo zypper refresh)...[/bold yellow]")
52
52
  subprocess.run(["sudo", "zypper", "--non-interactive", "refresh"], check=True)
53
53
 
54
- # 2. Install MariaDB, Nginx, curl, and Bubblewrap
55
- console.print("\n[bold yellow]Installing MariaDB, Nginx, curl, and Bubblewrap (sudo zypper install)...[/bold yellow]")
56
- packages = ["mariadb", "mariadb-client", "nginx", "curl", "bubblewrap"]
54
+ # 2. Install MariaDB, Nginx, curl, Bubblewrap, and SELinux tools
55
+ console.print("\n[bold yellow]Installing MariaDB, Nginx, curl, Bubblewrap, and system tools (sudo zypper install)...[/bold yellow]")
56
+ packages = ["mariadb", "mariadb-client", "nginx", "curl", "bubblewrap", "policycoreutils-python-utils"]
57
57
  subprocess.run(["sudo", "zypper", "--non-interactive", "install", "-y"] + packages, check=True)
58
58
 
59
59
  # Optional mkcert package
@@ -65,27 +65,16 @@ def setup_cmd():
65
65
  except Exception:
66
66
  pass
67
67
 
68
- try:
69
- subprocess.run(["sudo", "systemctl", "enable", "--now", "mariadb"], check=False, capture_output=True)
70
- subprocess.run(["sudo", "systemctl", "enable", "--now", "nginx"], check=False, capture_output=True)
71
- except Exception:
72
- pass
73
-
74
- try:
75
- from ndev.linux.runtime.nginx_helper import ensure_selinux_httpd_permissions
76
- ensure_selinux_httpd_permissions()
77
- except Exception:
78
- pass
79
-
80
- console.print("[bold green]MariaDB, Nginx, and Bubblewrap installed and configured successfully![/bold green]\n")
68
+ console.print("[bold green]System packages installed successfully![/bold green]\n")
81
69
  else:
82
70
  # 1. Update Package Lists
83
71
  console.print("[bold yellow]Updating package lists (sudo apt-get update)...[/bold yellow]")
84
72
  subprocess.run(["sudo", "apt-get", "update"], check=True)
85
73
 
86
74
  # 2. Install MariaDB and Nginx
87
- console.print("\n[bold yellow]Installing MariaDB and Nginx (sudo apt-get install)...[/bold yellow]")
88
- subprocess.run(["sudo", "apt-get", "install", "-y", "mariadb-server", "nginx", "curl"], check=True)
75
+ console.print("\n[bold yellow]Installing MariaDB, Nginx, curl, Bubblewrap, and system tools (sudo apt-get install)...[/bold yellow]")
76
+ packages = ["mariadb-server", "nginx", "curl", "bubblewrap", "policycoreutils-python-utils"]
77
+ subprocess.run(["sudo", "apt-get", "install", "-y"] + packages, check=True)
89
78
 
90
79
  # Optional mkcert package
91
80
  try:
@@ -96,9 +85,73 @@ def setup_cmd():
96
85
  except Exception:
97
86
  pass
98
87
 
99
- console.print("[bold green]MariaDB and Nginx installed successfully![/bold green]\n")
100
-
101
- # 3. Install Composer
88
+ console.print("[bold green]System packages installed successfully![/bold green]\n")
89
+
90
+ # 3. Auto-configure Stack Services, Permissions, SSL, and SELinux
91
+ console.print("[bold yellow]Auto-configuring stack services, directories, SSL, and SELinux...[/bold yellow]")
92
+ from ndev.linux.runtime.nginx_helper import (
93
+ ensure_selinux_httpd_permissions,
94
+ ensure_nginx_ssl_dir,
95
+ ensure_nginx_sites_enabled_included,
96
+ ensure_development_directories,
97
+ reload_nginx,
98
+ )
99
+
100
+ # Enable & start system services
101
+ try:
102
+ subprocess.run(["sudo", "systemctl", "enable", "--now", "mariadb"], check=False, capture_output=True)
103
+ subprocess.run(["sudo", "systemctl", "enable", "--now", "nginx"], check=False, capture_output=True)
104
+ console.print(" [bold green]✓[/bold green] Enabled and started MariaDB and Nginx system services.")
105
+ except Exception:
106
+ pass
107
+
108
+ # SELinux Permissions
109
+ try:
110
+ ok = ensure_selinux_httpd_permissions()
111
+ if ok:
112
+ console.print(" [bold green]✓[/bold green] Configured SELinux permissions for Nginx (httpd_t set to permissive).")
113
+ except Exception as e:
114
+ logger.warning(f"SELinux configuration notice: {e}")
115
+
116
+ # Nginx SSL Directory (/etc/nginx/ssl)
117
+ try:
118
+ ensure_nginx_ssl_dir()
119
+ console.print(" [bold green]✓[/bold green] Configured Nginx SSL certificate directory (/etc/nginx/ssl).")
120
+ except Exception as e:
121
+ logger.warning(f"SSL directory configuration notice: {e}")
122
+
123
+ # Nginx Sites-Enabled / Vhosts Bridge
124
+ try:
125
+ ensure_nginx_sites_enabled_included()
126
+ console.print(" [bold green]✓[/bold green] Configured Nginx sites-available/sites-enabled directory bridge.")
127
+ except Exception as e:
128
+ logger.warning(f"Nginx bridge configuration notice: {e}")
129
+
130
+ # Web development root permissions and docroot traversal
131
+ try:
132
+ ensure_development_directories()
133
+ console.print(" [bold green]✓[/bold green] Configured web development root permissions (~/Sites).")
134
+ except Exception as e:
135
+ logger.warning(f"Docroot permission notice: {e}")
136
+
137
+ # Reload Nginx
138
+ try:
139
+ reload_nginx()
140
+ console.print(" [bold green]✓[/bold green] Nginx web server configuration reloaded.")
141
+ except Exception:
142
+ pass
143
+
144
+ # MariaDB Root User Authentication (root / root)
145
+ try:
146
+ from ndev.linux.commands.db import configure_mariadb_root_password
147
+ if configure_mariadb_root_password(new_password="root"):
148
+ console.print(" [bold green]✓[/bold green] Configured MariaDB root access (user: root, password: root).")
149
+ else:
150
+ console.print(" [yellow]Notice: MariaDB root access could not be automatically set to root/root.[/yellow]")
151
+ except Exception as e:
152
+ logger.warning(f"MariaDB root configuration notice: {e}")
153
+
154
+ # 4. Install Composer
102
155
  local_bin = get_user_local_bin_dir()
103
156
  composer_bin = local_bin / "composer"
104
157
  if composer_bin.exists():
@@ -150,7 +203,12 @@ def setup_cmd():
150
203
  chown_to_sudo_user(ndev_link)
151
204
  console.print(f"[bold green]Created symlink {ndev_link} -> {ndev_venv_path}[/bold green]")
152
205
 
153
- console.print("\n[bold green]System setup completed successfully![/bold green]")
206
+ console.print("\n[bold green]System setup completed successfully![/bold green]\n")
207
+ try:
208
+ from ndev.linux.commands.doctor import doctor_cmd
209
+ doctor_cmd()
210
+ except Exception:
211
+ pass
154
212
 
155
213
  except subprocess.CalledProcessError as e:
156
214
  logger.error(f"Command execution failed: {e}")
@@ -74,14 +74,26 @@ def run_privileged_command(
74
74
  capture_output: bool = True
75
75
  ) -> subprocess.CompletedProcess:
76
76
  """
77
- Run a command with elevated privileges using sudo -n if non-root.
77
+ Run a command with elevated privileges using sudo if non-root.
78
78
  Validates sudo password first if provided to refresh the sudo credential timestamp.
79
+ If no password provided:
80
+ - Uses sudo -n if not in an interactive terminal or if sudo timestamp is active.
81
+ - Falls back to interactive sudo if in an interactive terminal.
79
82
  """
80
83
  full_cmd = list(cmd)
81
84
  if os.geteuid() != 0:
82
85
  if password:
83
86
  validate_sudo_password(password)
84
- full_cmd = ["sudo", "-n"] + full_cmd
87
+ full_cmd = ["sudo", "-n"] + full_cmd
88
+ else:
89
+ if not check_sudo_needs_password():
90
+ full_cmd = ["sudo", "-n"] + full_cmd
91
+ elif sys.stdin.isatty():
92
+ full_cmd = ["sudo"] + full_cmd
93
+ capture_output = False
94
+ else:
95
+ full_cmd = ["sudo", "-n"] + full_cmd
96
+
85
97
  return subprocess.run(
86
98
  full_cmd,
87
99
  check=check,
@@ -156,6 +168,67 @@ def ensure_selinux_httpd_permissions(password: Optional[str] = None) -> bool:
156
168
  return success or is_selinux_httpd_permissive()
157
169
 
158
170
 
171
+ def ensure_nginx_ssl_dir(password: Optional[str] = None) -> Path:
172
+ """
173
+ Ensure /etc/nginx/ssl directory exists with 0755 permissions and proper SELinux context.
174
+ """
175
+ ssl_dir = Path("/etc/nginx/ssl")
176
+ if os.geteuid() == 0:
177
+ ssl_dir.mkdir(parents=True, exist_ok=True)
178
+ try:
179
+ os.chmod(str(ssl_dir), 0o755)
180
+ except Exception:
181
+ pass
182
+ else:
183
+ run_privileged_command(["mkdir", "-p", str(ssl_dir)], password=password, check=False)
184
+ run_privileged_command(["chmod", "755", str(ssl_dir)], password=password, check=False)
185
+
186
+ if shutil.which("restorecon"):
187
+ run_privileged_command(["restorecon", "-Rv", str(ssl_dir)], password=password, check=False)
188
+
189
+ return ssl_dir
190
+
191
+
192
+ def ensure_development_directories(password: Optional[str] = None) -> None:
193
+ """
194
+ Ensure the user home directory has +x traversal permissions,
195
+ ~/Sites directory exists with 0755 permissions, and any configured vhost docroots are accessible.
196
+ """
197
+ home = Path.home()
198
+ sudo_user = os.environ.get("SUDO_USER")
199
+ if sudo_user:
200
+ try:
201
+ import pwd
202
+ home = Path(pwd.getpwnam(sudo_user).pw_dir)
203
+ except Exception:
204
+ pass
205
+
206
+ # Ensure home has traversal (+x)
207
+ try:
208
+ st = home.stat()
209
+ if not (st.st_mode & 0o001):
210
+ os.chmod(str(home), st.st_mode | 0o001)
211
+ except Exception:
212
+ pass
213
+
214
+ # Ensure ~/Sites exists
215
+ sites_dir = home / "Sites"
216
+ try:
217
+ sites_dir.mkdir(parents=True, exist_ok=True)
218
+ os.chmod(str(sites_dir), 0o755)
219
+ except Exception:
220
+ pass
221
+
222
+ # Ensure all configured vhost docroots are accessible
223
+ try:
224
+ for vhost in list_vhosts():
225
+ docroot = vhost.get("root")
226
+ if docroot and Path(docroot).exists():
227
+ ensure_docroot_accessible(Path(docroot))
228
+ except Exception:
229
+ pass
230
+
231
+
159
232
  def write_privileged_file(target: Path, content: str, password: Optional[str] = None) -> None:
160
233
  """
161
234
  Write content to a file, using sudo tee if current process lacks permissions.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ndev-stack
3
- Version: 0.3.13
3
+ Version: 0.3.15
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.13"
7
+ version = "0.3.15"
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"
@@ -549,6 +549,17 @@ class TestTuiSelectEventHandling(unittest.TestCase):
549
549
  app._switch_php_version.assert_called_once_with("8.4.27")
550
550
 
551
551
 
552
+ class TestDatabaseCommands(unittest.TestCase):
553
+ def test_configure_mariadb_root_password(self):
554
+ from ndev.linux.commands.db import configure_mariadb_root_password
555
+ with patch("ndev.linux.runtime.nginx_helper.run_privileged_command") as mock_priv:
556
+ with patch("subprocess.run") as mock_run:
557
+ mock_run.return_value = subprocess.CompletedProcess(args=[], returncode=0)
558
+ ok = configure_mariadb_root_password(new_password="root")
559
+ self.assertTrue(ok)
560
+ mock_priv.assert_called()
561
+
562
+
552
563
  class TestTuiEnsureSudo(unittest.TestCase):
553
564
  def test_ensure_sudo_when_not_needed(self):
554
565
  from ndev.linux.tui import NdevDashboard
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes