From 5f4b2f7c2bedd70584a35ffd17500ed60f775fd9 Mon Sep 17 00:00:00 2001 From: euzu Date: Sun, 17 Aug 2025 14:24:33 +0200 Subject: [PATCH 01/14] Content-Security-Policy optional --- CHANGELOG.md | 1 + backend/src/api/endpoints/web_index.rs | 13 +++-- backend/src/model/config/web_ui.rs | 3 ++ frontend/public/assets/i18n/en.json | 3 +- .../components/config/ipcheck_config_view.rs | 19 +++++-- .../components/config/webui_config_view.rs | 50 +++++++++++++------ .../components/playlist/playlist_explorer.rs | 4 +- shared/src/model/config/web_ui.rs | 2 + 8 files changed, 70 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d5d25541..5936209a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - EPG Config View - Fixed loading users for WebUI from user DB - Fixed auto EPG for batch inputs +- ContentSecurityPolicy can be enabled # 3.1.5 (2025-08-14) - Hot reload for config diff --git a/backend/src/api/endpoints/web_index.rs b/backend/src/api/endpoints/web_index.rs index ce6004b3c..d86a3f200 100644 --- a/backend/src/api/endpoints/web_index.rs +++ b/backend/src/api/endpoints/web_index.rs @@ -162,11 +162,14 @@ async fn index( new_content = new_content.replacen(script_tag, &new_tag, 1); } - return try_unwrap_body!(axum::response::Response::builder() - .header("Content-Type", mime::TEXT_HTML_UTF_8.as_ref()) - .header("Content-Security-Policy", - format!("default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-{nonce_b64}'; style-src 'self' 'nonce-{nonce_b64}'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'") - ).body(new_content)); + let mut builder = axum::response::Response::builder() + .header("Content-Type", mime::TEXT_HTML_UTF_8.as_ref()); + if config.web_ui.as_ref().is_some_and(|w| w.content_security_policy) { + builder = builder.header("Content-Security-Policy", + format!("default-src 'self'; script-src 'self' 'unsafe-eval' 'nonce-{nonce_b64}'; style-src 'self' 'nonce-{nonce_b64}'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'")); + + } + return try_unwrap_body!(builder.body(new_content)); } Err(err) => { error!("Failed to read web ui index.html: {err}"); diff --git a/backend/src/model/config/web_ui.rs b/backend/src/model/config/web_ui.rs index 1e9caf72a..8474783c6 100644 --- a/backend/src/model/config/web_ui.rs +++ b/backend/src/model/config/web_ui.rs @@ -6,6 +6,7 @@ use crate::model::{macros, WebAuthConfig}; pub struct WebUiConfig { pub enabled: bool, pub user_ui_enabled: bool, + pub content_security_policy: bool, pub path: Option, pub auth: Option, pub player_server: Option, @@ -30,6 +31,7 @@ impl From<&WebUiConfigDto> for WebUiConfig { Self { enabled: dto.enabled, user_ui_enabled: dto.user_ui_enabled, + content_security_policy: dto.content_security_policy, path: dto.path.clone(), auth: dto.auth.as_ref().map(Into::into), player_server: dto.player_server.clone(), @@ -41,6 +43,7 @@ impl From<&WebUiConfig> for WebUiConfigDto { Self { enabled: instance.enabled, user_ui_enabled: instance.user_ui_enabled, + content_security_policy: instance.content_security_policy, path: instance.path.clone(), auth: instance.auth.as_ref().map(Into::into), player_server: instance.player_server.clone(), diff --git a/frontend/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json index 592de3ad4..135fca0a7 100644 --- a/frontend/public/assets/i18n/en.json +++ b/frontend/public/assets/i18n/en.json @@ -243,7 +243,8 @@ "NAME_PREFIX_SEPARATOR":"Name Prefix Separator", "STRIP": "Strip", "COPY_LINK_TULIPROX": "Copy Virtual Id", - "COPY_LINK_PROVIDER": "Copy Provider Url" + "COPY_LINK_PROVIDER": "Copy Provider Url", + "CONTENT_SECURITY_POLICY" : "Content Security Policy" }, "TABLE": { "EMPTY": "", diff --git a/frontend/src/app/components/config/ipcheck_config_view.rs b/frontend/src/app/components/config/ipcheck_config_view.rs index e8ddbe9b4..a46c5c978 100644 --- a/frontend/src/app/components/config/ipcheck_config_view.rs +++ b/frontend/src/app/components/config/ipcheck_config_view.rs @@ -1,14 +1,25 @@ use yew::prelude::*; use yew_i18n::use_translation; -use crate::app::components::{NoContent}; use crate::app::context::ConfigContext; -use crate::{config_field_optional}; +use crate::{config_field_empty, config_field_optional}; #[function_component] pub fn IpCheckConfigView() -> Html { let translate = use_translation(); let config_ctx = use_context::().expect("ConfigContext not found"); + let render_empty = || html! { + html! { + <> + { config_field_empty!(translate.t("LABEL.URL")) } + { config_field_empty!(translate.t("LABEL.URL_IPV4")) } + { config_field_empty!(translate.t("LABEL.URL_IPV6")) } + { config_field_empty!(translate.t("LABEL.PATTERN_IPV4")) } + { config_field_empty!(translate.t("LABEL.PATTERN_IPV6")) } + + } + }; + html! {
@@ -25,10 +36,10 @@ pub fn IpCheckConfigView() -> Html { } } else { - html! { } + { render_empty() } } } else { - html! { } + { render_empty() } } }
diff --git a/frontend/src/app/components/config/webui_config_view.rs b/frontend/src/app/components/config/webui_config_view.rs index 51147b2bc..cb6b58943 100644 --- a/frontend/src/app/components/config/webui_config_view.rs +++ b/frontend/src/app/components/config/webui_config_view.rs @@ -1,6 +1,9 @@ -use crate::app::components::{Card, NoContent}; +use crate::app::components::Card; use crate::app::context::ConfigContext; -use crate::{config_field, config_field_bool, config_field_empty, config_field_hide, config_field_optional}; +use crate::{ + config_field, config_field_bool, config_field_bool_empty, config_field_empty, + config_field_hide, config_field_optional, +}; use yew::prelude::*; use yew_i18n::use_translation; @@ -9,6 +12,32 @@ pub fn WebUiConfigView() -> Html { let translate = use_translation(); let config_ctx = use_context::().expect("ConfigContext not found"); + let render_empty_auth = || { + html! { + +

{translate.t("LABEL.AUTH")}

+ { config_field_empty!(translate.t("LABEL.ENABLED")) } + { config_field_empty!(translate.t("LABEL.ISSUER")) } + { config_field_empty!(translate.t("LABEL.SECRET")) } + { config_field_empty!(translate.t("LABEL.TOKEN_TTL_MINS")) } + { config_field_empty!(translate.t("LABEL.USERFILE")) } +
+ } + }; + + let render_empty = || { + html! { + <> + { config_field_bool_empty!(translate.t("LABEL.ENABLED")) } + { config_field_bool_empty!(translate.t("LABEL.USER_UI_ENABLED")) } + { config_field_bool_empty!(translate.t("LABEL.CONTENT_SECURITY_POLICY")) } + { config_field_empty!(translate.t("LABEL.PATH")) } + { config_field_empty!(translate.t("LABEL.PLAYER_SERVER")) } + { render_empty_auth()} + + } + }; + html! {
@@ -19,6 +48,7 @@ pub fn WebUiConfigView() -> Html { <> { config_field_bool!(web_ui, translate.t("LABEL.ENABLED"), enabled) } { config_field_bool!(web_ui, translate.t("LABEL.USER_UI_ENABLED"), user_ui_enabled) } + { config_field_bool!(web_ui, translate.t("LABEL.CONTENT_SECURITY_POLICY"), content_security_policy) } { config_field_optional!(web_ui, translate.t("LABEL.PATH"), path) } { config_field_optional!(web_ui, translate.t("LABEL.PLAYER_SERVER"), player_server) } @@ -34,27 +64,19 @@ pub fn WebUiConfigView() -> Html { { config_field_optional!(auth, translate.t("LABEL.USERFILE"), userfile) } }, - None => html!{ - <> - { config_field_empty!(translate.t("LABEL.ENABLED")) } - { config_field_empty!(translate.t("LABEL.ISSUER")) } - { config_field_empty!(translate.t("LABEL.SECRET")) } - { config_field_empty!(translate.t("LABEL.TOKEN_TTL_MINS")) } - { config_field_empty!(translate.t("LABEL.USERFILE")) } - - } + None => render_empty_auth(), }} } } else { - html! { } + { render_empty() } } } else { - html! { } + { render_empty() } } }
} -} \ No newline at end of file +} diff --git a/frontend/src/app/components/playlist/playlist_explorer.rs b/frontend/src/app/components/playlist/playlist_explorer.rs index b48143d70..2a6400aaf 100644 --- a/frontend/src/app/components/playlist/playlist_explorer.rs +++ b/frontend/src/app/components/playlist/playlist_explorer.rs @@ -240,8 +240,10 @@ pub fn PlaylistExplorer() -> Html { } else { html! { {"n/a"}().unwrap(); + if let Some(target) = e.target() { + let img = target.dyn_into::().unwrap(); img.set_src("assets/missing-logo.svg"); + } })} />} } diff --git a/shared/src/model/config/web_ui.rs b/shared/src/model/config/web_ui.rs index e64d3e175..cf79c63e3 100644 --- a/shared/src/model/config/web_ui.rs +++ b/shared/src/model/config/web_ui.rs @@ -16,6 +16,8 @@ pub struct WebUiConfigDto { pub enabled: bool, #[serde(default = "default_as_true")] pub user_ui_enabled: bool, + #[serde(default)] + pub content_security_policy: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] From ee83562fc5c318c74c76cb0c4d1bd92526301b54 Mon Sep 17 00:00:00 2001 From: euzu Date: Sun, 17 Aug 2025 14:30:49 +0200 Subject: [PATCH 02/14] Content-Security-Policy optional --- README.md | 1 + frontend/src/app/components/config/ipcheck_config_view.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2121f6939..c2700f657 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,7 @@ log: ### 1.10 `web_ui` - enabled: default is true, if set to false the web_ui is disabled - user_ui_enabled, true or false, for user bouquet editor +- content_security_policy, default false, if set to true enables cross site scripting protection. - path is for web_ui path like `/ui` for reverse proxy integration if necessary. - auth for authentication settings - `enabled` can be deactivated if `enabled` is set to `false`. If not set default is `true`. diff --git a/frontend/src/app/components/config/ipcheck_config_view.rs b/frontend/src/app/components/config/ipcheck_config_view.rs index a46c5c978..9b8bcb922 100644 --- a/frontend/src/app/components/config/ipcheck_config_view.rs +++ b/frontend/src/app/components/config/ipcheck_config_view.rs @@ -8,7 +8,7 @@ pub fn IpCheckConfigView() -> Html { let translate = use_translation(); let config_ctx = use_context::().expect("ConfigContext not found"); - let render_empty = || html! { + let render_empty = || { html! { <> { config_field_empty!(translate.t("LABEL.URL")) } From 2e5829a7a07049fb5817dde88dd2d2846aaa8be2 Mon Sep 17 00:00:00 2001 From: euzu Date: Sun, 17 Aug 2025 14:32:44 +0200 Subject: [PATCH 03/14] Content-Security-Policy optional --- frontend/src/app/components/playlist/playlist_explorer.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/playlist/playlist_explorer.rs b/frontend/src/app/components/playlist/playlist_explorer.rs index 2a6400aaf..ebba99b88 100644 --- a/frontend/src/app/components/playlist/playlist_explorer.rs +++ b/frontend/src/app/components/playlist/playlist_explorer.rs @@ -241,8 +241,9 @@ pub fn PlaylistExplorer() -> Html { html! { {"n/a"}().unwrap(); - img.set_src("assets/missing-logo.svg"); + if let Ok(img) = target.dyn_into::() { + img.set_src("assets/missing-logo.svg"); + } } })} />} From 9f9c1cc5461b66f004a25098597c17adf11d6c71 Mon Sep 17 00:00:00 2001 From: euzu Date: Sun, 17 Aug 2025 14:37:30 +0200 Subject: [PATCH 04/14] Content-Security-Policy optional --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2700f657..9b775882f 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,8 @@ log: ### 1.10 `web_ui` - enabled: default is true, if set to false the web_ui is disabled - user_ui_enabled, true or false, for user bouquet editor -- content_security_policy, default false, if set to true enables cross site scripting protection. +- content_security_policy: default false; when true, sends a Content-Security-Policy (CSP) header to help protect against cross-site scripting (XSS). + Not: enabling CSP may block external images/logos unless allowed via the img-src directive. - path is for web_ui path like `/ui` for reverse proxy integration if necessary. - auth for authentication settings - `enabled` can be deactivated if `enabled` is set to `false`. If not set default is `true`. From 7b0601127cc0fd323d62abd05fff9585f030de18 Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 14:58:00 +0200 Subject: [PATCH 05/14] Add GitHub Actions workflow for Docker build and push --- .github/workflows/docker-build.yml | 106 +++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 000000000..3aa36b09c --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,106 @@ +name: Docker Build and Push + +on: + push: + branches: + - master + - develop + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + build-master: + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt, clippy + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Install trunk + run: | + wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.18.8/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + sudo mv trunk /usr/local/bin/ + + - name: Add Rust targets + run: | + rustup target add x86_64-unknown-linux-musl + rustup target add aarch64-unknown-linux-musl + rustup target add wasm32-unknown-unknown + + - name: Create GHCR_IO config + run: | + mkdir -p $HOME + echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io + + - name: Make scripts executable + run: | + chmod +x bin/build_github_docker.sh + chmod +x bin/build_resources.sh + + - name: Run master build script + run: ./bin/build_github_docker.sh + + build-develop: + if: github.ref == 'refs/heads/develop' + runs-on: ubuntu-latest + + strategy: + matrix: + build-script: + - build_github_docker_aarch64.sh + - build_github_docker_beta.sh + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: rustfmt, clippy + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Install trunk + run: | + wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.18.8/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + sudo mv trunk /usr/local/bin/ + + - name: Add Rust targets + run: | + rustup target add x86_64-unknown-linux-musl + rustup target add aarch64-unknown-linux-musl + rustup target add wasm32-unknown-unknown + + - name: Create GHCR_IO config + run: | + mkdir -p $HOME + echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io + + - name: Make scripts executable + run: | + chmod +x bin/${{ matrix.build-script }} + chmod +x bin/build_resources.sh + + - name: Run develop build script + run: ./bin/${{ matrix.build-script }} From 959dc79a97d83e920b3be43a366fb6b81edb6266 Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:06:36 +0200 Subject: [PATCH 06/14] Update trunk version in Docker build workflow to v0.21.14 --- .github/workflows/docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 3aa36b09c..1b897c71a 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -33,7 +33,7 @@ jobs: - name: Install trunk run: | - wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.18.8/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.21.14/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- sudo mv trunk /usr/local/bin/ - name: Add Rust targets @@ -83,7 +83,7 @@ jobs: - name: Install trunk run: | - wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.18.8/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- + wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.21.14/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- sudo mv trunk /usr/local/bin/ - name: Add Rust targets From 350aa6319e89daa759064af97067c47abee7895b Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:34:08 +0200 Subject: [PATCH 07/14] Refactor Docker build workflow to consolidate jobs and improve caching - Merged master and develop build jobs into a single `docker-build` job. - Updated steps for installing Rust and caching cargo registry. - Introduced a new script `build_docker.sh` for building and pushing Docker images. - Removed obsolete scripts `build_github_docker_aarch64.sh` and `build_github_docker_beta.sh`. --- .github/workflows/docker-build.yml | 136 ++++++++--------------- bin/build_docker.sh | 167 +++++++++++++++++++++++++++++ bin/build_github_docker.sh | 103 ------------------ bin/build_github_docker_aarch64.sh | 106 ------------------ bin/build_github_docker_beta.sh | 77 ------------- 5 files changed, 209 insertions(+), 380 deletions(-) create mode 100644 bin/build_docker.sh delete mode 100755 bin/build_github_docker.sh delete mode 100755 bin/build_github_docker_aarch64.sh delete mode 100755 bin/build_github_docker_beta.sh diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 1b897c71a..796ea8033 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -5,102 +5,50 @@ on: branches: - master - develop - workflow_dispatch: - -env: - CARGO_TERM_COLOR: always jobs: - build-master: - if: github.ref == 'refs/heads/master' + docker-build: runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - components: rustfmt, clippy - - - name: Install cross - run: cargo install cross --git https://github.com/cross-rs/cross - - - name: Install trunk - run: | - wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.21.14/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- - sudo mv trunk /usr/local/bin/ - - - name: Add Rust targets - run: | - rustup target add x86_64-unknown-linux-musl - rustup target add aarch64-unknown-linux-musl - rustup target add wasm32-unknown-unknown - - - name: Create GHCR_IO config - run: | - mkdir -p $HOME - echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io - - - name: Make scripts executable - run: | - chmod +x bin/build_github_docker.sh - chmod +x bin/build_resources.sh - - - name: Run master build script - run: ./bin/build_github_docker.sh - - build-develop: - if: github.ref == 'refs/heads/develop' - runs-on: ubuntu-latest - - strategy: - matrix: - build-script: - - build_github_docker_aarch64.sh - - build_github_docker_beta.sh - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - components: rustfmt, clippy - - - name: Install cross - run: cargo install cross --git https://github.com/cross-rs/cross - - - name: Install trunk - run: | - wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.21.14/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- - sudo mv trunk /usr/local/bin/ - - - name: Add Rust targets - run: | - rustup target add x86_64-unknown-linux-musl - rustup target add aarch64-unknown-linux-musl - rustup target add wasm32-unknown-unknown - - - name: Create GHCR_IO config - run: | - mkdir -p $HOME - echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io - - - name: Make scripts executable - run: | - chmod +x bin/${{ matrix.build-script }} - chmod +x bin/build_resources.sh - - - name: Run develop build script - run: ./bin/${{ matrix.build-script }} + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl + + - name: Install cross + run: cargo install cross --git https://github.com/cross-rs/cross + + - name: Install trunk + run: cargo install trunk + + - name: Cache cargo registry + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + + - name: Create GHCR token file + run: | + mkdir -p $HOME + echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io + + - name: Build and push Docker images + run: ./bin/build_docker.sh ${{ steps.extract_branch.outputs.branch }} diff --git a/bin/build_docker.sh b/bin/build_docker.sh new file mode 100644 index 000000000..55459944e --- /dev/null +++ b/bin/build_docker.sh @@ -0,0 +1,167 @@ +#!/bin/bash +set -euo pipefail + +# Check for required environment variable +if [ ! -f "${HOME}/.ghcr.io" ]; then + echo "๐Ÿงจ Error: ${HOME}/.ghcr.io file not found" + exit 1 +fi +source "${HOME}/.ghcr.io" + +# Function to print usage instructions +print_usage() { + echo "Usage: $(basename "$0") " + echo + echo "Arguments:" + echo " branch Git branch name (only 'master' and 'develop' are supported)" + echo + echo "Examples:" + echo " $(basename "$0") master # Builds and pushes with :latest tag" + echo " $(basename "$0") develop # Builds and pushes with :dev tag" + exit 1 +} + +# Validate arguments +if [ $# -ne 1 ]; then + echo "๐Ÿงจ Error: Exactly one argument required" + print_usage +fi + +BRANCH="$1" + +# Validate branch +case "$BRANCH" in + master) + TAG_SUFFIX="latest" + ;; + develop) + TAG_SUFFIX="dev" + ;; + *) + echo "๐Ÿงจ Error: Branch '$BRANCH' is not supported. Only 'master' and 'develop' are allowed." + exit 1 + ;; +esac + +echo "๐Ÿš€ Building Docker images for branch: $BRANCH (tag: $TAG_SUFFIX)" + +# Set up directories +WORKING_DIR=$(pwd) +BIN_DIR="${WORKING_DIR}/bin" +RESOURCES_DIR="${WORKING_DIR}/resources" +DOCKER_DIR="${WORKING_DIR}/docker" +BACKEND_DIR="${WORKING_DIR}/backend" +FRONTEND_DIR="${WORKING_DIR}/frontend" +FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist" + +# Define architectures and their corresponding builds +declare -A ARCHITECTURES=( + [LINUX]=x86_64-unknown-linux-musl + [AARCH64]=aarch64-unknown-linux-musl +) + +declare -A BUILDS=( + [LINUX]="tuliprox:scratch-final tuliprox-alpine:alpine-final" + [AARCH64]="tuliprox-aarch64:scratch-final" +) + +# Get version from Cargo.toml +VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml") +if [ -z "${VERSION}" ]; then + echo "๐Ÿงจ Error: Failed to determine the version from Cargo.toml" + exit 1 +fi + +echo "๐Ÿ“ฆ Version: ${VERSION}" + +# Build resources if needed +if [ ! -f "${BIN_DIR}/build_resources.sh" ]; then + echo "๐Ÿ› ๏ธ Building resources..." + "${BIN_DIR}/build_resources.sh" +fi + +# Build frontend +echo "๐ŸŽจ Building frontend..." +rm -rf "${FRONTEND_BUILD_DIR}" +cd "${FRONTEND_DIR}" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release + +# Check if the frontend build directory exists +if [ ! -d "${FRONTEND_BUILD_DIR}" ]; then + echo "๐Ÿงจ Error: Frontend build directory '${FRONTEND_BUILD_DIR}' does not exist" + exit 1 +fi + +cd "$WORKING_DIR" + +declare -a BUILT_IMAGES=() + +# Build for each platform +for PLATFORM in "${!ARCHITECTURES[@]}"; do + ARCHITECTURE=${ARCHITECTURES[$PLATFORM]} + + echo "๐Ÿ—๏ธ Building binary for architecture: $ARCHITECTURE" + + cargo clean || true + env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$ARCHITECTURE" + + BINARY_PATH="${WORKING_DIR}/target/${ARCHITECTURE}/release/tuliprox" + if [ ! -f "$BINARY_PATH" ]; then + echo "๐Ÿงจ Error: Binary $BINARY_PATH does not exist" + exit 1 + fi + + # Prepare Docker context + echo "๐Ÿ“‹ Preparing Docker context for $PLATFORM..." + cp "$BINARY_PATH" "${DOCKER_DIR}/" + rm -rf "${DOCKER_DIR}/web" + cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" + cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/resources" + + cd "${DOCKER_DIR}" + echo "๐Ÿณ Building Docker images for platform: $PLATFORM" + + # Build all configured images for this platform + for pair in ${BUILDS[$PLATFORM]}; do + IMAGE_NAME="${pair%%:*}" + BUILD_TARGET="${pair##*:}" + + echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" + + # Build with version tag + docker build -f Dockerfile-manual \ + -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ + --target "$BUILD_TARGET" . + + # Tag with branch-specific tag + docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" + + BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${VERSION}") + BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}") + done + + # Clean up Docker context for next iteration + rm -f "${DOCKER_DIR}/tuliprox" +done + +cd "$WORKING_DIR" + +# Login and push all images +echo "๐Ÿ”‘ Logging into GitHub Container Registry..." +docker login ghcr.io -u euzu -p "${GHCR_IO_TOKEN}" + +for img in "${BUILT_IMAGES[@]}"; do + echo "๐Ÿ“ค Pushing $img" + docker push "$img" +done + +# Final cleanup +echo "๐Ÿ—‘๏ธ Cleaning up Docker context..." +rm -rf "${DOCKER_DIR}/web" +rm -f "${DOCKER_DIR}/tuliprox" +rm -rf "${DOCKER_DIR}/resources" + +echo "๐ŸŽ‰ Docker images for branch '$BRANCH' (version ${VERSION}) have been successfully built and pushed!" +echo "๐Ÿ“‹ Built images:" +for img in "${BUILT_IMAGES[@]}"; do + echo " - $img" +done diff --git a/bin/build_github_docker.sh b/bin/build_github_docker.sh deleted file mode 100755 index e848b0e5f..000000000 --- a/bin/build_github_docker.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/bash -set -euo pipefail -source "${HOME}/.ghcr.io" - -WORKING_DIR=$(pwd) -BIN_DIR="${WORKING_DIR}/bin" -RESOURCES_DIR="${WORKING_DIR}/resources" -DOCKER_DIR="${WORKING_DIR}/docker" -BACKEND_DIR="${WORKING_DIR}/backend" -FRONTEND_DIR="${WORKING_DIR}/frontend" -FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist" -declare -A ARCHITECTURES=( - [LINUX]=x86_64-unknown-linux-musl - [AARCH64]=aarch64-unknown-linux-musl -) - -declare -A BUILDS=( - [LINUX]="tuliprox:scratch-final tuliprox-alpine:alpine-final" - [AARCH64]="tuliprox-aarch64:scratch-final" -) - - -if [ ! -f "${BIN_DIR}/build_resources.sh" ]; then - "${BIN_DIR}/build_resources.sh" -fi - -rm -rf "${FRONTEND_BUILD_DIR}" -cd "${FRONTEND_DIR}" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release - -# Check if the frontend build directory exists -if [ ! -d "${FRONTEND_BUILD_DIR}" ]; then - echo "Error: Web directory '${FRONTEND_BUILD_DIR}' does not exist." - exit 1 -fi - -cd "$WORKING_DIR" - - -VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml") -if [ -z "${VERSION}" ]; then - echo "๐Ÿงจ Error: Failed to determine the version." - exit 1 -fi - -declare -a BUILT_IMAGES=() - -# Start build loop per platform -for PLATFORM in "${!ARCHITECTURES[@]}"; do - ARCHITECTURE=${ARCHITECTURES[$PLATFORM]} - - echo "๐Ÿ› ๏ธ Building binary for architecture: $ARCHITECTURE" - - cargo clean || true - env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$ARCHITECTURE" - - BINARY_PATH="${WORKING_DIR}/target/${ARCHITECTURE}/release/tuliprox" - if [ ! -f "$BINARY_PATH" ]; then - echo "๐Ÿงจ Error: Binary $BINARY_PATH does not exist." - exit 1 - fi - - # Prepare Docker context - cp "$BINARY_PATH" "${DOCKER_DIR}/" - rm -rf "${DOCKER_DIR}/web" - cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" - cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/resources" - - cd "${DOCKER_DIR}" - echo "๐Ÿ› ๏ธ Building Docker images for platform: $PLATFORM, version: ${VERSION}" - - for pair in ${BUILDS[$PLATFORM]}; do - IMAGE_NAME="${pair%%:*}" - BUILD_TARGET="${pair##*:}" - - echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" - - docker build -f Dockerfile-manual \ - -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ - --target "$BUILD_TARGET" . - - docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:latest" - - BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${VERSION}") - BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:latest") - done -done - -cd "$WORKING_DIR" - -echo "๐Ÿ”‘ Logging into GitHub Container Registry..." -docker login ghcr.io -u euzu -p "${GHCR_IO_TOKEN}" -for img in "${BUILT_IMAGES[@]}"; do - echo "๐Ÿ“ฆ Pushing $img" - docker push "$img" -done - -# Clean up -echo "๐Ÿ—‘ Cleaning up Docker context..." -rm -rf "${DOCKER_DIR}/web" -rm -f "${DOCKER_DIR}/tuliprox" -rm -rf "${DOCKER_DIR}/resources" - -echo "๐ŸŽ‰ Docker images for version ${VERSION} have been successfully built, tagged, and pushed." diff --git a/bin/build_github_docker_aarch64.sh b/bin/build_github_docker_aarch64.sh deleted file mode 100755 index 96ee731ae..000000000 --- a/bin/build_github_docker_aarch64.sh +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source "${HOME}/.ghcr.io" - -# Function to print usage instructions -print_usage() { - echo "Usage: $(basename "$0") [-r] [-h]" - echo - echo "Options:" - echo " -r Enable release image mode (default: false)" - echo " -h Display this help message" - exit 0 -} - -beta_image=true - -# parse options -while getopts "rh" opt; do - case $opt in - r) beta_image=false ;; - h) print_usage ;; - \?) echo "Unknown option: -$OPTARG" >&2 ;; - esac -done - -WORKING_DIR=$(pwd) -BIN_DIR="${WORKING_DIR}/bin" -RESOURCES_DIR="${WORKING_DIR}/resources" -DOCKER_DIR="${WORKING_DIR}/docker" -BACKEND_DIR="${WORKING_DIR}/backend" -FRONTEND_DIR="${WORKING_DIR}/frontend" -FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist" -TARGET=aarch64-unknown-linux-musl -VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true) -if [ -z "${VERSION}" ]; then - echo "Error: Failed to determine the version from Cargo.toml." - exit 1 -fi - -if [ "$beta_image" = true ]; then - # Split the version into its components using '.' as a delimiter - IFS='.' read -r major minor patch <<< "$VERSION" - # Increment the patch version - patch=$((patch + 1)) - # Combine the components back into a version string - VERSION="$major.$minor.${patch}-beta" -fi - -if [ ! -f "${BIN_DIR}/build_resources.sh" ]; then - "${BIN_DIR}/build_resources.sh" -fi - -rm -rf "${FRONTEND_BUILD_DIR}" -cd "${FRONTEND_DIR}" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release - -# Check if the frontend build directory exists -if [ ! -d "${FRONTEND_BUILD_DIR}" ]; then - echo "Error: Web directory '${FRONTEND_BUILD_DIR}' does not exist." - exit 1 -fi - -cd "$WORKING_DIR" - -cargo clean -env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$TARGET" - -# Check if the binary exists -if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/tuliprox" ]; then - echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/tuliprox' does not exist." - exit 1 -fi - -cd "$WORKING_DIR" - -# Prepare Docker build context -cp "${WORKING_DIR}/target/${TARGET}/release/tuliprox" "${DOCKER_DIR}/" -rm -rf "${DOCKER_DIR}/web" -cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" -cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/" - -cd "${DOCKER_DIR}" -echo "Building Docker images for version ${VERSION}" -if [ "$beta_image" = true ]; then - SCRATCH_IMAGE_NAME=tuliprox-aarch64-beta -else - SCRATCH_IMAGE_NAME=tuliprox-aarch64 -fi - -# Build scratch image and tag as "latest" -docker build -f Dockerfile-manual -t ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:"${VERSION}" --target scratch-final . -docker tag ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:"${VERSION}" ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:latest - -echo "Logging into GitHub Container Registry..." -docker login ghcr.io -u euzu -p "${GHCR_IO_TOKEN}" - -# Push scratch -docker push ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:"${VERSION}" -docker push ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:latest - -# Clean up -echo "Cleaning up build artifacts..." -rm -rf "${DOCKER_DIR}/web" -rm -f "${DOCKER_DIR}/tuliprox" -rm -rf "${DOCKER_DIR}/resources" - -echo "Docker images ghcr.io/euzu/${SCRATCH_IMAGE_NAME}:${VERSION} have been successfully built, tagged, and pushed." diff --git a/bin/build_github_docker_beta.sh b/bin/build_github_docker_beta.sh deleted file mode 100755 index cae303531..000000000 --- a/bin/build_github_docker_beta.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail -source "${HOME}/.ghcr.io" - -WORKING_DIR=$(pwd) -BIN_DIR="${WORKING_DIR}/bin" -RESOURCES_DIR="${WORKING_DIR}/resources" -DOCKER_DIR="${WORKING_DIR}/docker" -BACKEND_DIR="${WORKING_DIR}/backend" -FRONTEND_DIR="${WORKING_DIR}/frontend" -FRONTEND_BUILD_DIR="${FRONTEND_DIR}/dist" -TARGET=x86_64-unknown-linux-musl -VERSION=$(grep -Po '^version\s*=\s*"\K[0-9\.]+' "${BACKEND_DIR}/Cargo.toml" || true) -if [ -z "${VERSION}" ]; then - echo "Error: Failed to determine the version from Cargo.toml." - exit 1 -fi - -# Split the version into its components using '.' as a delimiter -IFS='.' read -r major minor patch <<< "$VERSION" -# Increment the patch version -patch=$((patch + 1)) -# Combine the components back into a version string -VERSION="$major.$minor.${patch}-beta" - -if [ ! -f "${BIN_DIR}/build_resources.sh" ]; then - "${BIN_DIR}/build_resources.sh" -fi - -rm -rf "${FRONTEND_BUILD_DIR}" -cd "${FRONTEND_DIR}" && env RUSTFLAGS="--remap-path-prefix $HOME=~" trunk build --release - -# Check if the frontend build directory exists -if [ ! -d "${FRONTEND_BUILD_DIR}" ]; then - echo "Error: Web directory '${FRONTEND_BUILD_DIR}' does not exist." - exit 1 -fi - -cd "${WORKING_DIR}" - -cargo clean -env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$TARGET" - -# Check if the binary exists -if [ ! -f "${WORKING_DIR}/target/${TARGET}/release/tuliprox" ]; then - echo "Error: Static binary '${WORKING_DIR}/target/${TARGET}/release/tuliprox' does not exist." - exit 1 -fi - -# Prepare Docker build context -cp "${WORKING_DIR}/target/${TARGET}/release/tuliprox" "${DOCKER_DIR}/" -rm -rf "${DOCKER_DIR}/web" -cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" -cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/" - -cd "${DOCKER_DIR}" -echo "Building Docker images for version ${VERSION}" -BETA_IMAGE_NAME=tuliprox-beta - -# Build alpine image and tag as "latest" -docker build -f Dockerfile-manual -t ghcr.io/euzu/${BETA_IMAGE_NAME}:"${VERSION}" --target alpine-final . -docker tag ghcr.io/euzu/${BETA_IMAGE_NAME}:"${VERSION}" ghcr.io/euzu/${BETA_IMAGE_NAME}:latest - -echo "Logging into GitHub Container Registry..." -docker login ghcr.io -u euzu -p "${GHCR_IO_TOKEN}" - -# Push alpine -docker push ghcr.io/euzu/${BETA_IMAGE_NAME}:"${VERSION}" -docker push ghcr.io/euzu/${BETA_IMAGE_NAME}:latest - -# Clean up -echo "Cleaning up build artifacts..." -rm -rf "${DOCKER_DIR}/web" -rm -f "${DOCKER_DIR}/tuliprox" -rm -rf "${DOCKER_DIR}/resources" - -echo "Docker images ghcr.io/euzu/${BETA_IMAGE_NAME} with version ${VERSION} have been successfully built, tagged, and pushed." From ee52d65667107b348d4c0ab2f48f20de62823645 Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:41:50 +0200 Subject: [PATCH 08/14] Make build script executable in Docker workflow --- .github/workflows/docker-build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 796ea8033..856d04411 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -50,5 +50,8 @@ jobs: mkdir -p $HOME echo "GHCR_IO_TOKEN=${{ secrets.GHCR_IO_TOKEN }}" > $HOME/.ghcr.io + - name: Make build script executable + run: chmod +x ./bin/build_docker.sh + - name: Build and push Docker images run: ./bin/build_docker.sh ${{ steps.extract_branch.outputs.branch }} From b89957fd4190c0942861d3a46ffdaf3fbbbb6b9c Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:46:13 +0200 Subject: [PATCH 09/14] Enhance Docker build workflow with caching for cargo tools and registry --- .github/workflows/docker-build.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 856d04411..02ed47699 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -23,21 +23,32 @@ jobs: with: targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl + - name: Cache cargo tools + id: cache-tools + uses: actions/cache@v3 + with: + path: ~/.cargo/bin + key: ${{ runner.os }}-cargo-tools-cross-trunk + - name: Install cross + if: steps.cache-tools.outputs.cache-hit != 'true' run: cargo install cross --git https://github.com/cross-rs/cross - name: Install trunk + if: steps.cache-tools.outputs.cache-hit != 'true' run: cargo install trunk - name: Cache cargo registry uses: actions/cache@v3 with: path: | - ~/.cargo/registry - ~/.cargo/git + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | + ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} ${{ runner.os }}-cargo- - name: Extract branch name From c9781f077b7f3b7284d16ff405691f2f38d28fdb Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 15:50:49 +0200 Subject: [PATCH 10/14] Update Docker build workflow to include WebAssembly target in Rust installation --- .github/workflows/docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 02ed47699..61523321e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -21,7 +21,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl + targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,wasm32-unknown-unknown - name: Cache cargo tools id: cache-tools From 57c973dcb89a930f7291d2b8af2775018f139ffb Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 16:19:05 +0200 Subject: [PATCH 11/14] Enhance Docker build script to determine platform based on Rust target architecture --- bin/build_docker.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/bin/build_docker.sh b/bin/build_docker.sh index 55459944e..dddcf12e4 100644 --- a/bin/build_docker.sh +++ b/bin/build_docker.sh @@ -127,10 +127,27 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" + # Determine Docker platform from Rust target + case "$ARCHITECTURE" in + aarch64-*) + DOCKER_PLATFORM="linux/arm64" + ;; + x86_64-*) + DOCKER_PLATFORM="linux/amd64" + ;; + armv7-*) + DOCKER_PLATFORM="linux/arm/v7" + ;; + *) + DOCKER_PLATFORM="linux/amd64" # Default fallback + ;; + esac + # Build with version tag docker build -f Dockerfile-manual \ -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ - --target "$BUILD_TARGET" . + --target "$BUILD_TARGET" \ + --platform "$DOCKER_PLATFORM" . # Tag with branch-specific tag docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" From 7b3a620bd10f06d6632ecc26c22515cfcd77fdfe Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 16:30:58 +0200 Subject: [PATCH 12/14] Update Docker build workflow to set up QEMU and specify platforms for Buildx --- .github/workflows/docker-build.yml | 7 +++++++ bin/build_docker.sh | 19 +------------------ 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 61523321e..9a694e511 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -15,8 +15,15 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/bin/build_docker.sh b/bin/build_docker.sh index dddcf12e4..55459944e 100644 --- a/bin/build_docker.sh +++ b/bin/build_docker.sh @@ -127,27 +127,10 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" - # Determine Docker platform from Rust target - case "$ARCHITECTURE" in - aarch64-*) - DOCKER_PLATFORM="linux/arm64" - ;; - x86_64-*) - DOCKER_PLATFORM="linux/amd64" - ;; - armv7-*) - DOCKER_PLATFORM="linux/arm/v7" - ;; - *) - DOCKER_PLATFORM="linux/amd64" # Default fallback - ;; - esac - # Build with version tag docker build -f Dockerfile-manual \ -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ - --target "$BUILD_TARGET" \ - --platform "$DOCKER_PLATFORM" . + --target "$BUILD_TARGET" . # Tag with branch-specific tag docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" From 2b514ff1eb8758faaa9080ee609305198a808ec1 Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 16:53:49 +0200 Subject: [PATCH 13/14] Enhance Docker build workflow to create and utilize a multi-platform builder with Buildx --- .github/workflows/docker-build.yml | 5 +++++ bin/build_docker.sh | 27 ++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 9a694e511..ae361f656 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -24,6 +24,11 @@ jobs: uses: docker/setup-buildx-action@v3 with: platforms: linux/amd64,linux/arm64 + + - name: Create and use multi-platform builder + run: | + docker buildx create --name multiarch --use --bootstrap + docker buildx inspect multiarch - name: Install Rust uses: dtolnay/rust-toolchain@stable diff --git a/bin/build_docker.sh b/bin/build_docker.sh index 55459944e..9dbe94e06 100644 --- a/bin/build_docker.sh +++ b/bin/build_docker.sh @@ -127,10 +127,31 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" - # Build with version tag - docker build -f Dockerfile-manual \ + # Determine Docker platform from Rust target + case "$ARCHITECTURE" in + aarch64-*) + DOCKER_PLATFORM="linux/arm64" + ;; + x86_64-*) + DOCKER_PLATFORM="linux/amd64" + ;; + armv7-*) + DOCKER_PLATFORM="linux/arm/v7" + ;; + *) + DOCKER_PLATFORM="linux/amd64" # Default fallback + ;; + esac + + echo "๐Ÿ—๏ธ Building for platform: $DOCKER_PLATFORM" + + # Build with version tag using buildx for multi-platform support + docker buildx build -f Dockerfile-manual \ -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ - --target "$BUILD_TARGET" . + --target "$BUILD_TARGET" \ + --platform "$DOCKER_PLATFORM" \ + --load \ + . # Tag with branch-specific tag docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" From 1299d50d1b7a3983a460a102d7673fb46a7c072a Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Sun, 17 Aug 2025 17:17:49 +0200 Subject: [PATCH 14/14] Refactor Docker build process to support multi-platform images and streamline binary selection - Introduced a new stage in the Dockerfile to select binaries based on target architecture. - Updated the build script to copy architecture-specific binaries for multi-platform support. --- bin/build_docker.sh | 108 ++++++++++++++++----------------------- docker/Dockerfile-manual | 23 ++++++++- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/bin/build_docker.sh b/bin/build_docker.sh index 9dbe94e06..a9368caf5 100644 --- a/bin/build_docker.sh +++ b/bin/build_docker.sh @@ -60,9 +60,10 @@ declare -A ARCHITECTURES=( [AARCH64]=aarch64-unknown-linux-musl ) -declare -A BUILDS=( - [LINUX]="tuliprox:scratch-final tuliprox-alpine:alpine-final" - [AARCH64]="tuliprox-aarch64:scratch-final" +# Images to build with multi-platform support +declare -A MULTI_PLATFORM_IMAGES=( + [tuliprox]="scratch-final" + [tuliprox-alpine]="alpine-final" ) # Get version from Cargo.toml @@ -93,13 +94,12 @@ fi cd "$WORKING_DIR" -declare -a BUILT_IMAGES=() - -# Build for each platform +# Build binaries for all architectures first +echo "๐Ÿ—๏ธ Building binaries for all architectures..." for PLATFORM in "${!ARCHITECTURES[@]}"; do ARCHITECTURE=${ARCHITECTURES[$PLATFORM]} - echo "๐Ÿ—๏ธ Building binary for architecture: $ARCHITECTURE" + echo "๐Ÿ”จ Building binary for architecture: $ARCHITECTURE" cargo clean || true env RUSTFLAGS="--remap-path-prefix $HOME=~" cross build -p tuliprox --release --target "$ARCHITECTURE" @@ -110,71 +110,51 @@ for PLATFORM in "${!ARCHITECTURES[@]}"; do exit 1 fi - # Prepare Docker context - echo "๐Ÿ“‹ Preparing Docker context for $PLATFORM..." - cp "$BINARY_PATH" "${DOCKER_DIR}/" - rm -rf "${DOCKER_DIR}/web" - cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" - cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/resources" - - cd "${DOCKER_DIR}" - echo "๐Ÿณ Building Docker images for platform: $PLATFORM" - - # Build all configured images for this platform - for pair in ${BUILDS[$PLATFORM]}; do - IMAGE_NAME="${pair%%:*}" - BUILD_TARGET="${pair##*:}" - - echo "๐ŸŽฏ Building ${IMAGE_NAME} with target ${BUILD_TARGET}" - - # Determine Docker platform from Rust target - case "$ARCHITECTURE" in - aarch64-*) - DOCKER_PLATFORM="linux/arm64" - ;; - x86_64-*) - DOCKER_PLATFORM="linux/amd64" - ;; - armv7-*) - DOCKER_PLATFORM="linux/arm/v7" - ;; - *) - DOCKER_PLATFORM="linux/amd64" # Default fallback - ;; - esac - - echo "๐Ÿ—๏ธ Building for platform: $DOCKER_PLATFORM" - - # Build with version tag using buildx for multi-platform support - docker buildx build -f Dockerfile-manual \ - -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ - --target "$BUILD_TARGET" \ - --platform "$DOCKER_PLATFORM" \ - --load \ - . - - # Tag with branch-specific tag - docker tag "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" - - BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${VERSION}") - BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}") - done - - # Clean up Docker context for next iteration - rm -f "${DOCKER_DIR}/tuliprox" + # Copy binary with architecture suffix for multi-platform build + mkdir -p "${DOCKER_DIR}/binaries" + cp "$BINARY_PATH" "${DOCKER_DIR}/binaries/tuliprox-${ARCHITECTURE}" done -cd "$WORKING_DIR" +# Prepare common Docker context +echo "๐Ÿ“‹ Preparing Docker context..." +rm -rf "${DOCKER_DIR}/web" +rm -rf "${DOCKER_DIR}/resources" +cp -r "${FRONTEND_BUILD_DIR}" "${DOCKER_DIR}/web" +cp -r "${RESOURCES_DIR}" "${DOCKER_DIR}/resources" -# Login and push all images +cd "${DOCKER_DIR}" + +# Login to GitHub Container Registry (needed before buildx push) echo "๐Ÿ”‘ Logging into GitHub Container Registry..." docker login ghcr.io -u euzu -p "${GHCR_IO_TOKEN}" -for img in "${BUILT_IMAGES[@]}"; do - echo "๐Ÿ“ค Pushing $img" - docker push "$img" +declare -a BUILT_IMAGES=() + +# Build multi-platform images +for IMAGE_NAME in "${!MULTI_PLATFORM_IMAGES[@]}"; do + BUILD_TARGET="${MULTI_PLATFORM_IMAGES[$IMAGE_NAME]}" + + echo "๐ŸŽฏ Building multi-platform image: ${IMAGE_NAME} with target ${BUILD_TARGET}" + + # Build and push multi-platform image directly + docker buildx build -f Dockerfile-manual \ + -t "ghcr.io/euzu/${IMAGE_NAME}:${VERSION}" \ + -t "ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}" \ + --target "$BUILD_TARGET" \ + --platform "linux/amd64,linux/arm64" \ + --push \ + . + + BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${VERSION}") + BUILT_IMAGES+=("ghcr.io/euzu/${IMAGE_NAME}:${TAG_SUFFIX}") done +# Clean up Docker context +echo "๐Ÿ—‘๏ธ Cleaning up binaries..." +rm -rf "${DOCKER_DIR}/binaries" + +cd "$WORKING_DIR" + # Final cleanup echo "๐Ÿ—‘๏ธ Cleaning up Docker context..." rm -rf "${DOCKER_DIR}/web" diff --git a/docker/Dockerfile-manual b/docker/Dockerfile-manual index 9a9d9c530..ce65c1adf 100644 --- a/docker/Dockerfile-manual +++ b/docker/Dockerfile-manual @@ -11,6 +11,23 @@ RUN apk add --no-cache tzdata \ && mkdir -p /output/etc/ssl/certs \ && cp /etc/ssl/certs/ca-certificates.crt /output/etc/ssl/certs/ca-certificates.crt +# Binary selector stage +FROM alpine:latest AS binary-selector + +ARG TARGETARCH + +WORKDIR /app + +# Copy all binaries +COPY ./binaries ./binaries + +# Select the appropriate binary based on target architecture +RUN case "${TARGETARCH}" in \ + "amd64") cp ./binaries/tuliprox-x86_64-unknown-linux-musl ./tuliprox ;; \ + "arm64") cp ./binaries/tuliprox-aarch64-unknown-linux-musl ./tuliprox ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \ + esac + # Scratch Final container FROM scratch AS scratch-final @@ -26,7 +43,8 @@ COPY --from=tz-prep /output/etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca- WORKDIR /app -COPY ./tuliprox ./ +# Copy the selected binary from binary-selector stage +COPY --from=binary-selector /app/tuliprox ./tuliprox COPY ./web ./web COPY ./resources ./resources @@ -49,7 +67,8 @@ RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime WORKDIR /app -COPY ./tuliprox ./ +# Copy the selected binary from binary-selector stage +COPY --from=binary-selector /app/tuliprox ./tuliprox COPY ./web ./web COPY ./resources ./resources # config should be mounted as volume