initial wip

This commit is contained in:
Michael Zhang 2023-08-07 02:43:50 -05:00
commit 0043739228
27 changed files with 4851 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
/test.db
/node_modules

3356
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

30
Cargo.toml Normal file
View File

@ -0,0 +1,30 @@
[package]
name = "aah"
version = "0.1.0"
edition = "2021"
[workspace]
members = [
".",
"entity",
"migration",
]
[dependencies]
entity = { path = "entity" }
migration = { path = "migration" }
anyhow = "1.0.72"
axum = { version = "0.6.20", features = ["multipart"] }
axum-auth = "0.4.0"
bollard = "0.14.0"
clap = { version = "4.3.19", features = ["derive"] }
futures = "0.3.28"
sea-orm = { version = "0.12", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ] }
serde = { version = "1.0.183", features = ["derive"] }
serde_json = "1.0.104"
serde_yaml = "0.9.25"
sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "sqlite", "migrate"] }
tokio = { version = "1.29.1", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] }
base64 = "0.21.2"

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
generate_entities:
sea generate entity --database-url sqlite:test.db -o entity/src --lib --with-serde both
migrate:
rm -f test.db
touch test.db
cargo run -p migration
make generate_entities
test:
curl -v -X POST http://localhost:4000/upload -u "root:hellosu" -F name=hello -F config=@examples/codimd.yml

1
README.md Normal file
View File

@ -0,0 +1 @@
# aah - aws at home

5
client/App.tsx Normal file
View File

@ -0,0 +1,5 @@
import { StrictMode } from "react";
export default function App() {
return <StrictMode></StrictMode>;
}

6
client/index.ts Normal file
View File

@ -0,0 +1,6 @@
import { createRoot } from "react-dom/client";
import App from "./App";
const el = document.getElementById("app")!;
const root = createRoot(el);
root.render(App());

8
entity/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
[dependencies]
sea-orm = { version = "0.12" }
serde = { version = "1.0.183", features = ["derive"] }

6
entity/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
pub mod prelude;
pub mod secrets;
pub mod service;

4
entity/src/prelude.rs Normal file
View File

@ -0,0 +1,4 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
pub use super::secrets::Entity as Secrets;
pub use super::service::Entity as Service;

36
entity/src/secrets.rs Normal file
View File

@ -0,0 +1,36 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(
Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize,
)]
#[sea_orm(table_name = "secrets")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub service_id: i32,
pub name: String,
pub value: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::service::Entity",
from = "Column::ServiceId",
to = "super::service::Column::Id",
on_update = "NoAction",
on_delete = "NoAction"
)]
Service,
}
impl Related<super::service::Entity> for Entity {
fn to() -> RelationDef {
Relation::Service.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

30
entity/src/service.rs Normal file
View File

@ -0,0 +1,30 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.2
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(
Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize,
)]
#[sea_orm(table_name = "service")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub config: String,
pub overall_status: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::secrets::Entity")]
Secrets,
}
impl Related<super::secrets::Entity> for Entity {
fn to() -> RelationDef {
Relation::Secrets.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

25
examples/codimd.yml Normal file
View File

@ -0,0 +1,25 @@
service:
app:
source:
image: ghcr.io/linuxserver/codimd
domains:
scratch:
port: 3000
depends_on:
- db
entry: |
#!/usr/bin/env bash
export DB_HOST="postgres://name2"
exec npm run start
db:
source:
image: linuxserver/mariadb:latest
auto_secrets:
- MYSQL_ROOT_PASSWORD
- MYSQL_PASSWORD
environment:
MYSQL_DATABASE: codimd
MYSQL_USER: codimd
PGID: 1000
PUID: 1000

7
index.html Normal file
View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="module" src="client/index.ts"></script>
</body>
</html>

22
migration/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
[dependencies.sea-orm-migration]
version = "0.12.0"
features = [
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
# e.g.
"runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
"sqlx-sqlite", # `DATABASE_DRIVER` feature
]

41
migration/README.md Normal file
View File

@ -0,0 +1,41 @@
# Running Migrator CLI
- Generate a new migration file
```sh
cargo run -- generate MIGRATION_NAME
```
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

12
migration/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_create_table::Migration)]
}
}

