diff --git a/.dockerignore b/.dockerignore index 6f2659a8..0e32824f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ target/ +!target/x86_64-unknown-linux-musl/release/raisfast data/ logs/ uploads/ diff --git a/.gitignore b/.gitignore index 0ff90bb8..562816dc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ storage/* frontend/ dev-docs/ + +fly.toml diff --git a/Cargo.lock b/Cargo.lock index e45e5202..8ac94cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,9 +200,6 @@ name = "arbitrary" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] [[package]] name = "arc-swap" @@ -2233,17 +2230,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "derive_builder" version = "0.20.2" @@ -2803,7 +2789,6 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", - "zlib-rs", ] [[package]] @@ -6381,7 +6366,6 @@ dependencies = [ "ts-rs", "urlencoding", "utoipa", - "utoipa-swagger-ui", "uuid 1.23.0", "validator", "wasmtime", @@ -9629,24 +9613,6 @@ dependencies = [ "uuid 1.23.0", ] -[[package]] -name = "utoipa-swagger-ui" -version = "9.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" -dependencies = [ - "axum", - "base64 0.22.1", - "mime_guess", - "regex", - "rust-embed", - "serde", - "serde_json", - "url", - "utoipa", - "zip", -] - [[package]] name = "uuid" version = "0.8.2" @@ -11369,44 +11335,12 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "zip" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" -dependencies = [ - "arbitrary", - "crc32fast", - "flate2", - "indexmap 2.14.0", - "memchr", - "zopfli", -] - -[[package]] -name = "zlib-rs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" - [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 8edca59c..a4851bbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ payment-dodo = [] payment-all = ["payment-alipay", "payment-creem", "payment-dodo", "payment-stripe", "payment-wechat"] # ── OpenAPI / Swagger UI ──────────────────────────────────────────── -openapi = ["utoipa-swagger-ui"] +openapi = [] # ── TypeScript type generation ──────────────────────────────────── export-types = ["ts-rs"] @@ -57,9 +57,14 @@ proxy = ["dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:tokio-util"] tauri = ["dep:tauri"] [profile.dev] -debug = 1 # 默认是 2(最大),设为 1 或 false 可大幅减小体积 +debug = 1 + +[profile.release] +lto = "fat" +strip = true +opt-level = "z" +codegen-units = 1 -# The profile that 'dist' will build with [profile.dist] inherits = "release" lto = "thin" @@ -183,7 +188,6 @@ tokio-util = { version = "0.7", features = ["rt"], optional = true } # OpenAPI utoipa = { version = "5", features = ["axum_extras", "chrono", "uuid"] } -utoipa-swagger-ui = { version = "9", features = ["axum"], optional = true } # TypeScript type generation ts-rs = { version = "12", optional = true, features = ["chrono-impl"] } diff --git a/deploy/fly/.dockerignore b/deploy/fly/.dockerignore new file mode 100644 index 00000000..581d9bd4 --- /dev/null +++ b/deploy/fly/.dockerignore @@ -0,0 +1,8 @@ +target/ +.git/ +storage/ +frontend/ +node_modules/ +*.db +*.db-shm +*.db-wal diff --git a/deploy/fly/Dockerfile b/deploy/fly/Dockerfile new file mode 100644 index 00000000..9039cf25 --- /dev/null +++ b/deploy/fly/Dockerfile @@ -0,0 +1,40 @@ +# ============================================================ +# RaisFast — fly.io production Dockerfile +# Usage: +# 1. Cross-compile locally: +# cargo build --release --no-default-features \ +# --features "db-sqlite,plugin-js,plugin-rhai" \ +# --target x86_64-unknown-linux-musl +# +# 2. Deploy: +# fly deploy --local-only +# +# The binary is built on YOUR machine. Only the compiled binary +# and static assets are sent to fly.io — zero source code exposure. +# ============================================================ + +FROM docker.1ms.run/library/debian:bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + && rm -rf /var/lib/apt/lists/* + +RUN groupadd --gid 1000 app \ + && useradd --uid 1000 --gid app --shell /bin/bash --create-home app + +WORKDIR /app + +COPY --chmod=755 target/x86_64-unknown-linux-musl/release/raisfast /app/raisfast +COPY adminui/ /app/adminui/ + +RUN mkdir -p /app/storage/db /app/storage/uploads /app/extensions/content_types /app/extensions/plugins \ + && chown -R app:app /app/storage /app/extensions + +USER app + +EXPOSE 9898 + +VOLUME ["/app/storage"] + +CMD ["/app/raisfast"] diff --git a/deploy/nginx.conf b/deploy/nginx.conf deleted file mode 100644 index 0c86e0e8..00000000 --- a/deploy/nginx.conf +++ /dev/null @@ -1,24 +0,0 @@ -server { - listen 80; - server_name blog.example.com; - - client_max_body_size 5M; - - gzip on; - gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; - gzip_min_length 256; - - location / { - proxy_pass http://127.0.0.1:3000; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - location /uploads/ { - alias /app/uploads/; - expires 30d; - add_header Cache-Control "public, immutable"; - } -} diff --git a/justfile b/justfile index 7e4ba27e..bdc48007 100644 --- a/justfile +++ b/justfile @@ -14,7 +14,7 @@ plugin_type := "all" default: @just --list -features := "db-" + db + " plugin-" + plugin_type + " search-tantivy" +features := "db-" + db + " plugin-js plugin-rhai search-tantivy" # ── 编译 ────────────────────────────────────────────────────────── @@ -113,58 +113,23 @@ mysql-check: # CI: fmt → lint → test(确保所有检查通过) ci: fmt-check lint test -# ── 插件 ────────────────────────────────────────────────────────── +# ── 部署 ────────────────────────────────────────────────────────── -# 编译所有示例 WASM 插件并复制到 plugins/ 目录 -plugins-build: - @echo "Building seo-optimizer..." - cd plugins-examples/seo-optimizer && cargo build --target wasm32-unknown-unknown --release - @echo "Building content-filter..." - cd plugins-examples/content-filter && cargo build --target wasm32-unknown-unknown --release - @mkdir -p plugins/seo-optimizer plugins/content-filter - cp plugins-examples/seo-optimizer/target/wasm32-unknown-unknown/release/seo_optimizer.wasm plugins/seo-optimizer/ - cp plugins-examples/seo-optimizer/plugin.toml plugins/seo-optimizer/ - cp plugins-examples/content-filter/target/wasm32-unknown-unknown/release/content_filter.wasm plugins/content-filter/ - cp plugins-examples/content-filter/plugin.toml plugins/content-filter/ - @echo "Done. WASM plugins ready in plugins/" +fly_target := "x86_64-unknown-linux-musl" +fly_image := "raisfast-fly" -# 复制 JS 插件到 plugins/ 目录 -plugins-js-build: - @echo "Copying JS plugins..." - @mkdir -p plugins/welcome-email - @cp plugins-examples-js/welcome-email/plugin.toml plugins/welcome-email/ - @cp plugins-examples-js/welcome-email/index.js plugins/welcome-email/ - @mkdir -p plugins/seo-optimizer-js - @cp plugins-examples-js/seo-optimizer-js/plugin.toml plugins/seo-optimizer-js/ - @cp plugins-examples-js/seo-optimizer-js/index.js plugins/seo-optimizer-js/ - @echo "Done. JS plugins ready in plugins/" +# 安装 cross(Rust 交叉编译工具) +install-cross: + cargo install cross --git https://github.com/cross-rs/cross -# 编译 TypeScript JS 插件(需要 esbuild) -plugins-ts-build: - @echo "Compiling TypeScript plugins..." - @npx esbuild plugins-examples-js/seo-optimizer-js/src/index.ts \ - --outfile=plugins-examples-js/seo-optimizer-js/index.js \ - --bundle --format=iife --target=es2021 - @echo "Done." +# 交叉编译 Linux 二进制并部署到 fly.io +build-cross: + @echo "Cross-compiling for Linux via cross..." + cross build --release --features "{{features}}" --target {{fly_target}} -# 扫描 plugins-examples-lua/ 下所有含 plugin.toml 的子目录,复制到 plugins/ -# 扫描 plugins-examples-lua/ 下含 plugin.toml 的子目录,复制到 plugins/ -plugins-lua-build: - #!/usr/bin/env bash - echo "Scanning plugins-examples-lua/..." - count=0 - for dir in plugins-examples-lua/*/; do - if [ -f "${dir}plugin.toml" ]; then - name=$(basename "$dir") - echo " $name" - mkdir -p "plugins/$name" - cp -r "$dir"* "plugins/$name/" - count=$((count + 1)) - else - echo " Skipping $(basename "$dir"): missing plugin.toml" - fi - done - echo "Done. $count Lua plugin(s) copied to plugins/" - -# 编译/复制所有插件(WASM + JS + Lua) -plugins-all: plugins-build plugins-js-build plugins-lua-build +# 使用已编译的二进制直接部署到 fly.io(跳过编译) +deploy-fly: + @echo "Building Docker image..." + docker build --platform linux/amd64 -t {{fly_image}} -f deploy/fly/Dockerfile . + @echo "Deploying to fly.io..." + fly deploy --local-only -c deploy/fly/fly.toml --image {{fly_image}} diff --git a/src/content_type.rs b/src/content_type.rs index 7d7a5b59..f68762ce 100644 --- a/src/content_type.rs +++ b/src/content_type.rs @@ -75,6 +75,9 @@ impl ContentTypeRegistry { protocol_registry: &crate::protocols::ProtocolRegistry, ) -> Result { let registry = Self::new(); + if !dir.exists() { + let _ = std::fs::create_dir_all(dir); + } let entries = std::fs::read_dir(dir).map_err(|e| { AppError::Internal(anyhow::anyhow!( "cannot read content_types dir {dir:?}: {e}" diff --git a/src/db/connection.rs b/src/db/connection.rs index bad2d84b..bb8f231c 100644 --- a/src/db/connection.rs +++ b/src/db/connection.rs @@ -68,6 +68,13 @@ async fn try_connect(database_url: &str, pool_size: u32) -> Result::new() .max_connections(pool_size) .min_connections(1) diff --git a/src/plugins.rs b/src/plugins.rs index eceeae89..caf07bab 100644 --- a/src/plugins.rs +++ b/src/plugins.rs @@ -372,11 +372,11 @@ impl PluginManager { let plugin_dir = Path::new(plugin_dir); if !plugin_dir.exists() { + let _ = std::fs::create_dir_all(plugin_dir); tracing::info!( - "plugin directory does not exist, skipping: {}", + "created plugin directory: {}", plugin_dir.display() ); - return; } let entries = match std::fs::read_dir(plugin_dir) {