Compare commits
3 Commits
04c04612a2
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38ad3e23e1 | ||
| c5b9e6c4b2 | |||
| 3954954f86 |
44
install.sh
44
install.sh
@@ -32,6 +32,25 @@ TIMER="/etc/systemd/system/torpanel-collector.timer"
|
|||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
ensure_torrc_d_include() {
|
||||||
|
local main="$TOR_ETC/torrc"
|
||||||
|
install -d -m 755 "$TOR_TORRC_D"
|
||||||
|
|
||||||
|
if [[ ! -f "$main" ]]; then
|
||||||
|
echo "# Created by TorPanel installer" > "$main"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -Eq '^[[:space:]]*%include[[:space:]]+/etc/tor/torrc\.d/\*\.conf' "$main"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if sed -n 's/^[[:space:]]*#\s*%include[[:space:]]\+\/etc\/tor\/torrc\.d\/\*\.conf/%include \/etc\/tor\/torrc.d\/\*\.conf/p' "$main" | grep -q .; then
|
||||||
|
sed -i 's/^[[:space:]]*#\s*%include[[:space:]]\+\/etc\/tor\/torrc\.d\/\*\.conf/%include \/etc\/tor\/torrc.d\/\*\.conf/' "$main"
|
||||||
|
else
|
||||||
|
printf '\n%%include /etc/tor/torrc.d/*.conf\n' >> "$main"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo -e "${C_BOLD}Installing TorPanel...${C_RESET}"
|
echo -e "${C_BOLD}Installing TorPanel...${C_RESET}"
|
||||||
|
|
||||||
info "Updating apt and installing packages"
|
info "Updating apt and installing packages"
|
||||||
@@ -49,7 +68,7 @@ ln -sf "$PHP_SOCK" /run/php/php-fpm.sock || true
|
|||||||
|
|
||||||
info "Preparing directories"
|
info "Preparing directories"
|
||||||
install -d "$PANEL_PUBLIC" "$STATE_DIR" "$LOG_DIR" "$ETC_APP"
|
install -d "$PANEL_PUBLIC" "$STATE_DIR" "$LOG_DIR" "$ETC_APP"
|
||||||
touch "$STATE_DIR/stats.json"
|
echo -n '{"data":[]}' > "$STATE_DIR/stats.json"
|
||||||
rsync -a --delete "$SCRIPT_DIR/web/" "$PANEL_PUBLIC/"
|
rsync -a --delete "$SCRIPT_DIR/web/" "$PANEL_PUBLIC/"
|
||||||
chown -R www-data:www-data "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR"
|
chown -R www-data:www-data "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR"
|
||||||
chmod 750 "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR"
|
chmod 750 "$PANEL_ROOT" "$STATE_DIR" "$LOG_DIR"
|
||||||
@@ -105,6 +124,10 @@ CookieAuthFileGroupReadable 1
|
|||||||
TORRC
|
TORRC
|
||||||
ok "torrc written"
|
ok "torrc written"
|
||||||
|
|
||||||
|
info "Ensuring main torrc includes torrc.d/*.conf"
|
||||||
|
ensure_torrc_d_include
|
||||||
|
ok "torrc.d include active"
|
||||||
|
|
||||||
info "Setting permissions for Tor managed config"
|
info "Setting permissions for Tor managed config"
|
||||||
chown root:www-data "$TOR_TORRC_D"; chmod 775 "$TOR_TORRC_D"
|
chown root:www-data "$TOR_TORRC_D"; chmod 775 "$TOR_TORRC_D"
|
||||||
chown root:www-data "$TOR_PANEL_CONF"; chmod 664 "$TOR_PANEL_CONF"
|
chown root:www-data "$TOR_PANEL_CONF"; chmod 664 "$TOR_PANEL_CONF"
|
||||||
@@ -129,6 +152,7 @@ cat > "$SVC" <<'UNIT'
|
|||||||
Description=TorPanel minute collector
|
Description=TorPanel minute collector
|
||||||
After=tor.service
|
After=tor.service
|
||||||
Wants=tor.service
|
Wants=tor.service
|
||||||
|
ConditionPathExists=/run/tor/control.authcookie
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=oneshot
|
||||||
@@ -146,17 +170,22 @@ ProtectKernelModules=yes
|
|||||||
ProtectKernelTunables=yes
|
ProtectKernelTunables=yes
|
||||||
PrivateTmp=yes
|
PrivateTmp=yes
|
||||||
PrivateDevices=yes
|
PrivateDevices=yes
|
||||||
PrivateUsers=yes
|
PrivateUsers=no
|
||||||
LockPersonality=yes
|
LockPersonality=yes
|
||||||
MemoryDenyWriteExecute=yes
|
MemoryDenyWriteExecute=yes
|
||||||
RestrictRealtime=yes
|
RestrictRealtime=yes
|
||||||
RestrictSUIDSGID=yes
|
RestrictSUIDSGID=yes
|
||||||
UMask=0077
|
UMask=0077
|
||||||
ReadWriteDirectories=/var/lib/torpanel
|
ReadWritePaths=/var/lib/torpanel
|
||||||
ReadOnlyPaths=/run/tor /etc/tor
|
ReadOnlyPaths=/run/tor /etc/tor
|
||||||
RestrictAddressFamilies=AF_UNIX
|
RestrictAddressFamilies=AF_UNIX
|
||||||
SystemCallFilter=@system-service
|
SystemCallFilter=@system-service
|
||||||
CapabilityBoundingSet=
|
CapabilityBoundingSet=
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
Environment=PYTHONUNBUFFERED=1
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=15s
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=timers.target
|
WantedBy=timers.target
|
||||||
@@ -165,11 +194,13 @@ UNIT
|
|||||||
cat > "$TIMER" <<'TIMER'
|
cat > "$TIMER" <<'TIMER'
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Run TorPanel collector every minute
|
Description=Run TorPanel collector every minute
|
||||||
|
|
||||||
[Timer]
|
[Timer]
|
||||||
OnBootSec=30sec
|
OnCalendar=*-*-* *:*:00
|
||||||
OnUnitActiveSec=60sec
|
AccuracySec=15s
|
||||||
AccuracySec=15sec
|
|
||||||
Persistent=true
|
Persistent=true
|
||||||
|
Unit=torpanel-collector.service
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=timers.target
|
WantedBy=timers.target
|
||||||
TIMER
|
TIMER
|
||||||
@@ -181,6 +212,7 @@ systemctl enable --now tor
|
|||||||
systemctl enable "$PHP_FPM_SVC" nginx >/dev/null
|
systemctl enable "$PHP_FPM_SVC" nginx >/dev/null
|
||||||
systemctl restart "$PHP_FPM_SVC"
|
systemctl restart "$PHP_FPM_SVC"
|
||||||
systemctl restart nginx
|
systemctl restart nginx
|
||||||
|
systemctl start torpanel-collector.service
|
||||||
systemctl enable --now torpanel-collector.timer
|
systemctl enable --now torpanel-collector.timer
|
||||||
ok "Services running"
|
ok "Services running"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
require __DIR__ . '/../lib/app.php'; auth_require();
|
require __DIR__ . '/../lib/app.php';
|
||||||
|
auth_require();
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
$path = "/var/lib/torpanel/stats.json";
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||||
if (!file_exists($path)) { echo json_encode(["data"=>[]]); exit; }
|
header('Pragma: no-cache');
|
||||||
|
|
||||||
|
$path = '/var/lib/torpanel/stats.json';
|
||||||
|
if (!is_readable($path)) {
|
||||||
|
echo json_encode(['data' => []]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$raw = file_get_contents($path);
|
$raw = file_get_contents($path);
|
||||||
echo $raw ?: json_encode(["data"=>[]]);
|
echo $raw !== false && $raw !== '' ? $raw : json_encode(['data' => []]);
|
||||||
51
web/api/stats_rates.php
Normal file
51
web/api/stats_rates.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../lib/app.php';
|
||||||
|
auth_require();
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||||
|
header('Pragma: no-cache');
|
||||||
|
|
||||||
|
$path = '/var/lib/torpanel/stats.json';
|
||||||
|
if (!is_readable($path)) { echo json_encode(['ok'=>true,'data'=>[]]); exit; }
|
||||||
|
|
||||||
|
$raw = file_get_contents($path);
|
||||||
|
$state = json_decode($raw, true);
|
||||||
|
if (!is_array($state) || !is_array($state['data'] ?? null)) {
|
||||||
|
echo json_encode(['ok'=>true,'data'=>[]]); exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($state['data'], function($a,$b){
|
||||||
|
return (int)($a['t'] ?? 0) <=> (int)($b['t'] ?? 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$prev = null;
|
||||||
|
foreach ($state['data'] as $s) {
|
||||||
|
$t = (int)($s['t'] ?? 0);
|
||||||
|
if ($t <= 0) continue;
|
||||||
|
|
||||||
|
$rx_total = (int)($s['read'] ?? ($s['bytes_read'] ?? ($s['rx'] ?? 0)));
|
||||||
|
$tx_total = (int)($s['written'] ?? ($s['bytes_written'] ?? ($s['tx'] ?? 0)));
|
||||||
|
|
||||||
|
$rx_mbps = 0.0; $tx_mbps = 0.0;
|
||||||
|
if ($prev) {
|
||||||
|
$dt = max(1, $t - $prev['t']);
|
||||||
|
$drx = max(0, $rx_total - $prev['rx_total']);
|
||||||
|
$dtx = max(0, $tx_total - $prev['tx_total']);
|
||||||
|
$rx_mbps = ($drx * 8) / $dt / 1_000_000.0;
|
||||||
|
$tx_mbps = ($dtx * 8) / $dt / 1_000_000.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$out[] = [
|
||||||
|
'ts' => $t * 1000,
|
||||||
|
'rx_mbps' => round($rx_mbps, 3),
|
||||||
|
'tx_mbps' => round($tx_mbps, 3),
|
||||||
|
'rx_total' => $rx_total,
|
||||||
|
'tx_total' => $tx_total,
|
||||||
|
];
|
||||||
|
$prev = ['t'=>$t, 'rx_total'=>$rx_total, 'tx_total'=>$tx_total];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($out) > 1440) $out = array_slice($out, -1440);
|
||||||
|
echo json_encode(['ok'=>true,'data'=>$out]);
|
||||||
@@ -87,14 +87,14 @@ auth_require();
|
|||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="small">Read (total)</div>
|
<div class="small">RX (total)</div>
|
||||||
<div class="h4 mono" id="readTotal">—</div>
|
<div class="h4 mono" id="readTotal">—</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
<div class="small">Written (total)</div>
|
<div class="small">TX (total)</div>
|
||||||
<div class="h4 mono" id="writeTotal">—</div>
|
<div class="h4 mono" id="writeTotal">—</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -326,8 +326,8 @@ function setChartData(labels, rx, tx){
|
|||||||
chart = new Chart(ctx, {
|
chart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: { labels, datasets: [
|
data: { labels, datasets: [
|
||||||
{label:'Read/min', data: rx, tension:.3},
|
{label:'RX/min', data: rx, tension:.3},
|
||||||
{label:'Written/min', data: tx, tension:.3}
|
{label:'TX/min', data: tx, tension:.3}
|
||||||
]},
|
]},
|
||||||
options: {
|
options: {
|
||||||
responsive:true,
|
responsive:true,
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
<input class="form-control" id="orport" name="orport" type="number" min="1" max="65535" placeholder="9001"
|
<input class="form-control" id="orport" name="orport" type="number" min="1" max="65535" placeholder="9001"
|
||||||
value="<?= htmlspecialchars($_POST['orport'] ?? '9001') ?>">
|
value="<?= htmlspecialchars($_POST['orport'] ?? '9001') ?>">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-3">
|
||||||
<label class="form-label" for="rate_mbps">Bandwidth</label>
|
<label class="form-label" for="rate_mbps">Bandwidth</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control" id="rate_mbps" name="rate_mbps" type="number" min="1" step="1" placeholder="5"
|
<input class="form-control" id="rate_mbps" name="rate_mbps" type="number" min="1" step="1" placeholder="5"
|
||||||
|
|||||||
Reference in New Issue
Block a user