View File

@ -0,0 +1,99 @@
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Service::Table)
.if_not_exists()
.col(
ColumnDef::new(Service::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Service::Name).string().not_null())
.col(ColumnDef::new(Service::Config).string().not_null())
.col(
ColumnDef::new(Service::OverallStatus)
.enumeration(
Service::OverallStatus,
vec![
OverallStatus::New,
OverallStatus::NeedsAttention,
OverallStatus::Healthy,
OverallStatus::InProgress,
],
)
.not_null(),
)
.to_owned(),
)
.await?;
manager
.create_table(
Table::create()
.table(Secrets::Table)
.if_not_exists()
.col(
ColumnDef::new(Secrets::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Secrets::ServiceId).integer().not_null())
.col(ColumnDef::new(Secrets::Name).string().not_null())
.col(ColumnDef::new(Secrets::Value).string().not_null())
.foreign_key(
ForeignKey::create()
.from(Secrets::Table, Secrets::ServiceId)
.to(Service::Table, Service::Id),
)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Service::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum Service {
Table,
Id,
Name,
Config,
OverallStatus,
}
#[derive(DeriveIden)]
enum OverallStatus {
New,
Healthy,
NeedsAttention,
InProgress,
}
#[derive(DeriveIden)]
enum Secrets {
Table,
Id,
ServiceId,
Name,
Value,
}

6
migration/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}

844
package-lock.json generated Normal file
View File

