Compare commits
16 Commits
09205f8db2
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cb7257ed6f | |||
| 6c7730eb7f | |||
| 31fc9fe654 | |||
| 4b22a19837 | |||
| 544cbaf553 | |||
| 1893e63d08 | |||
| d2db097445 | |||
| f0c5261e7b | |||
| 2f75b75703 | |||
| 356b049849 | |||
|
|
230a9212fe | ||
|
|
28918880da | ||
|
|
37090d80b0 | ||
|
|
a45a9b0392 | ||
|
|
4ce94a5b17 | ||
|
|
fe8376dd6d |
23
.gitea/workflows/ci.yml
Normal file
23
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build relay
|
||||
run: cargo build -p relay
|
||||
|
||||
- name: Build client
|
||||
run: cargo build -p client
|
||||
198
.gitea/workflows/release.yml
Normal file
198
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,198 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
OWNER: ${{ github.repository_owner }}
|
||||
REPO: ${{ github.event.repository.name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install packaging tools
|
||||
run: |
|
||||
cargo install cargo-deb --locked
|
||||
cargo install cargo-rpm --locked
|
||||
|
||||
- name: Build release binaries
|
||||
run: cargo build --release -p relay -p client
|
||||
|
||||
- name: Resolve version
|
||||
id: version
|
||||
run: |
|
||||
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
IS_PRERELEASE=false
|
||||
else
|
||||
VERSION="main-${GITHUB_SHA:0:7}"
|
||||
IS_PRERELEASE=true
|
||||
fi
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_prerelease=${IS_PRERELEASE}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Package tarball
|
||||
run: |
|
||||
mkdir -p dist
|
||||
cp target/release/relay dist/
|
||||
cp target/release/client dist/
|
||||
tar -czf "dist/play-dvv-${{ steps.version.outputs.version }}-linux-x86_64.tar.gz" -C dist relay client
|
||||
|
||||
- name: Build deb packages
|
||||
run: |
|
||||
cargo deb -p relay --no-build
|
||||
cargo deb -p client --no-build
|
||||
cp target/debian/*.deb dist/ || true
|
||||
|
||||
- name: Build rpm packages
|
||||
run: |
|
||||
cargo rpm build -p relay --release
|
||||
cargo rpm build -p client --release
|
||||
find target/release/rpmbuild/RPMS -name "*.rpm" -exec cp {} dist/ \;
|
||||
|
||||
# -------------------------------------------------
|
||||
# FIXED: Generic package upload (Gitea correct API)
|
||||
# -------------------------------------------------
|
||||
- name: Publish generic packages
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
run: |
|
||||
if [[ -z "${TOKEN}" ]]; then
|
||||
echo "TOKEN missing; skipping generic packages"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
for file in dist/*; do
|
||||
name=$(basename "$file")
|
||||
curl -sSf -X PUT \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$file" \
|
||||
"${GITHUB_SERVER_URL}/api/packages/${OWNER}/generic/${REPO}/${VERSION}/${name}"
|
||||
done
|
||||
|
||||
# -------------------------------------------------
|
||||
# OCI Images (fixed repo casing issue)
|
||||
# -------------------------------------------------
|
||||
- name: Build and push OCI images
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
USERNAME: ${{ secrets.USERNAME }}
|
||||
run: |
|
||||
if [[ -z "${TOKEN}" || -z "${USERNAME}" ]]; then
|
||||
echo "TOKEN or USERNAME missing; skipping OCI"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
IMAGE_BASE="git.dvv.one/${OWNER}/${REPO}"
|
||||
|
||||
echo "${TOKEN}" | docker login git.dvv.one -u "${USERNAME}" --password-stdin
|
||||
|
||||
cat > Dockerfile.relay <<EOF
|
||||
FROM debian:bookworm-slim
|
||||
COPY target/release/relay /usr/local/bin/relay
|
||||
EXPOSE 7000 7001 25565
|
||||
ENTRYPOINT ["/usr/local/bin/relay"]
|
||||
EOF
|
||||
|
||||
cat > Dockerfile.client <<EOF
|
||||
FROM debian:bookworm-slim
|
||||
COPY target/release/client /usr/local/bin/client
|
||||
ENTRYPOINT ["/usr/local/bin/client"]
|
||||
EOF
|
||||
|
||||
docker build -f Dockerfile.relay -t ${IMAGE_BASE}/relay:${VERSION} .
|
||||
docker build -f Dockerfile.client -t ${IMAGE_BASE}/client:${VERSION} .
|
||||
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
docker tag ${IMAGE_BASE}/relay:${VERSION} ${IMAGE_BASE}/relay:main
|
||||
docker tag ${IMAGE_BASE}/client:${VERSION} ${IMAGE_BASE}/client:main
|
||||
fi
|
||||
|
||||
docker push ${IMAGE_BASE}/relay:${VERSION}
|
||||
docker push ${IMAGE_BASE}/client:${VERSION}
|
||||
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
docker push ${IMAGE_BASE}/relay:main
|
||||
docker push ${IMAGE_BASE}/client:main
|
||||
fi
|
||||
|
||||
# -------------------------------------------------
|
||||
# FIXED: Proper Gitea Cargo registry setup
|
||||
# -------------------------------------------------
|
||||
- name: Configure Cargo for Gitea
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
mkdir -p ~/.cargo
|
||||
cat > ~/.cargo/config.toml <<EOF
|
||||
[registries.gitea]
|
||||
index = "sparse+${GITHUB_SERVER_URL}/api/packages/${OWNER}/cargo/"
|
||||
EOF
|
||||
|
||||
- name: Publish Cargo packages
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
CARGO_REGISTRIES_GITEA_TOKEN: ${{ secrets.TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
|
||||
RELAY_VER=$(cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="relay") | .version')
|
||||
CLIENT_VER=$(cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="client") | .version')
|
||||
|
||||
if [[ "v${RELAY_VER}" == "${TAG}" ]]; then
|
||||
cargo publish -p relay --registry gitea
|
||||
fi
|
||||
|
||||
if [[ "v${CLIENT_VER}" == "${TAG}" ]]; then
|
||||
cargo publish -p client --registry gitea
|
||||
fi
|
||||
|
||||
# -------------------------------------------------
|
||||
# FIXED: Gitea Release Creation
|
||||
# -------------------------------------------------
|
||||
- name: Create release
|
||||
env:
|
||||
TOKEN: ${{ secrets.TOKEN }}
|
||||
run: |
|
||||
if [[ -z "${TOKEN}" ]]; then
|
||||
echo "TOKEN missing; skipping release"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
PRERELEASE=${{ steps.version.outputs.is_prerelease }}
|
||||
|
||||
release=$(curl -sSf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"tag_name\":\"${VERSION}\",\"target_commitish\":\"${GITHUB_SHA}\",\"name\":\"${VERSION}\",\"body\":\"Automated release for ${VERSION}.\",\"prerelease\":${PRERELEASE}}" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${OWNER}/${REPO}/releases")
|
||||
|
||||
release_id=$(echo "$release" | jq -r '.id')
|
||||
|
||||
for file in dist/*; do
|
||||
name=$(basename "$file")
|
||||
curl -sSf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-F "attachment=@${file}" \
|
||||
"${GITHUB_SERVER_URL}/api/v1/repos/${OWNER}/${REPO}/releases/${release_id}/assets?name=${name}"
|
||||
done
|
||||
1229
Cargo.lock
generated
1229
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -19,3 +19,13 @@ axum = "0.8"
|
||||
redis = { version = "0.32", features = ["tokio-comp", "connection-manager"] }
|
||||
jsonwebtoken = "10"
|
||||
chrono = { version = "0.4", features = ["serde", "clock"] }
|
||||
metrics = "0.24"
|
||||
metrics-exporter-prometheus = "0.17"
|
||||
tokio-postgres = { version = "0.7", features = ["with-chrono-0_4", "with-serde_json-1"] }
|
||||
hmac = "0.12"
|
||||
sha2 = "0.10"
|
||||
hex = "0.4"
|
||||
quinn = "0.11"
|
||||
rustls = "0.23"
|
||||
rcgen = "0.12"
|
||||
rustls-pemfile = "2.1"
|
||||
|
||||
@@ -15,3 +15,7 @@ tokio.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
fastrand.workspace = true
|
||||
metrics.workspace = true
|
||||
metrics-exporter-prometheus.workspace = true
|
||||
tokio-postgres.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use std::{net::SocketAddr, sync::Arc, time::Instant};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use axum::{
|
||||
@@ -10,14 +10,19 @@ use axum::{
|
||||
};
|
||||
use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
||||
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
|
||||
use redis::AsyncCommands;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_postgres::{Client as PgClient, NoTls};
|
||||
use tracing::{info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
jwt_secret: Arc<String>,
|
||||
redis: Option<redis::aio::ConnectionManager>,
|
||||
pg: Option<Arc<PgClient>>,
|
||||
metrics: PrometheusHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -58,12 +63,9 @@ struct ValidateResponse {
|
||||
max_tunnels: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct StripeWebhookEvent {
|
||||
event_type: String,
|
||||
user_id: String,
|
||||
#[derive(Debug, Clone)]
|
||||
struct PlanEntitlement {
|
||||
tier: String,
|
||||
#[serde(default = "default_max_tunnels")]
|
||||
max_tunnels: u32,
|
||||
}
|
||||
|
||||
@@ -78,6 +80,10 @@ async fn main() -> Result<()> {
|
||||
|
||||
let bind = std::env::var("AUTH_BIND").unwrap_or_else(|_| "0.0.0.0:8080".into());
|
||||
let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| "dev-secret-change-me".into());
|
||||
let metrics = PrometheusBuilder::new()
|
||||
.install_recorder()
|
||||
.context("install prometheus recorder")?;
|
||||
|
||||
let redis = if let Ok(url) = std::env::var("REDIS_URL") {
|
||||
let client = redis::Client::open(url.clone()).context("open redis client")?;
|
||||
match redis::aio::ConnectionManager::new(client).await {
|
||||
@@ -94,16 +100,20 @@ async fn main() -> Result<()> {
|
||||
None
|
||||
};
|
||||
|
||||
let pg = connect_postgres().await?;
|
||||
|
||||
let state = AppState {
|
||||
jwt_secret: Arc::new(jwt_secret),
|
||||
redis,
|
||||
pg,
|
||||
metrics,
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/healthz", get(healthz))
|
||||
.route("/metrics", get(metrics_endpoint))
|
||||
.route("/v1/token/dev", post(issue_dev_token))
|
||||
.route("/v1/token/validate", post(validate_token))
|
||||
.route("/v1/stripe/webhook", post(stripe_webhook))
|
||||
.with_state(state);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&bind)
|
||||
@@ -115,14 +125,36 @@ async fn main() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connect_postgres() -> Result<Option<Arc<PgClient>>> {
|
||||
let Some(url) = std::env::var("DATABASE_URL").ok() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (client, conn) = tokio_postgres::connect(&url, NoTls)
|
||||
.await
|
||||
.context("connect postgres")?;
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = conn.await {
|
||||
warn!(error = %e, "postgres connection task ended");
|
||||
}
|
||||
});
|
||||
info!("auth-api connected to postgres");
|
||||
Ok(Some(Arc::new(client)))
|
||||
}
|
||||
|
||||
async fn healthz() -> &'static str {
|
||||
"ok"
|
||||
}
|
||||
|
||||
async fn metrics_endpoint(State(state): State<AppState>) -> impl IntoResponse {
|
||||
state.metrics.render()
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state))]
|
||||
async fn issue_dev_token(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<DevTokenRequest>,
|
||||
) -> Result<Json<TokenResponse>, ApiError> {
|
||||
let started = Instant::now();
|
||||
let now = Utc::now();
|
||||
let exp = now + Duration::hours(24);
|
||||
let claims = Claims {
|
||||
@@ -141,9 +173,68 @@ async fn issue_dev_token(
|
||||
)
|
||||
.map_err(ApiError::internal)?;
|
||||
|
||||
sync_jwt_cache(&state, &claims).await?;
|
||||
metrics::counter!("auth_jwt_issued_total").increment(1);
|
||||
metrics::histogram!("auth_issue_token_latency_ms")
|
||||
.record(started.elapsed().as_secs_f64() * 1000.0);
|
||||
|
||||
Ok(Json(TokenResponse {
|
||||
token,
|
||||
expires_at: exp.timestamp(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(state, req))]
|
||||
async fn validate_token(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<ValidateRequest>,
|
||||
) -> Result<Json<ValidateResponse>, ApiError> {
|
||||
let started = Instant::now();
|
||||
let decoded = decode::<Claims>(
|
||||
&req.token,
|
||||
&DecodingKey::from_secret(state.jwt_secret.as_bytes()),
|
||||
&Validation::new(Algorithm::HS256),
|
||||
);
|
||||
|
||||
let response = match decoded {
|
||||
Ok(tok) => {
|
||||
let claims = tok.claims;
|
||||
let ent = match load_plan_for_user(&state, &claims.sub).await? {
|
||||
Some(db_plan) => db_plan,
|
||||
None => PlanEntitlement {
|
||||
tier: claims.tier.clone(),
|
||||
max_tunnels: claims.max_tunnels,
|
||||
},
|
||||
};
|
||||
sync_plan_cache(&state, &claims.sub, &ent, "auth-validate").await?;
|
||||
metrics::counter!("auth_token_validate_total", "result" => "valid").increment(1);
|
||||
ValidateResponse {
|
||||
valid: true,
|
||||
user_id: Some(claims.sub),
|
||||
tier: Some(ent.tier),
|
||||
max_tunnels: Some(ent.max_tunnels),
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
metrics::counter!("auth_token_validate_total", "result" => "invalid").increment(1);
|
||||
ValidateResponse {
|
||||
valid: false,
|
||||
user_id: None,
|
||||
tier: None,
|
||||
max_tunnels: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
metrics::histogram!("auth_validate_token_latency_ms")
|
||||
.record(started.elapsed().as_secs_f64() * 1000.0);
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
async fn sync_jwt_cache(state: &AppState, claims: &Claims) -> Result<(), ApiError> {
|
||||
if let Some(mut redis) = state.redis.clone() {
|
||||
let key = format!("auth:jwt:jti:{}", claims.jti);
|
||||
let ttl = (claims.exp as i64 - now.timestamp()).max(1);
|
||||
let ttl = (claims.exp as i64 - Utc::now().timestamp()).max(1);
|
||||
let payload = serde_json::json!({
|
||||
"user_id": claims.sub,
|
||||
"plan_tier": claims.tier,
|
||||
@@ -155,69 +246,53 @@ async fn issue_dev_token(
|
||||
.await
|
||||
.map_err(ApiError::internal)?;
|
||||
}
|
||||
|
||||
Ok(Json(TokenResponse {
|
||||
token,
|
||||
expires_at: exp.timestamp(),
|
||||
}))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn validate_token(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<ValidateRequest>,
|
||||
) -> Result<Json<ValidateResponse>, ApiError> {
|
||||
let decoded = decode::<Claims>(
|
||||
&req.token,
|
||||
&DecodingKey::from_secret(state.jwt_secret.as_bytes()),
|
||||
&Validation::new(Algorithm::HS256),
|
||||
);
|
||||
|
||||
match decoded {
|
||||
Ok(tok) => {
|
||||
let c = tok.claims;
|
||||
async fn sync_plan_cache(
|
||||
state: &AppState,
|
||||
user_id: &str,
|
||||
ent: &PlanEntitlement,
|
||||
source: &str,
|
||||
) -> Result<(), ApiError> {
|
||||
if let Some(mut redis) = state.redis.clone() {
|
||||
let key = format!("plan:user:{}", c.sub);
|
||||
let key = format!("plan:user:{user_id}");
|
||||
let payload = serde_json::json!({
|
||||
"tier": c.tier,
|
||||
"max_tunnels": c.max_tunnels,
|
||||
"source": "auth-api"
|
||||
"tier": ent.tier,
|
||||
"max_tunnels": ent.max_tunnels,
|
||||
"source": source,
|
||||
"updated_at": Utc::now().timestamp()
|
||||
})
|
||||
.to_string();
|
||||
let _: () = redis.set_ex(key, payload, 300).await.map_err(ApiError::internal)?;
|
||||
}
|
||||
Ok(Json(ValidateResponse {
|
||||
valid: true,
|
||||
user_id: Some(c.sub),
|
||||
tier: Some(c.tier),
|
||||
max_tunnels: Some(c.max_tunnels),
|
||||
}))
|
||||
}
|
||||
Err(_) => Ok(Json(ValidateResponse {
|
||||
valid: false,
|
||||
user_id: None,
|
||||
tier: None,
|
||||
max_tunnels: None,
|
||||
})),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stripe_webhook(
|
||||
State(state): State<AppState>,
|
||||
Json(event): Json<StripeWebhookEvent>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
if let Some(mut redis) = state.redis.clone() {
|
||||
let key = format!("plan:user:{}", event.user_id);
|
||||
let payload = serde_json::json!({
|
||||
"tier": event.tier,
|
||||
"max_tunnels": event.max_tunnels,
|
||||
"source": "stripe_webhook",
|
||||
"last_event_type": event.event_type,
|
||||
"updated_at": Utc::now().timestamp(),
|
||||
})
|
||||
.to_string();
|
||||
let _: () = redis.set_ex(key, payload, 300).await.map_err(ApiError::internal)?;
|
||||
}
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
async fn load_plan_for_user(state: &AppState, user_id: &str) -> Result<Option<PlanEntitlement>, ApiError> {
|
||||
let Some(pg) = &state.pg else {
|
||||
return Ok(None);
|
||||
};
|
||||
let _ = Uuid::parse_str(user_id).map_err(ApiError::bad_request)?;
|
||||
let row = pg
|
||||
.query_opt(
|
||||
r#"
|
||||
select p.id as plan_id, p.max_tunnels
|
||||
from subscriptions s
|
||||
join plans p on p.id = s.plan_id
|
||||
where s.user_id = $1::uuid and s.status in ('active', 'trialing')
|
||||
order by s.updated_at desc
|
||||
limit 1
|
||||
"#,
|
||||
&[&user_id],
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError::internal)?;
|
||||
|
||||
Ok(row.map(|r| PlanEntitlement {
|
||||
tier: r.get::<_, String>("plan_id"),
|
||||
max_tunnels: r.get::<_, i32>("max_tunnels") as u32,
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -233,6 +308,14 @@ impl ApiError {
|
||||
message: e.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bad_request<E: std::fmt::Display>(e: E) -> Self {
|
||||
Self {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
message: e.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl IntoResponse for ApiError {
|
||||
|
||||
@@ -2,6 +2,23 @@
|
||||
name = "client"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Play-DVV client"
|
||||
license = "MIT"
|
||||
authors = ["Play-DVV Team <ops@dvv.one>"]
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "Play-DVV Team <ops@dvv.one>"
|
||||
section = "net"
|
||||
priority = "optional"
|
||||
assets = [
|
||||
["target/release/client", "usr/bin/client", "755"]
|
||||
]
|
||||
|
||||
[package.metadata.rpm]
|
||||
package = "client"
|
||||
assets = [
|
||||
{ source = "target/release/client", dest = "/usr/bin/client", mode = "755" }
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
|
||||
@@ -57,6 +57,30 @@ pub struct RelayForwardPrelude {
|
||||
pub initial_data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct R2rStreamData {
|
||||
pub session_id: String,
|
||||
pub stream_id: String,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct R2rStreamClosed {
|
||||
pub session_id: String,
|
||||
pub stream_id: String,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum R2rFrame {
|
||||
Open(RelayForwardPrelude),
|
||||
Data(R2rStreamData),
|
||||
Close(R2rStreamClosed),
|
||||
Ping,
|
||||
Pong,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "data")]
|
||||
pub enum ClientFrame {
|
||||
|
||||
@@ -35,6 +35,9 @@ create table if not exists subscriptions (
|
||||
);
|
||||
|
||||
create index if not exists subscriptions_user_id_idx on subscriptions(user_id);
|
||||
create unique index if not exists subscriptions_provider_subscription_id_uidx
|
||||
on subscriptions(provider_subscription_id)
|
||||
where provider_subscription_id is not null;
|
||||
|
||||
create table if not exists tunnels (
|
||||
id uuid primary key default gen_random_uuid(),
|
||||
|
||||
@@ -2,6 +2,23 @@
|
||||
name = "relay"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "Play-DVV relay server"
|
||||
license = "MIT"
|
||||
authors = ["Play-DVV Team <ops@dvv.one>"]
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "Play-DVV Team <ops@dvv.one>"
|
||||
section = "net"
|
||||
priority = "optional"
|
||||
assets = [
|
||||
["target/release/relay", "usr/bin/relay", "755"]
|
||||
]
|
||||
|
||||
[package.metadata.rpm]
|
||||
package = "relay"
|
||||
assets = [
|
||||
{ source = "target/release/relay", dest = "/usr/bin/relay", mode = "755" }
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
@@ -14,4 +31,10 @@ redis.workspace = true
|
||||
serde_json.workspace = true
|
||||
chrono.workspace = true
|
||||
serde.workspace = true
|
||||
metrics.workspace = true
|
||||
metrics-exporter-prometheus.workspace = true
|
||||
common = { path = "../common" }
|
||||
quinn.workspace = true
|
||||
rustls.workspace = true
|
||||
rcgen.workspace = true
|
||||
rustls-pemfile.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user