diff --git a/app/test/backend-integration/setup-target.sh b/app/test/backend-integration/setup-target.sh index 4aec65c9..0253b77d 100755 --- a/app/test/backend-integration/setup-target.sh +++ b/app/test/backend-integration/setup-target.sh @@ -11,7 +11,6 @@ ARTIFACTS_DIR="$SCRIPT_DIR/artifacts/$TARGET_HOST" KNOWN_HOSTS_PATH="$ARTIFACTS_DIR/known_hosts" CONFIG_PATH="$ARTIFACTS_DIR/config.generated.json" -SMB_PASSWORD_FILE="$ARTIFACTS_DIR/smb-password.txt" SFTP_PASSWORD_FILE="$ARTIFACTS_DIR/sftp-password.txt" read_or_create_secret() { @@ -29,16 +28,14 @@ read_or_create_secret() { mkdir -p "$ARTIFACTS_DIR" chmod 700 "$ARTIFACTS_DIR" -SMB_PASSWORD="$(read_or_create_secret "$SMB_PASSWORD_FILE")" SFTP_PASSWORD="$(read_or_create_secret "$SFTP_PASSWORD_FILE")" -ssh "$TARGET" bash -s -- "$FIXTURE_UID" "$FIXTURE_GID" "$SMB_PASSWORD" "$SFTP_PASSWORD" <<'REMOTE' +ssh "$TARGET" bash -s -- "$FIXTURE_UID" "$FIXTURE_GID" "$SFTP_PASSWORD" <<'REMOTE' set -euo pipefail fixture_uid="$1" fixture_gid="$2" -smb_password="$3" -sftp_password="$4" +sftp_password="$3" legacy_sshd_dir="/etc/ssh/zerobyte-backend-integration-legacy" export DEBIAN_FRONTEND=noninteractive @@ -49,10 +46,9 @@ write_file() { } apt-get update -apt-get install -y nfs-kernel-server openssh-server rpcbind samba +apt-get install -y nfs-kernel-server openssh-server rpcbind id -u zerobyte-sftp >/dev/null 2>&1 || useradd --create-home --home-dir /home/zerobyte-sftp --shell /bin/bash zerobyte-sftp -id -u zerobyte-smb >/dev/null 2>&1 || useradd --create-home --home-dir /home/zerobyte-smb --shell /bin/bash zerobyte-smb install -d -m 0755 /srv/zerobyte-backend-integration/fixtures/case-a/docs printf 'hello from zerobyte integration\n' >/srv/zerobyte-backend-integration/fixtures/case-a/hello.txt @@ -61,8 +57,6 @@ chown -R "$fixture_uid:$fixture_gid" /srv/zerobyte-backend-integration/fixtures find /srv/zerobyte-backend-integration/fixtures -type d -exec chmod 0755 {} + find /srv/zerobyte-backend-integration/fixtures -type f -exec chmod 0644 {} + -printf '%s\n%s\n' "$smb_password" "$smb_password" | smbpasswd -a -s zerobyte-smb >/dev/null -smbpasswd -e zerobyte-smb >/dev/null printf 'zerobyte-sftp:%s\n' "$sftp_password" | chpasswd passwd -u zerobyte-sftp >/dev/null 2>&1 || true @@ -76,15 +70,6 @@ systemctl start rpcbind systemctl start proc-fs-nfsd.mount systemctl restart nfs-kernel-server -write_file /etc/samba/smb.conf <<'EOF' -[zerobyte-backend-integration] - path = /srv/zerobyte-backend-integration/fixtures - browseable = yes - read only = yes - guest ok = no - valid users = zerobyte-smb -EOF - install -d -m 0700 "$legacy_sshd_dir" if [[ ! -f "$legacy_sshd_dir/ssh_host_rsa_key" ]]; then ssh-keygen -q -t rsa -b 2048 -N "" -f "$legacy_sshd_dir/ssh_host_rsa_key" @@ -139,7 +124,6 @@ EOF systemctl daemon-reload systemctl enable --now zerobyte-backend-integration-legacy-sshd.service -systemctl restart smbd systemctl restart ssh systemctl restart zerobyte-backend-integration-legacy-sshd.service systemctl is-active --quiet zerobyte-backend-integration-legacy-sshd.service @@ -158,10 +142,9 @@ if ! ssh-keyscan -T 5 -p 2222 "$TARGET_HOST" >>"$KNOWN_HOSTS_PATH" 2>/dev/null; exit 1 fi -INTEGRATION_HOST="$TARGET_HOST" \ + INTEGRATION_HOST="$TARGET_HOST" \ FIXTURE_UID="$FIXTURE_UID" \ FIXTURE_GID="$FIXTURE_GID" \ - SMB_PASSWORD="$SMB_PASSWORD" \ SFTP_PASSWORD="$SFTP_PASSWORD" \ KNOWN_HOSTS_PATH="$KNOWN_HOSTS_PATH" \ CONFIG_PATH="$CONFIG_PATH" \ diff --git a/app/test/backend-integration/write-generated-config.ts b/app/test/backend-integration/write-generated-config.ts index 0ad35ed0..087a33c0 100644 --- a/app/test/backend-integration/write-generated-config.ts +++ b/app/test/backend-integration/write-generated-config.ts @@ -11,7 +11,6 @@ function getRequiredNumberEnv(name: string) { const host = getRequiredEnv("INTEGRATION_HOST"); const fixtureUid = getRequiredNumberEnv("FIXTURE_UID"); const fixtureGid = getRequiredNumberEnv("FIXTURE_GID"); -const smbPassword = getRequiredEnv("SMB_PASSWORD"); const sftpPassword = getRequiredEnv("SFTP_PASSWORD"); const knownHosts = fs.readFileSync(getRequiredEnv("KNOWN_HOSTS_PATH"), "utf8"); const configPath = getRequiredEnv("CONFIG_PATH"); @@ -25,12 +24,6 @@ const nfsEntries = [ { path: "docs/readme.md", type: "file", uid: fixtureUid, gid: fixtureGid, mode: "0644", text: readmeText }, ]; -const smbEntries = [ - { path: "hello.txt", type: "file", uid: fixtureUid, gid: fixtureGid, mode: "0644", text: fileText }, - { path: "docs", type: "directory", uid: fixtureUid, gid: fixtureGid, mode: "1755" }, - { path: "docs/readme.md", type: "file", uid: fixtureUid, gid: fixtureGid, mode: "0644", text: readmeText }, -]; - const contentOnlyEntries = [ { path: "hello.txt", type: "file", text: fileText }, { path: "docs", type: "directory" }, @@ -54,23 +47,6 @@ const config = { fixtureRoot: "case-a", expectedEntries: nfsEntries, }, - { - id: "smb-local-repo", - volume: { - backend: "smb", - server: host, - share: "zerobyte-backend-integration", - username: "zerobyte-smb", - password: smbPassword, - mapToContainerUidGid: false, - vers: "3.0", - port: 445, - readOnly: true, - }, - repository: { backend: "local", path: "repo-smb" }, - fixtureRoot: "case-a", - expectedEntries: smbEntries, - }, { id: "sftp-legacy-rsa-hostkey-local-repo", volume: { diff --git a/app/test/integration/infra/docker-compose.yml b/app/test/integration/infra/docker-compose.yml index 9d2014ba..0daee271 100644 --- a/app/test/integration/infra/docker-compose.yml +++ b/app/test/integration/infra/docker-compose.yml @@ -59,6 +59,28 @@ services: timeout: 5s retries: 30 + smb: + build: + context: ./smb + environment: + SMB_USER: "zerobyte-smb" + SMB_PASSWORD: "zerobyte-smb-password" + SMB_SHARE: "zerobyte-integration" + healthcheck: + test: + [ + "CMD", + "smbclient", + "//localhost/zerobyte-integration", + "-U", + "zerobyte-smb%zerobyte-smb-password", + "-c", + "get case-a/hello.txt /tmp/zerobyte-smb-healthcheck.txt", + ] + interval: 1s + timeout: 5s + retries: 30 + integration: build: context: ../../../.. @@ -72,6 +94,8 @@ services: condition: service_healthy webdav: condition: service_healthy + smb: + condition: service_healthy cap_add: - SYS_ADMIN devices: diff --git a/app/test/integration/infra/smb/Dockerfile b/app/test/integration/infra/smb/Dockerfile new file mode 100644 index 00000000..aced782b --- /dev/null +++ b/app/test/integration/infra/smb/Dockerfile @@ -0,0 +1,10 @@ +FROM alpine:3.21 + +RUN apk add --no-cache samba samba-common-tools samba-client + +COPY entrypoint.sh /usr/local/bin/zerobyte-smb-entrypoint +RUN chmod +x /usr/local/bin/zerobyte-smb-entrypoint + +EXPOSE 445 + +ENTRYPOINT ["/usr/local/bin/zerobyte-smb-entrypoint"] diff --git a/app/test/integration/infra/smb/entrypoint.sh b/app/test/integration/infra/smb/entrypoint.sh new file mode 100644 index 00000000..2e7e92eb --- /dev/null +++ b/app/test/integration/infra/smb/entrypoint.sh @@ -0,0 +1,44 @@ +#!/bin/sh +set -eu + +SMB_USER="${SMB_USER:-zerobyte-smb}" +SMB_PASSWORD="${SMB_PASSWORD:-zerobyte-smb-password}" +SMB_SHARE="${SMB_SHARE:-zerobyte-integration}" +FIXTURE_ROOT="/srv/zerobyte-integration/fixtures" + +adduser -D -H -s /sbin/nologin "$SMB_USER" 2>/dev/null || true + +install -d -m 0755 "$FIXTURE_ROOT/case-a/docs" +printf 'hello from zerobyte integration\n' >"$FIXTURE_ROOT/case-a/hello.txt" +printf 'fixture documentation\n' >"$FIXTURE_ROOT/case-a/docs/readme.md" +chown -R "$SMB_USER:$SMB_USER" "$FIXTURE_ROOT" +find "$FIXTURE_ROOT" -type d -exec chmod 0755 {} + +find "$FIXTURE_ROOT" -type f -exec chmod 0644 {} + + +printf '%s\n%s\n' "$SMB_PASSWORD" "$SMB_PASSWORD" | smbpasswd -a -s "$SMB_USER" >/dev/null +smbpasswd -e "$SMB_USER" >/dev/null + +cat >/etc/samba/smb.conf </dev/null +exec smbd --foreground --no-process-group --debug-stdout diff --git a/app/test/integration/run.sh b/app/test/integration/run.sh index 8dd4cbeb..40c213f7 100644 --- a/app/test/integration/run.sh +++ b/app/test/integration/run.sh @@ -29,18 +29,18 @@ chmod 644 "$sftp_artifacts_dir/id_ed25519.pub" docker build --progress quiet --target runtime-tools -t "$base_image" "$repo_root" >"$docker_output_log" 2>&1 exit_code=0 -"${compose[@]}" up --build --quiet-build --no-color --detach rustfs >>"$docker_output_log" 2>&1 || exit_code=$? +"${compose[@]}" up --build --no-color --detach rustfs >>"$docker_output_log" 2>&1 || exit_code=$? if [[ "$exit_code" -eq 0 ]]; then - "${compose[@]}" up --build --quiet-build --no-color --abort-on-container-exit --exit-code-from rustfs-setup rustfs-setup >>"$docker_output_log" 2>&1 || exit_code=$? + "${compose[@]}" up --build --no-color --abort-on-container-exit --exit-code-from rustfs-setup rustfs-setup >>"$docker_output_log" 2>&1 || exit_code=$? fi if [[ "$exit_code" -eq 0 ]]; then - "${compose[@]}" up --build --quiet-build --no-color --detach --wait sftp webdav >>"$docker_output_log" 2>&1 || exit_code=$? + "${compose[@]}" up --build --no-color --detach --wait sftp webdav smb >>"$docker_output_log" 2>&1 || exit_code=$? fi if [[ "$exit_code" -eq 0 ]]; then - "${compose[@]}" run --rm --no-deps --build --quiet-build integration 2>>"$docker_output_log" || exit_code=$? + "${compose[@]}" run --rm --no-deps --build integration 2>>"$docker_output_log" || exit_code=$? fi if [[ "$exit_code" -ne 0 ]]; then diff --git a/app/test/integration/src/helpers/smb.ts b/app/test/integration/src/helpers/smb.ts new file mode 100644 index 00000000..a1c4c5c7 --- /dev/null +++ b/app/test/integration/src/helpers/smb.ts @@ -0,0 +1,19 @@ +import type { BackendConfig } from "@zerobyte/contracts/volumes"; + +export const SMB_HOST = "smb"; +export const SMB_PORT = 445; +export const SMB_SHARE = "zerobyte-integration"; +export const SMB_USERNAME = "zerobyte-smb"; +export const SMB_PASSWORD = "zerobyte-smb-password"; + +export const buildSmbVolumeConfig = (): BackendConfig => ({ + backend: "smb", + server: SMB_HOST, + share: SMB_SHARE, + username: SMB_USERNAME, + password: SMB_PASSWORD, + mapToContainerUidGid: false, + vers: "3.0", + port: SMB_PORT, + readOnly: true, +}); diff --git a/app/test/integration/src/volume-backends.test.ts b/app/test/integration/src/volume-backends.test.ts index 68f096b7..050008bb 100644 --- a/app/test/integration/src/volume-backends.test.ts +++ b/app/test/integration/src/volume-backends.test.ts @@ -4,6 +4,7 @@ import path from "node:path"; import type { RepositoryConfig } from "@zerobyte/core/restic"; import { Effect } from "effect"; import { expect, test } from "vitest"; +import { makeSmbBackend } from "../../../../apps/agent/src/volume-host/backends/smb"; import { makeSftpBackend } from "../../../../apps/agent/src/volume-host/backends/sftp"; import { makeWebdavBackend } from "../../../../apps/agent/src/volume-host/backends/webdav"; import type { VolumeBackend } from "../../../../apps/agent/src/volume-host/types"; @@ -17,6 +18,7 @@ import { readSftpPrivateKey, scanSftpKnownHosts, } from "./helpers/sftp"; +import { buildSmbVolumeConfig } from "./helpers/smb"; import { buildWebdavVolumeConfig } from "./helpers/webdav"; type VolumeScenario = { @@ -50,6 +52,11 @@ const scenarios: VolumeScenario[] = [ name: "WebDAV volume with local repository", createBackend: async (mountPath) => makeWebdavBackend(buildWebdavVolumeConfig(), mountPath), }, + { + id: "smb-local-repo", + name: "SMB volume with local repository", + createBackend: async (mountPath) => makeSmbBackend(buildSmbVolumeConfig(), mountPath), + }, ]; const makeDirectoriesWritable = async (root: string): Promise => {