ndev-stack 0.3.8__tar.gz → 0.3.10__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.8 → ndev_stack-0.3.10}/PKG-INFO +1 -1
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/__init__.py +1 -1
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/vhost.py +153 -79
- ndev_stack-0.3.10/ndev/linux/runtime/nginx_helper.py +531 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/tui.py +139 -46
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/PKG-INFO +1 -1
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/pyproject.toml +1 -1
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/tests/test_opensuse_compat.py +96 -0
- ndev_stack-0.3.8/ndev/linux/runtime/nginx_helper.py +0 -269
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/README.md +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/__main__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/cli.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/config.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/constants.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/distro.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/github.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/logger.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/manifest.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/base.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/mailpit/module.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/mongodb/module.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/postgres/module.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/redis/manifest.json +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/builtin/redis/module.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/modules/manager.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/common/utils.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/chroot/manager.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/chroot/packages.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/chroot/shell.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/cli.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/available.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/clean.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/ctl.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/current.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/db.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/doctor.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/grok.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/install.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/list.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/logs.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/mailpit.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/modules.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/mongodb.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/postgres.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/reload.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/restart.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/setup.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/start.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/status.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/stop.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/uninstall.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/update.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/upgrade.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/commands/use.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/builder.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/downloader.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/extensions.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/installer.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/resolver.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/php/templates.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/fpm.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/mailpit.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/pma.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/process.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/services.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/sockets.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/linux/runtime/upgrade.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/main.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/tui.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/bin/shim.exe +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/cli.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/commands/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/__init__.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/db.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/elevate.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/ext.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/fcgi.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/grok.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/logs.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/mailpit.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/mkcert.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/paths.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/php.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/pma.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/redis_core.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/services.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/setup.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/shim.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/upgrade.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/core/vhost.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/templates/vhost.conf.tmpl +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/templates/vhost_ssl.conf.tmpl +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev/win/tui.py +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/SOURCES.txt +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/dependency_links.txt +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/entry_points.txt +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/requires.txt +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/ndev_stack.egg-info/top_level.txt +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/setup.cfg +0 -0
- {ndev_stack-0.3.8 → ndev_stack-0.3.10}/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.10
|
|
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
|
|
@@ -7,11 +7,14 @@ import typer
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from ndev.common.logger import logger
|
|
10
|
+
from ndev.common.utils import which_binary, resolve_installed_version
|
|
10
11
|
from typing import Optional
|
|
11
12
|
|
|
12
13
|
console = Console()
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
def chown_to_sudo_user(path: Path) -> None:
|
|
17
|
+
"""Change ownership of a path to the user who invoked sudo."""
|
|
15
18
|
sudo_user = os.environ.get("SUDO_USER")
|
|
16
19
|
if sudo_user:
|
|
17
20
|
try:
|
|
@@ -21,39 +24,73 @@ def chown_to_sudo_user(path: Path):
|
|
|
21
24
|
except Exception:
|
|
22
25
|
pass
|
|
23
26
|
|
|
27
|
+
|
|
28
|
+
def find_mkcert_binary() -> Optional[str]:
|
|
29
|
+
"""Find mkcert executable in system PATH or user local bin."""
|
|
30
|
+
found = which_binary("mkcert") or shutil.which("mkcert")
|
|
31
|
+
if found:
|
|
32
|
+
return found
|
|
33
|
+
|
|
34
|
+
candidates = [
|
|
35
|
+
Path.home() / ".local" / "bin" / "mkcert",
|
|
36
|
+
Path("/usr/local/bin/mkcert"),
|
|
37
|
+
Path("/usr/bin/mkcert"),
|
|
38
|
+
]
|
|
39
|
+
sudo_user = os.environ.get("SUDO_USER")
|
|
40
|
+
if sudo_user:
|
|
41
|
+
try:
|
|
42
|
+
import pwd
|
|
43
|
+
user_home = Path(pwd.getpwnam(sudo_user).pw_dir)
|
|
44
|
+
candidates.insert(0, user_home / ".local" / "bin" / "mkcert")
|
|
45
|
+
except Exception:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
for c in candidates:
|
|
49
|
+
if c.exists() and os.access(c, os.X_OK):
|
|
50
|
+
return str(c)
|
|
51
|
+
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
24
55
|
def generate_local_cert(domain: str, certs_dir: Path) -> tuple[Path, Path]:
|
|
56
|
+
"""Generate SSL certificate and private key using mkcert."""
|
|
25
57
|
cert_path = certs_dir / f"{domain}.crt"
|
|
26
58
|
key_path = certs_dir / f"{domain}.key"
|
|
27
|
-
|
|
59
|
+
|
|
28
60
|
if cert_path.exists() and key_path.exists():
|
|
29
61
|
return cert_path, key_path
|
|
30
|
-
|
|
62
|
+
|
|
31
63
|
certs_dir.mkdir(parents=True, exist_ok=True)
|
|
32
64
|
chown_to_sudo_user(certs_dir)
|
|
33
|
-
|
|
65
|
+
|
|
66
|
+
mkcert_bin = find_mkcert_binary()
|
|
67
|
+
if not mkcert_bin:
|
|
68
|
+
raise FileNotFoundError("mkcert binary not found in PATH or ~/.local/bin")
|
|
69
|
+
|
|
34
70
|
sudo_user = os.environ.get("SUDO_USER")
|
|
35
|
-
if sudo_user:
|
|
71
|
+
if sudo_user and os.geteuid() == 0:
|
|
36
72
|
cmd = [
|
|
37
73
|
"sudo", "-u", sudo_user,
|
|
38
|
-
|
|
74
|
+
mkcert_bin,
|
|
39
75
|
"-cert-file", str(cert_path),
|
|
40
76
|
"-key-file", str(key_path),
|
|
41
77
|
domain, f"*.{domain}"
|
|
42
78
|
]
|
|
43
79
|
else:
|
|
44
80
|
cmd = [
|
|
45
|
-
|
|
81
|
+
mkcert_bin,
|
|
46
82
|
"-cert-file", str(cert_path),
|
|
47
83
|
"-key-file", str(key_path),
|
|
48
84
|
domain, f"*.{domain}"
|
|
49
85
|
]
|
|
50
|
-
|
|
86
|
+
|
|
51
87
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
52
88
|
chown_to_sudo_user(key_path)
|
|
53
89
|
chown_to_sudo_user(cert_path)
|
|
54
|
-
|
|
90
|
+
|
|
55
91
|
return cert_path, key_path
|
|
56
92
|
|
|
93
|
+
|
|
57
94
|
def get_user_ndev_dir() -> Path:
|
|
58
95
|
sudo_user = os.environ.get("SUDO_USER")
|
|
59
96
|
if sudo_user:
|
|
@@ -64,6 +101,7 @@ def get_user_ndev_dir() -> Path:
|
|
|
64
101
|
pass
|
|
65
102
|
return Path(os.path.expanduser("~/.ndev"))
|
|
66
103
|
|
|
104
|
+
|
|
67
105
|
def get_installed_php_versions() -> list[dict]:
|
|
68
106
|
installed = []
|
|
69
107
|
ndev_dir = get_user_ndev_dir()
|
|
@@ -72,7 +110,6 @@ def get_installed_php_versions() -> list[dict]:
|
|
|
72
110
|
for path in php_dir.iterdir():
|
|
73
111
|
if path.is_dir():
|
|
74
112
|
ver = path.name
|
|
75
|
-
# get major/minor for socket
|
|
76
113
|
parts = ver.split(".")
|
|
77
114
|
if len(parts) >= 2:
|
|
78
115
|
mm = f"{parts[0]}{parts[1]}"
|
|
@@ -81,20 +118,19 @@ def get_installed_php_versions() -> list[dict]:
|
|
|
81
118
|
mm = ver
|
|
82
119
|
label = ver
|
|
83
120
|
sock = ndev_dir / "run" / f"php{mm}.sock"
|
|
84
|
-
|
|
85
|
-
# Check if it is running
|
|
121
|
+
|
|
86
122
|
pid_file = ndev_dir / "run" / f"php-fpm-{mm}.pid"
|
|
87
123
|
from ndev.linux.runtime.process import is_pid_running, read_pid_file
|
|
88
124
|
pid = read_pid_file(pid_file)
|
|
89
125
|
is_running = is_pid_running(pid) if pid else False
|
|
90
|
-
|
|
126
|
+
|
|
91
127
|
installed.append({
|
|
92
128
|
"version": ver,
|
|
93
129
|
"label": label,
|
|
94
130
|
"socket": sock,
|
|
95
131
|
"running": is_running
|
|
96
132
|
})
|
|
97
|
-
|
|
133
|
+
|
|
98
134
|
from packaging.version import parse as parse_version
|
|
99
135
|
try:
|
|
100
136
|
installed.sort(key=lambda x: parse_version(x["version"]))
|
|
@@ -102,11 +138,14 @@ def get_installed_php_versions() -> list[dict]:
|
|
|
102
138
|
installed.sort(key=lambda x: x["version"])
|
|
103
139
|
return installed
|
|
104
140
|
|
|
141
|
+
|
|
105
142
|
def vhost_cmd(
|
|
106
143
|
domain: str = typer.Option(None, "--domain", "-d", help="Domain (e.g. project.local)"),
|
|
107
144
|
root: str = typer.Option(None, "--root", "-r", help="Project Root Directory"),
|
|
108
145
|
php: str = typer.Option(None, "--php", "-p", help="PHP socket alias, version, or index"),
|
|
109
|
-
ssl: Optional[bool] = typer.Option(None, "--ssl/--no-ssl", help="Enable SSL/HTTPS with local certificate generation")
|
|
146
|
+
ssl: Optional[bool] = typer.Option(None, "--ssl/--no-ssl", help="Enable SSL/HTTPS with local certificate generation"),
|
|
147
|
+
cert: Optional[str] = typer.Option(None, "--cert", help="Path to existing SSL certificate file"),
|
|
148
|
+
key: Optional[str] = typer.Option(None, "--key", help="Path to existing SSL private key file"),
|
|
110
149
|
):
|
|
111
150
|
"""Create Nginx Virtual Host config, map to hosts file, and reload Nginx."""
|
|
112
151
|
if not domain:
|
|
@@ -114,7 +153,7 @@ def vhost_cmd(
|
|
|
114
153
|
if not domain:
|
|
115
154
|
logger.error("Domain is required.")
|
|
116
155
|
raise typer.Exit(code=1)
|
|
117
|
-
|
|
156
|
+
|
|
118
157
|
if not root:
|
|
119
158
|
sudo_user = os.environ.get("SUDO_USER")
|
|
120
159
|
if sudo_user:
|
|
@@ -130,7 +169,7 @@ def vhost_cmd(
|
|
|
130
169
|
if not root:
|
|
131
170
|
logger.error("Project Root is required.")
|
|
132
171
|
raise typer.Exit(code=1)
|
|
133
|
-
|
|
172
|
+
|
|
134
173
|
root_path = Path(root).resolve()
|
|
135
174
|
if not root_path.exists():
|
|
136
175
|
root_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -142,30 +181,52 @@ def vhost_cmd(
|
|
|
142
181
|
encoding="utf-8"
|
|
143
182
|
)
|
|
144
183
|
chown_to_sudo_user(index_php)
|
|
145
|
-
|
|
184
|
+
|
|
146
185
|
installed_phps = get_installed_php_versions()
|
|
147
186
|
if not installed_phps:
|
|
148
187
|
logger.error("No installed PHP versions found. Please install PHP using 'ndev install <version>' first.")
|
|
149
188
|
raise typer.Exit(code=1)
|
|
150
|
-
|
|
189
|
+
|
|
151
190
|
selected_sock = None
|
|
152
191
|
selected_ver = None
|
|
153
192
|
if php:
|
|
154
|
-
# Check if it is a socket path
|
|
155
193
|
php_path = Path(php)
|
|
156
|
-
if php_path.
|
|
194
|
+
if php_path.suffix == ".sock":
|
|
157
195
|
selected_sock = php_path
|
|
196
|
+
# Find matching version from socket name
|
|
197
|
+
for item in installed_phps:
|
|
198
|
+
if item["socket"].name == php_path.name or item["socket"] == php_path:
|
|
199
|
+
selected_ver = item["version"]
|
|
200
|
+
break
|
|
201
|
+
if not selected_ver:
|
|
202
|
+
m = re.search(r"\d+", php_path.stem)
|
|
203
|
+
if m:
|
|
204
|
+
digits = m.group(0)
|
|
205
|
+
for item in installed_phps:
|
|
206
|
+
item_mm = item["version"].replace(".", "")[:len(digits)]
|
|
207
|
+
if item_mm == digits:
|
|
208
|
+
selected_ver = item["version"]
|
|
209
|
+
break
|
|
158
210
|
else:
|
|
159
211
|
clean_php = php.lower()
|
|
160
212
|
if clean_php.startswith("ndev "):
|
|
161
213
|
clean_php = clean_php[len("ndev "):]
|
|
162
|
-
|
|
214
|
+
|
|
163
215
|
for item in installed_phps:
|
|
164
216
|
if clean_php == item["label"].lower() or clean_php == item["version"].lower():
|
|
165
217
|
selected_sock = item["socket"]
|
|
166
218
|
selected_ver = item["version"]
|
|
167
219
|
break
|
|
168
|
-
|
|
220
|
+
|
|
221
|
+
if not selected_sock:
|
|
222
|
+
resolved = resolve_installed_version(clean_php)
|
|
223
|
+
if resolved:
|
|
224
|
+
for item in installed_phps:
|
|
225
|
+
if item["version"] == resolved or item["label"] == resolved:
|
|
226
|
+
selected_sock = item["socket"]
|
|
227
|
+
selected_ver = item["version"]
|
|
228
|
+
break
|
|
229
|
+
|
|
169
230
|
if not selected_sock:
|
|
170
231
|
try:
|
|
171
232
|
idx = int(php)
|
|
@@ -174,7 +235,7 @@ def vhost_cmd(
|
|
|
174
235
|
selected_ver = installed_phps[idx - 1]["version"]
|
|
175
236
|
except ValueError:
|
|
176
237
|
pass
|
|
177
|
-
|
|
238
|
+
|
|
178
239
|
if not selected_sock:
|
|
179
240
|
logger.error(f"Invalid PHP selection: {php}")
|
|
180
241
|
raise typer.Exit(code=1)
|
|
@@ -191,10 +252,10 @@ def vhost_cmd(
|
|
|
191
252
|
raise typer.Exit(code=1)
|
|
192
253
|
selected_sock = installed_phps[choice - 1]["socket"]
|
|
193
254
|
selected_ver = installed_phps[choice - 1]["version"]
|
|
194
|
-
|
|
255
|
+
|
|
195
256
|
if ssl is None:
|
|
196
257
|
ssl = typer.confirm("Enable SSL/HTTPS?", default=False)
|
|
197
|
-
|
|
258
|
+
|
|
198
259
|
# If selected PHP version is stopped, start it
|
|
199
260
|
if selected_ver:
|
|
200
261
|
selected_item = next((item for item in installed_phps if item["version"] == selected_ver), None)
|
|
@@ -205,22 +266,27 @@ def vhost_cmd(
|
|
|
205
266
|
start_fpm(selected_ver)
|
|
206
267
|
except Exception as e:
|
|
207
268
|
logger.warning(f"Could not automatically start PHP-FPM: {e}")
|
|
208
|
-
|
|
269
|
+
|
|
209
270
|
cert_path, key_path = None, None
|
|
210
271
|
if ssl:
|
|
211
|
-
if
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
272
|
+
if cert and key and Path(cert).exists() and Path(key).exists():
|
|
273
|
+
cert_path = Path(cert)
|
|
274
|
+
key_path = Path(key)
|
|
275
|
+
else:
|
|
276
|
+
mkcert_bin = find_mkcert_binary()
|
|
277
|
+
if not mkcert_bin:
|
|
278
|
+
logger.error("mkcert binary not found. Please install mkcert to generate local certificates ('ndev upgrade mkcert').")
|
|
279
|
+
raise typer.Exit(code=1)
|
|
280
|
+
certs_dir = get_user_ndev_dir() / "certs"
|
|
281
|
+
try:
|
|
282
|
+
cert_path, key_path = generate_local_cert(domain, certs_dir)
|
|
283
|
+
console.print("Generated SSL Certificates:")
|
|
284
|
+
console.print(f" Cert: {cert_path}")
|
|
285
|
+
console.print(f" Key : {key_path}")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.error(f"Failed to generate SSL certificate: {e}")
|
|
288
|
+
raise typer.Exit(code=1)
|
|
289
|
+
|
|
224
290
|
if os.geteuid() != 0:
|
|
225
291
|
console.print("\n[bold yellow]Privileged operations required. Elevating via sudo...[/bold yellow]")
|
|
226
292
|
cmd = [
|
|
@@ -234,31 +300,44 @@ def vhost_cmd(
|
|
|
234
300
|
"--root",
|
|
235
301
|
root,
|
|
236
302
|
"--php",
|
|
237
|
-
str(selected_sock)
|
|
303
|
+
str(selected_sock),
|
|
238
304
|
]
|
|
239
305
|
if ssl:
|
|
240
306
|
cmd.append("--ssl")
|
|
307
|
+
if cert_path and key_path:
|
|
308
|
+
cmd.extend(["--cert", str(cert_path), "--key", str(key_path)])
|
|
241
309
|
else:
|
|
242
310
|
cmd.append("--no-ssl")
|
|
243
|
-
|
|
311
|
+
|
|
244
312
|
try:
|
|
245
313
|
res = subprocess.run(cmd)
|
|
246
314
|
raise typer.Exit(code=res.returncode)
|
|
247
315
|
except KeyboardInterrupt:
|
|
248
316
|
raise typer.Exit(code=1)
|
|
249
|
-
|
|
250
|
-
from ndev.linux.runtime.nginx_helper import
|
|
317
|
+
|
|
318
|
+
from ndev.linux.runtime.nginx_helper import (
|
|
319
|
+
get_nginx_vhost_dirs,
|
|
320
|
+
generate_vhost_config,
|
|
321
|
+
ensure_nginx_sites_enabled_included,
|
|
322
|
+
add_to_hosts_privileged,
|
|
323
|
+
reload_nginx,
|
|
324
|
+
)
|
|
251
325
|
|
|
252
326
|
vhost_dir, enabled_dir = get_nginx_vhost_dirs()
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
327
|
+
|
|
328
|
+
# Ensure target configuration directories exist
|
|
329
|
+
try:
|
|
330
|
+
vhost_dir.mkdir(parents=True, exist_ok=True)
|
|
331
|
+
if enabled_dir:
|
|
332
|
+
enabled_dir.mkdir(parents=True, exist_ok=True)
|
|
333
|
+
except Exception as e:
|
|
334
|
+
logger.error(f"Failed to create Nginx configuration directories: {e}")
|
|
335
|
+
raise typer.Exit(code=1)
|
|
336
|
+
|
|
337
|
+
# Ensure sites-enabled is bridged into nginx.conf if used
|
|
338
|
+
if enabled_dir:
|
|
339
|
+
ensure_nginx_sites_enabled_included()
|
|
340
|
+
|
|
262
341
|
conf_file = vhost_dir / f"{domain}.conf"
|
|
263
342
|
config_template = generate_vhost_config(
|
|
264
343
|
domain=domain,
|
|
@@ -269,45 +348,39 @@ def vhost_cmd(
|
|
|
269
348
|
key_path=key_path,
|
|
270
349
|
)
|
|
271
350
|
|
|
351
|
+
enabled_link = None
|
|
272
352
|
try:
|
|
273
|
-
conf_file.write_text(config_template)
|
|
274
|
-
|
|
353
|
+
conf_file.write_text(config_template, encoding="utf-8")
|
|
354
|
+
|
|
275
355
|
if enabled_dir and enabled_dir != vhost_dir:
|
|
276
356
|
enabled_link = enabled_dir / f"{domain}.conf"
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
found = True
|
|
287
|
-
break
|
|
288
|
-
if not found:
|
|
289
|
-
with hosts_file.open("a") as f:
|
|
290
|
-
f.write(f"\n127.0.0.1 {domain}\n")
|
|
291
|
-
|
|
292
|
-
res = subprocess.run(["nginx", "-t"], capture_output=True, text=True)
|
|
357
|
+
enabled_link.unlink(missing_ok=True)
|
|
358
|
+
enabled_link.symlink_to(conf_file.resolve())
|
|
359
|
+
|
|
360
|
+
# Update /etc/hosts
|
|
361
|
+
add_to_hosts_privileged(domain)
|
|
362
|
+
|
|
363
|
+
# Test Nginx configuration before reloading
|
|
364
|
+
nginx_bin = which_binary("nginx") or shutil.which("nginx") or "nginx"
|
|
365
|
+
res = subprocess.run([nginx_bin, "-t"], capture_output=True, text=True)
|
|
293
366
|
if res.returncode != 0:
|
|
294
367
|
logger.error(f"Nginx config test failed:\n{res.stderr}")
|
|
295
368
|
if enabled_link:
|
|
296
369
|
enabled_link.unlink(missing_ok=True)
|
|
297
370
|
conf_file.unlink(missing_ok=True)
|
|
298
371
|
raise typer.Exit(code=1)
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
subprocess.run(["service", "nginx", "reload"], check=True)
|
|
304
|
-
|
|
372
|
+
|
|
373
|
+
# Reload Nginx service
|
|
374
|
+
reload_nginx()
|
|
375
|
+
|
|
305
376
|
console.print("\n[bold green]VHost Created Successfully[/bold green]")
|
|
306
377
|
console.print("--------------------------")
|
|
307
378
|
console.print(f"Domain : {domain}")
|
|
308
379
|
console.print(f"Root : {root}")
|
|
309
380
|
console.print(f"PHP Socket : {selected_sock}")
|
|
310
381
|
console.print(f"Config : {conf_file}")
|
|
382
|
+
if enabled_link:
|
|
383
|
+
console.print(f"Symlink : {enabled_link}")
|
|
311
384
|
if ssl:
|
|
312
385
|
console.print(f"SSL Cert : {cert_path}")
|
|
313
386
|
console.print(f"SSL Key : {key_path}")
|
|
@@ -315,6 +388,9 @@ def vhost_cmd(
|
|
|
315
388
|
else:
|
|
316
389
|
console.print(f"\nOpen: http://{domain}")
|
|
317
390
|
except Exception as e:
|
|
391
|
+
if enabled_link:
|
|
392
|
+
enabled_link.unlink(missing_ok=True)
|
|
393
|
+
conf_file.unlink(missing_ok=True)
|
|
318
394
|
logger.error(f"Failed to create virtual host: {e}")
|
|
319
395
|
raise typer.Exit(code=1)
|
|
320
396
|
|
|
@@ -382,5 +458,3 @@ def vhost_remove_cmd(
|
|
|
382
458
|
console.print(f"[bold green]✓ Removed virtual host '{clean_domain}'.[/bold green]")
|
|
383
459
|
else:
|
|
384
460
|
console.print(f"[yellow]Virtual host '{clean_domain}' not found or could not be removed.[/yellow]")
|
|
385
|
-
|
|
386
|
-
|