@ -0,0 +1,844 @@
{
"name": "aah",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react-swc": "^3.3.2",
"vite": "^4.4.8"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.19.tgz",
"integrity": "sha512-1uOoDurJYh5MNqPqpj3l/TQCI1V25BXgChEldCB7D6iryBYqYKrbZIhYO5AI9fulf66sM8UJpc3UcCly2Tv28w==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.19.tgz",
"integrity": "sha512-4+jkUFQxZkQfQOOxfGVZB38YUWHMJX2ihZwF+2nh8m7bHdWXpixiurgGRN3c/KMSwlltbYI0/i929jwBRMFzbA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.19.tgz",
"integrity": "sha512-ae5sHYiP/Ogj2YNrLZbWkBmyHIDOhPgpkGvFnke7XFGQldBDWvc/AyYwSLpNuKw9UNkgnLlB/jPpnBmlF3G9Bg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.19.tgz",
"integrity": "sha512-HIpQvNQWFYROmWDANMRL+jZvvTQGOiTuwWBIuAsMaQrnStedM+nEKJBzKQ6bfT9RFKH2wZ+ej+DY7+9xHBTFPg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.19.tgz",
"integrity": "sha512-m6JdvXJQt0thNLIcWOeG079h2ivhYH4B5sVCgqb/B29zTcFd7EE8/J1nIUHhdtwGeItdUeqKaqqb4towwxvglQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.19.tgz",
"integrity": "sha512-G0p4EFMPZhGn/xVNspUyMQbORH3nlKTV0bFNHPIwLraBuAkTeMyxNviTe0ZXUbIXQrR1lrwniFjNFU4s+x7veQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.19.tgz",
"integrity": "sha512-hBxgRlG42+W+j/1/cvlnSa+3+OBKeDCyO7OG2ICya1YJaSCYfSpuG30KfOnQHI7Ytgu4bRqCgrYXxQEzy0zM5Q==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.19.tgz",
"integrity": "sha512-qtWyoQskfJlb9MD45mvzCEKeO4uCnDZ7lPFeNqbfaaJHqBiH9qA5Vu2EuckqYZuFMJWy1l4dxTf9NOulCVfUjg==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.19.tgz",
"integrity": "sha512-X8g33tczY0GsJq3lhyBrjnFtaKjWVpp1gMq5IlF9BQJ3TUfSK74nQnz9mRIEejmcV+OIYn6bkOJeUaU1Knrljg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.19.tgz",
"integrity": "sha512-SAkRWJgb+KN+gOhmbiE6/wu23D6HRcGQi15cB13IVtBZZgXxygTV5GJlUAKLQ5Gcx0gtlmt+XIxEmSqA6sZTOw==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.19.tgz",
"integrity": "sha512-YLAslaO8NsB9UOxBchos82AOMRDbIAWChwDKfjlGrHSzS3v1kxce7dGlSTsrb0PJwo1KYccypN3VNjQVLtz7LA==",
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.19.tgz",
"integrity": "sha512-vSYFtlYds/oTI8aflEP65xo3MXChMwBOG1eWPGGKs/ev9zkTeXVvciU+nifq8J1JYMz+eQ4J9JDN0O2RKF8+1Q==",
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.19.tgz",
"integrity": "sha512-tgG41lRVwlzqO9tv9l7aXYVw35BxKXLtPam1qALScwSqPivI8hjkZLNH0deaaSCYCFT9cBIdB+hUjWFlFFLL9A==",
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.19.tgz",
"integrity": "sha512-EgBZFLoN1S5RuB4cCJI31pBPsjE1nZ+3+fHRjguq9Ibrzo29bOLSBcH1KZJvRNh5qtd+fcYIGiIUia8Jw5r1lQ==",
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.19.tgz",
"integrity": "sha512-q1V1rtHRojAzjSigZEqrcLkpfh5K09ShCoIsdTakozVBnM5rgV58PLFticqDp5UJ9uE0HScov9QNbbl8HBo6QQ==",
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.19.tgz",
"integrity": "sha512-D0IiYjpZRXxGZLQfsydeAD7ZWqdGyFLBj5f2UshJpy09WPs3qizDCsEr8zyzcym6Woj/UI9ZzMIXwvoXVtyt0A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.19.tgz",
"integrity": "sha512-3tt3SOS8L3D54R8oER41UdDshlBIAjYhdWRPiZCTZ1E41+shIZBpTjaW5UaN/jD1ENE/Ok5lkeqhoNMbxstyxw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.19.tgz",
"integrity": "sha512-MxbhcuAYQPlfln1EMc4T26OUoeg/YQc6wNoEV8xvktDKZhLtBxjkoeESSo9BbPaGKhAPzusXYj5n8n5A8iZSrA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.19.tgz",
"integrity": "sha512-m0/UOq1wj25JpWqOJxoWBRM9VWc3c32xiNzd+ERlYstUZ6uwx5SZsQUtkiFHaYmcaoj+f6+Tfcl7atuAz3idwQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.19.tgz",
"integrity": "sha512-L4vb6pcoB1cEcXUHU6EPnUhUc4+/tcz4OqlXTWPcSQWxegfmcOprhmIleKKwmMNQVc4wrx/+jB7tGkjjDmiupg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.19.tgz",
"integrity": "sha512-rQng7LXSKdrDlNDb7/v0fujob6X0GAazoK/IPd9C3oShr642ri8uIBkgM37/l8B3Rd5sBQcqUXoDdEy75XC/jg==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.19.tgz",
"integrity": "sha512-z69jhyG20Gq4QL5JKPLqUT+eREuqnDAFItLbza4JCmpvUnIlY73YNjd5djlO7kBiiZnvTnJuAbOjIoZIOa1GjA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@swc/core": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.74.tgz",
"integrity": "sha512-P+MIExOTdWlfq8Heb1/NhBAke6UTckd4cRDuJoFcFMGBRvgoCMNWhnfP3FRRXPLI7GGg27dRZS+xHiqYyQmSrA==",
"dev": true,
"hasInstallScript": true,
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.3.74",
"@swc/core-darwin-x64": "1.3.74",
"@swc/core-linux-arm-gnueabihf": "1.3.74",
"@swc/core-linux-arm64-gnu": "1.3.74",
"@swc/core-linux-arm64-musl": "1.3.74",
"@swc/core-linux-x64-gnu": "1.3.74",
"@swc/core-linux-x64-musl": "1.3.74",
"@swc/core-win32-arm64-msvc": "1.3.74",
"@swc/core-win32-ia32-msvc": "1.3.74",
"@swc/core-win32-x64-msvc": "1.3.74"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.74.tgz",
"integrity": "sha512-2rMV4QxM583jXcREfo0MhV3Oj5pgRSfSh/kVrB1twL2rQxOrbzkAPT/8flmygdVoL4f2F7o1EY5lKlYxEBiIKQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.74.tgz",
"integrity": "sha512-KKEGE1wXneYXe15fWDRM8/oekd/Q4yAuccA0vWY/7i6nOSPqWYcSDR0nRtR030ltDxWt0rk/eCTmNkrOWrKs3A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.74.tgz",
"integrity": "sha512-HehH5DR6r/5fIVu7tu8ZqgrHkhSCQNewf1ztFQJgcmaQWn+H4AJERBjwkjosqh4TvUJucZv8vyRTvrFeBXaCSA==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.74.tgz",
"integrity": "sha512-+xkbCRz/wczgdknoV4NwYxbRI2dD7x/qkIFcVM2buzLCq8oWLweuV8+aL4pRqu0qDh7ZSb1jcaVTUIsySCJznA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.74.tgz",
"integrity": "sha512-maKFZSCD3tQznzPV7T3V+TtiWZFEFM8YrnSS5fQNNb+K9J65sL+170uTb3M7H4cFkG+9Sm5k5yCrCIutlvV48g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.74.tgz",
"integrity": "sha512-LEXpcShF6DLTWJSiBhMSYZkLQ27UvaQ24fCFhoIV/R3dhYaUpHmIyLPPBNC82T03lB3ONUFVwrRw6fxDJ/f00A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.74.tgz",
"integrity": "sha512-sxsFctbFMZEFmDE7CmYljG0dMumH8XBTwwtGr8s6z0fYAzXBGNq2AFPcmEh2np9rPWkt7pE1m0ByESD+dMkbxQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.74.tgz",
"integrity": "sha512-F7hY9/BjFCozA4YPFYFH5FGCyWwa44vIXHqG66F5cDwXDGFn8ZtBsYIsiPfUYcx0AeAo1ojnVWKPxokZhYNYqA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.74.tgz",
"integrity": "sha512-qBAsiD1AlIdqED6wy3UNRHyAys9pWMUidX0LJ6mj24r/vfrzzTBAUrLJe5m7bzE+F1Rgi001avYJeEW1DLEJ+Q==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.3.74",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.74.tgz",
"integrity": "sha512-S3YAvvLprTnPRwQuy9Dkwubb5SRLpVK3JJsqYDbGfgj8PGQyKHZcVJ5X3nfFsoWLy3j9B/3Os2nawprRSzeC5A==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz",
"integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.2.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"dev": true
},
"node_modules/@vitejs/plugin-react-swc": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz",
"integrity": "sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA==",
"dev": true,
"dependencies": {
"@swc/core": "^1.3.61"
},
"peerDependencies": {
"vite": "^4"
}
},
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.18.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.19.tgz",
"integrity": "sha512-ra3CaIKCzJp5bU5BDfrCc0FRqKj71fQi+gbld0aj6lN0ifuX2fWJYPgLVLGwPfA+ruKna+OWwOvf/yHj6n+i0g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.19",
"@esbuild/android-arm64": "0.18.19",
"@esbuild/android-x64": "0.18.19",
"@esbuild/darwin-arm64": "0.18.19",
"@esbuild/darwin-x64": "0.18.19",
"@esbuild/freebsd-arm64": "0.18.19",
"@esbuild/freebsd-x64": "0.18.19",
"@esbuild/linux-arm": "0.18.19",
"@esbuild/linux-arm64": "0.18.19",
"@esbuild/linux-ia32": "0.18.19",
"@esbuild/linux-loong64": "0.18.19",
"@esbuild/linux-mips64el": "0.18.19",
"@esbuild/linux-ppc64": "0.18.19",
"@esbuild/linux-riscv64": "0.18.19",
"@esbuild/linux-s390x": "0.18.19",
"@esbuild/linux-x64": "0.18.19",
"@esbuild/netbsd-x64": "0.18.19",
"@esbuild/openbsd-x64": "0.18.19",
"@esbuild/sunos-x64": "0.18.19",
"@esbuild/win32-arm64": "0.18.19",
"@esbuild/win32-ia32": "0.18.19",
"@esbuild/win32-x64": "0.18.19"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"dev": true
},
"node_modules/postcss": {
"version": "8.4.27",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
"integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/rollup": {
"version": "3.27.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.27.2.tgz",
"integrity": "sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=14.18.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.8.tgz",
"integrity": "sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.26",
"rollup": "^3.25.2"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
}
}
}

