mirror of
https://github.com/telemt/telemt.git
synced 2026-05-23 20:21:44 +03:00
Compare commits
3 Commits
toolchains
...
eeba759268
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeba759268 | ||
|
|
bbc69f945e | ||
|
|
b9eb1406bb |
28
.github/workflows/release.yml
vendored
28
.github/workflows/release.yml
vendored
@@ -130,14 +130,12 @@ jobs:
|
||||
pkg-config \
|
||||
curl
|
||||
|
||||
# 💾 cache toolchain
|
||||
- uses: actions/cache@v4
|
||||
if: matrix.target == 'aarch64-unknown-linux-musl'
|
||||
with:
|
||||
path: ~/.musl-aarch64
|
||||
key: musl-toolchain-aarch64-v1
|
||||
|
||||
# 🔥 надёжная установка
|
||||
- name: Install aarch64 musl toolchain
|
||||
if: matrix.target == 'aarch64-unknown-linux-musl'
|
||||
run: |
|
||||
@@ -145,27 +143,19 @@ jobs:
|
||||
|
||||
TOOLCHAIN_DIR="$HOME/.musl-aarch64"
|
||||
ARCHIVE="aarch64-linux-musl-cross.tgz"
|
||||
URL="https://github.com/telemt/telemt/releases/download/toolchains/$ARCHIVE"
|
||||
|
||||
if [ -x "$TOOLCHAIN_DIR/bin/aarch64-linux-musl-gcc" ]; then
|
||||
echo "✅ musl toolchain already installed"
|
||||
echo "✅ MUSL toolchain already installed"
|
||||
else
|
||||
echo "⬇️ downloading musl toolchain..."
|
||||
echo "⬇️ Downloading musl toolchain from Telemt GitHub Releases..."
|
||||
|
||||
download() {
|
||||
url="$1"
|
||||
echo "→ trying $url"
|
||||
curl -fL \
|
||||
--retry 5 \
|
||||
--retry-delay 3 \
|
||||
--connect-timeout 10 \
|
||||
--max-time 120 \
|
||||
-o "$ARCHIVE" "$url" && return 0
|
||||
return 1
|
||||
}
|
||||
|
||||
download "https://musl.cc/$ARCHIVE" || \
|
||||
download "https://more.musl.cc/$ARCHIVE" || \
|
||||
{ echo "❌ failed to download musl toolchain"; exit 1; }
|
||||
curl -fL \
|
||||
--retry 5 \
|
||||
--retry-delay 3 \
|
||||
--connect-timeout 10 \
|
||||
--max-time 120 \
|
||||
-o "$ARCHIVE" "$URL"
|
||||
|
||||
mkdir -p "$TOOLCHAIN_DIR"
|
||||
tar -xzf "$ARCHIVE" --strip-components=1 -C "$TOOLCHAIN_DIR"
|
||||
|
||||
@@ -1060,6 +1060,8 @@ Link generation uses active config and enabled modes:
|
||||
| `PATCH /v1/users/{username}` | Partial update of provided fields only. Missing fields remain unchanged. Current implementation persists full config document on success. |
|
||||
| `POST /v1/users/{username}/rotate-secret` | Currently returns `404` in runtime route matcher; request schema is reserved for intended behavior. |
|
||||
| `DELETE /v1/users/{username}` | Deletes only specified user, removes this user from related optional `access.user_*` maps, blocks last-user deletion, and atomically updates only related `access.*` TOML tables. |
|
||||
| `POST /v1/users/{username}/reset-octets` | Resets the per-user octet counters (`octets_from_client` and `octets_to_client`) to zero. Returns `{ "username": "...", "octets_reset": true }`. Useful for implementing periodic (monthly/daily) quota resets without restarting the proxy. |
|
||||
| `POST /v1/users/reset-octets` | Resets octet counters for **all** tracked users. Returns `{ "users_reset": N }`. |
|
||||
|
||||
All mutating endpoints:
|
||||
- Respect `read_only` mode.
|
||||
|
||||
@@ -372,6 +372,19 @@ async fn handle(
|
||||
.await;
|
||||
Ok(success_response(StatusCode::OK, users, revision))
|
||||
}
|
||||
("POST", "/v1/users/reset-octets") => {
|
||||
let count = shared.stats.reset_all_user_octets();
|
||||
shared.runtime_events.record(
|
||||
"api.users.reset_octets.ok",
|
||||
format!("users_reset={}", count),
|
||||
);
|
||||
let revision = current_revision(&shared.config_path).await?;
|
||||
Ok(success_response(
|
||||
StatusCode::OK,
|
||||
model::ResetAllOctetsResponse { users_reset: count },
|
||||
revision,
|
||||
))
|
||||
}
|
||||
("POST", "/v1/users") => {
|
||||
if api_cfg.read_only {
|
||||
return Ok(error_response(
|
||||
@@ -523,6 +536,37 @@ async fn handle(
|
||||
);
|
||||
return Ok(success_response(StatusCode::OK, data, revision));
|
||||
}
|
||||
// POST /v1/users/{username}/reset-octets
|
||||
if method == Method::POST
|
||||
&& let Some(base_user) = user.strip_suffix("/reset-octets")
|
||||
&& !base_user.is_empty()
|
||||
&& !base_user.contains('/')
|
||||
{
|
||||
let found = shared.stats.reset_user_octets(base_user);
|
||||
shared.runtime_events.record(
|
||||
if found { "api.user.reset_octets.ok" } else { "api.user.reset_octets.not_found" },
|
||||
format!("username={}", base_user),
|
||||
);
|
||||
if !found {
|
||||
return Ok(error_response(
|
||||
request_id,
|
||||
ApiFailure::new(
|
||||
StatusCode::NOT_FOUND,
|
||||
"user_not_found",
|
||||
&format!("No stats entry for user '{}'", base_user),
|
||||
),
|
||||
));
|
||||
}
|
||||
let revision = current_revision(&shared.config_path).await?;
|
||||
return Ok(success_response(
|
||||
StatusCode::OK,
|
||||
model::ResetOctetsResponse {
|
||||
username: base_user.to_string(),
|
||||
octets_reset: true,
|
||||
},
|
||||
revision,
|
||||
));
|
||||
}
|
||||
if method == Method::POST {
|
||||
return Ok(error_response(
|
||||
request_id,
|
||||
|
||||
@@ -459,6 +459,17 @@ pub(super) struct CreateUserRequest {
|
||||
pub(super) max_unique_ips: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct ResetOctetsResponse {
|
||||
pub(super) username: String,
|
||||
pub(super) octets_reset: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct ResetAllOctetsResponse {
|
||||
pub(super) users_reset: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(super) struct PatchUserRequest {
|
||||
pub(super) secret: Option<String>,
|
||||
|
||||
@@ -1745,6 +1745,29 @@ impl Stats {
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
|
||||
/// Reset per-user octet counters to zero (both from_client and to_client).
|
||||
/// Used by the API to implement periodic quota resets without restarting the proxy.
|
||||
pub fn reset_user_octets(&self, user: &str) -> bool {
|
||||
if let Some(entry) = self.user_stats.get(user) {
|
||||
entry.octets_from_client.store(0, Ordering::Relaxed);
|
||||
entry.octets_to_client.store(0, Ordering::Relaxed);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset octet counters for all tracked users.
|
||||
pub fn reset_all_user_octets(&self) -> usize {
|
||||
let mut count = 0;
|
||||
for entry in self.user_stats.iter() {
|
||||
entry.octets_from_client.store(0, Ordering::Relaxed);
|
||||
entry.octets_to_client.store(0, Ordering::Relaxed);
|
||||
count += 1;
|
||||
}
|
||||
count
|
||||
}
|
||||
pub fn get_handshake_timeouts(&self) -> u64 { self.handshake_timeouts.load(Ordering::Relaxed) }
|
||||
pub fn get_upstream_connect_attempt_total(&self) -> u64 {
|
||||
self.upstream_connect_attempt_total.load(Ordering::Relaxed)
|
||||
|
||||
Reference in New Issue
Block a user