diff --git a/.github/workflows/ghcr.yml b/.github/workflows/ghcr.yml new file mode 100644 index 0000000..831a2e7 --- /dev/null +++ b/.github/workflows/ghcr.yml @@ -0,0 +1,72 @@ +name: GHCR Publish + +on: + workflow_call: + inputs: + version: + required: true + type: string + ref: + required: true + type: string + add_latest: + required: false + type: boolean + default: false + dockerfile: + required: false + type: string + default: "./docker/Dockerfile" + target: + required: false + type: string + default: "prod" + +permissions: + contents: read + packages: write + +jobs: + publish: + name: Build and push to GHCR + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/agent + tags: | + type=semver,pattern={{version}},value=${{ inputs.version }} + type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }} + type=semver,pattern={{major}},value=${{ inputs.version }} + type=raw,value=latest,enable=${{ inputs.add_latest }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: ${{ inputs.dockerfile }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + target: ${{ inputs.target }} + provenance: false + cache-from: type=gha,scope=ghcr-build + cache-to: type=gha,mode=max,scope=ghcr-build,ignore-error=true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84131e9..2b80707 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,6 +100,15 @@ jobs: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + publish-docker-ghcr: + needs: create-release + if: ${{ needs.create-release.result == 'success' }} + uses: ./.github/workflows/ghcr.yml + with: + version: ${{ needs.create-release.outputs.version }} + ref: ${{ needs.create-release.outputs.version }} + add_latest: true + publish-helm: needs: create-release if: ${{ needs.create-release.result == 'success' }} @@ -113,6 +122,7 @@ jobs: needs: - create-release - publish-docker + - publish-docker-ghcr - publish-helm runs-on: ubuntu-latest outputs: diff --git a/docker-compose.yml b/docker-compose.yml index ff72c6c..d4e1bae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,11 +15,13 @@ services: # - cargo-target:/app/target - databases_sqlite-data:/sqlite-data/workspace/data - ./scripts/sqlite/test-db:/sqlite-data-2/workspace/data +# - /bigdisk:/scratch environment: APP_ENV: development LOG: debug TZ: "Europe/Paris" - EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiZWZhYTM0YTQtZDY1NC00OGQ3LTgwNDYtNjRkMWExYTA1M2FlIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" +# TMPDIR: /scratch + EDGE_KEY: "eyJzZXJ2ZXJVcmwiOiJodHRwOi8vbG9jYWxob3N0Ojg4ODciLCJhZ2VudElkIjoiNTljYzRjYTUtOTAyNy00ZThiLTk1NDktMjAzOTI3ZDVjNmUyIiwibWFzdGVyS2V5QjY0IjoiMUh0djdtWCtYVkJxL0IzUEV2WDlZZjlQeUdVZW5oRHlXemo5THRqNW90WT0ifQ==" #CHUNK_SIZE_MB: "1" #POOLING: 1 #DATABASES_CONFIG_FILE: "config.toml" diff --git a/src/services/api/models/agent/status.rs b/src/services/api/models/agent/status.rs index 34557ac..d8c4ea3 100644 --- a/src/services/api/models/agent/status.rs +++ b/src/services/api/models/agent/status.rs @@ -24,6 +24,8 @@ pub struct DatabaseStorage { #[serde(deserialize_with = "deserialize_snake_case")] pub config: Value, pub provider: String, + #[serde(default, rename = "folderName")] + pub folder_name: Option, } #[derive(Debug, Deserialize)] diff --git a/src/services/storage/providers/azure_blob/mod.rs b/src/services/storage/providers/azure_blob/mod.rs index b0b5e6b..c9762f2 100644 --- a/src/services/storage/providers/azure_blob/mod.rs +++ b/src/services/storage/providers/azure_blob/mod.rs @@ -95,7 +95,7 @@ impl StorageProvider for AzureBlobProvider { }; let file_name = full_file_name(encrypt); - let remote_file_path = full_file_path(&file_name); + let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref()); info!( "Starting block upload to azure blob {}/{}", config.container_name, remote_file_path diff --git a/src/services/storage/providers/google_cloud_storage/mod.rs b/src/services/storage/providers/google_cloud_storage/mod.rs index 453a63e..0220a09 100644 --- a/src/services/storage/providers/google_cloud_storage/mod.rs +++ b/src/services/storage/providers/google_cloud_storage/mod.rs @@ -85,7 +85,7 @@ impl StorageProvider for GoogleCloudStorageProvider { let file_name = full_file_name(encrypt); info!("Uploading file {}", file_name); - let remote_file_path = full_file_path(&file_name); + let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref()); let client = match build_client(&config).await { Ok(c) => c, diff --git a/src/services/storage/providers/google_drive/mod.rs b/src/services/storage/providers/google_drive/mod.rs index 89b11b5..e127414 100644 --- a/src/services/storage/providers/google_drive/mod.rs +++ b/src/services/storage/providers/google_drive/mod.rs @@ -85,7 +85,7 @@ impl StorageProvider for GoogleDriveProvider { info!("Uploading file {}", file_name); - let remote_file_path = full_file_path(&file_name); + let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref()); match upload_stream_to_google_drive( &config, diff --git a/src/services/storage/providers/local.rs b/src/services/storage/providers/local.rs index a574faa..b93920a 100644 --- a/src/services/storage/providers/local.rs +++ b/src/services/storage/providers/local.rs @@ -38,7 +38,7 @@ impl StorageProvider for LocalProvider { let encrypt = encrypt.unwrap_or(false); let file_name = full_file_name(encrypt); - let remote_file_path = full_file_path(&file_name); + let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref()); let total_size = match fs::metadata(&file_path).await { Ok(meta) => meta.len(), diff --git a/src/services/storage/providers/s3/mod.rs b/src/services/storage/providers/s3/mod.rs index f01db64..8ef436e 100644 --- a/src/services/storage/providers/s3/mod.rs +++ b/src/services/storage/providers/s3/mod.rs @@ -148,7 +148,7 @@ impl StorageProvider for S3Provider { info!("Uploading file {}", file_name); let bucket = &config.bucket_name; - let remote_file_path = full_file_path(&file_name); + let remote_file_path = full_file_path(&file_name, storage.folder_name.as_deref()); info!("S3 key {:}", remote_file_path); info!( "Starting multipart upload to s3://{}/{}", diff --git a/src/tests/utils/file_tests.rs b/src/tests/utils/file_tests.rs index 697a5d5..b3b0ee0 100644 --- a/src/tests/utils/file_tests.rs +++ b/src/tests/utils/file_tests.rs @@ -30,12 +30,16 @@ fn full_file_name_matches_expected_suffix() { } #[test] -fn full_file_path_prefixes_backups_directory_and_date() { +fn full_file_path_uses_default_or_configured_folder_name() { let file_name = "backup.tar.gz".to_string(); - let full_path = full_file_path(&file_name); - assert!(full_path.starts_with("backups/")); - assert!(full_path.ends_with("/backup.tar.gz")); + let default_path = full_file_path(&file_name, None); + assert!(default_path.starts_with("backups/")); + assert!(default_path.ends_with("/backup.tar.gz")); + + let configured_path = full_file_path(&file_name, Some("/portabase/")); + assert!(configured_path.starts_with("portabase/")); + assert!(configured_path.ends_with("/backup.tar.gz")); } #[tokio::test] diff --git a/src/utils/file.rs b/src/utils/file.rs index 13f0ea2..b39dd92 100644 --- a/src/utils/file.rs +++ b/src/utils/file.rs @@ -47,8 +47,14 @@ pub fn full_file_name(encrypt: bool) -> String { } } -pub fn full_file_path(file_name: &String) -> String { - format!("backups/{}/{}", Utc::now().format("%Y-%m-%d"), file_name) +pub fn full_file_path(file_name: &String, folder_name: Option<&str>) -> String { + let folder_name = folder_name + .map(str::trim) + .map(|folder_name| folder_name.trim_matches(char::from(47))) + .filter(|folder_name| !folder_name.is_empty()) + .unwrap_or("backups"); + + format!("{}/{}/{}", folder_name, Utc::now().format("%Y-%m-%d"), file_name) } const CHUNK_SIZE: usize = 16 * 1024 * 1024;