diff --git a/.dockerignore b/.dockerignore index b1042809d..98b7055bf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -50,6 +50,8 @@ /app-dist-output/ /artifacts/ +/desktop-shared-assets/ +/desktop-modules/ /s3_payload/ /upload_staging/ diff --git a/.github/workflows/build-app-proxy-self-hosted.yaml b/.github/workflows/build-app-proxy-self-hosted.yaml index 9cd141267..79f6ee375 100644 --- a/.github/workflows/build-app-proxy-self-hosted.yaml +++ b/.github/workflows/build-app-proxy-self-hosted.yaml @@ -154,6 +154,7 @@ jobs: BUILD_VERSION=${{ needs.meta.outputs.build_version }} SOURCE_SHA=${{ github.sha }} SOURCE_DATE=${{ steps.source.outputs.date }} + FLUXER_SELF_HOSTED=true APP_ASSETS_REF=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:${{ needs.meta.outputs.build_version }}-assets APP_ASSETS_PLATFORM=linux/amd64 cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy-self-hosted:buildcache-${{ matrix.platform }} diff --git a/.github/workflows/build-app-proxy.yaml b/.github/workflows/build-app-proxy.yaml index 84c065f58..ba873103f 100644 --- a/.github/workflows/build-app-proxy.yaml +++ b/.github/workflows/build-app-proxy.yaml @@ -200,6 +200,7 @@ jobs: tags: ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy:${{ needs.meta.outputs.build_version }}-arm64 build-args: | BUILD_VERSION=${{ needs.meta.outputs.build_version }} + PUBLIC_ASSET_BASE_URL=https://fluxerstatic.com SOURCE_SHA=${{ github.sha }} SOURCE_DATE=${{ steps.source.outputs.date }} APP_ASSETS_REF=ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy:${{ needs.meta.outputs.build_version }}-assets diff --git a/.github/workflows/build-desktop.yaml b/.github/workflows/build-desktop.yaml index 4f12e4c3b..60b9cf6aa 100644 --- a/.github/workflows/build-desktop.yaml +++ b/.github/workflows/build-desktop.yaml @@ -103,11 +103,150 @@ jobs: --step set_matrix --skip-targets "${{ inputs.skip_targets }}" + shared_assets: + name: Build shared renderer assets + needs: + - meta + runs-on: ubuntu-24.04 + environment: desktop-releases + timeout-minutes: 60 + permissions: + contents: read + env: + CHANNEL: ${{ needs.meta.outputs.channel }} + BUILD_CHANNEL: ${{ needs.meta.outputs.build_channel }} + RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }} + PUBLIC_RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }} + VERSION: ${{ needs.meta.outputs.version }} + BUILD_VERSION: ${{ needs.meta.outputs.version }} + PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }} + SOURCE_SHA: ${{ needs.meta.outputs.source_sha }} + steps: + - name: Checkout CI helpers + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + ref: ${{ needs.meta.outputs.source_sha }} + path: _ci + + - name: Checkout source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + ref: ${{ needs.meta.outputs.source_sha }} + path: source + + - name: Set up Rust toolchain (renderer wasm) + uses: dtolnay/rust-toolchain@02cb101ec7c40f2c49e1d9714d64511d8e1b74de + with: + toolchain: "1.98.1" + targets: wasm32-unknown-unknown + + - name: Set workdir (Unix) + env: + SUBST_TARGET: ${{ github.workspace }}/source + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step set_workdir_unix + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version: 26 + + - name: Set up pnpm + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step setup_pnpm + + - name: Resolve pnpm store path (Unix) + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step resolve_pnpm_store_unix + + - name: Cache pnpm store + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 + with: + path: ${{ env.PNPM_STORE_PATH }} + key: ${{ runner.os }}-shared-renderer-pnpm-store-${{ hashFiles('source/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-shared-renderer-pnpm-store- + + - name: Cache cargo registry + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-shared-renderer-cargo-registry-${{ hashFiles('source/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-shared-renderer-cargo-registry- + + - name: Install dependencies + working-directory: ${{ env.WORKDIR }}/fluxer_desktop + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step install_dependencies + + - name: Update version + working-directory: ${{ env.WORKDIR }}/fluxer_desktop + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step update_version + + - name: Set build channel + working-directory: ${{ env.WORKDIR }}/fluxer_desktop + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step set_build_channel + + - name: Build shared renderer assets + working-directory: ${{ env.WORKDIR }}/fluxer_desktop + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step build_shared_assets + + - name: Prepare shared renderer artifact + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step prepare_shared_assets + + - name: Upload shared renderer artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: desktop-shared-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }} + path: source/desktop-shared-assets + if-no-files-found: error + retention-days: 1 + compression-level: 0 + + - name: Split renderer into desktop modules + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step split_modules + + - name: Pack desktop modules + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step pack_modules + + - name: Upload desktop module packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 + with: + name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }} + path: | + source/desktop-modules/classification.json + source/desktop-modules/*/module.json + source/desktop-modules/*/package.br + source/desktop-modules/*/package.br.sha256 + if-no-files-found: error + retention-days: 1 + compression-level: 0 + build: name: Build ${{ matrix.platform }} (${{ matrix.arch }}) needs: - meta - matrix + - shared_assets runs-on: ${{ matrix.os }} environment: desktop-releases timeout-minutes: 180 @@ -130,6 +269,7 @@ jobs: SOURCE_SHA: ${{ needs.meta.outputs.source_sha }} DESKTOP_PLATFORM: ${{ matrix.platform }} DESKTOP_ARCH: ${{ matrix.arch }} + FLUXER_MODULES: "1" PLATFORM: ${{ matrix.platform }} ARCH: ${{ matrix.arch }} ELECTRON_ARCH: ${{ matrix.electron_arch }} @@ -276,6 +416,17 @@ jobs: cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop --step set_build_channel + - name: Download shared renderer artifact + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: desktop-shared-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }} + path: source/desktop-shared-assets + + - name: Restore shared renderer assets + run: >- + cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop + --step restore_shared_assets + - name: Build Electron main process working-directory: ${{ env.WORKDIR }}/fluxer_desktop env: @@ -505,11 +656,13 @@ jobs: if: ${{ !cancelled() && needs.build.result == 'success' }} needs: - meta + - shared_assets - build runs-on: ubuntu-24.04-arm environment: desktop-releases timeout-minutes: 180 permissions: + actions: read contents: read env: CHANNEL: ${{ needs.meta.outputs.build_channel }} @@ -547,6 +700,17 @@ jobs: cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop --step build_payload + - name: Download desktop module packages + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 + with: + name: desktop-modules-${{ needs.meta.outputs.build_channel }}-${{ needs.meta.outputs.version }}-${{ needs.meta.outputs.source_sha }} + path: desktop-modules + + - name: Build desktop module manifest + run: >- + cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop + --step build_module_manifest + - name: Prepare GitHub release assets run: >- cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop diff --git a/.gitignore b/.gitignore index 64ced687d..50fa77bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,8 @@ /app-dist-output/ /artifacts/ +/desktop-shared-assets/ +/desktop-modules/ /s3_payload/ /upload_staging/ diff --git a/Cargo.lock b/Cargo.lock index 6ab8fcbe9..8a3c72cb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1710,6 +1710,16 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.12" @@ -1741,6 +1751,7 @@ dependencies = [ "aws-config", "aws-sdk-s3", "base64 0.23.1", + "brotli", "bytes", "chrono", "clap", @@ -1750,6 +1761,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.11.0", + "tar", "tempfile", "tokio", "walkdir", @@ -2042,7 +2054,6 @@ dependencies = [ "hex", "rand 0.10.2", "reqwest", - "serde", "serde_json", "sha2 0.11.0", "tokio", @@ -4790,6 +4801,17 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -5818,6 +5840,16 @@ dependencies = [ "tls_codec", ] +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + [[package]] name = "xmlparser" version = "0.13.6" diff --git a/deploy/self-hosting/docker-compose.yml b/deploy/self-hosting/docker-compose.yml index bda6677b0..70e1c97e1 100644 --- a/deploy/self-hosting/docker-compose.yml +++ b/deploy/self-hosting/docker-compose.yml @@ -671,6 +671,7 @@ services: FLUXER_S3_SECRET_ACCESS_KEY: ${FLUXER_S3_SECRET_KEY:?set FLUXER_S3_SECRET_KEY in .env} FLUXER_S3_BUCKET_UPLOADS: ${FLUXER_S3_BUCKET_UPLOADS:-} FLUXER_STATIC_CDN_ENDPOINT: ${FLUXER_STATIC_CDN_ENDPOINT:-} + FLUXER_MEDIA_ENDPOINT: ${FLUXER_MEDIA_ENDPOINT:-} DISCOVERY_UPSTREAM_URL: http://edge:8088/.well-known/fluxer DISCOVERY_REFRESH_INTERVAL_MS: ${DISCOVERY_REFRESH_INTERVAL_MS:-} PUBLIC_BOOTSTRAP_API_ENDPOINT: /api diff --git a/fluxer_api/src/api/app/MiddlewarePipeline.ts b/fluxer_api/src/api/app/MiddlewarePipeline.ts index d7dd61913..485ca10b3 100644 --- a/fluxer_api/src/api/app/MiddlewarePipeline.ts +++ b/fluxer_api/src/api/app/MiddlewarePipeline.ts @@ -58,10 +58,17 @@ export function configureMiddleware(routes: HonoApp, options: MiddlewarePipeline allowedHeaders: [ HttpHeaders.CONTENT_TYPE, HttpHeaders.AUTHORIZATION, - 'X-Requested-With', - 'Accept-Language', + HttpHeaders.X_REQUESTED_WITH, + HttpHeaders.ACCEPT_LANGUAGE, HttpHeaders.X_REQUEST_ID, HttpHeaders.IF_NONE_MATCH, + HttpHeaders.X_AUDIT_LOG_REASON, + HttpHeaders.X_CAPTCHA_ID, + HttpHeaders.X_CAPTCHA_TOKEN, + HttpHeaders.X_FLUXER_CLIENT_INSTALLATION_ID, + HttpHeaders.X_FLUXER_FEATURES, + HttpHeaders.X_FLUXER_PLATFORM, + HttpHeaders.X_FLUXER_SUDO_MODE_JWT, ], exposedHeaders: [HttpHeaders.X_FLUXER_VERSION, HttpHeaders.ETAG], }, diff --git a/fluxer_api/src/api/instance/InstanceController.ts b/fluxer_api/src/api/instance/InstanceController.ts index c94594733..6dd394082 100644 --- a/fluxer_api/src/api/instance/InstanceController.ts +++ b/fluxer_api/src/api/instance/InstanceController.ts @@ -65,6 +65,7 @@ function buildDiscoveryStaticInput( api_public: apiPublicEndpoint, gateway: Config.endpoints.gateway, media: Config.endpoints.media, + upload_relay: Config.mediaProxy.uploadRelay.endpoint, static_cdn: Config.endpoints.staticCdn, marketing: Config.endpoints.marketing, admin: Config.endpoints.admin, diff --git a/fluxer_api/src/api/openapi/openapi.json b/fluxer_api/src/api/openapi/openapi.json index 37e700e39..361809c54 100644 --- a/fluxer_api/src/api/openapi/openapi.json +++ b/fluxer_api/src/api/openapi/openapi.json @@ -33000,6 +33000,7 @@ "WellKnownFluxerResponse": { "type": "object", "properties": { + "codename": {"description": "Protocol generation spoken by this instance", "type": "string"}, "api_code_version": { "type": "integer", "minimum": -9007199254740991, @@ -33104,6 +33105,7 @@ "description": "Public application configuration for client-side features", "$ref": "#/components/schemas/InstanceAppPublicSchema" }, + "age_policy": {"$ref": "#/components/schemas/InstanceAgePolicySchema"}, "domain_migration": { "description": "Web domain migration switch and anonymous rollout, only acted on by official instance clients", "$ref": "#/components/schemas/DomainMigrationDiscoveryResponse" @@ -33150,6 +33152,19 @@ "required": ["enabled", "anonymous_rollout_basis_points", "rollout_salt", "standalone_forwarding"], "additionalProperties": false }, + "InstanceAgePolicySchema": { + "type": "object", + "properties": { + "geos": { + "type": "array", + "items": {"$ref": "#/components/schemas/InstanceAgePolicyGeoSchema"}, + "description": "Regions with an age policy" + } + }, + "required": ["geos"], + "additionalProperties": false, + "description": "Regional age policy for this instance" + }, "InstanceAppPublicSchema": { "type": "object", "properties": { @@ -33253,7 +33268,23 @@ "InstancePushSchema": { "type": "object", "properties": { - "public_vapid_key": {"description": "VAPID public key for web push notifications", "type": ["string", "null"]} + "public_vapid_key": { + "description": "VAPID public key for web push notifications", + "type": ["string", "null"] + }, + "delivery_mode": { + "description": "How push notifications reach devices", + "$ref": "#/components/schemas/InstancePushDeliveryModeSchema" + }, + "event_resolution_path": { + "description": "API path clients call to resolve the payload of a push event", + "type": "string" + }, + "external_relay_url": {"description": "URL of the external push relay, if any", "type": ["string", "null"]}, + "relay_signing_key_hashes": { + "description": "Hashes of the keys the external push relay signs with", + "anyOf": [{"type": "array", "items": {"type": "string"}}, {"type": "null"}] + } }, "required": ["public_vapid_key"], "additionalProperties": false, @@ -33327,7 +33358,8 @@ "enabled": {"type": "boolean", "description": "Whether SSO is enabled for this instance"}, "enforced": {"type": "boolean", "description": "Whether SSO is required for all users"}, "display_name": {"description": "Display name of the SSO provider", "type": ["string", "null"]}, - "redirect_uri": {"type": "string", "description": "OAuth redirect URI for SSO"} + "redirect_uri": {"type": "string", "description": "OAuth redirect URI for SSO"}, + "available": {"description": "Whether an SSO provider is configured for this instance", "type": "boolean"} }, "required": ["enabled", "enforced", "display_name", "redirect_uri"], "additionalProperties": false, @@ -33373,6 +33405,22 @@ "description": "Whether the instance sends emails (verification, password reset, etc.)" }, "phone_verification_enabled": {"type": "boolean", "description": "Deprecated. Always false."}, + "desktop_modules_enabled": { + "description": "Whether desktop clients may load downloadable modules", + "type": "boolean" + }, + "account_deletion_grace_period_hours": { + "description": "Hours between a deletion request and the permanent deletion of the account", + "type": "integer", + "minimum": 1, + "maximum": 8760 + }, + "max_background_gateway_connections": { + "description": "Maximum gateway connections a client may keep open for background accounts", + "type": "integer", + "minimum": 0, + "maximum": 64 + }, "account_identity": { "description": "How people sign in on this instance. Clients treat a missing value as email", "$ref": "#/components/schemas/AccountIdentityModeSchema" @@ -33415,8 +33463,10 @@ "api_public": {"type": "string", "description": "Base URL for public API requests"}, "gateway": {"type": "string", "description": "WebSocket URL for the gateway"}, "media": {"type": "string", "description": "Base URL for the media proxy"}, + "upload_relay": {"description": "Base URL for proxied attachment and preview uploads", "type": "string"}, "static_cdn": {"type": "string", "description": "Base URL for static assets (avatars, emojis, etc.)"}, "marketing": {"type": "string", "description": "Base URL for the marketing website"}, + "docs": {"description": "Base URL for the documentation website", "type": "string"}, "admin": {"type": "string", "description": "Base URL for the admin panel"}, "invite": {"type": "string", "description": "Base URL for invite links"}, "gift": {"type": "string", "description": "Base URL for gift links"}, @@ -33467,6 +33517,29 @@ "enum": ["open", "approval", "closed"], "type": "string" }, + "InstancePushDeliveryModeSchema": {"type": "string", "enum": ["direct", "external_relay", "hybrid"]}, + "InstanceAgePolicyGeoSchema": { + "type": "object", + "properties": { + "country_code": {"type": "string", "description": "ISO 3166-1 alpha-2 country code"}, + "region_code": { + "description": "ISO 3166-2 subdivision code, or null for the whole country", + "type": ["string", "null"] + }, + "action": { + "description": "Whether the region restricts or blocks access", + "$ref": "#/components/schemas/InstanceAgePolicyActionSchema" + }, + "card_verification_available": { + "type": "boolean", + "description": "Whether card age verification is available in the region" + } + }, + "required": ["country_code", "region_code", "action", "card_verification_available"], + "additionalProperties": false, + "description": "Age policy for one region" + }, + "InstanceAgePolicyActionSchema": {"type": "string", "enum": ["restrict", "block"]}, "BotFlags": { "type": "integer", "minimum": 0, diff --git a/fluxer_app/index.html b/fluxer_app/index.html index e9f1b8821..9b678b48e 100644 --- a/fluxer_app/index.html +++ b/fluxer_app/index.html @@ -22,13 +22,301 @@ - - - - + + + + +
+ +