diff options
-rw-r--r-- | .github/workflows/test.yml | 23 | ||||
-rw-r--r-- | Dockerfile | 27 | ||||
-rw-r--r-- | README.md | 10 | ||||
-rw-r--r-- | docker-compose.yml | 1 | ||||
-rw-r--r-- | src/auth.rs | 7 | ||||
-rw-r--r-- | src/main.rs | 17 | ||||
-rw-r--r-- | src/routes/device.rs | 4 | ||||
-rw-r--r-- | src/routes/start.rs | 3 |
8 files changed, 69 insertions, 23 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1bfdf7..90a5fea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml | |||
@@ -12,7 +12,6 @@ env: | |||
12 | RUSTC_WRAPPER: "sccache" | 12 | RUSTC_WRAPPER: "sccache" |
13 | SQLX_OFFLINE: "true" | 13 | SQLX_OFFLINE: "true" |
14 | CARGO_TERM_COLOR: always | 14 | CARGO_TERM_COLOR: always |
15 | CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc | ||
16 | 15 | ||
17 | jobs: | 16 | jobs: |
18 | build: | 17 | build: |
@@ -21,23 +20,25 @@ jobs: | |||
21 | - name: Run sccache-cache | 20 | - name: Run sccache-cache |
22 | uses: mozilla-actions/[email protected] | 21 | uses: mozilla-actions/[email protected] |
23 | 22 | ||
24 | - uses: actions/checkout@v3 | 23 | - uses: actions/checkout@v4 |
25 | - uses: actions-rs/toolchain@v1 | 24 | - uses: actions-rs/toolchain@v1 |
26 | with: | 25 | with: |
27 | toolchain: stable | 26 | toolchain: stable |
28 | target: armv7-unknown-linux-gnueabihf | ||
29 | components: rustfmt, clippy | 27 | components: rustfmt, clippy |
30 | override: true | 28 | override: true |
31 | 29 | ||
32 | - name: Install arm linker | 30 | - name: Run cargo check |
33 | run: sudo apt update && sudo apt install gcc-arm-linux-gnueabihf -y | 31 | uses: actions-rs/cargo@v1 |
32 | with: | ||
33 | command: check | ||
34 | 34 | ||
35 | - name: Cargo build | 35 | - name: Run Clippy |
36 | uses: actions-rs/cargo@v1 | 36 | uses: actions-rs/cargo@v1 |
37 | with: | 37 | with: |
38 | command: build | 38 | command: clippy |
39 | args: --release --target armv7-unknown-linux-gnueabihf | ||
40 | 39 | ||
40 | - name: Set up QEMU | ||
41 | uses: docker/setup-qemu-action@v3 | ||
41 | - name: Set up Docker Buildx | 42 | - name: Set up Docker Buildx |
42 | uses: docker/setup-buildx-action@v2 | 43 | uses: docker/setup-buildx-action@v2 |
43 | - name: Login to DockerHub | 44 | - name: Login to DockerHub |
@@ -50,8 +51,10 @@ jobs: | |||
50 | id: docker_build | 51 | id: docker_build |
51 | uses: docker/build-push-action@v3 | 52 | uses: docker/build-push-action@v3 |
52 | with: | 53 | with: |
53 | file: Dockerfile | ||
54 | push: true | 54 | push: true |
55 | platforms: linux/amd64,linux/arm64 | ||
56 | cache-from: type=gha | ||
57 | cache-to: type=gha,mode=max | ||
55 | tags: | | 58 | tags: | |
56 | ghcr.io/fxqnlr/webol:dev-latest | 59 | ghcr.io/fxqnlr/webol:dev-latest |
57 | ghcr.io/fxqnlr/webol:dev-${{ github.run_number }} \ No newline at end of file | 60 | ghcr.io/fxqnlr/webol:dev-${{ github.run_number }} |
@@ -1,7 +1,26 @@ | |||
1 | FROM --platform=arm64 debian:12-slim | 1 | FROM debian:bookworm AS deb_extractor |
2 | RUN cd /tmp && \ | ||
3 | apt-get update && apt-get download \ | ||
4 | libc6 && \ | ||
5 | mkdir /dpkg && \ | ||
6 | for deb in *.deb; do dpkg --extract $deb /dpkg || exit 10; done | ||
2 | 7 | ||
3 | WORKDIR /usr/local/webol | 8 | FROM lukemathwalker/cargo-chef:latest-rust-1.73.0 as chef |
4 | COPY ./target/armv7-unknown-linux-gnueabihf/release/webol /usr/local/bin/webol | 9 | WORKDIR app |
10 | |||
11 | FROM chef AS planner | ||
12 | COPY . . | ||
13 | RUN cargo chef prepare --recipe-path recipe.json | ||
14 | |||
15 | FROM chef as builder | ||
16 | COPY --from=planner /app/recipe.json recipe.json | ||
17 | RUN cargo chef cook --release --recipe-path recipe.json | ||
18 | COPY . . | ||
19 | RUN cargo build --release | ||
20 | |||
21 | FROM gcr.io/distroless/cc | ||
22 | COPY --from=builder /app/target/release/webol / | ||
23 | COPY --from=deb_extractor /dpkg / | ||
5 | 24 | ||
6 | EXPOSE 7229 | 25 | EXPOSE 7229 |
7 | CMD ["webol"] | 26 | ENTRYPOINT ["./webol"] \ No newline at end of file |
@@ -1 +1,9 @@ | |||
1 | # webol \ No newline at end of file | 1 | # webol |
2 | |||
3 | DATABASE_URL: `String` | ||
4 | |||
5 | WEBOL_APIKEY: `String` | ||
6 | |||
7 | WEBOL_SERVERADDR: `Option<String>` | ||
8 | |||
9 | WEBOL_BINDADDR: `Option<String>` | ||
diff --git a/docker-compose.yml b/docker-compose.yml index 1980356..9d73e9b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml | |||
@@ -3,6 +3,7 @@ version: '3.1' | |||
3 | services: | 3 | services: |
4 | db: | 4 | db: |
5 | image: postgres | 5 | image: postgres |
6 | container_name: webol_dev_postgres | ||
6 | restart: no | 7 | restart: no |
7 | environment: | 8 | environment: |
8 | POSTGRES_PASSWORD: postgres | 9 | POSTGRES_PASSWORD: postgres |
diff --git a/src/auth.rs b/src/auth.rs index b7693a0..81e798f 100644 --- a/src/auth.rs +++ b/src/auth.rs | |||
@@ -1,21 +1,26 @@ | |||
1 | use std::error::Error; | 1 | use std::error::Error; |
2 | use axum::headers::HeaderValue; | 2 | use axum::headers::HeaderValue; |
3 | use axum::http::StatusCode; | 3 | use axum::http::StatusCode; |
4 | use tracing::error; | 4 | use tracing::{debug, error, trace}; |
5 | use crate::auth::AuthError::{MissingSecret, ServerError, WrongSecret}; | 5 | use crate::auth::AuthError::{MissingSecret, ServerError, WrongSecret}; |
6 | use crate::config::SETTINGS; | 6 | use crate::config::SETTINGS; |
7 | 7 | ||
8 | pub fn auth(secret: Option<&HeaderValue>) -> Result<bool, AuthError> { | 8 | pub fn auth(secret: Option<&HeaderValue>) -> Result<bool, AuthError> { |
9 | debug!("auth request with secret {:?}", secret); | ||
9 | if let Some(value) = secret { | 10 | if let Some(value) = secret { |
11 | trace!("value exists"); | ||
10 | let key = SETTINGS | 12 | let key = SETTINGS |
11 | .get_string("apikey") | 13 | .get_string("apikey") |
12 | .map_err(|err| ServerError(Box::new(err)))?; | 14 | .map_err(|err| ServerError(Box::new(err)))?; |
13 | if value.to_str().map_err(|err| ServerError(Box::new(err)))? == key.as_str() { | 15 | if value.to_str().map_err(|err| ServerError(Box::new(err)))? == key.as_str() { |
16 | debug!("successful auth"); | ||
14 | Ok(true) | 17 | Ok(true) |
15 | } else { | 18 | } else { |
19 | debug!("unsuccessful auth (wrong secret)"); | ||
16 | Err(WrongSecret) | 20 | Err(WrongSecret) |
17 | } | 21 | } |
18 | } else { | 22 | } else { |
23 | debug!("unsuccessful auth (no secret)"); | ||
19 | Err(MissingSecret) | 24 | Err(MissingSecret) |
20 | } | 25 | } |
21 | } | 26 | } |
diff --git a/src/main.rs b/src/main.rs index bb37dc2..b7306ea 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -7,6 +7,7 @@ use sqlx::postgres::PgPoolOptions; | |||
7 | use time::util::local_offset; | 7 | use time::util::local_offset; |
8 | use tracing::{debug, info, level_filters::LevelFilter}; | 8 | use tracing::{debug, info, level_filters::LevelFilter}; |
9 | use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*}; | 9 | use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*}; |
10 | use crate::config::SETTINGS; | ||
10 | use crate::routes::device::{get_device, post_device, put_device}; | 11 | use crate::routes::device::{get_device, post_device, put_device}; |
11 | use crate::routes::start::start; | 12 | use crate::routes::start::start; |
12 | 13 | ||
@@ -37,13 +38,12 @@ async fn main() { | |||
37 | 38 | ||
38 | let version = env!("CARGO_PKG_VERSION"); | 39 | let version = env!("CARGO_PKG_VERSION"); |
39 | 40 | ||
40 | info!("starting webol v{}", version); | 41 | info!("start webol v{}", version); |
41 | 42 | ||
42 | let db = init_db_pool().await; | 43 | let db = init_db_pool().await; |
43 | 44 | ||
44 | let shared_state = Arc::new(AppState { db }); | 45 | let shared_state = Arc::new(AppState { db }); |
45 | 46 | ||
46 | // build our application with a single route | ||
47 | let app = Router::new() | 47 | let app = Router::new() |
48 | .route("/start", post(start)) | 48 | .route("/start", post(start)) |
49 | .route("/device", get(get_device)) | 49 | .route("/device", get(get_device)) |
@@ -51,8 +51,9 @@ async fn main() { | |||
51 | .route("/device", post(post_device)) | 51 | .route("/device", post(post_device)) |
52 | .with_state(shared_state); | 52 | .with_state(shared_state); |
53 | 53 | ||
54 | // run it with hyper on localhost:3000 | 54 | let addr = SETTINGS.get_string("serveraddr").unwrap_or("0.0.0.0:7229".to_string()); |
55 | axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) | 55 | info!("start server on {}", addr); |
56 | axum::Server::bind(&addr.parse().unwrap()) | ||
56 | .serve(app.into_make_service()) | 57 | .serve(app.into_make_service()) |
57 | .await | 58 | .await |
58 | .unwrap(); | 59 | .unwrap(); |
@@ -63,9 +64,13 @@ pub struct AppState { | |||
63 | } | 64 | } |
64 | 65 | ||
65 | async fn init_db_pool() -> PgPool { | 66 | async fn init_db_pool() -> PgPool { |
67 | #[cfg(not(debug_assertions))] | ||
68 | let db_url = SETTINGS.get_string("database.url").unwrap(); | ||
69 | |||
70 | #[cfg(debug_assertions)] | ||
66 | let db_url = env::var("DATABASE_URL").unwrap(); | 71 | let db_url = env::var("DATABASE_URL").unwrap(); |
67 | 72 | ||
68 | debug!("attempting to connect dbPool to '{}'", db_url); | 73 | debug!("attempt to connect dbPool to '{}'", db_url); |
69 | 74 | ||
70 | let pool = PgPoolOptions::new() | 75 | let pool = PgPoolOptions::new() |
71 | .max_connections(5) | 76 | .max_connections(5) |
@@ -76,4 +81,4 @@ async fn init_db_pool() -> PgPool { | |||
76 | info!("dbPool successfully connected to '{}'", db_url); | 81 | info!("dbPool successfully connected to '{}'", db_url); |
77 | 82 | ||
78 | pool | 83 | pool |
79 | } \ No newline at end of file | 84 | } |
diff --git a/src/routes/device.rs b/src/routes/device.rs index d5d7144..025c7d0 100644 --- a/src/routes/device.rs +++ b/src/routes/device.rs | |||
@@ -4,11 +4,13 @@ use axum::headers::HeaderMap; | |||
4 | use axum::Json; | 4 | use axum::Json; |
5 | use serde::{Deserialize, Serialize}; | 5 | use serde::{Deserialize, Serialize}; |
6 | use serde_json::{json, Value}; | 6 | use serde_json::{json, Value}; |
7 | use tracing::info; | ||
7 | use crate::auth::auth; | 8 | use crate::auth::auth; |
8 | use crate::db::Device; | 9 | use crate::db::Device; |
9 | use crate::error::WebolError; | 10 | use crate::error::WebolError; |
10 | 11 | ||
11 | pub async fn get_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, WebolError> { | 12 | pub async fn get_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, WebolError> { |
13 | info!("GET request"); | ||
12 | let secret = headers.get("authorization"); | 14 | let secret = headers.get("authorization"); |
13 | if auth(secret).map_err(WebolError::Auth)? { | 15 | if auth(secret).map_err(WebolError::Auth)? { |
14 | let device = sqlx::query_as!( | 16 | let device = sqlx::query_as!( |
@@ -33,6 +35,7 @@ pub struct GetDevicePayload { | |||
33 | } | 35 | } |
34 | 36 | ||
35 | pub async fn put_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, WebolError> { | 37 | pub async fn put_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, WebolError> { |
38 | info!("PUT request"); | ||
36 | let secret = headers.get("authorization"); | 39 | let secret = headers.get("authorization"); |
37 | if auth(secret).map_err(WebolError::Auth)? { | 40 | if auth(secret).map_err(WebolError::Auth)? { |
38 | sqlx::query!( | 41 | sqlx::query!( |
@@ -64,6 +67,7 @@ pub struct PutDeviceResponse { | |||
64 | } | 67 | } |
65 | 68 | ||
66 | pub async fn post_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, WebolError> { | 69 | pub async fn post_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, WebolError> { |
70 | info!("POST request"); | ||
67 | let secret = headers.get("authorization"); | 71 | let secret = headers.get("authorization"); |
68 | if auth(secret).map_err(WebolError::Auth)? { | 72 | if auth(secret).map_err(WebolError::Auth)? { |
69 | let device = sqlx::query_as!( | 73 | let device = sqlx::query_as!( |
diff --git a/src/routes/start.rs b/src/routes/start.rs index d16ea4e..163d58c 100644 --- a/src/routes/start.rs +++ b/src/routes/start.rs | |||
@@ -12,6 +12,7 @@ use crate::db::Device; | |||
12 | use crate::error::WebolError; | 12 | use crate::error::WebolError; |
13 | 13 | ||
14 | pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<StartPayload>) -> Result<Json<Value>, WebolError> { | 14 | pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<StartPayload>) -> Result<Json<Value>, WebolError> { |
15 | info!("POST request"); | ||
15 | let secret = headers.get("authorization"); | 16 | let secret = headers.get("authorization"); |
16 | if auth(secret).map_err(WebolError::Auth)? { | 17 | if auth(secret).map_err(WebolError::Auth)? { |
17 | let device = sqlx::query_as!( | 18 | let device = sqlx::query_as!( |
@@ -28,7 +29,7 @@ pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap | |||
28 | 29 | ||
29 | let bind_addr = SETTINGS | 30 | let bind_addr = SETTINGS |
30 | .get_string("bindaddr") | 31 | .get_string("bindaddr") |
31 | .map_err(|err| WebolError::Server(Box::new(err)))?; | 32 | .unwrap_or("0.0.0.0:1111".to_string()); |
32 | 33 | ||
33 | let _ = send_packet( | 34 | let _ = send_packet( |
34 | &bind_addr.parse().map_err(|err| WebolError::Server(Box::new(err)))?, | 35 | &bind_addr.parse().map_err(|err| WebolError::Server(Box::new(err)))?, |