ndev-stack 0.3.4__tar.gz → 0.3.5__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.4 → ndev_stack-0.3.5}/PKG-INFO +1 -1
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/__init__.py +1 -1
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/utils.py +35 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/doctor.py +9 -8
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/setup.py +20 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/services.py +11 -4
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/upgrade.py +56 -19
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/tui.py +3 -2
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/PKG-INFO +1 -1
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/pyproject.toml +1 -1
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/tests/test_opensuse_compat.py +12 -1
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/README.md +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/__main__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/cli.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/config.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/constants.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/distro.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/github.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/logger.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/manifest.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/base.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/mailpit/module.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/mongodb/module.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/postgres/module.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/redis/manifest.json +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/builtin/redis/module.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/common/modules/manager.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/chroot/manager.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/chroot/packages.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/chroot/shell.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/cli.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/available.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/clean.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/ctl.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/current.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/db.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/grok.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/install.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/list.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/logs.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/mailpit.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/modules.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/mongodb.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/postgres.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/reload.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/restart.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/start.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/status.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/stop.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/uninstall.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/update.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/upgrade.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/use.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/commands/vhost.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/builder.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/downloader.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/extensions.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/installer.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/resolver.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/php/templates.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/fpm.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/mailpit.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/nginx_helper.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/pma.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/process.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/linux/runtime/sockets.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/main.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/tui.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/bin/shim.exe +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/cli.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/commands/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/__init__.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/db.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/elevate.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/ext.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/fcgi.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/grok.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/logs.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/mailpit.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/mkcert.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/paths.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/php.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/pma.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/redis_core.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/services.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/setup.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/shim.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/upgrade.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/core/vhost.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/templates/vhost.conf.tmpl +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/templates/vhost_ssl.conf.tmpl +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev/win/tui.py +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/SOURCES.txt +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/dependency_links.txt +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/entry_points.txt +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/requires.txt +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/ndev_stack.egg-info/top_level.txt +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/setup.cfg +0 -0
- {ndev_stack-0.3.4 → ndev_stack-0.3.5}/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.5
|
|
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
|
|
@@ -94,3 +94,38 @@ def get_version_or_prompt(version: str = None, prompt_message: str = "PHP versio
|
|
|
94
94
|
|
|
95
95
|
return typer.prompt(prompt_message).strip()
|
|
96
96
|
|
|
97
|
+
def which_binary(cmd: str) -> str | None:
|
|
98
|
+
"""
|
|
99
|
+
Find executable in PATH plus standard system admin/daemon directories
|
|
100
|
+
(/usr/sbin, /sbin, /usr/local/sbin, ~/.local/bin, ~/.ndev/bin).
|
|
101
|
+
Essential on Linux where daemons (e.g. nginx, mysqld) are in /usr/sbin,
|
|
102
|
+
which is not in standard non-root user PATH.
|
|
103
|
+
"""
|
|
104
|
+
if not cmd:
|
|
105
|
+
return None
|
|
106
|
+
import shutil
|
|
107
|
+
import os
|
|
108
|
+
from pathlib import Path
|
|
109
|
+
from ndev.common.constants import NDEV_DIR
|
|
110
|
+
|
|
111
|
+
found = shutil.which(cmd)
|
|
112
|
+
if found:
|
|
113
|
+
return found
|
|
114
|
+
|
|
115
|
+
p = Path(cmd)
|
|
116
|
+
if p.is_file() and os.access(p, os.X_OK):
|
|
117
|
+
return str(p.resolve())
|
|
118
|
+
|
|
119
|
+
extra_dirs = [
|
|
120
|
+
"/usr/sbin",
|
|
121
|
+
"/sbin",
|
|
122
|
+
"/usr/local/sbin",
|
|
123
|
+
"/usr/bin",
|
|
124
|
+
"/bin",
|
|
125
|
+
"/usr/local/bin",
|
|
126
|
+
str(Path.home() / ".local" / "bin"),
|
|
127
|
+
str(NDEV_DIR / "bin"),
|
|
128
|
+
]
|
|
129
|
+
path_env = os.environ.get("PATH", "")
|
|
130
|
+
extended_path = ":".join([path_env] + extra_dirs) if path_env else ":".join(extra_dirs)
|
|
131
|
+
return shutil.which(cmd, path=extended_path)
|
|
@@ -4,6 +4,7 @@ from rich.console import Console
|
|
|
4
4
|
from rich.table import Table
|
|
5
5
|
from ndev.common.constants import NDEV_DIR, CONFIG_FILE
|
|
6
6
|
from ndev.common.distro import get_distro_summary, get_pkg_manager
|
|
7
|
+
from ndev.common.utils import which_binary
|
|
7
8
|
|
|
8
9
|
console = Console()
|
|
9
10
|
|
|
@@ -27,47 +28,47 @@ def doctor_cmd():
|
|
|
27
28
|
build_install_cmd = "sudo apt install -y bubblewrap build-essential pkg-config"
|
|
28
29
|
stack_install_cmd = "sudo apt install -y mariadb-server nginx curl"
|
|
29
30
|
|
|
30
|
-
bwrap_path =
|
|
31
|
+
bwrap_path = which_binary("bwrap")
|
|
31
32
|
if bwrap_path:
|
|
32
33
|
try:
|
|
33
|
-
res = subprocess.run([
|
|
34
|
+
res = subprocess.run([bwrap_path, "--version"], capture_output=True, text=True)
|
|
34
35
|
table.add_row("Bubblewrap (bwrap)", "[green]OK[/green]", f"Found at {bwrap_path} ({res.stdout.strip()})")
|
|
35
36
|
except Exception as e:
|
|
36
37
|
table.add_row("Bubblewrap (bwrap)", "[red]FAILED[/red]", f"Found at {bwrap_path} but failed to run: {e}")
|
|
37
38
|
else:
|
|
38
39
|
table.add_row("Bubblewrap (bwrap)", "[red]MISSING[/red]", f"Required for PHP sandbox. Install via: {build_install_cmd}")
|
|
39
40
|
|
|
40
|
-
gcc_path =
|
|
41
|
+
gcc_path = which_binary("gcc")
|
|
41
42
|
if gcc_path:
|
|
42
43
|
table.add_row("GCC Compiler", "[green]OK[/green]", f"Found at {gcc_path}")
|
|
43
44
|
else:
|
|
44
45
|
table.add_row("GCC Compiler", "[red]MISSING[/red]", f"gcc is required to compile PHP. Install via: {build_install_cmd}")
|
|
45
46
|
|
|
46
|
-
make_path =
|
|
47
|
+
make_path = which_binary("make")
|
|
47
48
|
if make_path:
|
|
48
49
|
table.add_row("Make Utility", "[green]OK[/green]", f"Found at {make_path}")
|
|
49
50
|
else:
|
|
50
51
|
table.add_row("Make Utility", "[red]MISSING[/red]", f"make is required to build PHP. Install via: {build_install_cmd}")
|
|
51
52
|
|
|
52
|
-
pkgconfig_path =
|
|
53
|
+
pkgconfig_path = which_binary("pkg-config")
|
|
53
54
|
if pkgconfig_path:
|
|
54
55
|
table.add_row("Pkg-Config Utility", "[green]OK[/green]", f"Found at {pkgconfig_path}")
|
|
55
56
|
else:
|
|
56
57
|
table.add_row("Pkg-Config Utility", "[red]MISSING[/red]", f"pkg-config is required to find libraries. Install via: {build_install_cmd}")
|
|
57
58
|
|
|
58
|
-
nginx_path =
|
|
59
|
+
nginx_path = which_binary("nginx")
|
|
59
60
|
if nginx_path:
|
|
60
61
|
table.add_row("Nginx Web Server", "[green]OK[/green]", f"Found at {nginx_path}")
|
|
61
62
|
else:
|
|
62
63
|
table.add_row("Nginx Web Server", "[yellow]NOT INSTALLED[/yellow]", f"Run 'ndev setup' or: {stack_install_cmd}")
|
|
63
64
|
|
|
64
|
-
db_path =
|
|
65
|
+
db_path = which_binary("mariadb") or which_binary("mysql") or which_binary("mysqld")
|
|
65
66
|
if db_path:
|
|
66
67
|
table.add_row("MariaDB / MySQL", "[green]OK[/green]", f"Found at {db_path}")
|
|
67
68
|
else:
|
|
68
69
|
table.add_row("MariaDB / MySQL", "[yellow]NOT INSTALLED[/yellow]", f"Run 'ndev setup' or: {stack_install_cmd}")
|
|
69
70
|
|
|
70
|
-
mkcert_path =
|
|
71
|
+
mkcert_path = which_binary("mkcert")
|
|
71
72
|
if mkcert_path:
|
|
72
73
|
table.add_row("mkcert Local SSL", "[green]OK[/green]", f"Found at {mkcert_path}")
|
|
73
74
|
else:
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from ndev.common.constants import CURRENT_LINK
|
|
10
10
|
from ndev.common.logger import logger
|
|
11
|
+
from ndev.common.utils import which_binary
|
|
11
12
|
|
|
12
13
|
console = Console()
|
|
13
14
|
|
|
@@ -55,6 +56,15 @@ def setup_cmd():
|
|
|
55
56
|
packages = ["mariadb", "mariadb-client", "nginx", "curl", "bubblewrap"]
|
|
56
57
|
subprocess.run(["sudo", "zypper", "--non-interactive", "install", "-y"] + packages, check=True)
|
|
57
58
|
|
|
59
|
+
# Optional mkcert package
|
|
60
|
+
try:
|
|
61
|
+
subprocess.run(["sudo", "zypper", "--non-interactive", "install", "-y", "mkcert"], check=False, capture_output=True)
|
|
62
|
+
mkcert_bin = which_binary("mkcert")
|
|
63
|
+
if mkcert_bin:
|
|
64
|
+
subprocess.run([mkcert_bin, "-install"], check=False, capture_output=True)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
58
68
|
try:
|
|
59
69
|
subprocess.run(["sudo", "systemctl", "enable", "--now", "mariadb"], check=False, capture_output=True)
|
|
60
70
|
subprocess.run(["sudo", "systemctl", "enable", "--now", "nginx"], check=False, capture_output=True)
|
|
@@ -70,6 +80,16 @@ def setup_cmd():
|
|
|
70
80
|
# 2. Install MariaDB and Nginx
|
|
71
81
|
console.print("\n[bold yellow]Installing MariaDB and Nginx (sudo apt-get install)...[/bold yellow]")
|
|
72
82
|
subprocess.run(["sudo", "apt-get", "install", "-y", "mariadb-server", "nginx", "curl"], check=True)
|
|
83
|
+
|
|
84
|
+
# Optional mkcert package
|
|
85
|
+
try:
|
|
86
|
+
subprocess.run(["sudo", "apt-get", "install", "-y", "mkcert"], check=False, capture_output=True)
|
|
87
|
+
mkcert_bin = which_binary("mkcert")
|
|
88
|
+
if mkcert_bin:
|
|
89
|
+
subprocess.run([mkcert_bin, "-install"], check=False, capture_output=True)
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
73
93
|
console.print("[bold green]MariaDB and Nginx installed successfully![/bold green]\n")
|
|
74
94
|
|
|
75
95
|
# 3. Install Composer
|
|
@@ -14,6 +14,7 @@ from typing import Optional, Dict, Any, List, Tuple
|
|
|
14
14
|
from ndev.common.constants import PHP_DIR, CURRENT_LINK
|
|
15
15
|
from ndev.common.logger import logger
|
|
16
16
|
from ndev.common.modules import get_module_manager
|
|
17
|
+
from ndev.common.utils import which_binary
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def resolve_system_service_name(service: str) -> str:
|
|
@@ -52,8 +53,13 @@ def system_service_exists(service: str) -> bool:
|
|
|
52
53
|
resolved = resolve_system_service_name(service)
|
|
53
54
|
if shutil.which("systemctl"):
|
|
54
55
|
res = subprocess.run(["systemctl", "list-unit-files", f"{resolved}.service"], capture_output=True, text=True)
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
if resolved in res.stdout:
|
|
57
|
+
return True
|
|
58
|
+
return (
|
|
59
|
+
Path(f"/etc/init.d/{resolved}").exists()
|
|
60
|
+
or bool(which_binary(resolved))
|
|
61
|
+
or (resolved == "mariadb" and bool(which_binary("mariadbd") or which_binary("mysqld")))
|
|
62
|
+
)
|
|
57
63
|
|
|
58
64
|
|
|
59
65
|
def system_service_status(service: str) -> Dict[str, Any]:
|
|
@@ -85,9 +91,10 @@ def execute_system_service_action(service: str, action: str) -> Tuple[bool, str]
|
|
|
85
91
|
resolved = resolve_system_service_name(service)
|
|
86
92
|
|
|
87
93
|
# For Nginx reload/restart, validate configuration first
|
|
94
|
+
nginx_bin = which_binary("nginx")
|
|
88
95
|
if resolved == "nginx" and action in ["reload", "restart"]:
|
|
89
|
-
if
|
|
90
|
-
t_res = subprocess.run([
|
|
96
|
+
if nginx_bin:
|
|
97
|
+
t_res = subprocess.run([nginx_bin, "-t"], capture_output=True, text=True)
|
|
91
98
|
if t_res.returncode != 0:
|
|
92
99
|
err = t_res.stderr.strip() or t_res.stdout.strip()
|
|
93
100
|
return False, f"Nginx configuration test failed:\n{err}"
|
|
@@ -7,6 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
import json
|
|
8
8
|
import re
|
|
9
9
|
import shutil
|
|
10
|
+
import platform
|
|
10
11
|
import subprocess
|
|
11
12
|
import urllib.request
|
|
12
13
|
from dataclasses import dataclass
|
|
@@ -14,6 +15,7 @@ from pathlib import Path
|
|
|
14
15
|
from typing import Optional
|
|
15
16
|
|
|
16
17
|
from ndev.common.constants import NDEV_DIR
|
|
18
|
+
from ndev.common.utils import which_binary
|
|
17
19
|
from ndev.linux.runtime import mailpit as mailpit_rt, pma as pma_rt
|
|
18
20
|
|
|
19
21
|
USER_AGENT = "ndev/0.1.0"
|
|
@@ -80,14 +82,14 @@ def _upgrade_system_packages(packages: list[str]) -> tuple[bool, str]:
|
|
|
80
82
|
"""Upgrade system packages using zypper or apt."""
|
|
81
83
|
if shutil.which("zypper"):
|
|
82
84
|
try:
|
|
83
|
-
subprocess.run(["sudo", "zypper", "--non-interactive", "
|
|
85
|
+
subprocess.run(["sudo", "zypper", "--non-interactive", "install", "-y"] + packages, check=True)
|
|
84
86
|
return True, "Packages upgraded successfully via zypper."
|
|
85
87
|
except Exception as e:
|
|
86
88
|
return False, f"zypper upgrade failed: {e}"
|
|
87
89
|
elif shutil.which("apt") or shutil.which("apt-get"):
|
|
88
90
|
try:
|
|
89
91
|
subprocess.run(["sudo", "apt-get", "update"], check=True)
|
|
90
|
-
subprocess.run(["sudo", "apt-get", "
|
|
92
|
+
subprocess.run(["sudo", "apt-get", "install", "-y"] + packages, check=True)
|
|
91
93
|
return True, "Packages upgraded successfully via apt."
|
|
92
94
|
except Exception as e:
|
|
93
95
|
return False, f"apt upgrade failed: {e}"
|
|
@@ -96,14 +98,15 @@ def _upgrade_system_packages(packages: list[str]) -> tuple[bool, str]:
|
|
|
96
98
|
|
|
97
99
|
# 1. NGINX
|
|
98
100
|
def get_nginx_info() -> ComponentInfo:
|
|
99
|
-
|
|
101
|
+
nginx_bin = which_binary("nginx")
|
|
102
|
+
installed = bool(nginx_bin)
|
|
100
103
|
curr_ver = None
|
|
101
104
|
latest_ver = None
|
|
102
105
|
err = None
|
|
103
106
|
|
|
104
107
|
if installed:
|
|
105
108
|
try:
|
|
106
|
-
res = subprocess.run([
|
|
109
|
+
res = subprocess.run([nginx_bin, "-v"], capture_output=True, text=True, timeout=5)
|
|
107
110
|
out = res.stderr or res.stdout
|
|
108
111
|
m = re.search(r"nginx/(\d+\.\d+\.\d+)", out)
|
|
109
112
|
if m:
|
|
@@ -200,14 +203,17 @@ def upgrade_mailpit() -> tuple[bool, str]:
|
|
|
200
203
|
|
|
201
204
|
# 3. MARIADB
|
|
202
205
|
def get_mariadb_info() -> ComponentInfo:
|
|
203
|
-
|
|
206
|
+
installed_exe = (
|
|
207
|
+
which_binary("mariadb") or which_binary("mariadbd") or which_binary("mysql") or which_binary("mysqld")
|
|
208
|
+
)
|
|
209
|
+
installed = bool(installed_exe)
|
|
204
210
|
curr_ver = None
|
|
205
211
|
latest_ver = None
|
|
206
212
|
err = None
|
|
207
213
|
|
|
208
214
|
if installed:
|
|
209
215
|
try:
|
|
210
|
-
exe =
|
|
216
|
+
exe = installed_exe
|
|
211
217
|
if exe:
|
|
212
218
|
res = subprocess.run([exe, "--version"], capture_output=True, text=True, timeout=5)
|
|
213
219
|
m = re.search(r"(\d+\.\d+\.\d+)", res.stdout or res.stderr)
|
|
@@ -317,14 +323,15 @@ def upgrade_pma() -> tuple[bool, str]:
|
|
|
317
323
|
|
|
318
324
|
# 5. MKCERT
|
|
319
325
|
def get_mkcert_info() -> ComponentInfo:
|
|
320
|
-
|
|
326
|
+
mkcert_bin = which_binary("mkcert")
|
|
327
|
+
installed = bool(mkcert_bin)
|
|
321
328
|
curr_ver = None
|
|
322
329
|
latest_ver = None
|
|
323
330
|
err = None
|
|
324
331
|
|
|
325
332
|
if installed:
|
|
326
333
|
try:
|
|
327
|
-
res = subprocess.run([
|
|
334
|
+
res = subprocess.run([mkcert_bin, "-version"], capture_output=True, text=True, timeout=5)
|
|
328
335
|
curr_ver = res.stdout.strip() or res.stderr.strip()
|
|
329
336
|
except Exception as e:
|
|
330
337
|
err = str(e)
|
|
@@ -357,44 +364,73 @@ def _download_latest_mkcert() -> tuple[bool, str]:
|
|
|
357
364
|
target_ver = f"v{target_ver}"
|
|
358
365
|
dest = Path.home() / ".local" / "bin" / "mkcert"
|
|
359
366
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
360
|
-
|
|
367
|
+
|
|
368
|
+
m_arch = platform.machine().lower()
|
|
369
|
+
if m_arch in ["x86_64", "amd64"]:
|
|
370
|
+
arch_suffix = "amd64"
|
|
371
|
+
elif m_arch in ["aarch64", "arm64"]:
|
|
372
|
+
arch_suffix = "arm64"
|
|
373
|
+
elif "arm" in m_arch:
|
|
374
|
+
arch_suffix = "arm"
|
|
375
|
+
else:
|
|
376
|
+
arch_suffix = "amd64"
|
|
377
|
+
|
|
378
|
+
url = f"https://github.com/FiloSottile/mkcert/releases/download/{target_ver}/mkcert-{target_ver}-linux-{arch_suffix}"
|
|
361
379
|
try:
|
|
362
380
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
|
363
381
|
with urllib.request.urlopen(req, timeout=120) as resp, open(dest, "wb") as f:
|
|
364
382
|
f.write(resp.read())
|
|
365
383
|
dest.chmod(0o755)
|
|
366
|
-
|
|
384
|
+
|
|
385
|
+
# Also link to ~/.ndev/bin/mkcert
|
|
386
|
+
ndev_bin = NDEV_DIR / "bin" / "mkcert"
|
|
387
|
+
try:
|
|
388
|
+
ndev_bin.parent.mkdir(parents=True, exist_ok=True)
|
|
389
|
+
if ndev_bin.exists() or ndev_bin.is_symlink():
|
|
390
|
+
ndev_bin.unlink()
|
|
391
|
+
ndev_bin.symlink_to(dest)
|
|
392
|
+
except Exception:
|
|
393
|
+
pass
|
|
394
|
+
|
|
395
|
+
try:
|
|
396
|
+
subprocess.run([str(dest), "-install"], check=False, capture_output=True)
|
|
397
|
+
except Exception:
|
|
398
|
+
pass
|
|
399
|
+
|
|
367
400
|
info = get_mkcert_info()
|
|
368
|
-
return True, f"mkcert upgraded successfully ({info.current_version})."
|
|
401
|
+
return True, f"mkcert upgraded successfully ({info.current_version or target_ver})."
|
|
369
402
|
except Exception as e:
|
|
370
403
|
return False, f"mkcert binary download failed: {e}"
|
|
371
404
|
|
|
372
405
|
|
|
373
406
|
def upgrade_mkcert() -> tuple[bool, str]:
|
|
407
|
+
# 1. Try system package manager first
|
|
374
408
|
if shutil.which("zypper") or shutil.which("apt") or shutil.which("apt-get"):
|
|
375
|
-
|
|
376
|
-
|
|
409
|
+
_upgrade_system_packages(["mkcert"])
|
|
410
|
+
mkcert_bin = which_binary("mkcert")
|
|
411
|
+
if mkcert_bin:
|
|
377
412
|
try:
|
|
378
|
-
subprocess.run([
|
|
413
|
+
subprocess.run([mkcert_bin, "-install"], check=False)
|
|
379
414
|
except Exception:
|
|
380
415
|
pass
|
|
381
416
|
info = get_mkcert_info()
|
|
382
417
|
return True, f"mkcert upgraded successfully ({info.current_version})."
|
|
383
418
|
|
|
384
|
-
# Fallback to direct download
|
|
419
|
+
# 2. Fallback to direct prebuilt binary download
|
|
385
420
|
return _download_latest_mkcert()
|
|
386
421
|
|
|
387
422
|
|
|
388
423
|
# 6. COMPOSER
|
|
389
424
|
def get_composer_info() -> ComponentInfo:
|
|
390
|
-
|
|
425
|
+
composer_bin = which_binary("composer")
|
|
426
|
+
installed = bool(composer_bin)
|
|
391
427
|
curr_ver = None
|
|
392
428
|
latest_ver = None
|
|
393
429
|
err = None
|
|
394
430
|
|
|
395
431
|
if installed:
|
|
396
432
|
try:
|
|
397
|
-
res = subprocess.run([
|
|
433
|
+
res = subprocess.run([composer_bin, "--version"], capture_output=True, text=True, timeout=10)
|
|
398
434
|
m = re.search(r"Composer (?:version )?(\d+\.\d+\.\d+)", res.stdout or res.stderr)
|
|
399
435
|
if m:
|
|
400
436
|
curr_ver = m.group(1)
|
|
@@ -425,8 +461,9 @@ def get_composer_info() -> ComponentInfo:
|
|
|
425
461
|
|
|
426
462
|
def upgrade_composer() -> tuple[bool, str]:
|
|
427
463
|
try:
|
|
428
|
-
|
|
429
|
-
|
|
464
|
+
composer_bin = which_binary("composer")
|
|
465
|
+
if composer_bin:
|
|
466
|
+
res = subprocess.run([composer_bin, "self-update"], capture_output=True, text=True, timeout=60)
|
|
430
467
|
if res.returncode == 0:
|
|
431
468
|
info = get_composer_info()
|
|
432
469
|
return True, f"Composer upgraded successfully ({info.current_version})."
|
|
@@ -35,6 +35,7 @@ from textual.widgets import (
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
from ndev.common.constants import NDEV_DIR, PHP_DIR, CURRENT_LINK, LOGS_DIR
|
|
38
|
+
from ndev.common.utils import which_binary
|
|
38
39
|
from ndev.linux.runtime.fpm import get_fpm_status, start_fpm, stop_fpm, restart_fpm
|
|
39
40
|
from ndev.linux.runtime.pma import get_pma_status, start_pma, stop_pma, restart_pma, setup_pma
|
|
40
41
|
from ndev.linux.runtime.mailpit import get_mailpit_status, start_mailpit, stop_mailpit, restart_mailpit, is_installed as is_mailpit_installed, setup_mailpit
|
|
@@ -570,7 +571,7 @@ class NdevDashboard(App):
|
|
|
570
571
|
results = []
|
|
571
572
|
|
|
572
573
|
# 1. Nginx
|
|
573
|
-
ng_installed = bool(
|
|
574
|
+
ng_installed = bool(which_binary("nginx"))
|
|
574
575
|
ng_running = False
|
|
575
576
|
if ng_installed:
|
|
576
577
|
if shutil.which("systemctl"):
|
|
@@ -591,7 +592,7 @@ class NdevDashboard(App):
|
|
|
591
592
|
})
|
|
592
593
|
|
|
593
594
|
# 2. MariaDB / MySQL
|
|
594
|
-
db_installed = bool(
|
|
595
|
+
db_installed = bool(which_binary("mariadb") or which_binary("mysql") or which_binary("mariadbd") or which_binary("mysqld"))
|
|
595
596
|
db_running = False
|
|
596
597
|
if db_installed:
|
|
597
598
|
if shutil.which("systemctl"):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndev-stack
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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.5"
|
|
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"
|
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
import tempfile
|
|
11
11
|
import shutil
|
|
12
12
|
|
|
13
|
-
from ndev.common import distro
|
|
13
|
+
from ndev.common import distro, utils
|
|
14
14
|
from ndev.linux.runtime import nginx_helper
|
|
15
15
|
from ndev.linux.runtime import services
|
|
16
16
|
from ndev.linux.runtime import process
|
|
@@ -230,6 +230,17 @@ class TestSocketsAndProcess(unittest.TestCase):
|
|
|
230
230
|
mock_kill.side_effect = ProcessLookupError("No such process")
|
|
231
231
|
self.assertFalse(process.is_pid_running(5678))
|
|
232
232
|
|
|
233
|
+
def test_which_binary_finds_sbin(self):
|
|
234
|
+
with patch.dict(os.environ, {"PATH": "/usr/bin:/bin"}):
|
|
235
|
+
with patch("shutil.which") as mock_which:
|
|
236
|
+
def fake_which(cmd, path=None):
|
|
237
|
+
if path and "/usr/sbin" in path and cmd == "nginx":
|
|
238
|
+
return "/usr/sbin/nginx"
|
|
239
|
+
return None
|
|
240
|
+
mock_which.side_effect = fake_which
|
|
241
|
+
found = utils.which_binary("nginx")
|
|
242
|
+
self.assertEqual(found, "/usr/sbin/nginx")
|
|
243
|
+
|
|
233
244
|
|
|
234
245
|
class TestServiceManager(unittest.TestCase):
|
|
235
246
|
def test_resolve_service_name(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
|