14
package.json Normal file
View File

@ -0,0 +1,14 @@
{
"scripts": {
"dev": "vite"
},
"devDependencies": {
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react-swc": "^3.3.2",
"vite": "^4.4.8"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

4
rustfmt.toml Normal file
View File

@ -0,0 +1,4 @@
max_width = 80
tab_spaces = 2
wrap_comments = true
fn_single_line = true

39
src/config.rs Normal file
View File

@ -0,0 +1,39 @@
use std::{collections::HashMap, hash::Hash};
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub service: HashMap<String, ServiceConfig>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceConfig {
pub source: SourceConfig,
#[serde(default)]
pub domains: HashMap<String, DomainConfig>,
#[serde(default)]
pub auto_secrets: Vec<String>,
#[serde(default)]
pub environment: HashMap<String, String>,
#[serde(default)]
pub depends_on: Vec<String>,
#[serde(default)]
pub entry: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DomainConfig {
// The left-most component of the domain (must not collide with any other domains)
pub port: u16,
#[serde(default = "default_true")]
pub https: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SourceConfig {
Image { image: String },
}

28
src/error.rs Normal file
View File

@ -0,0 +1,28 @@
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
pub struct AppError(anyhow::Error);
// Tell axum how to convert `AppError` into a response.
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", self.0),
)
.into_response()
}
}
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
// `Result<_, AppError>`. That way you don't need to do that manually.
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}

