feature
This commit is contained in:
@@ -12,6 +12,9 @@ class ChannelProjectService
|
|||||||
/**
|
/**
|
||||||
* Run tools/new_project.py to materialize public/web/{channelId}/.
|
* Run tools/new_project.py to materialize public/web/{channelId}/.
|
||||||
* Domains come from config (系统设置 / env); script rebuilds sync only when domains changed.
|
* Domains come from config (系统设置 / env); script rebuilds sync only when domains changed.
|
||||||
|
*
|
||||||
|
* Panel hosts often set open_basedir to server/ only — do not probe lab-root
|
||||||
|
* paths with is_file(); the Python child process is not bound by open_basedir.
|
||||||
*/
|
*/
|
||||||
public function generate(string $channelId): void
|
public function generate(string $channelId): void
|
||||||
{
|
{
|
||||||
@@ -26,14 +29,12 @@ class ChannelProjectService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$labRoot = dirname(base_path()); // coruna-lab/
|
$labRoot = dirname(base_path()); // coruna-lab/
|
||||||
$script = $labRoot.'/tools/new_project.py';
|
$script = $this->resolveNewProjectScript($labRoot);
|
||||||
if (! is_file($script)) {
|
$python = $this->resolvePythonBin();
|
||||||
throw new RuntimeException('new_project.py not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
$joined = implode(',', $domains);
|
$joined = implode(',', $domains);
|
||||||
$cmd = [
|
$cmd = [
|
||||||
'python3',
|
$python,
|
||||||
$script,
|
$script,
|
||||||
'--channel-id',
|
'--channel-id',
|
||||||
$channelId,
|
$channelId,
|
||||||
@@ -43,7 +44,8 @@ class ChannelProjectService
|
|||||||
$joined,
|
$joined,
|
||||||
];
|
];
|
||||||
|
|
||||||
$process = new Process($cmd, $labRoot);
|
// cwd must stay inside open_basedir (server/). Script resolves LAB_ROOT from __file__.
|
||||||
|
$process = new Process($cmd, base_path());
|
||||||
$process->setTimeout(600);
|
$process->setTimeout(600);
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
@@ -71,4 +73,41 @@ class ChannelProjectService
|
|||||||
File::deleteDirectory($dir);
|
File::deleteDirectory($dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveNewProjectScript(string $labRoot): string
|
||||||
|
{
|
||||||
|
$configured = trim((string) config('coruna.new_project_py', ''));
|
||||||
|
if ($configured !== '') {
|
||||||
|
return $configured;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $labRoot.'/tools/new_project.py';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer an absolute python path so the child process gets a non-empty sys.executable.
|
||||||
|
*/
|
||||||
|
private function resolvePythonBin(): string
|
||||||
|
{
|
||||||
|
$configured = trim((string) config('coruna.python_bin', 'python3'));
|
||||||
|
if ($configured === '') {
|
||||||
|
$configured = 'python3';
|
||||||
|
}
|
||||||
|
if (str_contains($configured, '/')) {
|
||||||
|
return $configured;
|
||||||
|
}
|
||||||
|
|
||||||
|
$which = trim((string) shell_exec('command -v '.escapeshellarg($configured).' 2>/dev/null'));
|
||||||
|
if ($which !== '' && ! str_contains($which, "\n")) {
|
||||||
|
return $which;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (['/usr/bin/python3', '/usr/local/bin/python3'] as $fallback) {
|
||||||
|
if (@is_file($fallback)) {
|
||||||
|
return $fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $configured;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ return [
|
|||||||
// Absolute path, project-local bin/7z, or bare "7z" on PATH. Avoid probing
|
// Absolute path, project-local bin/7z, or bare "7z" on PATH. Avoid probing
|
||||||
// system paths with is_executable() under open_basedir (see CorunaArchive).
|
// system paths with is_executable() under open_basedir (see CorunaArchive).
|
||||||
'seven_zip' => env('CORUNA_7Z_BIN', ''),
|
'seven_zip' => env('CORUNA_7Z_BIN', ''),
|
||||||
|
// Channel pack builder (outside server/ — do not is_file() under tight open_basedir).
|
||||||
|
'new_project_py' => env('CORUNA_NEW_PROJECT_PY', ''),
|
||||||
|
'python_bin' => env('CORUNA_PYTHON_BIN', 'python3'),
|
||||||
'telegram' => [
|
'telegram' => [
|
||||||
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
|
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
|
||||||
'owner_chat_id' => env('TELEGRAM_OWNER_CHAT_ID'),
|
'owner_chat_id' => env('TELEGRAM_OWNER_CHAT_ID'),
|
||||||
|
|||||||
+15
-1
@@ -87,6 +87,20 @@ def pick_channel(explicit: str | None, web_root: Path) -> str:
|
|||||||
return channel
|
return channel
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_python() -> str:
|
||||||
|
"""PHP/panel often launches `python3` such that sys.executable is ''."""
|
||||||
|
exe = (sys.executable or "").strip()
|
||||||
|
if exe:
|
||||||
|
return exe
|
||||||
|
for name in ("python3", "python"):
|
||||||
|
found = shutil.which(name)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
raise SystemExit(
|
||||||
|
"cannot locate python interpreter (sys.executable is empty; install python3 on PATH)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def run(cmd: list[str]) -> None:
|
def run(cmd: list[str]) -> None:
|
||||||
print("+", " ".join(cmd), flush=True)
|
print("+", " ".join(cmd), flush=True)
|
||||||
subprocess.run(cmd, cwd=str(LAB_ROOT), check=True)
|
subprocess.run(cmd, cwd=str(LAB_ROOT), check=True)
|
||||||
@@ -230,7 +244,7 @@ def main() -> int:
|
|||||||
for item in fixed_rep:
|
for item in fixed_rep:
|
||||||
domain_args += ["--reporting-domains", item]
|
domain_args += ["--reporting-domains", item]
|
||||||
|
|
||||||
py = sys.executable
|
py = resolve_python()
|
||||||
|
|
||||||
if same_domains:
|
if same_domains:
|
||||||
dep = args.deployment_seed or prev_seeds.get("deployment_seed")
|
dep = args.deployment_seed or prev_seeds.get("deployment_seed")
|
||||||
|
|||||||
Reference in New Issue
Block a user