Skip to content

Commit e661085

Browse files
Tux82claude
andcommitted
Tarball installer, update channels, Multi-PHP fix (v1.23)
- Installer now downloads release zip instead of git clone — repo is beta code, releases are stable. latest-beta keeps the old git-clone method for testing. - Add Release Channel setting (Settings > Updates): toggle between stable (GitHub releases) and beta (main branch). panel_update.php respects the channel and fetches accordingly. - Fix Multi-PHP install broken on all environments: systemd-run --scope fails silently in LXC containers with no fallback. Added direct background execution fallback when systemd-run fails. Simplified API exec() call, set 3-minute timeout. - Update README with release channel documentation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5a705d5 commit e661085

9 files changed

Lines changed: 129 additions & 26 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ Requires a **clean Debian 12** server with root access. The guided installer han
2020

2121
> **[Full installation guide →](https://inetpanel.tuxxin.com/install)**
2222
23+
### Release Channels
24+
25+
| Channel | Installer | Updates | Use case |
26+
|---|---|---|---|
27+
| **Stable** | `inetpanel.tuxxin.com/latest` | Tagged GitHub releases | Production servers (default) |
28+
| **Beta** | `inetpanel.tuxxin.com/latest-beta` | Latest code from `main` branch | Testing new features before release |
29+
30+
The **stable** installer downloads the latest tagged release. Updates are pulled from GitHub Releases only when a new version is published.
31+
32+
The **beta** installer clones the `main` branch directly. Updates pull the latest commit from `main`, which may include untested changes.
33+
34+
You can switch between channels at any time in **Settings → Updates → Release Channel** without reinstalling.
35+
2336
---
2437

2538
## Why iNetPanel?

TiCore/Version.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
class Version
1010
{
11-
const APP_VERSION = '1.22.3';
11+
const APP_VERSION = '1.23';
1212

1313
/**
1414
* Return current version string.

api/multiphp.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ function phpIsInstalled(string $ver): bool
8080
// Launch via inetp as a detached background process.
8181
// This runs entirely outside PHP-FPM and survives FPM restarts.
8282
$logFile = '/var/www/inetpanel/storage/multiphp.log';
83-
$cmd = 'nohup sudo /usr/local/bin/inetp multiphp_manage'
83+
$cmd = 'sudo /usr/local/bin/inetp multiphp_manage'
8484
. ' --action ' . escapeshellarg($action)
8585
. ' --version ' . escapeshellarg($ver)
86-
. ' > ' . escapeshellarg($logFile) . ' 2>&1 &';
86+
. ' </dev/null >> ' . escapeshellarg($logFile) . ' 2>&1 &';
8787
exec($cmd);
8888

8989
echo json_encode(['success' => true, 'output' => "PHP {$ver} {$action} started..."]);

api/settings.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,17 @@
137137
echo json_encode(['success' => true, 'saved' => $saved]);
138138
break;
139139

140+
case 'set_update_channel':
141+
Auth::requireAdmin();
142+
$channel = trim($_POST['channel'] ?? '');
143+
if (!in_array($channel, ['stable', 'beta'])) {
144+
echo json_encode(['success' => false, 'error' => 'Invalid channel.']);
145+
break;
146+
}
147+
DB::saveSetting('update_channel', $channel);
148+
echo json_encode(['success' => true, 'channel' => $channel]);
149+
break;
150+
140151
case 'update_now':
141152
Auth::requireAdmin();
142153
$phpBin = 'php' . DB::setting('php_default_version', '8.4');

public/install.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,8 @@
424424
'auto_update_time' => '02:00',
425425
// PHP
426426
'php_default_version' => $detectedPhpVer,
427+
// Updates
428+
'update_channel' => 'stable',
427429
// SSH
428430
'ssh_port' => '1022',
429431
];

scripts/panel_update.php

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,66 @@ function abort(string $msg, int $code = 1): never
4444
if (!defined('API_PATH')) define('API_PATH', PANEL_PATH . '/api');
4545

4646
$currentVersion = class_exists('Version') ? Version::get() : '0.000';
47-
log_msg("iNetPanel update check — current version: {$currentVersion}");
47+
$updateChannel = class_exists('DB') ? (DB::setting('update_channel', 'stable') ?? 'stable') : 'stable';
48+
log_msg("iNetPanel update check — current version: {$currentVersion}, channel: {$updateChannel}");
4849

4950
// Fetch latest release info from GitHub
5051
$ghHeaders = class_exists('Version') ? Version::githubHeaders() : ['User-Agent: iNetPanel/' . $currentVersion];
51-
$ch = curl_init(GH_API_URL);
52-
curl_setopt_array($ch, [
53-
CURLOPT_RETURNTRANSFER => true,
54-
CURLOPT_TIMEOUT => 15,
55-
CURLOPT_HTTPHEADER => $ghHeaders,
56-
]);
57-
$raw = curl_exec($ch);
58-
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
59-
curl_close($ch);
6052

61-
if ($raw === false || $code !== 200) {
62-
abort('Failed to reach GitHub API (HTTP ' . $code . ').');
63-
}
53+
if ($updateChannel === 'beta') {
54+
// Beta channel: fetch main branch info via commits API
55+
$ch = curl_init('https://api.github.com/repos/tuxxin/iNetPanel/commits/main');
56+
curl_setopt_array($ch, [
57+
CURLOPT_RETURNTRANSFER => true,
58+
CURLOPT_TIMEOUT => 15,
59+
CURLOPT_HTTPHEADER => $ghHeaders,
60+
]);
61+
$raw = curl_exec($ch);
62+
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
63+
curl_close($ch);
64+
65+
if ($raw === false || $code !== 200) {
66+
abort('Failed to reach GitHub API (HTTP ' . $code . ').');
67+
}
6468

65-
$release = json_decode($raw, true);
66-
if (!is_array($release) || empty($release['tag_name'])) {
67-
abort('Invalid response from GitHub API.');
68-
}
69+
$commitData = json_decode($raw, true);
70+
$latestSha = substr($commitData['sha'] ?? '', 0, 7);
71+
$latestTag = $currentVersion . '-beta.' . $latestSha;
6972

70-
$latestTag = ltrim($release['tag_name'], 'v');
71-
log_msg("Latest release on GitHub: {$latestTag}");
73+
// For beta, always update (can't reliably compare versions)
74+
log_msg("Beta channel — latest commit: {$latestSha}");
75+
76+
// Build a fake $release structure for the download step
77+
$release = [
78+
'tag_name' => $latestTag,
79+
'zipball_url' => 'https://api.github.com/repos/tuxxin/iNetPanel/zipball/main',
80+
'assets' => [],
81+
'body' => 'Beta update from main branch (commit ' . $latestSha . ')',
82+
];
83+
} else {
84+
// Stable channel: fetch latest tagged release
85+
$ch = curl_init(GH_API_URL);
86+
curl_setopt_array($ch, [
87+
CURLOPT_RETURNTRANSFER => true,
88+
CURLOPT_TIMEOUT => 15,
89+
CURLOPT_HTTPHEADER => $ghHeaders,
90+
]);
91+
$raw = curl_exec($ch);
92+
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
93+
curl_close($ch);
94+
95+
if ($raw === false || $code !== 200) {
96+
abort('Failed to reach GitHub API (HTTP ' . $code . ').');
97+
}
98+
99+
$release = json_decode($raw, true);
100+
if (!is_array($release) || empty($release['tag_name'])) {
101+
abort('Invalid response from GitHub API.');
102+
}
103+
104+
$latestTag = ltrim($release['tag_name'], 'v');
105+
log_msg("Stable channel — latest release: {$latestTag}");
106+
}
72107

73108
// Update SQLite cache
74109
if (class_exists('DB')) {
@@ -78,8 +113,8 @@ function abort(string $msg, int $code = 1): never
78113
} catch (Throwable $e) { log_msg('WARNING: Failed to cache version in DB - ' . $e->getMessage()); }
79114
}
80115

81-
// Check if update is needed
82-
if (!$force && version_compare($latestTag, $currentVersion, '<=')) {
116+
// Check if update is needed (beta channel always updates when forced or on schedule)
117+
if (!$force && $updateChannel !== 'beta' && version_compare($latestTag, $currentVersion, '<=')) {
83118
log_msg('Already up to date. No update needed.');
84119
exit(0);
85120
}

scripts/system/multiphp_manage.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,17 @@ if [[ -z "$RUN_DETACHED" ]]; then
6363
chmod 0666 "$STATUS_FILE"
6464
echo '{"success":true,"output":"started"}'
6565

66-
systemd-run --scope --quiet bash "$0" \
66+
# Try systemd-run scope (isolates from FPM restarts).
67+
# Falls back to direct execution if systemd-run fails (LXC containers).
68+
if systemd-run --scope --quiet bash "$0" \
6769
--action "$ACTION" --version "$VERSION" --run-detached \
6870
>> "$STATUS_DIR/multiphp.log" 2>&1 &
71+
then
72+
:
73+
else
74+
bash "$0" --action "$ACTION" --version "$VERSION" --run-detached \
75+
>> "$STATUS_DIR/multiphp.log" 2>&1 &
76+
fi
6977
exit 0
7078
fi
7179

src/multiphp.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ function toggleVersion(ver, action) {
232232
}
233233
})
234234
.catch(() => {});
235-
if (attempts > 120) {
235+
if (attempts > 60) {
236236
clearInterval(poll);
237237
modal.hide();
238238
showAlert(`PHP ${ver} ${action} timed out. Check logs and reload the page.`, 'warning');

src/settings.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,23 @@ function human_time_diff(int $ts): string {
118118
</div>
119119
</div>
120120

121+
<!-- Release Channel -->
122+
<div class="row g-3 mb-4">
123+
<div class="col-md-6">
124+
<div class="p-3 border rounded bg-light">
125+
<div class="fw-semibold mb-2"><i class="fas fa-code-branch me-1"></i>Release Channel</div>
126+
<div class="text-muted small mb-2">
127+
<strong>Stable</strong> pulls from tagged GitHub releases.
128+
<strong>Beta</strong> pulls the latest code from the main branch.
129+
</div>
130+
<select class="form-select form-select-sm w-auto" id="update-channel-select">
131+
<option value="stable" <?= s('update_channel', 'stable') === 'stable' ? 'selected' : '' ?>>Stable (Recommended)</option>
132+
<option value="beta" <?= s('update_channel', 'stable') === 'beta' ? 'selected' : '' ?>>Beta</option>
133+
</select>
134+
</div>
135+
</div>
136+
</div>
137+
121138
<hr class="my-4">
122139
<h6 class="text-muted text-uppercase small fw-bold mb-3">Scheduled Jobs</h6>
123140

@@ -783,6 +800,23 @@ function refreshUpdateStatus() {
783800
// Auto-load on page ready
784801
document.addEventListener('DOMContentLoaded', refreshUpdateStatus);
785802

803+
// Release channel selector
804+
const channelSelect = document.getElementById('update-channel-select');
805+
if (channelSelect) {
806+
channelSelect.addEventListener('change', function() {
807+
const fd = new FormData();
808+
fd.append('action', 'set_update_channel');
809+
fd.append('channel', this.value);
810+
fetch('/api/settings', { method: 'POST', body: fd })
811+
.then(r => r.json())
812+
.then(data => {
813+
if (data.success) showAlert(`Release channel set to ${data.channel}.`, 'success');
814+
else showAlert(data.error || 'Failed.', 'danger');
815+
})
816+
.catch(() => showAlert('Request failed.', 'danger'));
817+
});
818+
}
819+
786820
const checkNowBtn = document.getElementById('check-now-btn');
787821
const updateNowBtn = document.getElementById('update-now-btn');
788822

0 commit comments

Comments
 (0)