197
src/main.rs Normal file
View File

@ -0,0 +1,197 @@
#[macro_use]
extern crate serde;
mod config;
mod error;
use anyhow::{anyhow, Result};
use axum::{
extract::{Multipart, State},
http::{
header::{AUTHORIZATION, WWW_AUTHENTICATE},
HeaderName, Request,
},
middleware::{from_fn, Next},
response::{Html, Response},
routing::{get, post},
Form, Json, Router,
};
use axum_auth::AuthBasic;
use base64::{prelude::BASE64_STANDARD, Engine};
use bollard::{
auth,
container::{
Config as ContainerConfig, CreateContainerOptions, StartContainerOptions,
},
image::CreateImageOptions,
Docker,
};
use entity::service;
use error::AppError;
use futures::{StreamExt, TryStreamExt};
use migration::{Migrator, MigratorTrait};
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
use crate::config::{Config, SourceConfig};
#[tokio::main]
async fn main() -> Result<()> {
let options = "sqlite:test.db";
let conn = sea_orm::Database::connect(options).await?;
Migrator::up(&conn, None).await?;
// Run a web server
let state = AppState { conn };
let app = Router::new()
.route(
"/",
get(|| async {
Html::from("<form action='/upload' method=POST><input type='text' name='name' /><textarea name='config'></textarea><input type='submit' /></form>")
}),
)
.route("/upload", post(handle_upload_configuration))
.layer(from_fn(http_basic_auth))
.with_state(state);
let webserver = axum::Server::bind(&"0.0.0.0:4000".parse().unwrap())
.serve(app.into_make_service());
let webserver_handle = tokio::spawn(webserver);
webserver_handle.await??;
Ok(())
}
async fn http_basic_auth<B>(request: Request<B>, next: Next<B>) -> Response {
// do something with `request`...
let headers = request.headers();
let auth_fail_response = Response::builder()
.status(401)
.header(WWW_AUTHENTICATE, "Basic")
.body(axum::body::boxed(String::from("")))
.unwrap();
let authorization = match headers.get(AUTHORIZATION) {
Some(v) => v.to_str().unwrap(),
None => return auth_fail_response,
};
let parts = authorization.split_ascii_whitespace().collect::<Vec<_>>();
let base64part = match &parts[..] {
["Basic", base64part] => *base64part,
_ => return auth_fail_response,
};
let decoded = match BASE64_STANDARD
.decode(base64part.as_bytes())
.map_err(|e| anyhow::Error::from(e))
.and_then(|e| String::from_utf8(e).map_err(|e| e.into()))
{
Ok(v) => v,
Err(_) => return auth_fail_response,
};
let parts = decoded.split(":").collect::<Vec<_>>();
let (user, pass) = match &parts[..] {
[user, pass] => (*user, *pass),
_ => return auth_fail_response,
};
if !(user == "root" && pass == "hellosu") {
return auth_fail_response;
}
let response = next.run(request).await;
response
}
#[derive(Clone)]
struct AppState {
conn: DatabaseConnection,
}
#[derive(Deserialize)]
struct UploadForm {
name: String,
config: String,
}
async fn handle_upload_configuration(
AuthBasic((id, password)): AuthBasic,
state: State<AppState>,
mut form: Multipart,
// Form(upload_form): Form<UploadForm>,
) -> Result<(), AppError> {
if id != "root" || password.as_ref().unwrap() != "hellosu" {
return Err(anyhow!("error").into());
}
let (name, config) = {
let mut name = None;
let mut config = None::<Config>;
while let Some(field) = form.next_field().await.unwrap() {
let key = field.name().unwrap().to_owned();
let value =
String::from_utf8(field.bytes().await.unwrap().to_vec()).unwrap();
match key.as_ref() {
"name" => name = Some(value),
"config" => config = Some(serde_yaml::from_str(&value).unwrap()),
_ => return Err(anyhow!("error").into()),
}
}
(name.unwrap(), config.unwrap())
};
println!("Loading config: {config:?}");
let docker = Docker::connect_with_local_defaults().unwrap();
for (service_name, service_config) in config.service.iter() {
let SourceConfig::Image { image: from_image } = &service_config.source;
let result_stream = docker.create_image(
Some(CreateImageOptions {
from_image: from_image.to_owned(),
..Default::default()
}),
None,
None,
);
result_stream
.try_for_each(|x| async move {
println!("{x:?}");
Ok(())
})
.await
.unwrap();
println!("completed pulling.");
// create a container
let result = docker
.create_container(
Some(CreateContainerOptions::<String> {
..Default::default()
}),
ContainerConfig {
image: Some("hello-world"),
cmd: Some(vec!["/hello"]),
..Default::default()
},
)
.await
.unwrap();
docker
.start_container(&result.id, None::<StartContainerOptions<String>>)
.await
.unwrap();
println!("result: {result:?}");
}
service::ActiveModel {
name: Set(name),
config: Set(serde_json::to_string(&config).unwrap()),
..Default::default()
}
.save(&state.conn)
.await
.unwrap();
Ok(())
}

1
tsconfig.json Normal file
View File

@ -0,0 +1 @@
{ "compilerOptions": { "jsx": "react-jsx" } }

16
vite.config.ts Normal file
View File

@ -0,0 +1,16 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
export default defineConfig({
plugins: [react()],
server: {
proxy: {
"/api": {
auth: "root:hellosu",
target: "http://localhost:4000",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
});