ndev-stack 0.3.12__tar.gz → 0.3.14__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.12 → ndev_stack-0.3.14}/PKG-INFO +1 -1
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/__init__.py +1 -1
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/doctor.py +16 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/setup.py +70 -16
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/vhost.py +2 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/nginx_helper.py +181 -4
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/tui.py +141 -60
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/PKG-INFO +1 -1
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/pyproject.toml +1 -1
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/tests/test_opensuse_compat.py +77 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/README.md +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/__main__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/cli.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/config.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/constants.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/distro.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/github.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/logger.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/manifest.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/base.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/mailpit/manifest.json +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/mailpit/module.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/mongodb/manifest.json +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/mongodb/module.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/postgres/manifest.json +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/postgres/module.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/redis/manifest.json +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/builtin/redis/module.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/modules/manager.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/common/utils.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/chroot/manager.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/chroot/packages.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/chroot/shell.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/cli.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/available.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/clean.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/ctl.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/current.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/db.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/grok.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/install.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/list.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/logs.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/mailpit.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/modules.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/mongodb.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/postgres.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/reload.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/restart.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/start.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/status.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/stop.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/uninstall.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/update.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/upgrade.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/commands/use.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/builder.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/downloader.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/extensions.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/installer.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/resolver.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/php/templates.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/fpm.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/mailpit.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/pma.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/process.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/services.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/sockets.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/linux/runtime/upgrade.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/main.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/tui.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/bin/shim.exe +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/cli.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/commands/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/__init__.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/db.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/elevate.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/ext.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/fcgi.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/grok.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/logs.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/mailpit.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/mkcert.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/paths.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/php.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/pma.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/redis_core.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/services.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/setup.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/shim.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/upgrade.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/core/vhost.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/templates/vhost.conf.tmpl +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/templates/vhost_ssl.conf.tmpl +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev/win/tui.py +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/SOURCES.txt +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/dependency_links.txt +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/entry_points.txt +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/requires.txt +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/ndev_stack.egg-info/top_level.txt +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/setup.cfg +0 -0
- {ndev_stack-0.3.12 → ndev_stack-0.3.14}/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.14
|
|
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
|
|
@@ -74,6 +74,22 @@ def doctor_cmd():
|
|
|
74
74
|
else:
|
|
75
75
|
table.add_row("mkcert Local SSL", "[yellow]NOT INSTALLED[/yellow]", "Optional for SSL. Run 'ndev upgrade mkcert' to install.")
|
|
76
76
|
|
|
77
|
+
try:
|
|
78
|
+
from ndev.linux.runtime.nginx_helper import is_selinux_enforcing, is_selinux_httpd_permissive
|
|
79
|
+
if is_selinux_enforcing():
|
|
80
|
+
if is_selinux_httpd_permissive():
|
|
81
|
+
table.add_row("SELinux Web Server Access", "[green]OK[/green]", "Enforcing mode, httpd_t is permissive")
|
|
82
|
+
else:
|
|
83
|
+
table.add_row(
|
|
84
|
+
"SELinux Web Server Access",
|
|
85
|
+
"[yellow]RESTRICTED[/yellow]",
|
|
86
|
+
"Enforcing. Nginx cannot access ~/Sites or FastCGI sockets. Run: sudo /usr/sbin/semanage permissive -a httpd_t"
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
table.add_row("SELinux Web Server Access", "[green]OK[/green]", "SELinux is permissive or disabled")
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
77
93
|
if NDEV_DIR.exists():
|
|
78
94
|
table.add_row("Layout Directory (~/.ndev)", "[green]OK[/green]", f"Exists at {NDEV_DIR}")
|
|
79
95
|
else:
|
|
@@ -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
|
|
55
|
-
console.print("\n[bold yellow]Installing MariaDB, Nginx, curl, and
|
|
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,21 +65,16 @@ def setup_cmd():
|
|
|
65
65
|
except Exception:
|
|
66
66
|
pass
|
|
67
67
|
|
|
68
|
-
|
|
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
|
-
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")
|
|
75
69
|
else:
|
|
76
70
|
# 1. Update Package Lists
|
|
77
71
|
console.print("[bold yellow]Updating package lists (sudo apt-get update)...[/bold yellow]")
|
|
78
72
|
subprocess.run(["sudo", "apt-get", "update"], check=True)
|
|
79
73
|
|
|
80
74
|
# 2. Install MariaDB and Nginx
|
|
81
|
-
console.print("\n[bold yellow]Installing MariaDB and
|
|
82
|
-
|
|
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)
|
|
83
78
|
|
|
84
79
|
# Optional mkcert package
|
|
85
80
|
try:
|
|
@@ -90,9 +85,63 @@ def setup_cmd():
|
|
|
90
85
|
except Exception:
|
|
91
86
|
pass
|
|
92
87
|
|
|
93
|
-
console.print("[bold green]
|
|
94
|
-
|
|
95
|
-
# 3.
|
|
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
|
+
# 4. Install Composer
|
|
96
145
|
local_bin = get_user_local_bin_dir()
|
|
97
146
|
composer_bin = local_bin / "composer"
|
|
98
147
|
if composer_bin.exists():
|
|
@@ -144,7 +193,12 @@ def setup_cmd():
|
|
|
144
193
|
chown_to_sudo_user(ndev_link)
|
|
145
194
|
console.print(f"[bold green]Created symlink {ndev_link} -> {ndev_venv_path}[/bold green]")
|
|
146
195
|
|
|
147
|
-
console.print("\n[bold green]System setup completed successfully![/bold green]")
|
|
196
|
+
console.print("\n[bold green]System setup completed successfully![/bold green]\n")
|
|
197
|
+
try:
|
|
198
|
+
from ndev.linux.commands.doctor import doctor_cmd
|
|
199
|
+
doctor_cmd()
|
|
200
|
+
except Exception:
|
|
201
|
+
pass
|
|
148
202
|
|
|
149
203
|
except subprocess.CalledProcessError as e:
|
|
150
204
|
logger.error(f"Command execution failed: {e}")
|
|
@@ -321,6 +321,7 @@ def vhost_cmd(
|
|
|
321
321
|
ensure_nginx_sites_enabled_included,
|
|
322
322
|
install_nginx_ssl_cert,
|
|
323
323
|
ensure_docroot_accessible,
|
|
324
|
+
ensure_selinux_httpd_permissions,
|
|
324
325
|
add_to_hosts_privileged,
|
|
325
326
|
reload_nginx,
|
|
326
327
|
)
|
|
@@ -350,6 +351,7 @@ def vhost_cmd(
|
|
|
350
351
|
logger.warning(f"Could not install SSL certificate to /etc/nginx/ssl: {e}")
|
|
351
352
|
|
|
352
353
|
ensure_docroot_accessible(root_path)
|
|
354
|
+
ensure_selinux_httpd_permissions()
|
|
353
355
|
|
|
354
356
|
conf_file = vhost_dir / f"{domain}.conf"
|
|
355
357
|
config_template = generate_vhost_config(
|
|
@@ -66,6 +66,169 @@ def check_sudo_needs_password() -> bool:
|
|
|
66
66
|
return True
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
def run_privileged_command(
|
|
70
|
+
cmd: list[str],
|
|
71
|
+
password: Optional[str] = None,
|
|
72
|
+
check: bool = True,
|
|
73
|
+
timeout: int = 60,
|
|
74
|
+
capture_output: bool = True
|
|
75
|
+
) -> subprocess.CompletedProcess:
|
|
76
|
+
"""
|
|
77
|
+
Run a command with elevated privileges using sudo if non-root.
|
|
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.
|
|
82
|
+
"""
|
|
83
|
+
full_cmd = list(cmd)
|
|
84
|
+
if os.geteuid() != 0:
|
|
85
|
+
if password:
|
|
86
|
+
validate_sudo_password(password)
|
|
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
|
+
|
|
97
|
+
return subprocess.run(
|
|
98
|
+
full_cmd,
|
|
99
|
+
check=check,
|
|
100
|
+
timeout=timeout,
|
|
101
|
+
capture_output=capture_output,
|
|
102
|
+
text=True
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_selinux_enforcing() -> bool:
|
|
107
|
+
"""Check if SELinux is present and in Enforcing mode."""
|
|
108
|
+
enforce_file = Path("/sys/fs/selinux/enforce")
|
|
109
|
+
if enforce_file.exists():
|
|
110
|
+
try:
|
|
111
|
+
return enforce_file.read_text().strip() == "1"
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
getenforce = shutil.which("getenforce") or "/usr/sbin/getenforce"
|
|
115
|
+
if Path(getenforce).exists():
|
|
116
|
+
try:
|
|
117
|
+
res = subprocess.run([getenforce], capture_output=True, text=True, timeout=2)
|
|
118
|
+
return res.stdout.strip().lower() == "enforcing"
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def is_selinux_httpd_permissive() -> bool:
|
|
125
|
+
"""Check if httpd_t is in SELinux permissive types."""
|
|
126
|
+
if not is_selinux_enforcing():
|
|
127
|
+
return True
|
|
128
|
+
seinfo = shutil.which("seinfo") or "/usr/bin/seinfo"
|
|
129
|
+
if Path(seinfo).exists():
|
|
130
|
+
try:
|
|
131
|
+
res = subprocess.run([seinfo, "--permissive"], capture_output=True, text=True, timeout=3)
|
|
132
|
+
return "httpd_t" in res.stdout
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def ensure_selinux_httpd_permissions(password: Optional[str] = None) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Ensure SELinux allows Nginx (httpd_t) to access user document roots and FastCGI sockets.
|
|
141
|
+
If SELinux is enforcing and httpd_t is not permissive, add httpd_t to permissive types
|
|
142
|
+
via semanage and enable relevant SELinux booleans.
|
|
143
|
+
"""
|
|
144
|
+
if not is_selinux_enforcing() or is_selinux_httpd_permissive():
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
success = False
|
|
148
|
+
semanage = shutil.which("semanage") or "/usr/sbin/semanage"
|
|
149
|
+
if Path(semanage).exists():
|
|
150
|
+
try:
|
|
151
|
+
run_privileged_command([semanage, "permissive", "-a", "httpd_t"], password=password, check=False)
|
|
152
|
+
success = is_selinux_httpd_permissive()
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.warning(f"Failed to set httpd_t permissive via semanage: {e}")
|
|
155
|
+
|
|
156
|
+
# Also enable booleans as supplementary fallback
|
|
157
|
+
setsebool = shutil.which("setsebool") or "/usr/sbin/setsebool"
|
|
158
|
+
if Path(setsebool).exists():
|
|
159
|
+
try:
|
|
160
|
+
run_privileged_command(
|
|
161
|
+
[setsebool, "-P", "httpd_read_user_content=1", "httpd_enable_homedirs=1", "httpd_can_network_connect=1"],
|
|
162
|
+
password=password,
|
|
163
|
+
check=False
|
|
164
|
+
)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
return success or is_selinux_httpd_permissive()
|
|
169
|
+
|
|
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
|
+
|
|
69
232
|
def write_privileged_file(target: Path, content: str, password: Optional[str] = None) -> None:
|
|
70
233
|
"""
|
|
71
234
|
Write content to a file, using sudo tee if current process lacks permissions.
|
|
@@ -527,10 +690,24 @@ def ensure_docroot_accessible(root_path: Path) -> None:
|
|
|
527
690
|
try:
|
|
528
691
|
resolved_root = root_path.resolve()
|
|
529
692
|
resolved_home = home.resolve()
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
693
|
+
|
|
694
|
+
# Traverse every directory from root_path up to home and ensure +x (0o001)
|
|
695
|
+
curr = resolved_root
|
|
696
|
+
while curr != curr.parent:
|
|
697
|
+
try:
|
|
698
|
+
st = curr.stat()
|
|
699
|
+
if not (st.st_mode & 0o001):
|
|
700
|
+
os.chmod(str(curr), st.st_mode | 0o001)
|
|
701
|
+
except Exception:
|
|
702
|
+
pass
|
|
703
|
+
if curr == resolved_home:
|
|
704
|
+
break
|
|
705
|
+
curr = curr.parent
|
|
706
|
+
|
|
707
|
+
# Also ensure home directory itself has +x
|
|
708
|
+
st = resolved_home.stat()
|
|
709
|
+
if not (st.st_mode & 0o001):
|
|
710
|
+
os.chmod(str(resolved_home), st.st_mode | 0o001)
|
|
534
711
|
except Exception:
|
|
535
712
|
pass
|
|
536
713
|
|