diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a74bc260de3..1a8d7b516d5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -35,3 +35,5 @@ jobs: go-version-file: 'go.mod' - name: Lint run: make lint + - name: Check Locks + run: make check-locks diff --git a/.github/workflows/quay_binaries_push.yml b/.github/workflows/quay_binaries_push.yml new file mode 100644 index 00000000000..4bcd2a5fe0d --- /dev/null +++ b/.github/workflows/quay_binaries_push.yml @@ -0,0 +1,94 @@ +# Push binaries to Quay.io on pushes to oadp-* branches +name: Multi-Arch Binary Push to Quay.io + +on: + push: + branches: + - 'oadp-*' + pull_request: + branches: + - 'oadp-*' + +env: + IMAGE_REPO: quay.io/konveyor/oadp-vmdp-binaries + +jobs: + + multi-arch-build: + name: Build Multi-Arch Images + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, arm64] + steps: + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Build Image for ${{ matrix.arch }} + id: build_image + uses: redhat-actions/buildah-build@v2 + with: + image: oadp-vmdp-binaries-local + tags: ${{ matrix.arch }} + archs: ${{ matrix.arch }} + build-args: | + TARGETOS=linux + TARGETARCH=${{ matrix.arch }} + containerfiles: | + ./Containerfile.download + + - name: Save image as tar + run: | + buildah push ${{ steps.build_image.outputs.image-with-tag }} oci-archive:oadp-vmdp-${{ matrix.arch }}.tar + + - name: Upload image artifact + uses: actions/upload-artifact@v4 + with: + name: oadp-vmdp-image-${{ matrix.arch }} + path: oadp-vmdp-${{ matrix.arch }}.tar + retention-days: 1 + + push-manifest: + name: Create and Push Multi-Arch Manifest + runs-on: ubuntu-latest + needs: multi-arch-build + if: github.event_name == 'push' + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: oadp-vmdp-image-* + + - name: Buildah login + run: buildah login -u ${{ secrets.QUAY_USER }} -p ${{ secrets.QUAY_TOKEN }} quay.io + + - name: Load images and tag archs + run: | + AMD64_ID=$(buildah pull oci-archive:oadp-vmdp-image-amd64/oadp-vmdp-amd64.tar) + ARM64_ID=$(buildah pull oci-archive:oadp-vmdp-image-arm64/oadp-vmdp-arm64.tar) + + buildah tag $AMD64_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64 + buildah tag $ARM64_ID ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64 + + - name: Create and push multi-arch manifest (version tag) + if: github.ref_name != 'oadp-dev' + run: | + buildah manifest create ${{ env.IMAGE_REPO }}:${{ github.ref_name }} + buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64 + buildah manifest add ${{ env.IMAGE_REPO }}:${{ github.ref_name }} ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64 + buildah manifest push --all ${{ env.IMAGE_REPO }}:${{ github.ref_name }} + + - name: Create and push multi-arch manifest (latest tag) + if: github.ref_name == 'oadp-dev' + run: | + buildah manifest create ${{ env.IMAGE_REPO }}:latest + buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-amd64 + buildah manifest add ${{ env.IMAGE_REPO }}:latest ${{ env.IMAGE_REPO }}:${{ github.ref_name }}-arm64 + buildah manifest push --all ${{ env.IMAGE_REPO }}:latest diff --git a/.golangci.yml b/.golangci.yml index 0f45788b484..377f280bfd2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -120,6 +120,79 @@ linters: exclusions: generated: lax rules: + # OADP: Exclude unused code from commands not included in oadp-vmdp CLI + # These commands are intentionally not wired up in the CLI but kept for easier rebasing + # Prefer broad regex patterns over per-file lists to reduce rebase churn. + - path: cli/command_(acl|benchmark|maintenance|mount|notification|policy|server|user).*\.go + linters: + - unused + - path: cli/command_(diff|ls)\.go + linters: + - unused + - path: cli/command_repository_(connect_from_config|repair|set_client|set_parameters|sync|throttle|upgrade|validate_provider)\.go + linters: + - unused + - path: cli/command_repository_throttle.*\.go + linters: + - unused + - path: cli/command_snapshot_(copy_move_history|estimate|expire|fix|migrate|pin|verify).*\.go + linters: + - unused + - path: cli/storage_(azure|b2|gcs|gdrive|rclone|sftp|webdav)\.go + linters: + - unused + - path: cli/throttle_(get|set)\.go + linters: + - unused + - path: cli/sighup_unix\.go + linters: + - unused + - path: cli/show_utils\.go + text: "(indentMultilineString|maybeHumanReadableCount|formatTimestampPrecise) is unused" + linters: + - unused + - path: cli/config\.go + text: "isWindows is unused" + linters: + - unused + # OADP: Exclude unused helper functions and command trees that are intentionally not wired up + # (kept in-tree to simplify rebases). + - path: cli/app\.go + text: "func safetyFlagVar is unused" + linters: + - unused + - path: cli/command_blob.*\.go + linters: + - unused + - path: cli/command_content.*\.go + linters: + - unused + - path: cli/command_index.*\.go + linters: + - unused + - path: cli/command_manifest.*\.go + linters: + - unused + # OADP: Exclude unused fields in structs for commands not wired up + - path: cli/app\.go + text: "field (benchmark|diff|list|server|policy|mount|maintenance|notification|blob|content|index|manifest) is unused" + linters: + - unused + - path: cli/command_repository\.go + text: "field (repair|setClient|setParameters|syncTo|throttle|validateProvider|upgrade) is unused" + linters: + - unused + - path: cli/command_snapshot\.go + text: "field (copyHistory|moveHistory|estimate|expire|fix|migrate|pin|verify) is unused" + linters: + - unused + # OADP: Exclude revive unused-parameter warning for azure/patch.go + - path: repo/blob/azure/patch\.go + linters: + - revive + - path: reporter.go + linters: + - musttag - path: _test\.go|testing|tests|test_env|fshasher|fault linters: - contextcheck diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000000..ccd030b83d3 --- /dev/null +++ b/Containerfile @@ -0,0 +1,123 @@ +# Copyright 2025 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================== +# OADP VM Data Protection - Multi-Architecture Container Build +# ============================================================================== +# +# This Containerfile builds a statically-linked oadp-vmdp CLI binary for Linux +# and packages it in a minimal UBI container image. +# +# Supported architectures: +# - linux/amd64 (x86_64) +# - linux/arm64 (aarch64) +# +# Usage with Docker: +# docker buildx build --platform linux/amd64,linux/arm64 -t oadp-vmdp . +# +# Usage with Podman: +# podman build --arch amd64 -t oadp-vmdp:amd64 . +# podman build --arch arm64 -t oadp-vmdp:arm64 . +# podman build --arch amd64 \ +# --build-arg VERSION=1.0.0 \ +# --build-arg GIT_COMMIT=5eaa13d1 \ +# --build-arg BUILD_DATE=2025-12-15T00:00:00Z \ +# --build-arg BUILDTAGS=oadp \ +# -t oadp-vmdp:amd64 . +# + +# ============================================================================== +# Build Stage - Compile the Go binary +# ============================================================================== + +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder + +# Build arguments for cross-compilation +ARG BUILDPLATFORM +ARG TARGETPLATFORM +ARG TARGETOS=linux +ARG TARGETARCH + +# Version information (passed from Makefile.ubi) +ARG VERSION=dev +ARG GIT_COMMIT=unknown +ARG BUILD_DATE=unknown +ARG BUILDTAGS= + +# Install git for version detection (if not passed via args) +RUN apk add --no-cache git + +WORKDIR /build + +# Copy Go module files first for better layer caching +COPY go.mod go.sum ./ +RUN go mod download + +# Copy source code +COPY . . + +# Build the binary with static linking +# CGO_ENABLED=0 ensures a fully static binary that works on any Linux distro +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ + go build \ + -trimpath \ + -mod=mod \ + -tags="${BUILDTAGS}" \ + -ldflags="-s -w \ + -X github.com/kopia/kopia/repo.BuildVersion=${VERSION} \ + -X github.com/kopia/kopia/repo.BuildInfo=${BUILD_DATE}-${GIT_COMMIT} \ + -X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp" \ + -o /build/oadp-vmdp \ + . + +# ============================================================================== +# Runtime Stage - Minimal container with just the binary +# ============================================================================== + +FROM registry.access.redhat.com/ubi9-minimal:latest + +# Version information (re-declared for this stage so LABEL can use them) +# NOTE: ARG scope does not automatically carry across FROM boundaries. +ARG VERSION=dev +ARG GIT_COMMIT=unknown +ARG BUILD_DATE=unknown +ARG BUILDTAGS= + +# Labels for container metadata +LABEL name="oadp-vmdp" \ + vendor="Red Hat, Inc." \ + version="${VERSION}" \ + summary="OADP VM Data Protection CLI" \ + description="Virtual Machine Data Protection tool for OpenShift Virtualization backup and restore operations" \ + io.k8s.display-name="OADP VM Data Protection" \ + io.k8s.description="CLI tool for VM data protection in OpenShift Virtualization" \ + io.openshift.tags="oadp,backup,restore,virtualization" + +# Create cache directory for the application +# This is needed by kopia/oadp-vmdp for caching operations +WORKDIR / + +RUN mkdir -p /.cache && \ + chown 65532:65532 /.cache + +# Copy the binary from builder stage +COPY --from=builder /build/oadp-vmdp /oadp-vmdp + +# Run as non-root user for security +USER 65532:65532 + +# Set the entrypoint +ENTRYPOINT ["/oadp-vmdp"] diff --git a/Containerfile.download b/Containerfile.download new file mode 100644 index 00000000000..648fc6a931d --- /dev/null +++ b/Containerfile.download @@ -0,0 +1,108 @@ +# Copyright 2025 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================== +# OADP VM Data Protection - Download Server Container +# ============================================================================== +# +# This Containerfile cross-builds oadp-vmdp binaries for all supported platforms +# (Linux and Windows, x86_64 and arm64) and packages them with a Go download +# server that serves the binaries for KubeVirt guest OS consumption. +# +# The resulting container is intended to run inside an OpenShift cluster, +# exposing the binaries via a ConsoleCLIDownload resource so users can +# download the correct binary for their KubeVirt VM's guest OS. +# +# Supported binary platforms: +# - linux/amd64 +# - linux/arm64 +# - windows/amd64 +# - windows/arm64 +# + +FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder + +ARG TARGETOS +ARG TARGETARCH + +# Version information +ARG VERSION=dev +ARG GIT_COMMIT=unknown +ARG BUILD_DATE=unknown +ARG BUILDTAGS= + +RUN apk add --no-cache git + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + +# Build oadp-vmdp binaries for all target platforms as direct executables +# with clean names (no version/commit hash) for direct curl/wget download. +# Per-file SHA256 checksums are generated for integrity verification. +RUN mkdir -p /archives && \ + for platform in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do \ + os=$(echo $platform | cut -d'/' -f1); \ + arch=$(echo $platform | cut -d'/' -f2); \ + if [ "$os" = "windows" ]; then \ + out_name="oadp-vmdp_${os}_${arch}.exe"; \ + else \ + out_name="oadp-vmdp_${os}_${arch}"; \ + fi; \ + echo "Building oadp-vmdp for ${os}/${arch}..."; \ + CGO_ENABLED=0 GOOS=$os GOARCH=$arch \ + go build -trimpath -mod=mod \ + -tags="${BUILDTAGS}" \ + -ldflags="-s -w \ + -X github.com/kopia/kopia/repo.BuildVersion=${VERSION} \ + -X github.com/kopia/kopia/repo.BuildInfo=${BUILD_DATE}-${GIT_COMMIT} \ + -X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp" \ + -o /archives/$out_name \ + . ; \ + sha256sum /archives/$out_name > /archives/$out_name.sha256; \ + done && \ + cp LICENSE /archives/LICENSE && \ + rm -rf /root/.cache/go-build /tmp/* + +# Build the download server for the TARGET platform (the arch this container will run on) +RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o download-server ./cmd/downloads/ && \ + go clean -cache -modcache -testcache && \ + rm -rf /root/.cache/go-build /go/pkg + +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest + +# Version information +ARG VERSION=dev + +LABEL name="oadp-vmdp-binaries" \ + vendor="Red Hat, Inc." \ + version="${VERSION}" \ + summary="OADP VM Data Protection CLI Download Server" \ + description="Serves pre-built oadp-vmdp binaries for Red Hat Enterprise Linux and Microsoft Windows (x86_64/aarch64) for KubeVirt guest OS consumption" \ + io.k8s.display-name="OADP VM Data Protection Downloads" \ + io.k8s.description="Download server for oadp-vmdp CLI binaries" \ + io.openshift.tags="oadp,backup,restore,virtualization,vmdp" + +# Copy the pre-built binaries +COPY --from=builder /archives /archives + +# Copy the download server +COPY --from=builder /app/download-server /usr/local/bin/download-server + +EXPOSE 8080 + +CMD ["/usr/local/bin/download-server"] diff --git a/Makefile b/Makefile index 54b1a0fbb03..2586f70b4df 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ COVERAGE_PACKAGES=./repo/...,./fs/...,./snapshot/...,./cli/...,./internal/...,./notification/... TEST_FLAGS?= -KOPIA_INTEGRATION_EXE=$(CURDIR)/dist/testing_$(GOOS)_$(GOARCH)/kopia.exe +# OADP: Renamed from kopia to oadp-vmdp +KOPIA_INTEGRATION_EXE=$(CURDIR)/dist/testing_$(GOOS)_$(GOARCH)/oadp-vmdp.exe TESTING_ACTION_EXE=$(CURDIR)/dist/testing_$(GOOS)_$(GOARCH)/testingaction.exe FIO_DOCKER_TAG=ljishen/fio REPEAT_TEST=1 @@ -16,24 +17,22 @@ all: include tools/tools.mk -KOPIA_BUILD_TAGS= -KOPIA_BUILD_FLAGS=-trimpath -ldflags "-s -w -X github.com/kopia/kopia/repo.BuildVersion=$(KOPIA_VERSION_NO_PREFIX) -X github.com/kopia/kopia/repo.BuildInfo=$(shell git rev-parse HEAD) -X github.com/kopia/kopia/repo.BuildGitHubRepo=$(GITHUB_REPOSITORY)" - -kopia_ui_embedded_exe=dist/kopia_$(GOOS)_$(GOARCH)/kopia$(exe_suffix) +# OADP: Renamed from kopia to oadp-vmdp +kopia_ui_embedded_exe=dist/oadp-vmdp_$(GOOS)_$(GOARCH)/oadp-vmdp$(exe_suffix) ifeq ($(GOOS),darwin) - # on macOS, Kopia uses universal binary that works for AMD64 and ARM64 - kopia_ui_embedded_exe=dist/kopia_darwin_universal/kopia + # on macOS, uses universal binary that works for AMD64 and ARM64 + kopia_ui_embedded_exe=dist/oadp-vmdp_darwin_universal/oadp-vmdp endif ifeq ($(GOOS),linux) ifeq ($(GOARCH),arm) - kopia_ui_embedded_exe=dist/kopia_linux_armv7l/kopia + kopia_ui_embedded_exe=dist/oadp-vmdp_linux_armv7l/oadp-vmdp endif ifeq ($(GOARCH),amd64) - kopia_ui_embedded_exe=dist/kopia_linux_x64/kopia + kopia_ui_embedded_exe=dist/oadp-vmdp_linux_x64/oadp-vmdp endif endif @@ -277,17 +276,17 @@ dev-deps: test-with-coverage: export KOPIA_COVERAGE_TEST=1 test-with-coverage: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE) test-with-coverage: $(gotestsum) $(TESTING_ACTION_EXE) - $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -short -covermode=atomic -coverprofile=coverage.txt --coverpkg $(COVERAGE_PACKAGES) -timeout $(UNIT_TESTS_TIMEOUT) ./... + $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing,oadp -count=$(REPEAT_TEST) -short -covermode=atomic -coverprofile=coverage.txt --coverpkg $(COVERAGE_PACKAGES) -timeout $(UNIT_TESTS_TIMEOUT) ./... test: GOTESTSUM_FLAGS=--format=$(GOTESTSUM_FORMAT) --no-summary=skipped --jsonfile=.tmp.unit-tests.json test: export TESTING_ACTION_EXE ?= $(TESTING_ACTION_EXE) test: $(gotestsum) $(TESTING_ACTION_EXE) - $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) -skip '^TestIndexBlobManagerStress$$' ./... + $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing,oadp -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) -skip '^TestIndexBlobManagerStress$$' ./... -$(gotestsum) tool slowest --jsonfile .tmp.unit-tests.json --threshold 1000ms test-index-blob-v0: GOTESTSUM_FLAGS=--format=pkgname --no-summary=output,skipped test-index-blob-v0: $(gotestsum) $(TESTING_ACTION_EXE) - $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) -run '^TestIndexBlobManagerStress$$' ./repo/content/indexblob/... + $(GO_TEST) $(UNIT_TEST_RACE_FLAGS) -tags testing,oadp -count=$(REPEAT_TEST) -timeout $(UNIT_TESTS_TIMEOUT) -run '^TestIndexBlobManagerStress$$' ./repo/content/indexblob/... provider-tests-deps: $(gotestsum) $(rclone) $(MINIO_MC_PATH) @@ -310,10 +309,10 @@ vtest: $(gotestsum) $(GO_TEST) -count=$(REPEAT_TEST) -short -v -timeout $(UNIT_TESTS_TIMEOUT) ./... build-integration-test-binary: - go build $(KOPIA_BUILD_FLAGS) $(INTEGRATION_TEST_RACE_FLAGS) -o $(KOPIA_INTEGRATION_EXE) -tags testing github.com/kopia/kopia + go build $(KOPIA_BUILD_FLAGS) $(INTEGRATION_TEST_RACE_FLAGS) -o $(KOPIA_INTEGRATION_EXE) -tags testing,oadp github.com/kopia/kopia $(TESTING_ACTION_EXE): tests/testingaction/main.go - go build -o $(TESTING_ACTION_EXE) -tags testing github.com/kopia/kopia/tests/testingaction + go build -o $(TESTING_ACTION_EXE) -tags testing,oadp github.com/kopia/kopia/tests/testingaction compat-tests: export KOPIA_CURRENT_EXE=$(CURDIR)/$(kopia_ui_embedded_exe) compat-tests: export KOPIA_08_EXE=$(kopia08) diff --git a/Makefile.ubi b/Makefile.ubi new file mode 100644 index 00000000000..ee21012dcb4 --- /dev/null +++ b/Makefile.ubi @@ -0,0 +1,525 @@ +# Copyright 2025 Red Hat Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ============================================================================== +# OADP VM Data Protection - Multi-Platform Build System +# ============================================================================== +# +# This Makefile builds statically-linked oadp-vmdp CLI binaries for various +# platforms and architectures to support OpenShift Virtualization guest OSes. +# +# Certified/Supported Guest Operating Systems (from Red Hat KB 4234591): +# - Linux x86_64 (amd64): RHEL, Ubuntu, CentOS, Debian, Fedora, Oracle Linux, SUSE +# - Linux arm64: RHEL, Ubuntu, CentOS, Debian, Fedora, Oracle Linux +# - Windows x86_64: Windows 10/11, Windows Server 2016-2025 +# +# Usage: +# make -f Makefile.ubi build-all # Build all platform binaries +# make -f Makefile.ubi build-linux # Build Linux binaries only +# make -f Makefile.ubi build-windows # Build Windows binaries only +# make -f Makefile.ubi container-build # Build multi-arch container image +# + +# ============================================================================== +# Configuration Variables +# ============================================================================== + +# Binary name +BIN ?= oadp-vmdp + +# Version information (can be overridden) +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev") +GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") +GIT_TREE_STATE ?= $(shell test -n "$$(git status --porcelain 2>/dev/null)" && echo "dirty" || echo "clean") +BUILD_DATE ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ') + +# Build tags (e.g., for conditional compilation) +BUILDTAGS ?= + +# Output directory for built binaries +DIST_DIR ?= dist + +# Container image settings +TAG ?= latest +IMAGE ?= quay.io/migtools/oadp-vmdp +IMAGE_REF := $(IMAGE):$(TAG) + +# Multi-arch container platforms (Linux only for containers) +CONTAINER_PLATFORMS ?= linux/amd64,linux/arm64 + +# ============================================================================== +# Build Flags +# ============================================================================== + +# Linker flags for version injection +LDFLAGS := -s -w \ + -X github.com/kopia/kopia/repo.BuildVersion=$(VERSION) \ + -X github.com/kopia/kopia/repo.BuildInfo=$(BUILD_DATE)-$(GIT_COMMIT) \ + -X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp + +# Go build flags for static compilation +GO_BUILD_FLAGS := -trimpath -mod=mod + +# Build tags if specified +ifneq ($(BUILDTAGS),) + GO_BUILD_FLAGS += -tags="$(BUILDTAGS)" +endif + +# ============================================================================== +# Platform Definitions +# ============================================================================== + +# Default platforms to build +# Linux: x86_64 (amd64), arm64 - covers all certified Linux guests +# Windows: x86_64 (amd64), arm64 - covers certified Windows guests + future arm64 +DEFAULT_PLATFORMS := linux-amd64 linux-arm64 windows-amd64 windows-arm64 + +# Platform-specific output names +linux-amd64_OUTPUT := $(DIST_DIR)/$(BIN)_linux_amd64/$(BIN) +linux-arm64_OUTPUT := $(DIST_DIR)/$(BIN)_linux_arm64/$(BIN) +windows-amd64_OUTPUT := $(DIST_DIR)/$(BIN)_windows_amd64/$(BIN).exe +windows-arm64_OUTPUT := $(DIST_DIR)/$(BIN)_windows_arm64/$(BIN).exe + +# ============================================================================== +# Container Tool Detection +# ============================================================================== + +CONTAINER_TOOL ?= $(shell \ + if command -v docker >/dev/null 2>&1; then echo docker; \ + elif command -v podman >/dev/null 2>&1; then echo podman; \ + else echo ""; \ + fi \ +) + +ifeq ($(CONTAINER_TOOL),) + $(warning Container tool (docker/podman) not found - container targets will fail) +endif + +# Host platform detection +HOST_OS := $(shell uname -s | tr '[:upper:]' '[:lower:]') +HOST_ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + +$(info Build Configuration:) +$(info Version: $(VERSION)) +$(info Git Commit: $(GIT_COMMIT)) +$(info Host: $(HOST_OS)/$(HOST_ARCH)) +$(info Container Tool: $(CONTAINER_TOOL)) +$(info ) + +# ============================================================================== +# Phony Targets +# ============================================================================== + +.PHONY: all help clean build-all build-linux build-windows \ + build-linux-amd64 build-linux-arm64 \ + build-windows-amd64 build-windows-arm64 \ + container-build container-build-push container-push \ + container-download-build container-download-build-push \ + verify checksums release-binaries + +# ============================================================================== +# Default Target +# ============================================================================== + +all: build-all + +# ============================================================================== +# Help Target +# ============================================================================== + +##@ General + +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make -f Makefile.ubi \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build Targets + +# ============================================================================== +# Build All Platforms +# ============================================================================== + +build-all: build-linux build-windows checksums ## Build binaries for all platforms + @echo "" + @echo "============================================================" + @echo "Build complete! Binaries available in $(DIST_DIR)/" + @echo "============================================================" + @ls -la $(DIST_DIR)/*/ + +# ============================================================================== +# Linux Builds +# ============================================================================== + +build-linux: build-linux-amd64 build-linux-arm64 ## Build all Linux binaries + +build-linux-amd64: $(linux-amd64_OUTPUT) ## Build Linux amd64 binary +$(linux-amd64_OUTPUT): + @echo "Building $(BIN) for linux/amd64..." + @mkdir -p $(dir $@) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build $(GO_BUILD_FLAGS) -ldflags="$(LDFLAGS)" \ + -o $@ . + @echo " -> $@" + +build-linux-arm64: $(linux-arm64_OUTPUT) ## Build Linux arm64 binary +$(linux-arm64_OUTPUT): + @echo "Building $(BIN) for linux/arm64..." + @mkdir -p $(dir $@) + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ + go build $(GO_BUILD_FLAGS) -ldflags="$(LDFLAGS)" \ + -o $@ . + @echo " -> $@" + +# ============================================================================== +# Windows Builds +# ============================================================================== + +build-windows: build-windows-amd64 build-windows-arm64 ## Build all Windows binaries + +build-windows-amd64: $(windows-amd64_OUTPUT) ## Build Windows amd64 binary +$(windows-amd64_OUTPUT): + @echo "Building $(BIN) for windows/amd64..." + @mkdir -p $(dir $@) + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 \ + go build $(GO_BUILD_FLAGS) -ldflags="$(LDFLAGS)" \ + -o $@ . + @echo " -> $@" + +build-windows-arm64: $(windows-arm64_OUTPUT) ## Build Windows arm64 binary (future support) +$(windows-arm64_OUTPUT): + @echo "Building $(BIN) for windows/arm64..." + @mkdir -p $(dir $@) + CGO_ENABLED=0 GOOS=windows GOARCH=arm64 \ + go build $(GO_BUILD_FLAGS) -ldflags="$(LDFLAGS)" \ + -o $@ . + @echo " -> $@" + +# ============================================================================== +# Checksums +# ============================================================================== + +checksums: ## Generate SHA256 checksums for all binaries + @echo "Generating checksums..." + @find $(DIST_DIR) -type f \( -name "$(BIN)" -o -name "$(BIN).exe" \) -exec sha256sum {} \; > $(DIST_DIR)/checksums.txt + @cat $(DIST_DIR)/checksums.txt + +# ============================================================================== +# Verification +# ============================================================================== + +verify: ## Verify built binaries (show version info) + @echo "Verifying built binaries..." + @for binary in $(linux-amd64_OUTPUT) $(linux-arm64_OUTPUT); do \ + if [ -f "$$binary" ]; then \ + echo ""; \ + echo "$$binary:"; \ + file "$$binary"; \ + if [ "$(HOST_OS)" = "linux" ] && [ "$$(echo $$binary | grep -c $(HOST_ARCH))" -gt 0 ]; then \ + "$$binary" --version 2>/dev/null || true; \ + fi; \ + fi; \ + done + @for binary in $(windows-amd64_OUTPUT) $(windows-arm64_OUTPUT); do \ + if [ -f "$$binary" ]; then \ + echo ""; \ + echo "$$binary:"; \ + file "$$binary"; \ + fi; \ + done + +# ============================================================================== +# Clean +# ============================================================================== + +clean: ## Remove built binaries and container images + @echo "Cleaning build artifacts..." + rm -rf $(DIST_DIR)/$(BIN)_* + rm -f $(DIST_DIR)/checksums.txt + -$(CONTAINER_TOOL) image rm -f $(IMAGE_REF) 2>/dev/null || true + -$(CONTAINER_TOOL) image rm -f $(IMAGE):amd64-$(TAG) 2>/dev/null || true + -$(CONTAINER_TOOL) image rm -f $(IMAGE):arm64-$(TAG) 2>/dev/null || true + +##@ Container Targets + +# ============================================================================== +# Container Build (Multi-arch) +# ============================================================================== + +container-build: ## Build multi-arch container image locally +ifeq ($(CONTAINER_TOOL),docker) + @echo "Building multi-arch container with Docker buildx..." + $(CONTAINER_TOOL) buildx build \ + --platform $(CONTAINER_PLATFORMS) \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + -t $(IMAGE_REF) \ + --load \ + -f Containerfile \ + . +else ifeq ($(CONTAINER_TOOL),podman) + @echo "Building for amd64 with Podman..." + $(CONTAINER_TOOL) build --arch amd64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETARCH=amd64 \ + -t $(IMAGE):amd64-$(TAG) \ + -f Containerfile \ + . + @echo "" + @echo "Building for arm64 with Podman..." + $(CONTAINER_TOOL) build --arch arm64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETARCH=arm64 \ + -t $(IMAGE):arm64-$(TAG) \ + -f Containerfile \ + . +else + $(error No container tool found. Please install docker or podman.) +endif + +# ============================================================================== +# Container Build and Push +# ============================================================================== + +container-build-push: ## Build and push multi-arch container image +ifeq ($(CONTAINER_TOOL),docker) + @echo "Building and pushing multi-arch container with Docker buildx..." + $(CONTAINER_TOOL) buildx build \ + --platform $(CONTAINER_PLATFORMS) \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + -t $(IMAGE_REF) \ + --push \ + -f Containerfile \ + . +else ifeq ($(CONTAINER_TOOL),podman) + @echo "Building and pushing for amd64 with Podman..." + $(CONTAINER_TOOL) build --arch amd64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETARCH=amd64 \ + -t $(IMAGE):amd64-$(TAG) \ + -f Containerfile \ + . + $(CONTAINER_TOOL) push $(IMAGE):amd64-$(TAG) + @echo "" + @echo "Building and pushing for arm64 with Podman..." + $(CONTAINER_TOOL) build --arch arm64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETARCH=arm64 \ + -t $(IMAGE):arm64-$(TAG) \ + -f Containerfile \ + . + $(CONTAINER_TOOL) push $(IMAGE):arm64-$(TAG) + @echo "" + @echo "Creating and pushing manifest..." + -$(CONTAINER_TOOL) rmi -f $(IMAGE_REF) 2>/dev/null || true + -$(CONTAINER_TOOL) manifest rm $(IMAGE_REF) 2>/dev/null || true + $(CONTAINER_TOOL) manifest create $(IMAGE_REF) \ + $(IMAGE):amd64-$(TAG) \ + $(IMAGE):arm64-$(TAG) + $(CONTAINER_TOOL) manifest push --all $(IMAGE_REF) +else + $(error No container tool found. Please install docker or podman.) +endif + +# ============================================================================== +# Container Push (existing images) +# ============================================================================== + +container-push: ## Push existing container images +ifeq ($(CONTAINER_TOOL),podman) + $(CONTAINER_TOOL) push $(IMAGE):amd64-$(TAG) + $(CONTAINER_TOOL) push $(IMAGE):arm64-$(TAG) + -$(CONTAINER_TOOL) rmi -f $(IMAGE_REF) 2>/dev/null || true + -$(CONTAINER_TOOL) manifest rm $(IMAGE_REF) 2>/dev/null || true + $(CONTAINER_TOOL) manifest create $(IMAGE_REF) \ + $(IMAGE):amd64-$(TAG) \ + $(IMAGE):arm64-$(TAG) + $(CONTAINER_TOOL) manifest push --all $(IMAGE_REF) +else + $(CONTAINER_TOOL) push $(IMAGE_REF) +endif + +##@ Archive Targets + +# ============================================================================== +# Create Distribution Archives +# ============================================================================== + +archives: build-all ## Create tar.gz/zip archives for distribution + @echo "Creating distribution archives..." + @# Linux archives (tar.gz) + @for arch in amd64 arm64; do \ + if [ -f "$(DIST_DIR)/$(BIN)_linux_$${arch}/$(BIN)" ]; then \ + echo "Creating $(BIN)-$(VERSION)-linux-$${arch}.tar.gz..."; \ + tar -czf $(DIST_DIR)/$(BIN)-$(VERSION)-linux-$${arch}.tar.gz \ + -C $(DIST_DIR)/$(BIN)_linux_$${arch} $(BIN); \ + fi; \ + done + @# Windows archives (zip) + @for arch in amd64 arm64; do \ + if [ -f "$(DIST_DIR)/$(BIN)_windows_$${arch}/$(BIN).exe" ]; then \ + echo "Creating $(BIN)-$(VERSION)-windows-$${arch}.zip..."; \ + (cd $(DIST_DIR)/$(BIN)_windows_$${arch} && zip -q ../$(BIN)-$(VERSION)-windows-$${arch}.zip $(BIN).exe); \ + fi; \ + done + @echo "Archives created:" + @ls -la $(DIST_DIR)/*.tar.gz $(DIST_DIR)/*.zip 2>/dev/null || true + +# ============================================================================== +# Release Binaries (used by Containerfile.download) +# ============================================================================== + +release-binaries: build-all ## Copy binaries with versioned names for the download server + @echo "Preparing release binaries..." + @mkdir -p $(DIST_DIR)/release + @for arch in amd64 arm64; do \ + if [ -f "$(DIST_DIR)/$(BIN)_linux_$${arch}/$(BIN)" ]; then \ + cp $(DIST_DIR)/$(BIN)_linux_$${arch}/$(BIN) \ + $(DIST_DIR)/release/$(BIN)_$(VERSION)_linux_$${arch}; \ + echo " -> $(BIN)_$(VERSION)_linux_$${arch}"; \ + fi; \ + done + @for arch in amd64 arm64; do \ + if [ -f "$(DIST_DIR)/$(BIN)_windows_$${arch}/$(BIN).exe" ]; then \ + cp $(DIST_DIR)/$(BIN)_windows_$${arch}/$(BIN).exe \ + $(DIST_DIR)/release/$(BIN)_$(VERSION)_windows_$${arch}.exe; \ + echo " -> $(BIN)_$(VERSION)_windows_$${arch}.exe"; \ + fi; \ + done + @echo "Generating SHA256 checksums..." + @cd $(DIST_DIR)/release && sha256sum $(BIN)_* > sha256sum.txt + @cat $(DIST_DIR)/release/sha256sum.txt + @echo "" + @echo "Release binaries:" + @ls -la $(DIST_DIR)/release/ + +##@ Download Server Targets + +# ============================================================================== +# Download Server Container Image Settings +# ============================================================================== + +DOWNLOAD_IMAGE ?= quay.io/konveyor/oadp-vmdp-binaries +DOWNLOAD_IMAGE_REF := $(DOWNLOAD_IMAGE):$(TAG) + +# ============================================================================== +# Download Server Container Build +# ============================================================================== + +container-download-build: ## Build download server container image +ifeq ($(CONTAINER_TOOL),docker) + @echo "Building download server container with Docker buildx..." + $(CONTAINER_TOOL) buildx build \ + --platform $(CONTAINER_PLATFORMS) \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + -t $(DOWNLOAD_IMAGE_REF) \ + --load \ + -f Containerfile.download \ + . +else ifeq ($(CONTAINER_TOOL),podman) + @echo "Building download server for amd64 with Podman..." + $(CONTAINER_TOOL) build --arch amd64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETOS=linux \ + --build-arg TARGETARCH=amd64 \ + -t $(DOWNLOAD_IMAGE):amd64-$(TAG) \ + -f Containerfile.download \ + . + @echo "" + @echo "Building download server for arm64 with Podman..." + $(CONTAINER_TOOL) build --arch arm64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETOS=linux \ + --build-arg TARGETARCH=arm64 \ + -t $(DOWNLOAD_IMAGE):arm64-$(TAG) \ + -f Containerfile.download \ + . +else + $(error No container tool found. Please install docker or podman.) +endif + +container-download-build-push: ## Build and push download server container image +ifeq ($(CONTAINER_TOOL),docker) + @echo "Building and pushing download server container with Docker buildx..." + $(CONTAINER_TOOL) buildx build \ + --platform $(CONTAINER_PLATFORMS) \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + -t $(DOWNLOAD_IMAGE_REF) \ + --push \ + -f Containerfile.download \ + . +else ifeq ($(CONTAINER_TOOL),podman) + @echo "Building and pushing download server for amd64 with Podman..." + $(CONTAINER_TOOL) build --arch amd64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETOS=linux \ + --build-arg TARGETARCH=amd64 \ + -t $(DOWNLOAD_IMAGE):amd64-$(TAG) \ + -f Containerfile.download \ + . + $(CONTAINER_TOOL) push $(DOWNLOAD_IMAGE):amd64-$(TAG) + @echo "" + @echo "Building and pushing download server for arm64 with Podman..." + $(CONTAINER_TOOL) build --arch arm64 \ + --build-arg VERSION="$(VERSION)" \ + --build-arg GIT_COMMIT="$(GIT_COMMIT)" \ + --build-arg BUILD_DATE="$(BUILD_DATE)" \ + --build-arg BUILDTAGS="$(BUILDTAGS)" \ + --build-arg TARGETOS=linux \ + --build-arg TARGETARCH=arm64 \ + -t $(DOWNLOAD_IMAGE):arm64-$(TAG) \ + -f Containerfile.download \ + . + $(CONTAINER_TOOL) push $(DOWNLOAD_IMAGE):arm64-$(TAG) + @echo "" + @echo "Creating and pushing download server manifest..." + -$(CONTAINER_TOOL) rmi -f $(DOWNLOAD_IMAGE_REF) 2>/dev/null || true + -$(CONTAINER_TOOL) manifest rm $(DOWNLOAD_IMAGE_REF) 2>/dev/null || true + $(CONTAINER_TOOL) manifest create $(DOWNLOAD_IMAGE_REF) \ + $(DOWNLOAD_IMAGE):amd64-$(TAG) \ + $(DOWNLOAD_IMAGE):arm64-$(TAG) + $(CONTAINER_TOOL) manifest push --all $(DOWNLOAD_IMAGE_REF) +else + $(error No container tool found. Please install docker or podman.) +endif diff --git a/OWNERS b/OWNERS index 302c452a189..9863545f690 100644 --- a/OWNERS +++ b/OWNERS @@ -2,7 +2,6 @@ approvers: - joeavaikath - kaovilai - mpryc - - rayfordj - shawn-hurley - shubham-pampattiwar - sseago diff --git a/README_OADP_VMDP.md b/README_OADP_VMDP.md new file mode 100644 index 00000000000..3227f96ab7d --- /dev/null +++ b/README_OADP_VMDP.md @@ -0,0 +1,352 @@ +# OADP VM Data Protection (oadp-vmdp) + +Virtual Machine Data Protection for OpenShift Virtualization. + +OADP-VMDP is a command-line tool that runs inside virtual machines to back up and restore user data. It supports S3-compatible and filesystem storage backends. + +--- + +## Supported Platforms + +OADP-VMDP is built for [OpenShift Virtualization certified guest operating systems](https://access.redhat.com/articles/4234591) on x86_64 (amd64) and arm64 architectures. + +--- + +## Quick Start + +### 1. Create a Backup Storage Location (BSL) + +```bash +oadp-vmdp bsl create s3 \ + --bucket my-backup-bucket \ + --endpoint s3.example.com \ + --access-key YOUR_ACCESS_KEY \ + --secret-access-key YOUR_SECRET_KEY +``` + +### 2. Create a Backup + +```bash +oadp-vmdp backup create /path/to/data +``` + +### 3. Restore from Backup + +```bash +oadp-vmdp restore /path/to/data +``` + +--- + +## Commands + +### BSL (Backup Storage Location) + +| Command | Description | +|---------|-------------| +| `bsl create` | Create and connect to a new BSL | +| `bsl connect` | Connect to an existing BSL | +| `bsl disconnect` | Disconnect from current BSL | +| `bsl status` | Show current BSL connection status | +| `bsl change-password` | Change the BSL encryption password | + +### Backup + +| Command | Description | +|---------|-------------| +| `backup create` | Create a new backup of specified path(s) | +| `backup list` | List all available backups | +| `backup delete` | Delete a specific backup | +| `restore` | Restore data from a backup | + +--- + +## Storage Backends + +For certified providers, see [OADP Certified Backup Storage Providers](https://docs.redhat.com/en/documentation/openshift_container_platform/latest/html/backup_and_restore/oadp-application-backup-and-restore#oadp-certified-backup-storage-providers_about-installing-oadp). + +### S3-Compatible Storage + +| Option | Description | Default | +|--------|-------------|---------| +| `--bucket` | Name of the S3 bucket | (required) | +| `--access-key` | Access Key ID | (required) | +| `--secret-access-key` | Secret Access Key | (required) | +| `--endpoint` | S3 endpoint URL | `s3.amazonaws.com` | +| `--region` | S3 region | (auto-detect) | +| `--prefix` | Object prefix in bucket | (none) | +| `--session-token` | Session token for temporary credentials | (none) | +| `--disable-tls` | Disable HTTPS | `false` | +| `--disable-tls-verification` | Skip TLS certificate verification | `false` | +| `--root-ca-pem-path` | Path to custom CA certificate file | (none) | +| `--root-ca-pem-base64` | Base64-encoded CA certificate | (none) | + +> **Note:** OADP-VMDP automatically prepends `oadp-vmdp/` to your prefix. + +### Filesystem Storage + +| Option | Description | Default | +|--------|-------------|---------| +| `--path` | Absolute path to storage directory | (required) | +| `--owner-uid` | User ID for new files | (current user) | +| `--owner-gid` | Group ID for new files | (current group) | +| `--file-mode` | Permission mode for files | `0600` | +| `--dir-mode` | Permission mode for directories | `0700` | + +--- + +## Environment Variables + +### Credentials + +| Variable | Description | +|----------|-------------| +| `BSLS_PASSWORD` | BSL encryption password (avoids interactive prompt) | +| `AWS_ACCESS_KEY_ID` | Access key for S3 storage | +| `AWS_SECRET_ACCESS_KEY` | Secret key for S3 storage | +| `AWS_SESSION_TOKEN` | Session token for temporary credentials | + +### Configuration + +| Variable | Description | Default | +|----------|-------------|---------| +| `OADP_CONFIG_PATH` | Path to configuration file | `~/.config/oadp/repository.config` | +| `OADP_CACHE_DIRECTORY` | Path to cache directory | (system dependent) | +| `OADP_LOG_DIR` | Directory for log files | `~/.cache/oadp/` | + +### Behavior + +| Variable | Description | Default | +|----------|-------------|---------| +| `OADP_CHECK_FOR_UPDATES` | Enable/disable update checks | `true` | +| `OADP_PERSIST_CREDENTIALS_ON_CONNECT` | Save credentials after connecting | `true` | +| `OADP_USE_KEYRING` | Use system keyring for password storage | `false` | +| `OADP_BACKUP_FAIL_FAST` | Fail immediately on first error | `false` | + +### Logging + +| Variable | Description | Default | +|----------|-------------|---------| +| `OADP_LOG_DIR_MAX_FILES` | Maximum number of log files | `1000` | +| `OADP_LOG_DIR_MAX_AGE` | Maximum age of log files | `720h` | +| `OADP_LOG_DIR_MAX_SIZE_MB` | Maximum total size of log files (MB) | `1000` | + +--- + +## Workflows + +### Non-Interactive Usage (Scripts/Automation) + +Set credentials via environment variables to avoid interactive prompts: + +```bash +export BSLS_PASSWORD="your-secure-password" +export AWS_ACCESS_KEY_ID="your-access-key" +export AWS_SECRET_ACCESS_KEY="your-secret-key" + +oadp-vmdp bsl create s3 --bucket my-bucket --endpoint s3.example.com +oadp-vmdp backup create /path/to/data +``` + +### Connecting from Another System + +To access backups from a different VM, use `bsl connect` instead of `bsl create`: + +```bash +oadp-vmdp bsl connect s3 \ + --bucket my-backup-bucket \ + --endpoint s3.example.com \ + --access-key YOUR_ACCESS_KEY \ + --secret-access-key YOUR_SECRET_KEY +``` + +### Restoring a Specific Backup + +```bash +# List available backups +oadp-vmdp backup list + +# Restore a specific backup by ID to a custom location +oadp-vmdp restore /path/to/restore/ +``` + +--- + +## File Locations + +| Type | Linux | Windows | +|------|-------|---------| +| Configuration | `~/.config/oadp/repository.config` | `%APPDATA%\oadp\repository.config` | +| Logs | `~/.cache/oadp/` | `%LOCALAPPDATA%\oadp\` | + +--- + +## Troubleshooting + +### "Not connected to a Backup Storage Location" + +```bash +oadp-vmdp bsl status # Check current status +oadp-vmdp bsl connect # Connect to existing BSL +``` + +### "prefix must not contain 'oadp-vmdp'" + +The `oadp-vmdp/` prefix is added automatically. Don't include `oadp-vmdp` as a path segment in `--prefix`. +Also ensure your `--prefix` does not start or end with whitespace. + +### S3 Connection Issues + +For self-hosted S3-compatible services, you may need: + +- `--disable-tls` for non-HTTPS endpoints +- `--disable-tls-verification` for self-signed certificates +- `--root-ca-pem-path` to specify a custom CA certificate + +--- + +## Getting Help + +```bash +oadp-vmdp --help +oadp-vmdp bsl --help +oadp-vmdp backup create --help +``` + +--- + +## Download Server (ConsoleCLIDownload) + +OADP-VMDP includes a download server that runs inside an OpenShift cluster and serves pre-built binaries for KubeVirt guest VMs. Users can download the correct binary for their VM's guest operating system directly from the OpenShift console or via HTTP. + +Supported guest operating systems: +- **Red Hat Enterprise Linux** (x86_64, aarch64) +- **Microsoft Windows** (x86_64, aarch64) + +Each binary is statically linked and includes a SHA256 checksum for integrity verification. + +This is powered by: +- **`cmd/downloads/server.go`** - A lightweight Go HTTP server that serves the binaries from `/archives` +- **`Containerfile.download`** - Builds all platform binaries with SHA256 checksums and packages them with the download server +- **`.github/workflows/quay_binaries_push.yml`** - CI workflow that builds and pushes the image to `quay.io/konveyor/oadp-vmdp-binaries` + +### Building the Download Server Locally + +```bash +# Build with Podman (builds for amd64 and arm64 container platforms) +make -f Makefile.ubi container-download-build + +# Build and push +make -f Makefile.ubi container-download-build-push \ + DOWNLOAD_IMAGE=quay.io/youruser/oadp-vmdp-binaries TAG=dev +``` + +### Running the Download Server Locally + +The easiest way is to pull the pre-built image from Quay: + +```console +$ podman run --rm -p 8080:8080 quay.io/konveyor/oadp-vmdp-binaries:latest +``` + +Then open http://localhost:8080 in your browser to see the download page with all available binaries and their SHA256 checksums. + +Alternatively, build the image from source: + +```console +$ podman build \ + --build-arg TARGETOS=linux \ + --build-arg TARGETARCH=amd64 \ + --build-arg VERSION=dev \ + -t oadp-vmdp-binaries:dev \ + -f Containerfile.download . + +$ podman run --rm -p 8080:8080 oadp-vmdp-binaries:dev +``` + +### Downloading and Verifying Binaries + +**Red Hat Enterprise Linux (x86_64):** + +```console +$ curl -O http://localhost:8080/download/oadp-vmdp_v1.0.0_linux_amd64 +$ curl -O http://localhost:8080/download/sha256sum.txt +$ sha256sum -c sha256sum.txt +oadp-vmdp_v1.0.0_linux_amd64: OK +$ chmod +x oadp-vmdp_v1.0.0_linux_amd64 +$ sudo mv oadp-vmdp_v1.0.0_linux_amd64 /usr/local/bin/oadp-vmdp +$ oadp-vmdp --version +``` + +**Red Hat Enterprise Linux (aarch64):** + +```console +$ curl -O http://localhost:8080/download/oadp-vmdp_v1.0.0_linux_arm64 +$ curl -O http://localhost:8080/download/sha256sum.txt +$ sha256sum -c sha256sum.txt +oadp-vmdp_v1.0.0_linux_arm64: OK +$ chmod +x oadp-vmdp_v1.0.0_linux_arm64 +$ sudo mv oadp-vmdp_v1.0.0_linux_arm64 /usr/local/bin/oadp-vmdp +$ oadp-vmdp --version +``` + +**Microsoft Windows (x86_64) - PowerShell:** + +```powershell +PS> Invoke-WebRequest -Uri http://localhost:8080/download/oadp-vmdp_v1.0.0_windows_amd64.exe -OutFile oadp-vmdp.exe +PS> Invoke-WebRequest -Uri http://localhost:8080/download/sha256sum.txt -OutFile sha256sum.txt +PS> (Get-FileHash oadp-vmdp.exe -Algorithm SHA256).Hash +PS> Select-String -Path sha256sum.txt -Pattern "windows_amd64" +PS> .\oadp-vmdp.exe --version +``` + +**Microsoft Windows (aarch64) - PowerShell:** + +```powershell +PS> Invoke-WebRequest -Uri http://localhost:8080/download/oadp-vmdp_v1.0.0_windows_arm64.exe -OutFile oadp-vmdp.exe +PS> Invoke-WebRequest -Uri http://localhost:8080/download/sha256sum.txt -OutFile sha256sum.txt +PS> (Get-FileHash oadp-vmdp.exe -Algorithm SHA256).Hash +PS> Select-String -Path sha256sum.txt -Pattern "windows_arm64" +PS> .\oadp-vmdp.exe --version +``` + +### How It Works in OpenShift + +1. The OADP operator deploys the download server container (via `RELATED_IMAGE_VMDP_CLI_DOWNLOAD` env var) +2. A `ConsoleCLIDownload` resource is created, linking to the download server's routes +3. Users see download links in the OpenShift console and can fetch the binary matching their guest OS +4. Each binary is statically linked and includes a SHA256 checksum - download, verify, and run + +--- + +## Kopia Compatibility + +OADP-VMDP is based on [Kopia](https://kopia.io) and uses the same repository format. Repositories are fully compatible between the two tools. + +**Command mapping:** + +| oadp-vmdp | kopia | +|-----------|-------| +| `bsl` | `repository` | +| `backup` | `snapshot` | + +**Using Kopia CLI with oadp-vmdp repositories:** + +When connecting with Kopia CLI, include the `oadp-vmdp/` prefix that oadp-vmdp adds automatically: + +```bash +kopia repository connect s3 \ + --bucket my-bucket \ + --prefix oadp-vmdp/my-prefix/ \ + ... +``` + +**Using oadp-vmdp with existing Kopia repositories:** + +oadp-vmdp will prepend `oadp-vmdp/` to your prefix. To access an existing Kopia repository at prefix `backups/`, you cannot connect directly - the prefix manipulation would cause a mismatch. + +--- + +## License + +OADP-VMDP is based on Kopia and is distributed by Red Hat, Inc. diff --git a/cli/app.go b/cli/app.go index 3c6b5bf1b07..c75fa3f0d24 100644 --- a/cli/app.go +++ b/cli/app.go @@ -1,4 +1,4 @@ -// Package cli implements command-line commands for the Kopia. +// Package cli implements command-line commands for OADP VM Data Protection. package cli import ( @@ -29,7 +29,7 @@ import ( "github.com/kopia/kopia/snapshot/snapshotmaintenance" ) -var log = logging.Module("kopia/cli") +var log = logging.Module("oadp/cli") var tracer = otel.Tracer("cli") @@ -118,7 +118,7 @@ type advancedAppServices interface { enableErrorNotifications() bool } -// App contains per-invocation flags and state of Kopia CLI. +// App contains per-invocation flags and state of OADP-VMDP CLI. type App struct { // global flags enableAutomaticMaintenance bool @@ -184,7 +184,7 @@ type App struct { } func (c *App) enableTestOnlyFlags() bool { - return c.isInProcessTest || os.Getenv("KOPIA_TESTONLY_FLAGS") != "" + return c.isInProcessTest || os.Getenv("OADP_TESTONLY_FLAGS") != "" } func (c *App) getProgress() *cliProgress { @@ -268,21 +268,20 @@ func (c *App) setup(app *kingpin.Application) { app.Flag("auto-maintenance", "Automatic maintenance").Default("true").Hidden().BoolVar(&c.enableAutomaticMaintenance) // hidden flags to control auto-update behavior. - app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar(c.EnvName("KOPIA_INITIAL_UPDATE_CHECK_DELAY")).DurationVar(&c.initialUpdateCheckDelay) - app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_CHECK_INTERVAL")).DurationVar(&c.updateCheckInterval) - app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar(c.EnvName("KOPIA_UPDATE_NOTIFY_INTERVAL")).DurationVar(&c.updateAvailableNotifyInterval) - app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar(c.EnvName("KOPIA_CONFIG_PATH")).StringVar(&c.configPath) + app.Flag("initial-update-check-delay", "Initial delay before first time update check").Default("24h").Hidden().Envar(c.EnvName("OADP_INITIAL_UPDATE_CHECK_DELAY")).DurationVar(&c.initialUpdateCheckDelay) + app.Flag("update-check-interval", "Interval between update checks").Default("168h").Hidden().Envar(c.EnvName("OADP_UPDATE_CHECK_INTERVAL")).DurationVar(&c.updateCheckInterval) + app.Flag("update-available-notify-interval", "Interval between update notifications").Default("1h").Hidden().Envar(c.EnvName("OADP_UPDATE_NOTIFY_INTERVAL")).DurationVar(&c.updateAvailableNotifyInterval) + app.Flag("config-file", "Specify the config file to use").Default("repository.config").Envar(c.EnvName("OADP_CONFIG_PATH")).StringVar(&c.configPath) app.Flag("trace-storage", "Enables tracing of storage operations.").Default("true").Hidden().BoolVar(&c.traceStorage) app.Flag("timezone", "Format time according to specified time zone (local, utc, original or time zone name)").Hidden().StringVar(&timeZone) - app.Flag("password", "Repository password.").Envar(c.EnvName("KOPIA_PASSWORD")).Short('p').StringVar(&c.password) - app.Flag("persist-credentials", "Persist credentials").Default("true").Envar(c.EnvName("KOPIA_PERSIST_CREDENTIALS_ON_CONNECT")).BoolVar(&c.persistCredentials) - app.Flag("disable-repository-log", "Disable repository log").Hidden().Envar(c.EnvName("KOPIA_DISABLE_REPOSITORY_LOG")).BoolVar(&c.disableRepositoryLog) - app.Flag("dangerous-commands", "Enable dangerous commands that could result in data loss and repository corruption.").Hidden().Envar(c.EnvName("KOPIA_DANGEROUS_COMMANDS")).StringVar(&c.DangerousCommands) - app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar(c.EnvName("KOPIA_TRACK_RELEASABLE")).StringsVar(&c.trackReleasable) - app.Flag("upgrade-owner-id", "Repository format upgrade owner-id.").Hidden().Envar(c.EnvName("KOPIA_REPO_UPGRADE_OWNER_ID")).StringVar(&c.upgradeOwnerID) - app.Flag("upgrade-no-block", "Do not block when repository format upgrade is in progress, instead exit with a message.").Hidden().Default("false").Envar(c.EnvName("KOPIA_REPO_UPGRADE_NO_BLOCK")).BoolVar(&c.doNotWaitForUpgrade) + app.Flag("password", "BSL password.").Envar(c.EnvName("BSLS_PASSWORD")).Short('p').StringVar(&c.password) + app.Flag("persist-credentials", "Persist credentials").Default("true").Envar(c.EnvName("OADP_PERSIST_CREDENTIALS_ON_CONNECT")).BoolVar(&c.persistCredentials) + app.Flag("disable-repository-log", "Disable repository log").Hidden().Envar(c.EnvName("OADP_DISABLE_REPOSITORY_LOG")).BoolVar(&c.disableRepositoryLog) + app.Flag("track-releasable", "Enable tracking of releasable resources.").Hidden().Envar(c.EnvName("OADP_TRACK_RELEASABLE")).StringsVar(&c.trackReleasable) + app.Flag("upgrade-owner-id", "BSL format upgrade owner-id.").Hidden().Envar(c.EnvName("OADP_BSL_UPGRADE_OWNER_ID")).StringVar(&c.upgradeOwnerID) + app.Flag("upgrade-no-block", "Do not block when BSL format upgrade is in progress, instead exit with a message.").Hidden().Default("false").Envar(c.EnvName("OADP_BSL_UPGRADE_NO_BLOCK")).BoolVar(&c.doNotWaitForUpgrade) app.Flag("error-notifications", "Send notification on errors").Hidden(). - Envar(c.EnvName("KOPIA_SEND_ERROR_NOTIFICATIONS")). + Envar(c.EnvName("OADP_SEND_ERROR_NOTIFICATIONS")). Default(errorNotificationsNonInteractive). EnumVar(&c.errorNotifications, errorNotificationsAlways, errorNotificationsNever, errorNotificationsNonInteractive) @@ -296,25 +295,18 @@ func (c *App) setup(app *kingpin.Application) { c.progress.setup(c, app) - c.blob.setup(c, app) - c.benchmark.setup(c, app) + // OADP: Only include commands needed for VM backup/restore workflow + // Keep the CLI surface minimal to match supported workflows and reduce risk. + // NOTE: Advanced/admin command trees (blob/content/index/manifest) are intentionally + // not wired up here to keep future rebases simpler while preventing accidental use. c.cache.setup(c, app) - c.content.setup(c, app) - c.diff.setup(c, app) - c.index.setup(c, app) - c.list.setup(c, app) c.logs.setup(c, app) - c.notification.setup(c, app) - c.server.setup(c, app) c.session.setup(c, app) c.restore.setup(c, app) c.show.setup(c, app) - c.snapshot.setup(c, app) - c.manifest.setup(c, app) - c.policy.setup(c, app) - c.mount.setup(c, app) - c.maintenance.setup(c, app) - c.repository.setup(c, app) + c.snapshot.setup(c, app) // renamed to "backup" in command_snapshot.go + // manifest commands intentionally not wired (advanced/admin) + c.repository.setup(c, app) // renamed to "bsl" in command_repository.go } // commandParent is implemented by app and commands that can have sub-commands. @@ -326,19 +318,12 @@ type commandParent interface { func NewApp() *App { return &App{ progress: &cliProgress{}, + // OADP: Only include storage backends needed for VM users + // To add more backends later, uncomment or add lines here cliStorageProviders: []StorageProvider{ - {"from-config", "the provided configuration file", func() StorageFlags { return &storageFromConfigFlags{} }}, - - {"azure", "an Azure blob storage", func() StorageFlags { return &storageAzureFlags{} }}, - {"b2", "a B2 bucket", func() StorageFlags { return &storageB2Flags{} }}, {"filesystem", "a filesystem", func() StorageFlags { return &storageFilesystemFlags{} }}, - {"gcs", "a Google Cloud Storage bucket", func() StorageFlags { return &storageGCSFlags{} }}, - {"gdrive", "a Google Drive folder", func() StorageFlags { return &storageGDriveFlags{} }}, - - {"rclone", "a rclone-based provided", func() StorageFlags { return &storageRcloneFlags{} }}, {"s3", "an S3 bucket", func() StorageFlags { return &storageS3Flags{} }}, - {"sftp", "an SFTP storage", func() StorageFlags { return &storageSFTPFlags{} }}, - {"webdav", "a WebDAV storage", func() StorageFlags { return &storageWebDAVFlags{} }}, + // Removed: from-config, azure, b2, gcs, gdrive, rclone, sftp, webdav }, // testability hooks @@ -618,10 +603,9 @@ func (c *App) maybeRunMaintenance(ctx context.Context, rep repo.Repository) erro func (c *App) dangerousCommand() { if c.DangerousCommands != "enabled" { _, _ = errorColor.Fprintf(c.stderrWriter, ` -This command is dangerous, it can corrupt the repository and result in data loss. +This command could be dangerous or lead to BSL corruption when used improperly. -Running this command is not needed for using Kopia. Instead, rely on periodic repository maintenance. See https://kopia.io/docs/advanced/maintenance/ for more information. -To run this command despite the warning, set --dangerous-commands=enabled +Running this command is not needed for normal usage. Instead, most users should rely on periodic automatic maintenance. `) diff --git a/cli/auto_upgrade.go b/cli/auto_upgrade.go index 79b5cf1fdb0..419629932c2 100644 --- a/cli/auto_upgrade.go +++ b/cli/auto_upgrade.go @@ -43,11 +43,10 @@ func setDefaultMaintenanceParameters(ctx context.Context, rep repo.RepositoryWri return errors.Wrap(err, "unable to set maintenance params") } + // OADP: Updated maintenance notice log(ctx).Infof(` -NOTE: Kopia will perform quick maintenance of the repository automatically every %v +NOTE: OADP-VMDP will perform quick maintenance of the BSL automatically every %v and full maintenance every %v when running as %v. - -See https://kopia.io/docs/advanced/maintenance/ for more information. `, p.QuickCycle.Interval, p.FullCycle.Interval, p.Owner) return nil diff --git a/cli/command_benchmark_test.go b/cli/command_benchmark_test.go index 1be703177ec..a1fc21332ef 100644 --- a/cli/command_benchmark_test.go +++ b/cli/command_benchmark_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_blob_shards_modify_test.go b/cli/command_blob_shards_modify_test.go index 395f403b976..e038654b196 100644 --- a/cli/command_blob_shards_modify_test.go +++ b/cli/command_blob_shards_modify_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_blob_show_test.go b/cli/command_blob_show_test.go index 7d52b956c9c..2027376c364 100644 --- a/cli/command_blob_show_test.go +++ b/cli/command_blob_show_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_cache_set.go b/cli/command_cache_set.go index ad22a85644e..c1f9dda41f4 100644 --- a/cli/command_cache_set.go +++ b/cli/command_cache_set.go @@ -47,7 +47,8 @@ type commandCacheSetParams struct { } func (c *commandCacheSetParams) setup(svc appServices, parent commandParent) { - cmd := parent.Command("set", "Sets parameters local caching of repository data") + // OADP: Updated terminology + cmd := parent.Command("set", "Sets parameters for local caching of BSL data") c.contentMinSweepAge = -1 c.metadataMinSweepAge = -1 diff --git a/cli/command_content_verify_test.go b/cli/command_content_verify_test.go index a4d2ec171f6..0f988703a36 100644 --- a/cli/command_content_verify_test.go +++ b/cli/command_content_verify_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_diff.go b/cli/command_diff.go index c8033dce14c..e8fa5cefeea 100644 --- a/cli/command_diff.go +++ b/cli/command_diff.go @@ -30,7 +30,7 @@ func (c *commandDiff) setup(svc appServices, parent commandParent) { cmd.Arg("object-path2", "Second object/path").Required().StringVar(&c.diffSecondObjectPath) cmd.Flag("files", "Compare files by launching diff command for all pairs of (old,new)").Short('f').BoolVar(&c.diffCompareFiles) cmd.Flag("stats-only", "Displays only aggregate statistics of the changes between two repository objects").BoolVar(&c.diffStatsOnly) - cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar(svc.EnvName("KOPIA_DIFF")).StringVar(&c.diffCommandCommand) + cmd.Flag("diff-command", "Displays differences between two repository objects (files or directories)").Default(defaultDiffCommand()).Envar(svc.EnvName("OADP_DIFF")).StringVar(&c.diffCommandCommand) cmd.Action(svc.repositoryReaderAction(c.run)) c.out.setup(svc) diff --git a/cli/command_index_inspect_test.go b/cli/command_index_inspect_test.go index 310d5c0ae4d..e02827ef5ec 100644 --- a/cli/command_index_inspect_test.go +++ b/cli/command_index_inspect_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_logs_test.go b/cli/command_logs_test.go index f8a0fecde6d..fbbfcddcbe1 100644 --- a/cli/command_logs_test.go +++ b/cli/command_logs_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_maintenance_info_test.go b/cli/command_maintenance_info_test.go index 26ab44e2065..15202c9757f 100644 --- a/cli/command_maintenance_info_test.go +++ b/cli/command_maintenance_info_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_maintenance_set_test.go b/cli/command_maintenance_set_test.go index d5c190ca448..eb1d21e5f55 100644 --- a/cli/command_maintenance_set_test.go +++ b/cli/command_maintenance_set_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_notification_profile_internal_test.go b/cli/command_notification_profile_internal_test.go index 2194babaa86..da436631e9e 100644 --- a/cli/command_notification_profile_internal_test.go +++ b/cli/command_notification_profile_internal_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli import ( diff --git a/cli/command_notification_profile_test.go b/cli/command_notification_profile_test.go index 25dabd3b568..637ac70c6b5 100644 --- a/cli/command_notification_profile_test.go +++ b/cli/command_notification_profile_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_notification_template_internal_test.go b/cli/command_notification_template_internal_test.go index 1df137b9915..8185b6e9afe 100644 --- a/cli/command_notification_template_internal_test.go +++ b/cli/command_notification_template_internal_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli import ( diff --git a/cli/command_notification_template_test.go b/cli/command_notification_template_test.go index 061213203a5..243c7ce63e5 100644 --- a/cli/command_notification_template_test.go +++ b/cli/command_notification_template_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_export_test.go b/cli/command_policy_export_test.go index 4257d86db60..06752fbd3aa 100644 --- a/cli/command_policy_export_test.go +++ b/cli/command_policy_export_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_import_test.go b/cli/command_policy_import_test.go index 0589a31ce11..fa774d13354 100644 --- a/cli/command_policy_import_test.go +++ b/cli/command_policy_import_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_set_logging_test.go b/cli/command_policy_set_logging_test.go index da744241ccb..bd5853b8d92 100644 --- a/cli/command_policy_set_logging_test.go +++ b/cli/command_policy_set_logging_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_set_os_snapshot_test.go b/cli/command_policy_set_os_snapshot_test.go index a91083d0ae2..4d792b89fbf 100644 --- a/cli/command_policy_set_os_snapshot_test.go +++ b/cli/command_policy_set_os_snapshot_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_set_splitter_test.go b/cli/command_policy_set_splitter_test.go index 9449f485b7b..03e8b1d9b8d 100644 --- a/cli/command_policy_set_splitter_test.go +++ b/cli/command_policy_set_splitter_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_policy_set_test.go b/cli/command_policy_set_test.go index 04e63a1478d..96ebf3fc137 100644 --- a/cli/command_policy_set_test.go +++ b/cli/command_policy_set_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli import ( diff --git a/cli/command_policy_set_upload_test.go b/cli/command_policy_set_upload_test.go index 395efc18266..d1c58290fe3 100644 --- a/cli/command_policy_set_upload_test.go +++ b/cli/command_policy_set_upload_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_repo_throttle_test.go b/cli/command_repo_throttle_test.go index e8b1a7bd7f0..dd7367a6db2 100644 --- a/cli/command_repo_throttle_test.go +++ b/cli/command_repo_throttle_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_repository.go b/cli/command_repository.go index 17eeaab39ba..594fb2f161f 100644 --- a/cli/command_repository.go +++ b/cli/command_repository.go @@ -16,18 +16,13 @@ type commandRepository struct { } func (c *commandRepository) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("repository", "Commands to manipulate repository.").Alias("repo") + // OADP: Renamed from "repository" to "bsl" (Backup Storage Location) + cmd := parent.Command("bsl", "Commands to manage Backup Storage Location (BSL).") + // OADP: Only include subcommands needed for VM users c.connect.setup(svc, cmd) c.create.setup(svc, cmd) c.disconnect.setup(svc, cmd) - c.repair.setup(svc, cmd) - c.setClient.setup(svc, cmd) - c.setParameters.setup(svc, cmd) c.status.setup(svc, cmd) - c.syncTo.setup(svc, cmd) - c.throttle.setup(svc, cmd) c.changePassword.setup(svc, cmd) - c.validateProvider.setup(svc, cmd) - c.upgrade.setup(svc, cmd) } diff --git a/cli/command_repository_change_password.go b/cli/command_repository_change_password.go index adfd841f85b..41336ac4bec 100644 --- a/cli/command_repository_change_password.go +++ b/cli/command_repository_change_password.go @@ -15,8 +15,10 @@ type commandRepositoryChangePassword struct { } func (c *commandRepositoryChangePassword) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("change-password", "Change repository password") - cmd.Flag("new-password", "New password").Envar(svc.EnvName("KOPIA_NEW_PASSWORD")).StringVar(&c.newPassword) + // OADP: Updated terminology + cmd := parent.Command("change-password", "Change BSL password") + // OADP: Changed from KOPIA_NEW_PASSWORD to OADP_NEW_PASSWORD + cmd.Flag("new-password", "New password").Envar(svc.EnvName("OADP_NEW_PASSWORD")).StringVar(&c.newPassword) c.svc = svc cmd.Action(svc.directRepositoryWriteAction(c.run)) @@ -40,7 +42,8 @@ func (c *commandRepositoryChangePassword) run(ctx context.Context, rep repo.Dire return errors.Wrap(err, "unable to change password") } - log(ctx).Infof(`NOTE: Repository password has been changed.`) + // OADP: Updated terminology + log(ctx).Infof(`NOTE: BSL password has been changed.`) if err := c.svc.passwordPersistenceStrategy().PersistPassword(ctx, c.svc.repositoryConfigFileName(), newPass); err != nil { return errors.Wrap(err, "unable to persist password") diff --git a/cli/command_repository_change_password_test.go b/cli/command_repository_change_password_test.go index 7d888cd0338..0fef4970b4c 100644 --- a/cli/command_repository_change_password_test.go +++ b/cli/command_repository_change_password_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_repository_connect.go b/cli/command_repository_connect.go index b60372bf1b8..d7700f1001a 100644 --- a/cli/command_repository_connect.go +++ b/cli/command_repository_connect.go @@ -20,7 +20,8 @@ type commandRepositoryConnect struct { } func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("connect", "Connect to a repository.") + // OADP: Updated terminology + cmd := parent.Command("connect", "Connect to a BSL.") c.co.setup(svc, cmd) c.server.setup(svc, cmd, &c.co) @@ -28,7 +29,8 @@ func (c *commandRepositoryConnect) setup(svc advancedAppServices, parent command for _, prov := range svc.storageProviders() { // Set up 'connect' subcommand f := prov.NewFlags() - cc := cmd.Command(prov.Name, "Connect to repository in "+prov.Description) + // OADP: Updated terminology + cc := cmd.Command(prov.Name, "Connect to BSL in "+prov.Description) f.Setup(svc, cc) cc.Action(func(kpc *kingpin.ParseContext) error { return svc.runAppWithContext(kpc.SelectedCommand, func(ctx context.Context) error { @@ -63,7 +65,8 @@ type connectOptions struct { func (c *connectOptions) setup(svc appServices, cmd *kingpin.CmdClause) { // Set up flags shared between 'create' and 'connect'. Note that because those flags are used by both command // we must use *Var() methods, otherwise one of the commands would always get default flag values. - cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar(svc.EnvName("KOPIA_CACHE_DIRECTORY")).StringVar(&c.connectCacheDirectory) + // OADP: Changed from KOPIA_CACHE_DIRECTORY to OADP_CACHE_DIRECTORY + cmd.Flag("cache-directory", "Cache directory").PlaceHolder("PATH").Envar(svc.EnvName("OADP_CACHE_DIRECTORY")).StringVar(&c.connectCacheDirectory) c.maxListCacheDuration = 30 * time.Second //nolint:mnd c.contentCacheSizeMB = 5000 @@ -72,7 +75,8 @@ func (c *connectOptions) setup(svc appServices, cmd *kingpin.CmdClause) { cmd.Flag("override-hostname", "Override hostname used by this repository connection").Hidden().StringVar(&c.connectHostname) cmd.Flag("override-username", "Override username used by this repository connection").Hidden().StringVar(&c.connectUsername) - cmd.Flag("check-for-updates", "Periodically check for Kopia updates on GitHub").Default("true").Envar(svc.EnvName(checkForUpdatesEnvar)).BoolVar(&c.connectCheckForUpdates) + // OADP: Updated help text + cmd.Flag("check-for-updates", "Periodically check for OADP-VMDP updates on GitHub").Default("true").Envar(svc.EnvName(checkForUpdatesEnvar)).BoolVar(&c.connectCheckForUpdates) cmd.Flag("readonly", "Make repository read-only to avoid accidental changes").BoolVar(&c.connectReadonly) cmd.Flag("permissive-cache-loading", "Do not fail when loading bad cache index entries. Repository must be opened in read-only mode").Hidden().BoolVar(&c.connectPermissiveCacheLoading) cmd.Flag("description", "Human-readable description of the repository").StringVar(&c.connectDescription) diff --git a/cli/command_repository_connect_server.go b/cli/command_repository_connect_server.go index 2c436dea943..b3ad8137ce8 100644 --- a/cli/command_repository_connect_server.go +++ b/cli/command_repository_connect_server.go @@ -26,7 +26,8 @@ func (c *commandRepositoryConnectServer) setup(svc advancedAppServices, parent c c.svc = svc c.out.setup(svc) - cmd := parent.Command("server", "Connect to a repository API Server.") + // OADP: Updated terminology + cmd := parent.Command("server", "Connect to a BSL API Server.") cmd.Flag("url", "Server URL").Required().StringVar(&c.connectAPIServerURL) cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").StringVar(&c.connectAPIServerCertFingerprint) //nolint:lll diff --git a/cli/command_repository_create.go b/cli/command_repository_create.go index 985676ef244..0df34fbf092 100644 --- a/cli/command_repository_create.go +++ b/cli/command_repository_create.go @@ -17,11 +17,8 @@ import ( "github.com/kopia/kopia/snapshot/policy" ) -const runValidationNote = `NOTE: To validate that your provider is compatible with Kopia, please run: - -$ kopia repository validate-provider - -` +// OADP: Removed validation note since validate-provider command is not included. +const runValidationNote = `` type commandRepositoryCreate struct { createBlockHashFormat string @@ -41,14 +38,16 @@ type commandRepositoryCreate struct { } func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("create", "Create new repository in a specified location.") + // OADP: Updated terminology + cmd := parent.Command("create", "Create new BSL in a specified location.") cmd.Flag("block-hash", "Content hash algorithm.").PlaceHolder("ALGO").Default(hashing.DefaultAlgorithm).EnumVar(&c.createBlockHashFormat, hashing.SupportedAlgorithms()...) cmd.Flag("encryption", "Content encryption algorithm.").PlaceHolder("ALGO").Default(encryption.DefaultAlgorithm).EnumVar(&c.createBlockEncryptionFormat, encryption.SupportedAlgorithms(false)...) cmd.Flag("ecc", "[EXPERIMENTAL] Error correction algorithm.").PlaceHolder("ALGO").Default(ecc.DefaultAlgorithm).EnumVar(&c.createBlockECCFormat, ecc.SupportedAlgorithms()...) cmd.Flag("ecc-overhead-percent", "[EXPERIMENTAL] How much space overhead can be used for error correction, in percentage. Use 0 to disable ECC.").Default("0").IntVar(&c.createBlockECCOverheadPercent) cmd.Flag("object-splitter", "The splitter to use for new objects in the repository").Default(splitter.DefaultAlgorithm).EnumVar(&c.createSplitter, splitter.SupportedAlgorithms()...) - cmd.Flag("create-only", "Create repository, but don't connect to it.").Short('c').BoolVar(&c.createOnly) + // OADP: Updated terminology + cmd.Flag("create-only", "Create BSL, but don't connect to it.").Short('c').BoolVar(&c.createOnly) cmd.Flag("format-version", "Force a particular repository format version (1, 2 or 3, 0==default)").IntVar(&c.createFormatVersion) cmd.Flag("retention-mode", "Set the blob retention-mode for supported storage backends.").EnumVar(&c.retentionMode, blob.Governance.String(), blob.Compliance.String()) cmd.Flag("retention-period", "Set the blob retention-period for supported storage backends.").DurationVar(&c.retentionPeriod) @@ -62,7 +61,8 @@ func (c *commandRepositoryCreate) setup(svc advancedAppServices, parent commandP for _, prov := range svc.storageProviders() { // Set up 'create' subcommand f := prov.NewFlags() - cc := cmd.Command(prov.Name, "Create repository in "+prov.Description) + // OADP: Updated terminology + cc := cmd.Command(prov.Name, "Create BSL in "+prov.Description) f.Setup(svc, cc) cc.Action(func(kpc *kingpin.ParseContext) error { return svc.runAppWithContext(kpc.SelectedCommand, func(ctx context.Context) error { @@ -185,7 +185,7 @@ func (c *commandRepositoryCreate) populateRepository(ctx context.Context, passwo c.out.printStdout("%v\n", alignedPolicyTableRows(rows)) - c.out.printStderr("\nTo find more information about default policy run 'kopia policy get'.\nTo change the policy use 'kopia policy set' command.\n") + // OADP: Removed policy command reference since policy command is not included if err := setDefaultMaintenanceParameters(ctx, w); err != nil { return errors.Wrap(err, "unable to set maintenance parameters") diff --git a/cli/command_repository_create_test.go b/cli/command_repository_create_test.go index 9fd128b7f23..65d5765bc6f 100644 --- a/cli/command_repository_create_test.go +++ b/cli/command_repository_create_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_repository_disconnect.go b/cli/command_repository_disconnect.go index 729e05b340d..20f02095297 100644 --- a/cli/command_repository_disconnect.go +++ b/cli/command_repository_disconnect.go @@ -13,7 +13,8 @@ type commandRepositoryDisconnect struct { } func (c *commandRepositoryDisconnect) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("disconnect", "Disconnect from a repository.") + // OADP: Updated terminology + cmd := parent.Command("disconnect", "Disconnect from a BSL.") cmd.Action(svc.noRepositoryAction(c.run)) c.svc = svc diff --git a/cli/command_repository_set_parameters_test.go b/cli/command_repository_set_parameters_test.go index 68dfa5ae259..07850240904 100644 --- a/cli/command_repository_set_parameters_test.go +++ b/cli/command_repository_set_parameters_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_repository_status.go b/cli/command_repository_status.go index 7eb2cf22212..5a87056fde0 100644 --- a/cli/command_repository_status.go +++ b/cli/command_repository_status.go @@ -42,7 +42,8 @@ type RepositoryStatus struct { } func (c *commandRepositoryStatus) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("status", "Display the status of connected repository.") + // OADP: Updated terminology + cmd := parent.Command("status", "Display the status of connected BSL.") cmd.Flag("reconnect-token", "Display reconnect command").Short('t').BoolVar(&c.statusReconnectToken) cmd.Flag("reconnect-token-with-password", "Include password in reconnect token").Short('s').BoolVar(&c.statusReconnectTokenIncludePassword) cmd.Action(svc.repositoryReaderAction(c.run)) diff --git a/cli/command_repository_upgrade.go b/cli/command_repository_upgrade.go index 8a6f3e5adfe..90a490f387e 100644 --- a/cli/command_repository_upgrade.go +++ b/cli/command_repository_upgrade.go @@ -35,9 +35,9 @@ type commandRepositoryUpgrade struct { const ( experimentalWarning = `WARNING: The upgrade command is an EXPERIMENTAL feature. Please DO NOT use it, it may corrupt your repository and cause data loss. -You will need to set the env variable KOPIA_UPGRADE_LOCK_ENABLED in order to use this feature. +You will need to set the env variable OADP_UPGRADE_LOCK_ENABLED in order to use this feature. ` - upgradeLockFeatureEnv = "KOPIA_UPGRADE_LOCK_ENABLED" + upgradeLockFeatureEnv = "OADP_UPGRADE_LOCK_ENABLED" maxPermittedClockDriftDefault = 5 * time.Minute ) diff --git a/cli/command_repository_upgrade_test.go b/cli/command_repository_upgrade_test.go index a9a995dd52b..a76c93b2ea1 100644 --- a/cli/command_repository_upgrade_test.go +++ b/cli/command_repository_upgrade_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_restore.go b/cli/command_restore.go index dabaef30856..36ad06a3c5f 100644 --- a/cli/command_restore.go +++ b/cli/command_restore.go @@ -145,7 +145,8 @@ func (c *commandRestore) setup(svc appServices, parent commandParent) { cmd.Flag("overwrite-files", "Specifies whether or not to overwrite already existing files").Default("true").BoolVar(&c.restoreOverwriteFiles) cmd.Flag("overwrite-symlinks", "Specifies whether or not to overwrite already existing symlinks").Default("true").BoolVar(&c.restoreOverwriteSymlinks) cmd.Flag("write-sparse-files", "When doing a restore, attempt to write files sparsely-allocating the minimum amount of disk space needed.").Default("false").BoolVar(&c.restoreWriteSparseFiles) - cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar(svc.EnvName("KOPIA_RESTORE_CONSISTENT_ATTRIBUTES")).BoolVar(&c.restoreConsistentAttributes) + // OADP: Changed from KOPIA_RESTORE_CONSISTENT_ATTRIBUTES to OADP_RESTORE_CONSISTENT_ATTRIBUTES + cmd.Flag("consistent-attributes", "When multiple snapshots match, fail if they have inconsistent attributes").Envar(svc.EnvName("OADP_RESTORE_CONSISTENT_ATTRIBUTES")).BoolVar(&c.restoreConsistentAttributes) cmd.Flag("mode", "Override restore mode").Default(restoreModeAuto).EnumVar(&c.restoreMode, restoreModeAuto, restoreModeLocal, restoreModeZip, restoreModeZipNoCompress, restoreModeTar, restoreModeTgz) cmd.Flag("parallel", "Restore parallelism (1=disable)").Default("8").IntVar(&c.restoreParallel) cmd.Flag("skip-owners", "Skip owners during restore").BoolVar(&c.restoreSkipOwners) diff --git a/cli/command_server.go b/cli/command_server.go index 4eee5a0c1f6..1dba88aa371 100644 --- a/cli/command_server.go +++ b/cli/command_server.go @@ -30,8 +30,8 @@ type serverFlags struct { func (c *serverFlags) setup(svc appServices, cmd *kingpin.CmdClause) { cmd.Flag("address", "Server address").Default("http://127.0.0.1:51515").StringVar(&c.serverAddress) - cmd.Flag("server-username", "HTTP server username (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).Default("kopia").StringVar(&c.serverUsername) - cmd.Flag("server-password", "HTTP server password (basic auth)").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword) + cmd.Flag("server-username", "HTTP server username (basic auth)").Envar(svc.EnvName("OADP_SERVER_USERNAME")).Default("oadp-vmdp").StringVar(&c.serverUsername) + cmd.Flag("server-password", "HTTP server password (basic auth)").Envar(svc.EnvName("OADP_SERVER_PASSWORD")).StringVar(&c.serverPassword) } type serverClientFlags struct { @@ -44,15 +44,15 @@ type serverClientFlags struct { func (c *serverClientFlags) setup(svc appServices, cmd *kingpin.CmdClause) { c.serverUsername = defaultServerControlUsername - cmd.Flag("address", "Address of the server to connect to").Envar(svc.EnvName("KOPIA_SERVER_ADDRESS")).Default("http://127.0.0.1:51515").StringVar(&c.serverAddress) - cmd.Flag("server-control-username", "Server control username").Envar(svc.EnvName("KOPIA_SERVER_USERNAME")).StringVar(&c.serverUsername) - cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_PASSWORD")).StringVar(&c.serverPassword) + cmd.Flag("address", "Address of the server to connect to").Envar(svc.EnvName("OADP_SERVER_ADDRESS")).Default("http://127.0.0.1:51515").StringVar(&c.serverAddress) + cmd.Flag("server-control-username", "Server control username").Envar(svc.EnvName("OADP_SERVER_USERNAME")).StringVar(&c.serverUsername) + cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("OADP_SERVER_PASSWORD")).StringVar(&c.serverPassword) // aliases for backwards compat cmd.Flag("server-username", "Server control username").Hidden().StringVar(&c.serverUsername) cmd.Flag("server-password", "Server control password").Hidden().StringVar(&c.serverPassword) - cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar(svc.EnvName("KOPIA_SERVER_CERT_FINGERPRINT")).StringVar(&c.serverCertFingerprint) + cmd.Flag("server-cert-fingerprint", "Server certificate fingerprint").PlaceHolder("SHA256-FINGERPRINT").Envar(svc.EnvName("OADP_SERVER_CERT_FINGERPRINT")).StringVar(&c.serverCertFingerprint) } func (c *commandServer) setup(svc advancedAppServices, parent commandParent) { diff --git a/cli/command_server_control_test.go b/cli/command_server_control_test.go index 953a3de8a45..0eed5eb79d4 100644 --- a/cli/command_server_control_test.go +++ b/cli/command_server_control_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_server_notifications_test.go b/cli/command_server_notifications_test.go index 59d0d7ad75e..0d294737e80 100644 --- a/cli/command_server_notifications_test.go +++ b/cli/command_server_notifications_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_server_start.go b/cli/command_server_start.go index 5f00dac10b3..a9b8db07a72 100644 --- a/cli/command_server_start.go +++ b/cli/command_server_start.go @@ -99,10 +99,10 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent cmd.Flag("htpasswd-file", "Path to htpasswd file that contains allowed user@hostname entries").Hidden().ExistingFileVar(&c.serverStartHtpasswdFile) cmd.Flag("random-server-control-password", "Generate random server control password and print to stderr").Hidden().BoolVar(&c.randomServerControlPassword) - cmd.Flag("server-control-username", "Server control username").Default(defaultServerControlUsername).Envar(svc.EnvName("KOPIA_SERVER_CONTROL_USER")).StringVar(&c.serverControlUsername) - cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("KOPIA_SERVER_CONTROL_PASSWORD")).StringVar(&c.serverControlPassword) + cmd.Flag("server-control-username", "Server control username").Default(defaultServerControlUsername).Envar(svc.EnvName("OADP_SERVER_CONTROL_USER")).StringVar(&c.serverControlUsername) + cmd.Flag("server-control-password", "Server control password").PlaceHolder("PASSWORD").Envar(svc.EnvName("OADP_SERVER_CONTROL_PASSWORD")).StringVar(&c.serverControlPassword) - cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar(svc.EnvName("KOPIA_AUTH_COOKIE_SIGNING_KEY")).Hidden().StringVar(&c.serverAuthCookieSingingKey) + cmd.Flag("auth-cookie-signing-key", "Force particular auth cookie signing key").Envar(svc.EnvName("OADP_AUTH_COOKIE_SIGNING_KEY")).Hidden().StringVar(&c.serverAuthCookieSingingKey) cmd.Flag("log-scheduler", "Enable logging of scheduler actions").Hidden().Default("true").BoolVar(&c.debugScheduler) cmd.Flag("min-maintenance-interval", "Minimum maintenance interval").Hidden().Default("60s").DurationVar(&c.minMaintenanceInterval) @@ -118,7 +118,7 @@ func (c *commandServerStart) setup(svc advancedAppServices, parent commandParent cmd.Flag("async-repo-connect", "Connect to repository asynchronously").Hidden().BoolVar(&c.asyncRepoConnect) cmd.Flag("persistent-logs", "Persist logs in a file").Default("true").BoolVar(&c.persistentLogs) - cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar(svc.EnvName("KOPIA_UI_TITLE_PREFIX")).StringVar(&c.uiTitlePrefix) + cmd.Flag("ui-title-prefix", "UI title prefix").Hidden().Envar(svc.EnvName("OADP_UI_TITLE_PREFIX")).StringVar(&c.uiTitlePrefix) cmd.Flag("ui-preferences-file", "Path to JSON file storing UI preferences").StringVar(&c.uiPreferencesFile) cmd.Flag("log-server-requests", "Log server requests").Hidden().BoolVar(&c.logServerRequests) diff --git a/cli/command_snapshot.go b/cli/command_snapshot.go index d571e567739..ac7bba1cc72 100644 --- a/cli/command_snapshot.go +++ b/cli/command_snapshot.go @@ -16,17 +16,12 @@ type commandSnapshot struct { } func (c *commandSnapshot) setup(svc advancedAppServices, parent commandParent) { - cmd := parent.Command("snapshot", "Commands to manipulate snapshots.").Alias("snap") - c.copyHistory.setup(svc, cmd, false) - c.moveHistory.setup(svc, cmd, true) + // OADP: Renamed from "snapshot" to "backup" + cmd := parent.Command("backup", "Commands to manage backups.") + + // OADP: Only include subcommands needed for VM users c.create.setup(svc, cmd) c.delete.setup(svc, cmd) - c.estimate.setup(svc, cmd) - c.expire.setup(svc, cmd) - c.fix.setup(svc, cmd) c.list.setup(svc, cmd) - c.migrate.setup(svc, cmd) - c.pin.setup(svc, cmd) c.restore.setup(svc, cmd) - c.verify.setup(svc, cmd) } diff --git a/cli/command_snapshot_create.go b/cli/command_snapshot_create.go index 6a9a994fe7b..ff11f4e8575 100644 --- a/cli/command_snapshot_create.go +++ b/cli/command_snapshot_create.go @@ -62,7 +62,8 @@ func (c *commandSnapshotCreate) setup(svc appServices, parent commandParent) { cmd.Flag("upload-limit-mb", "Stop the backup process after the specified amount of data (in MB) has been uploaded.").PlaceHolder("MB").Default("0").Int64Var(&c.snapshotCreateCheckpointUploadLimitMB) cmd.Flag("checkpoint-interval", "Interval between periodic checkpoints (must be <= 45 minutes).").Hidden().DurationVar(&c.snapshotCreateCheckpointInterval) cmd.Flag("description", "Free-form snapshot description.").StringVar(&c.snapshotCreateDescription) - cmd.Flag("fail-fast", "Fail fast when creating snapshot.").Envar(svc.EnvName("KOPIA_SNAPSHOT_FAIL_FAST")).BoolVar(&c.snapshotCreateFailFast) + // OADP: Changed from KOPIA_SNAPSHOT_FAIL_FAST to OADP_BACKUP_FAIL_FAST + cmd.Flag("fail-fast", "Fail fast when creating backup.").Envar(svc.EnvName("OADP_BACKUP_FAIL_FAST")).BoolVar(&c.snapshotCreateFailFast) cmd.Flag("force-hash", "Force hashing of source files for a given percentage of files [0.0 .. 100.0]").Default("0").Float64Var(&c.snapshotCreateForceHash) cmd.Flag("parallel", "Upload N files in parallel").PlaceHolder("N").Default("0").IntVar(&c.snapshotCreateParallelUploads) cmd.Flag("start-time", "Override snapshot start timestamp.").StringVar(&c.snapshotCreateStartTime) diff --git a/cli/command_snapshot_estimate_test.go b/cli/command_snapshot_estimate_test.go index 89e82bc333c..e874a5099fe 100644 --- a/cli/command_snapshot_estimate_test.go +++ b/cli/command_snapshot_estimate_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_snapshot_fix_test.go b/cli/command_snapshot_fix_test.go index c4410073094..5ed4a47c3b1 100644 --- a/cli/command_snapshot_fix_test.go +++ b/cli/command_snapshot_fix_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_snapshot_list_test.go b/cli/command_snapshot_list_test.go index 851834be70d..7c0eb773412 100644 --- a/cli/command_snapshot_list_test.go +++ b/cli/command_snapshot_list_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_snapshot_pin_test.go b/cli/command_snapshot_pin_test.go index d80b5782a4a..c7d0250798d 100644 --- a/cli/command_snapshot_pin_test.go +++ b/cli/command_snapshot_pin_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_snapshot_verify_test.go b/cli/command_snapshot_verify_test.go index b495e7e6e85..081bb2d7495 100644 --- a/cli/command_snapshot_verify_test.go +++ b/cli/command_snapshot_verify_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/command_user_hash_password_test.go b/cli/command_user_hash_password_test.go index fcea45653b5..a04a4566dad 100644 --- a/cli/command_user_hash_password_test.go +++ b/cli/command_user_hash_password_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/config.go b/cli/config.go index f3ca28b5aab..2e0933f7f7f 100644 --- a/cli/config.go +++ b/cli/config.go @@ -45,7 +45,8 @@ func (c *App) openRepository(ctx context.Context, required bool) (repo.Repositor return nil, nil } - return nil, errors.New("repository is not connected. See https://kopia.io/docs/repositories/") + // OADP: Updated error message for BSL terminology + return nil, errors.New("not connected to a Backup Storage Location (BSL). Use 'oadp-vmdp bsl connect' or 'oadp-vmdp bsl create'") } c.maybePrintUpdateNotification(ctx) @@ -57,7 +58,8 @@ func (c *App) openRepository(ctx context.Context, required bool) (repo.Repositor r, err := repo.Open(ctx, c.repositoryConfigFileName(), pass, c.optionsFromFlags(ctx)) if os.IsNotExist(err) { - return nil, errors.New("not connected to a repository, use 'kopia connect'") + // OADP: Updated error message for BSL terminology + return nil, errors.New("not connected to a BSL, use 'oadp-vmdp bsl connect'") } return r, errors.Wrap(err, "unable to open repository") diff --git a/cli/oadp_config.go b/cli/oadp_config.go new file mode 100644 index 00000000000..d3f70ccda05 --- /dev/null +++ b/cli/oadp_config.go @@ -0,0 +1,35 @@ +package cli + +// OADP-VMDP Branding Constants. +const ( + // AppName is the CLI application name. + AppName = "oadp-vmdp" + + // AppDisplayName is a human-friendly display name for the CLI. + AppDisplayName = "OADP VM Data Protection" + + // AppDescription is the CLI application description. + AppDescription = "Virtual Machine Data Protection for OpenShift Virtualization" + + // AppLongDescription is the kingpin application description. + AppLongDescription = AppDisplayName + " - " + AppDescription + + // AppAuthor is the CLI application author. + AppAuthor = "Red Hat, Inc. " +) + +// S3 Storage Constants. +const ( + // OADPPrefix is automatically prepended to all S3 storage prefixes. + // This ensures OADP data is isolated within shared buckets. + OADPPrefix = "oadp-vmdp/" +) + +// Directory Constants. +const ( + // ConfigDirName is the directory name for configuration files. + ConfigDirName = "oadp" + + // LogFilePrefix is the prefix for log files. + LogFilePrefix = "oadp-" +) diff --git a/cli/observability_flags.go b/cli/observability_flags.go index 442d59acdde..393de392934 100644 --- a/cli/observability_flags.go +++ b/cli/observability_flags.go @@ -68,20 +68,20 @@ type observabilityFlags struct { } func (c *observabilityFlags) setup(svc appServices, app *kingpin.Application) { - app.Flag("dump-allocator-stats", "Dump allocator stats at the end of execution.").Hidden().Envar(svc.EnvName("KOPIA_DUMP_ALLOCATOR_STATS")).BoolVar(&c.dumpAllocatorStats) + app.Flag("dump-allocator-stats", "Dump allocator stats at the end of execution.").Hidden().Envar(svc.EnvName("OADP_DUMP_ALLOCATOR_STATS")).BoolVar(&c.dumpAllocatorStats) app.Flag("metrics-listen-addr", "Expose Prometheus metrics on a given host:port").Hidden().StringVar(&c.metricsListenAddr) app.Flag("enable-pprof", "Expose pprof handlers").Hidden().BoolVar(&c.enablePProfEndpoint) // push gateway parameters - app.Flag("metrics-push-addr", "Address of push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_ADDR")).Hidden().StringVar(&c.metricsPushAddr) - app.Flag("metrics-push-interval", "Frequency of metrics push").Envar(svc.EnvName("KOPIA_METRICS_PUSH_INTERVAL")).Hidden().Default("5s").DurationVar(&c.metricsPushInterval) - app.Flag("metrics-push-job", "Job ID for to push gateway").Envar(svc.EnvName("KOPIA_METRICS_JOB")).Hidden().Default("kopia").StringVar(&c.metricsJob) - app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_GROUPING")).Hidden().StringsVar(&c.metricsGroupings) - app.Flag("metrics-push-username", "Username for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_USERNAME")).Hidden().StringVar(&c.metricsPushUsername) - app.Flag("metrics-push-password", "Password for push gateway").Envar(svc.EnvName("KOPIA_METRICS_PUSH_PASSWORD")).Hidden().StringVar(&c.metricsPushPassword) + app.Flag("metrics-push-addr", "Address of push gateway").Envar(svc.EnvName("OADP_METRICS_PUSH_ADDR")).Hidden().StringVar(&c.metricsPushAddr) + app.Flag("metrics-push-interval", "Frequency of metrics push").Envar(svc.EnvName("OADP_METRICS_PUSH_INTERVAL")).Hidden().Default("5s").DurationVar(&c.metricsPushInterval) + app.Flag("metrics-push-job", "Job ID for to push gateway").Envar(svc.EnvName("OADP_METRICS_JOB")).Hidden().Default("oadp-vmdp").StringVar(&c.metricsJob) + app.Flag("metrics-push-grouping", "Grouping for push gateway").Envar(svc.EnvName("OADP_METRICS_PUSH_GROUPING")).Hidden().StringsVar(&c.metricsGroupings) + app.Flag("metrics-push-username", "Username for push gateway").Envar(svc.EnvName("OADP_METRICS_PUSH_USERNAME")).Hidden().StringVar(&c.metricsPushUsername) + app.Flag("metrics-push-password", "Password for push gateway").Envar(svc.EnvName("OADP_METRICS_PUSH_PASSWORD")).Hidden().StringVar(&c.metricsPushPassword) // tracing (OTLP) parameters - app.Flag("otlp-trace", "Send OpenTelemetry traces to OTLP collector using gRPC").Hidden().Envar(svc.EnvName("KOPIA_ENABLE_OTLP_TRACE")).BoolVar(&c.otlpTrace) + app.Flag("otlp-trace", "Send OpenTelemetry traces to OTLP collector using gRPC").Hidden().Envar(svc.EnvName("OADP_ENABLE_OTLP_TRACE")).BoolVar(&c.otlpTrace) var formats []string @@ -91,10 +91,10 @@ func (c *observabilityFlags) setup(svc appServices, app *kingpin.Application) { sort.Strings(formats) - app.Flag("metrics-push-format", "Format to use for push gateway").Envar(svc.EnvName("KOPIA_METRICS_FORMAT")).Hidden().EnumVar(&c.metricsPushFormat, formats...) + app.Flag("metrics-push-format", "Format to use for push gateway").Envar(svc.EnvName("OADP_METRICS_FORMAT")).Hidden().EnumVar(&c.metricsPushFormat, formats...) //nolint:lll - app.Flag("diagnostics-output-directory", "Directory where the diagnostics output should be stored saved when kopia exits. Diagnostics data includes among others: metrics, traces, profiles. The output files are stored in a sub-directory for each kopia (process) execution").Hidden().Default(filepath.Join(os.TempDir(), "kopia-diagnostics")).StringVar(&c.outputDirectory) + app.Flag("diagnostics-output-directory", "Directory where the diagnostics output should be stored saved when oadp-vmdp exits. Diagnostics data includes among others: metrics, traces, profiles. The output files are stored in a sub-directory for each oadp-vmdp (process) execution").Hidden().Default(filepath.Join(os.TempDir(), "oadp-vmdp-diagnostics")).StringVar(&c.outputDirectory) app.Flag("metrics-store-on-exit", "Writes metrics to a file in a sub-directory of the directory specified with the --diagnostics-output-directory").Hidden().BoolVar(&c.saveMetrics) @@ -247,7 +247,7 @@ func (c *observabilityFlags) maybeStartTraceExporter(ctx context.Context) error r := resource.NewWithAttributes( semconv.SchemaURL, - semconv.ServiceNameKey.String("kopia"), + semconv.ServiceNameKey.String("oadp-vmdp"), semconv.ServiceVersionKey.String(repo.BuildVersion), ) @@ -288,7 +288,7 @@ func (c *observabilityFlags) stop(ctx context.Context) { if metricsDir, err := mkSubdirectories(c.outputDirectory, c.outputSubdirectoryName); err != nil { log(ctx).Warnf("unable to create metrics output directory '%s': %v", metricsDir, err) } else { - if err := prometheus.WriteToTextfile(filepath.Join(metricsDir, "kopia-metrics.prom"), prometheus.DefaultGatherer); err != nil { + if err := prometheus.WriteToTextfile(filepath.Join(metricsDir, "oadp-vmdp-metrics.prom"), prometheus.DefaultGatherer); err != nil { log(ctx).Warnf("unable to write metrics to file: %v", err) } } diff --git a/cli/observability_flags_test.go b/cli/observability_flags_test.go index e0ab61ce955..4193d0ab042 100644 --- a/cli/observability_flags_test.go +++ b/cli/observability_flags_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/password_linux.go b/cli/password_linux.go index adc985bb493..fb6d885b500 100644 --- a/cli/password_linux.go +++ b/cli/password_linux.go @@ -5,5 +5,6 @@ import ( ) func (c *App) setupOSSpecificKeychainFlags(svc appServices, app *kingpin.Application) { - app.Flag("use-keyring", "Use Gnome Keyring for storing repository password.").Default("false").Envar(svc.EnvName("KOPIA_USE_KEYRING")).BoolVar(&c.keyRingEnabled) + // OADP: Changed from KOPIA_USE_KEYRING to OADP_USE_KEYRING, updated terminology + app.Flag("use-keyring", "Use Gnome Keyring for storing BSL password.").Default("false").Envar(svc.EnvName("OADP_USE_KEYRING")).BoolVar(&c.keyRingEnabled) } diff --git a/cli/storage_s3.go b/cli/storage_s3.go index 89f947c8733..e42a1ee046b 100644 --- a/cli/storage_s3.go +++ b/cli/storage_s3.go @@ -4,6 +4,7 @@ import ( "context" "encoding/base64" "os" + "strings" "time" "github.com/alecthomas/kingpin/v2" @@ -83,9 +84,55 @@ func (c *storageS3Flags) Connect(ctx context.Context, isCreate bool, formatVersi _ = formatVersion if isCreate && c.s3options.PointInTime != nil && !c.s3options.PointInTime.IsZero() { - return nil, errors.New("Cannot specify a 'point-in-time' option when creating a repository") + return nil, errors.New("Cannot specify a 'point-in-time' option when creating a BSL") } + // OADP: Normalize prefix to include oadp-vmdp/ prefix + normalizedPrefix, err := normalizeOADPPrefix(c.s3options.Prefix) + if err != nil { + return nil, err + } + + // OADP: Do not mutate c.s3options in-place (prevents double-normalization on repeated calls). + // Allocate opts explicitly so its lifetime is unambiguous to readers/review tools. + opts := new(s3.Options) + *opts = c.s3options + opts.Prefix = normalizedPrefix + //nolint:wrapcheck - return s3.New(ctx, &c.s3options, isCreate) + return s3.New(ctx, opts, isCreate) +} + +// normalizeOADPPrefix prepends "oadp-vmdp/" to the user-provided prefix. +// This ensures OADP data is isolated within shared buckets. +func normalizeOADPPrefix(userPrefix string) (string, error) { + const oadpPrefix = OADPPrefix // "oadp-vmdp/" from oadp_config.go + + // OADP: Reject leading/trailing whitespace to avoid hard-to-debug prefix mismatches. + // Internal spaces (e.g. "my backups/") are valid in S3 keys and are allowed. + if strings.TrimSpace(userPrefix) != userPrefix { + return "", errors.New("prefix must not start or end with whitespace") + } + + // OADP: Reject control whitespace which is almost certainly accidental. + if strings.ContainsAny(userPrefix, "\t\r\n") { + return "", errors.New("prefix must not contain control whitespace (tabs/newlines)") + } + + // Clean up any leading slashes from user prefix + cleanedPrefix := strings.TrimLeft(userPrefix, "/") + + // OADP: Ensure user doesn't include 'oadp-vmdp' as a path segment in their prefix. + // This prefix segment is automatically added. + for seg := range strings.SplitSeq(cleanedPrefix, "/") { + if seg == "" { + continue + } + + if strings.EqualFold(seg, "oadp-vmdp") { + return "", errors.New("prefix must not contain 'oadp-vmdp' as a path segment - this prefix is automatically added") + } + } + + return oadpPrefix + cleanedPrefix, nil } diff --git a/cli/storage_s3_test.go b/cli/storage_s3_test.go index ae4a146b370..86f7ee4a700 100644 --- a/cli/storage_s3_test.go +++ b/cli/storage_s3_test.go @@ -11,6 +11,92 @@ import ( "github.com/kopia/kopia/internal/testutil" ) +func TestNormalizeOADPPrefix(t *testing.T) { + cases := []struct { + name string + in string + want string + wantError bool + }{ + { + name: "empty-prefix", + in: "", + want: OADPPrefix, + }, + { + name: "leading-slash-trimmed", + in: "/my-prefix/", + want: OADPPrefix + "my-prefix/", + }, + { + name: "internal-spaces-allowed", + in: "my backups/", + want: OADPPrefix + "my backups/", + }, + { + name: "leading-whitespace-rejected", + in: " my-prefix/", + wantError: true, + }, + { + name: "trailing-whitespace-rejected", + in: "my-prefix/ ", + wantError: true, + }, + { + name: "tab-rejected", + in: "my\tprefix/", + wantError: true, + }, + { + name: "newline-rejected", + in: "my-prefix/\n", + wantError: true, + }, + { + name: "segment-oadp-vmdp-rejected", + in: "oadp-vmdp/foo/", + wantError: true, + }, + { + name: "segment-oadp-vmdp-case-insensitive-rejected", + in: "OADP-VMDP/foo/", + wantError: true, + }, + { + name: "segment-oadp-vmdp-in-middle-rejected", + in: "foo/oadp-vmdp/bar/", + wantError: true, + }, + { + name: "substring-not-a-segment-allowed", + in: "my-oadp-vmdp-migration/", + want: OADPPrefix + "my-oadp-vmdp-migration/", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := normalizeOADPPrefix(tc.in) + if tc.wantError { + if err == nil { + t.Fatalf("expected error, got none (result=%q)", got) + } + + return + } + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if got != tc.want { + t.Fatalf("got %q, want %q", got, tc.want) + } + }) + } +} + var ( fakeCertContent = []byte("fake certificate content") fakeCertContentAsBase64 = base64.StdEncoding.EncodeToString(fakeCertContent) diff --git a/cli/storage_webdav.go b/cli/storage_webdav.go index f86dd22ea34..391f5fcc7c1 100644 --- a/cli/storage_webdav.go +++ b/cli/storage_webdav.go @@ -18,8 +18,8 @@ type storageWebDAVFlags struct { func (c *storageWebDAVFlags) Setup(svc StorageProviderServices, cmd *kingpin.CmdClause) { cmd.Flag("url", "URL of WebDAV server").Required().StringVar(&c.options.URL) cmd.Flag("flat", "Use flat directory structure").BoolVar(&c.connectFlat) - cmd.Flag("webdav-username", "WebDAV username").Envar(svc.EnvName("KOPIA_WEBDAV_USERNAME")).StringVar(&c.options.Username) - cmd.Flag("webdav-password", "WebDAV password").Envar(svc.EnvName("KOPIA_WEBDAV_PASSWORD")).StringVar(&c.options.Password) + cmd.Flag("webdav-username", "WebDAV username").Envar(svc.EnvName("OADP_WEBDAV_USERNAME")).StringVar(&c.options.Username) + cmd.Flag("webdav-password", "WebDAV password").Envar(svc.EnvName("OADP_WEBDAV_PASSWORD")).StringVar(&c.options.Password) cmd.Flag("list-parallelism", "Set list parallelism").Hidden().IntVar(&c.options.ListParallelism) cmd.Flag("atomic-writes", "Assume WebDAV provider implements atomic writes").BoolVar(&c.options.AtomicWrites) diff --git a/cli/terminate_signal_test.go b/cli/terminate_signal_test.go index 4bffaab37ba..2010a8048ff 100644 --- a/cli/terminate_signal_test.go +++ b/cli/terminate_signal_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package cli_test import ( diff --git a/cli/update_check.go b/cli/update_check.go index 4089f02dd72..e96b2242b73 100644 --- a/cli/update_check.go +++ b/cli/update_check.go @@ -20,20 +20,22 @@ import ( ) const ( - checkForUpdatesEnvar = "KOPIA_CHECK_FOR_UPDATES" + // OADP: Changed from KOPIA_CHECK_FOR_UPDATES to OADP_CHECK_FOR_UPDATES. + checkForUpdatesEnvar = "OADP_CHECK_FOR_UPDATES" githubTimeout = 10 * time.Second ) const ( latestReleaseGitHubURLFormat = "https://api.github.com/repos/%v/releases/latest" checksumsURLFormat = "https://github.com/%v/releases/download/%v/checksums.txt.sig" - autoUpdateNotice = ` -NOTICE: Kopia will check for updates on GitHub every 7 days, starting 24 hours after first use. + // OADP: Updated notice messages. + autoUpdateNotice = ` +NOTICE: OADP-VMDP will check for updates on GitHub every 7 days, starting 24 hours after first use. To disable this behavior, set environment variable ` + checkForUpdatesEnvar + `=false Alternatively you can remove the file "%v". ` updateAvailableNoticeFormat = ` -Upgrade of Kopia from %v to %v is available. +Upgrade of OADP-VMDP from %v to %v is available. Visit https://github.com/%v/releases/latest to download it. ` diff --git a/cmd/downloads/server.go b/cmd/downloads/server.go new file mode 100644 index 00000000000..34a94192bee --- /dev/null +++ b/cmd/downloads/server.go @@ -0,0 +1,218 @@ +// Package main implements the oadp-vmdp download server for binary distribution. +package main + +import ( + "embed" + "fmt" + "html/template" + "io/fs" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +const ( + binaryPrefix = "oadp-vmdp_" + licenseFile = "LICENSE" + bytesPerMB = 1024 * 1024 + minPlatformParts = 3 + readTimeout = 10 * time.Second + writeTimeout = 60 * time.Second +) + +//go:embed templates/*.html +var templateFS embed.FS + +//go:embed static/* +var staticFS embed.FS + +var ( + binaryDir = getEnv("ARCHIVE_DIR", "/archives") + port = getEnv("PORT", "8080") + pageTemplate = template.Must(template.ParseFS(templateFS, "templates/index.html")) +) + +func getEnv(key, fallback string) string { + if v := os.Getenv(key); v != "" { + return v + } + + return fallback +} + +type binaryFile struct { + Name string + Size float64 + OS string + Arch string + Checksum string +} + +func main() { + files, err := discoverBinaries() + if err != nil || len(files) == 0 { + log.Fatal("No binaries found in ", binaryDir) + } + + log.Printf("Found %d binaries", len(files)) + + staticContent, err := fs.Sub(staticFS, "static") + if err != nil { + log.Fatal("Failed to load static files: ", err) + } + + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticContent)))) + http.HandleFunc("/", listBinaries) + http.HandleFunc("/download/", downloadBinary) + + log.Printf("Starting server on port %s", port) + log.Printf("Serving binaries from %s", binaryDir) + + srv := &http.Server{ + Addr: ":" + port, + ReadTimeout: readTimeout, + WriteTimeout: writeTimeout, + } + + if err := srv.ListenAndServe(); err != nil { + log.Fatal(err) + } +} + +// discoverBinaries finds oadp-vmdp binaries (excluding .sha256 and LICENSE files). +func discoverBinaries() ([]string, error) { + entries, err := os.ReadDir(binaryDir) + if err != nil { + return nil, fmt.Errorf("reading binary directory: %w", err) + } + + var binaries []string + + for _, e := range entries { + name := e.Name() + + if e.IsDir() || strings.HasSuffix(name, ".sha256") || name == licenseFile { + continue + } + + if strings.HasPrefix(name, binaryPrefix) { + binaries = append(binaries, filepath.Join(binaryDir, name)) + } + } + + return binaries, nil +} + +// readChecksum reads a SHA256 checksum from a per-file .sha256 sidecar file. +func readChecksum(filePath string) string { + data, err := os.ReadFile(filePath + ".sha256") //nolint:gosec // path is constructed from binaryDir constant + if err != nil { + return "" + } + + fields := strings.Fields(string(data)) + if len(fields) > 0 { + return fields[0] + } + + return "" +} + +// parsePlatform extracts OS and architecture from a filename like +// oadp-vmdp_linux_amd64 or oadp-vmdp_windows_arm64.exe. +func parsePlatform(filename string) (string, string) { + name := strings.TrimSuffix(filename, ".exe") + + parts := strings.Split(name, "_") + if len(parts) >= minPlatformParts { + return parts[len(parts)-2], parts[len(parts)-1] + } + + return "unknown", "unknown" +} + +func listBinaries(w http.ResponseWriter, _ *http.Request) { + files, err := discoverBinaries() + if err != nil { + http.Error(w, "Error listing binaries", http.StatusInternalServerError) + return + } + + hasLicense := false + if _, err := os.Stat(filepath.Join(binaryDir, licenseFile)); err == nil { + hasLicense = true + } + + var linuxFiles, windowsFiles []binaryFile + + for _, file := range files { + name := filepath.Base(file) + + info, err := os.Stat(file) + if err != nil { + continue + } + + size := float64(info.Size()) / bytesPerMB + osName, arch := parsePlatform(name) + checksum := readChecksum(file) + + bf := binaryFile{Name: name, Size: size, OS: osName, Arch: arch, Checksum: checksum} + + switch osName { + case "linux": + linuxFiles = append(linuxFiles, bf) + case "windows": + windowsFiles = append(windowsFiles, bf) + } + } + + data := struct { + LinuxFiles []binaryFile + WindowsFiles []binaryFile + HasLicense bool + }{linuxFiles, windowsFiles, hasLicense} + + w.Header().Set("Content-Type", "text/html") + + if err := pageTemplate.Execute(w, data); err != nil { + log.Printf("Template error: %v", err) + } +} + +func downloadBinary(w http.ResponseWriter, r *http.Request) { + filename := filepath.Base(r.URL.Path[len("/download/"):]) + + // Security: only allow known file prefixes and the LICENSE file. + if filepath.Dir(filename) != "." { + http.Error(w, "Invalid filename", http.StatusBadRequest) + return + } + + if !strings.HasPrefix(filename, binaryPrefix) && filename != licenseFile { + http.Error(w, "Invalid filename", http.StatusBadRequest) + return + } + + filePath := filepath.Join(binaryDir, filename) + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + http.Error(w, "File not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Disposition", "attachment; filename="+filename) + + if filename == licenseFile { + w.Header().Set("Content-Type", "text/plain") + } else { + w.Header().Set("Content-Type", "application/octet-stream") + } + + http.ServeFile(w, r, filePath) + + log.Printf("Downloaded: %s from %s", filename, r.RemoteAddr) +} diff --git a/cmd/downloads/static/style.css b/cmd/downloads/static/style.css new file mode 100644 index 00000000000..abe16972f19 --- /dev/null +++ b/cmd/downloads/static/style.css @@ -0,0 +1,319 @@ +/* Red Hat Brand Colors (https://ux.redhat.com/foundations/color/) */ +:root { + --rh-red: #ee0000; + --rh-red-dark: #a60000; + --rh-black: #151515; + --rh-white: #ffffff; + --rh-gray-10: #f2f2f2; + --rh-gray-20: #e0e0e0; + --rh-gray-30: #c7c7c7; + --rh-gray-50: #707070; + --rh-gray-60: #4d4d4d; + --rh-gray-90: #1f1f1f; +} + +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + font-family: "Red Hat Text", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + background: var(--rh-gray-10); + color: var(--rh-black); + min-height: 100vh; +} + +.header { + background: var(--rh-red); + padding: 2.5rem 0; + color: var(--rh-white); +} + +.container { + max-width: 860px; + margin: 0 auto; + padding: 0 1.5rem; +} + +.logo { + font-size: 1.6rem; + font-weight: 700; +} + +.logo-link { + color: var(--rh-white); + text-decoration: none; +} + +.logo-link:hover { text-decoration: underline; } + +.subtitle { + color: rgba(255,255,255,0.8); + margin-top: 0.4rem; + font-size: 0.95rem; +} + +.binary-note { + margin-top: 0.5rem; + font-size: 0.8rem; + color: rgba(255,255,255,0.65); +} + +.binary-note code { + background: rgba(255,255,255,0.15); + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-size: 0.8rem; +} + +.content { + padding: 2rem 0; +} + +.section { + background: var(--rh-white); + border: 1px solid var(--rh-gray-20); + border-radius: 0.75rem; + overflow: hidden; + margin-bottom: 1.5rem; +} + +.section-header { + padding: 0.85rem 1.25rem; + background: var(--rh-gray-10); + border-bottom: 1px solid var(--rh-gray-20); + font-weight: 600; + font-size: 0.9rem; + color: var(--rh-black); + display: flex; + align-items: center; + gap: 0.5rem; +} + +.section-header .os-logo { + height: 1.6rem; + width: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +th { + text-align: left; + padding: 0.6rem 1.25rem; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--rh-gray-50); + border-bottom: 1px solid var(--rh-gray-20); + background: var(--rh-gray-10); +} + +td { + padding: 0.85rem 1.25rem; + border-bottom: 1px solid var(--rh-gray-20); + font-size: 0.9rem; +} + +tr:last-child td { border-bottom: none; } +tr:hover td { background: var(--rh-gray-10); } + +.arch-badge { + display: inline-block; + background: var(--rh-gray-10); + color: var(--rh-gray-60); + padding: 0.2rem 0.6rem; + border-radius: 0.35rem; + font-size: 0.8rem; + font-weight: 500; + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + border: 1px solid var(--rh-gray-20); +} + +.size { + color: var(--rh-gray-50); + font-size: 0.85rem; +} + +.checksum { + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + font-size: 0.7rem; + color: var(--rh-gray-50); + word-break: break-all; + max-width: 220px; + cursor: pointer; + position: relative; +} + +.checksum:hover { color: var(--rh-gray-60); } + +.checksum .copy-hint { + display: none; + position: absolute; + top: -1.6rem; + left: 0; + background: var(--rh-black); + color: var(--rh-white); + padding: 0.15rem 0.4rem; + border-radius: 0.25rem; + font-size: 0.65rem; + white-space: nowrap; +} + +.checksum:hover .copy-hint { display: block; } + +.download-btn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: var(--rh-red); + color: var(--rh-white); + padding: 0.45rem 0.9rem; + border-radius: 0.4rem; + text-decoration: none; + font-size: 0.82rem; + font-weight: 500; + transition: background 0.15s; +} + +.download-btn:hover { background: var(--rh-red-dark); } + +.install-section { + margin-top: 0.5rem; +} + +.install-section h3 { + font-size: 1rem; + font-weight: 600; + color: var(--rh-black); + margin-bottom: 0.75rem; +} + +.code-block { + background: var(--rh-gray-90); + border-radius: 0.5rem; + padding: 0.75rem 0; + overflow-x: auto; + font-family: "Red Hat Mono", "SF Mono", Consolas, monospace; + font-size: 0.85rem; + line-height: 1.4; + margin-bottom: 1.5rem; +} + +.code-line { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.3rem 1.25rem; +} + +.code-line:hover { background: rgba(255,255,255,0.04); } + +.code-line .comment { + color: var(--rh-gray-50); + padding-top: 0.4rem; +} + +.code-line .cmd { + color: var(--rh-gray-20); + font-family: inherit; +} + +.copy-btn { + background: transparent; + border: 1px solid var(--rh-gray-60); + color: var(--rh-gray-50); + padding: 0.2rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.7rem; + font-family: "Red Hat Text", -apple-system, sans-serif; + cursor: pointer; + transition: all 0.15s; + white-space: nowrap; + flex-shrink: 0; + margin-left: 1rem; +} + +.copy-btn:hover { + border-color: var(--rh-gray-30); + color: var(--rh-gray-20); + background: rgba(255,255,255,0.08); +} + +.tab-bar { + display: flex; + gap: 0; + margin-bottom: 0; +} + +.tab-btn { + background: var(--rh-gray-20); + border: none; + padding: 0.5rem 1.2rem; + font-size: 0.82rem; + font-family: inherit; + font-weight: 500; + color: var(--rh-gray-60); + cursor: pointer; + border-radius: 0.5rem 0.5rem 0 0; + transition: background 0.15s, color 0.15s; +} + +.tab-btn.active { + background: var(--rh-gray-90); + color: var(--rh-gray-20); +} + +.tab-btn:hover:not(.active) { + background: var(--rh-gray-30); +} + +.tab-content { + display: none; +} + +.tab-content.active { + display: block; +} + +.tab-content .code-block { + border-radius: 0 0.5rem 0.5rem 0.5rem; + margin-bottom: 0; +} + +.tip { + background: var(--rh-white); + border: 1px solid var(--rh-gray-20); + border-left: 4px solid var(--rh-red); + border-radius: 0 0.5rem 0.5rem 0; + padding: 0.85rem 1.25rem; + margin-top: 1.5rem; + font-size: 0.88rem; + color: var(--rh-gray-60); +} + +.tip strong { + color: var(--rh-black); +} + +.tip code { + background: var(--rh-gray-10); + padding: 0.1rem 0.35rem; + border-radius: 0.2rem; + font-size: 0.82rem; +} + +.footer { + text-align: center; + padding: 2rem 0; + color: var(--rh-gray-50); + font-size: 0.8rem; + border-top: 1px solid var(--rh-gray-20); +} + +.footer a { + color: var(--rh-red); + text-decoration: none; +} + +.footer a:hover { text-decoration: underline; } diff --git a/cmd/downloads/templates/index.html b/cmd/downloads/templates/index.html new file mode 100644 index 00000000000..e8ec2212dd1 --- /dev/null +++ b/cmd/downloads/templates/index.html @@ -0,0 +1,204 @@ + + + + + + OADP VMDP Downloads + + + +
+
+ +

OpenShift API for Data Protection — Virtual Machine Data Protection

+

Back up and restore data inside OpenShift Virtualization guest VMs to S3-compatible or filesystem storage

+
+
+ +
+ {{if .LinuxFiles}} +
+
Download for Linux & Unix
+ + + + + + + + + + + + {{range .LinuxFiles}} + + + + + + + + {{end}} + +
BinaryArchitectureSizeSHA256
{{.Name}}{{.Arch}}{{printf "%.2f" .Size}} MB{{if .Checksum}}Click to copy{{slice .Checksum 0 16}}...{{else}}—{{end}}Download
+
+ {{end}} + + {{if .WindowsFiles}} +
+
Download for Windows
+ + + + + + + + + + + + {{range .WindowsFiles}} + + + + + + + + {{end}} + +
BinaryArchitectureSizeSHA256
{{.Name}}{{.Arch}}{{printf "%.2f" .Size}} MB{{if .Checksum}}Click to copy{{slice .Checksum 0 16}}...{{else}}—{{end}}Download
+
+ {{end}} + + {{if .HasLicense}} +
+
License
+
+ LICENSE + View License +
+
+ {{end}} + +
+

Installation

+ +
+ + +
+ +
+
+
+ # Make it executable +
+
+ chmod +x oadp-vmdp_linux_* + +
+
+ # Move to your PATH +
+
+ sudo mv oadp-vmdp_linux_* /usr/local/bin/oadp-vmdp + +
+
+ # Verify it works +
+
+ oadp-vmdp --version + +
+
+
+ +
+
+
+ # Rename the downloaded binary +
+
+ Rename-Item oadp-vmdp_windows_*.exe oadp-vmdp.exe + +
+
+ # Verify it works +
+
+ .\oadp-vmdp.exe --version + +
+
+
+
+ +
+

Quick Start

+
+
+ # Connect to a backup storage location +
+
+ oadp-vmdp bsl create s3 --bucket my-bucket --endpoint s3.example.com --access-key <KEY> --secret-access-key <SECRET> + +
+
+ # Back up your data +
+
+ oadp-vmdp backup create /path/to/data + +
+
+ # Restore from a backup +
+
+ oadp-vmdp restore /path/to/data + +
+
+ # See all available commands +
+
+ oadp-vmdp --help + +
+
+
+ +
+ Tip: Set the BSLS_PASSWORD environment variable before + running any command to avoid the interactive password prompt — useful for + scripts and automation. +
+ +
+ + + + diff --git a/internal/logfile/logfile.go b/internal/logfile/logfile.go index 51f310e1b8a..e9031b4c5ec 100644 --- a/internal/logfile/logfile.go +++ b/internal/logfile/logfile.go @@ -64,23 +64,24 @@ func (c *loggingFlags) setup(cliApp *cli.App, app *kingpin.Application) { app.Flag("disable-file-logging", "Disable file-based logging.").BoolVar(&c.disableFileLogging) app.Flag("disable-content-log", "Disable creation of content logs.").BoolVar(&c.disableContentLogs) - app.Flag("log-dir", "Directory where log files should be written.").Envar(cliApp.EnvName("KOPIA_LOG_DIR")).Default(ospath.LogsDir()).StringVar(&c.logDir) - app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_FILES")).Default("1000").Hidden().IntVar(&c.logDirMaxFiles) - app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_AGE")).Hidden().Default("720h").DurationVar(&c.logDirMaxAge) - app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB) - app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar(cliApp.EnvName("KOPIA_LOG_FILE_MAX_SEGMENT_SIZE")).Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize) + // OADP: Changed KOPIA_* env vars to OADP_* + app.Flag("log-dir", "Directory where log files should be written.").Envar(cliApp.EnvName("OADP_LOG_DIR")).Default(ospath.LogsDir()).StringVar(&c.logDir) + app.Flag("log-dir-max-files", "Maximum number of log files to retain").Envar(cliApp.EnvName("OADP_LOG_DIR_MAX_FILES")).Default("1000").Hidden().IntVar(&c.logDirMaxFiles) + app.Flag("log-dir-max-age", "Maximum age of log files to retain").Envar(cliApp.EnvName("OADP_LOG_DIR_MAX_AGE")).Hidden().Default("720h").DurationVar(&c.logDirMaxAge) + app.Flag("log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("OADP_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.logDirMaxTotalSizeMB) + app.Flag("max-log-file-segment-size", "Maximum size of a single log file segment").Envar(cliApp.EnvName("OADP_LOG_FILE_MAX_SEGMENT_SIZE")).Default("50000000").Hidden().IntVar(&c.logFileMaxSegmentSize) app.Flag("wait-for-log-sweep", "Wait for log sweep before program exit").Default("true").Hidden().BoolVar(&c.waitForLogSweep) - app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_FILES")).Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles) - app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_AGE")).Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge) - app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("KOPIA_CONTENT_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB) + app.Flag("content-log-dir-max-files", "Maximum number of content log files to retain").Envar(cliApp.EnvName("OADP_CONTENT_LOG_DIR_MAX_FILES")).Default("5000").Hidden().IntVar(&c.contentLogDirMaxFiles) + app.Flag("content-log-dir-max-age", "Maximum age of content log files to retain").Envar(cliApp.EnvName("OADP_CONTENT_LOG_DIR_MAX_AGE")).Default("720h").Hidden().DurationVar(&c.contentLogDirMaxAge) + app.Flag("content-log-dir-max-total-size-mb", "Maximum total size of log files to retain").Envar(cliApp.EnvName("OADP_CONTENT_LOG_DIR_MAX_SIZE_MB")).Hidden().Default("1000").Float64Var(&c.contentLogDirMaxTotalSizeMB) app.Flag("log-level", "Console log level").Default("info").EnumVar(&c.logLevel, logLevels...) app.Flag("json-log-console", "JSON log file").Hidden().BoolVar(&c.jsonLogConsole) app.Flag("json-log-file", "JSON log file").Hidden().BoolVar(&c.jsonLogFile) app.Flag("file-log-level", "File log level").Default("debug").EnumVar(&c.fileLogLevel, logLevels...) - app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Default("false").Hidden().Envar(cliApp.EnvName("KOPIA_FILE_LOG_LOCAL_TZ")).BoolVar(&c.fileLogLocalTimezone) - app.Flag("force-color", "Force color output").Hidden().Envar(cliApp.EnvName("KOPIA_FORCE_COLOR")).BoolVar(&c.forceColor) - app.Flag("disable-color", "Disable color output").Hidden().Envar(cliApp.EnvName("KOPIA_DISABLE_COLOR")).BoolVar(&c.disableColor) - app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar(cliApp.EnvName("KOPIA_CONSOLE_TIMESTAMPS")).BoolVar(&c.consoleLogTimestamps) + app.Flag("file-log-local-tz", "When logging to a file, use local timezone").Hidden().Envar(cliApp.EnvName("OADP_FILE_LOG_LOCAL_TZ")).BoolVar(&c.fileLogLocalTimezone) + app.Flag("force-color", "Force color output").Hidden().Envar(cliApp.EnvName("OADP_FORCE_COLOR")).BoolVar(&c.forceColor) + app.Flag("disable-color", "Disable color output").Hidden().Envar(cliApp.EnvName("OADP_DISABLE_COLOR")).BoolVar(&c.disableColor) + app.Flag("console-timestamps", "Log timestamps to stderr.").Hidden().Default("false").Envar(cliApp.EnvName("OADP_CONSOLE_TIMESTAMPS")).BoolVar(&c.consoleLogTimestamps) app.PreAction(c.initialize) c.cliApp = cliApp @@ -92,10 +93,11 @@ func Attach(cliApp *cli.App, app *kingpin.Application) { lf.setup(cliApp, app) } -var log = logging.Module("kopia") +var log = logging.Module("oadp") const ( - logFileNamePrefix = "kopia-" + // OADP: Changed from "kopia-" to "oadp-". + logFileNamePrefix = "oadp-" logFileNameSuffix = ".log" ) diff --git a/internal/ospath/ospath.go b/internal/ospath/ospath.go index 92b94b71980..e332479e27f 100644 --- a/internal/ospath/ospath.go +++ b/internal/ospath/ospath.go @@ -16,12 +16,14 @@ var ( // ConfigDir returns the directory where configuration data (possibly roaming) needs to be stored. func ConfigDir() string { - return filepath.Join(userSettingsDir, "kopia") + // OADP: Changed from "kopia" to "oadp" + return filepath.Join(userSettingsDir, "oadp") } // LogsDir returns the directory where per-user logs should be written. func LogsDir() string { - return filepath.Join(userLogsDir, "kopia") + // OADP: Changed from "kopia" to "oadp" + return filepath.Join(userLogsDir, "oadp") } // IsAbs determines if a given path is absolute, in particular treating \\hostname\share as absolute on Windows. diff --git a/konflux.Dockerfile b/konflux.Dockerfile new file mode 100644 index 00000000000..2e0f8018927 --- /dev/null +++ b/konflux.Dockerfile @@ -0,0 +1,69 @@ +# Konflux hermetic build for the oadp-vmdp download server +# Dependencies are prefetched by the Konflux pipeline (Hermeto) and injected +# into the build context before this Containerfile runs. + +FROM brew.registry.redhat.io/rh-osbs/openshift-golang-builder:rhel_9_golang_1.25 AS builder + +COPY . /workspace +WORKDIR /workspace + +ENV GOEXPERIMENT=strictfipsruntime + +# Version information +ARG VERSION=dev +ARG GIT_COMMIT=unknown +ARG BUILD_DATE=unknown +ARG BUILDTAGS= + +# Build oadp-vmdp binaries for all target platforms as direct executables +# with clean names (no version/commit hash) for direct curl/wget download. +RUN mkdir -p /archives && \ + for platform in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do \ + os=$(echo $platform | cut -d'/' -f1); \ + arch=$(echo $platform | cut -d'/' -f2); \ + if [ "$os" = "windows" ]; then \ + out_name="oadp-vmdp_${os}_${arch}.exe"; \ + else \ + out_name="oadp-vmdp_${os}_${arch}"; \ + fi; \ + echo "Building oadp-vmdp for ${os}/${arch}..."; \ + CGO_ENABLED=0 GOOS=$os GOARCH=$arch \ + go build -trimpath -mod=mod \ + -tags="${BUILDTAGS}" \ + -ldflags="-s -w \ + -X github.com/kopia/kopia/repo.BuildVersion=${VERSION} \ + -X github.com/kopia/kopia/repo.BuildInfo=${BUILD_DATE}-${GIT_COMMIT} \ + -X github.com/kopia/kopia/repo.BuildGitHubRepo=github.com/openshift/oadp-vmdp" \ + -o /archives/$out_name \ + . ; \ + sha256sum /archives/$out_name > /archives/$out_name.sha256; \ + done && \ + chmod -x /archives/oadp-vmdp_* && \ + cp LICENSE /archives/LICENSE && \ + rm -rf /root/.cache/go-build /tmp/* + +# Build the download server (FIPS-compliant) +RUN CGO_ENABLED=1 GOOS=linux go build -mod=mod -a -tags strictfipsruntime \ + -o /workspace/bin/download-server ./cmd/downloads/ && \ + go clean -cache -modcache -testcache && \ + rm -rf /root/.cache/go-build /go/pkg + +FROM registry.redhat.io/ubi9/ubi:latest + +RUN dnf -y install openssl && dnf -y reinstall tzdata && dnf clean all + +COPY --from=builder /archives /archives +COPY --from=builder /workspace/bin/download-server /usr/local/bin/download-server +COPY LICENSE /licenses/ + +EXPOSE 8080 + +USER 65532:65532 + +ENTRYPOINT ["/usr/local/bin/download-server"] + +LABEL description="OADP VMDP - Binary Download Server" +LABEL io.k8s.description="OADP VMDP - Binary Download Server" +LABEL io.k8s.display-name="OADP VMDP Downloads" +LABEL io.openshift.tags="oadp,backup,restore,virtualization,vmdp" +LABEL summary="Serves pre-built oadp-vmdp binaries for Linux and Windows" diff --git a/main.go b/main.go index eb78e801aae..22562e488eb 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,11 @@ /* -Command-line tool for creating and accessing backups. +OADP VM Data Protection - Virtual Machine Data Protection for OpenShift Virtualization. Usage: - $ kopia [] [ ...] + $ oadp-vmdp [] [ ...] -Use 'kopia help' to see more details. +Use 'oadp-vmdp help' to see more details. */ package main @@ -65,7 +65,7 @@ Commands (use --help-full to list all commands): func main() { app := cli.NewApp() - kp := kingpin.New("kopia", "Kopia - Fast And Secure Open-Source Backup").Author("http://kopia.github.io/") + kp := kingpin.New(cli.AppName, cli.AppLongDescription).Author(cli.AppAuthor) kp.Version(repo.BuildVersion + " build: " + repo.BuildInfo + " from: " + repo.BuildGitHubRepo) logfile.Attach(app, kp) diff --git a/repo/blob/sftp/sftp_storage_test.go b/repo/blob/sftp/sftp_storage_test.go index 306473ca26e..1d02610404a 100644 --- a/repo/blob/sftp/sftp_storage_test.go +++ b/repo/blob/sftp/sftp_storage_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package sftp_test import ( diff --git a/repo/local_config.go b/repo/local_config.go index f6567a723b7..cdf3bae113b 100644 --- a/repo/local_config.go +++ b/repo/local_config.go @@ -88,7 +88,7 @@ func (o ClientOptions) UsernameAtHost() string { return o.Username + "@" + o.Hostname } -// LocalConfig is a configuration of Kopia stored in a configuration file. +// LocalConfig is a configuration of OADP-VMDP stored in a configuration file. type LocalConfig struct { // APIServer is only provided for remote repository. APIServer *APIServerInfo `json:"apiServer,omitempty"` @@ -147,14 +147,15 @@ func LoadConfigFromFile(fileName string) (*LocalConfig, error) { lc.Caching.CacheDirectory = filepath.Join(filepath.Dir(fileName), lc.Caching.CacheDirectory) } - // override cache directory from the environment variable. - if cd := os.Getenv("KOPIA_CACHE_DIRECTORY"); cd != "" && ospath.IsAbs(cd) { + // OADP: override cache directory from the environment variable. + if cd := os.Getenv("OADP_CACHE_DIRECTORY"); cd != "" && ospath.IsAbs(cd) { lc.Caching.CacheDirectory = cd } } - if lc.PermissiveCacheLoading && os.Getenv("KOPIA_UPGRADE_LOCK_ENABLED") == "" { - return nil, errors.New("must have set KOPIA_UPGRADE_LOCK_ENABLED when connecting to repository with permissive cache loading") + // OADP: Changed KOPIA_UPGRADE_LOCK_ENABLED to OADP_UPGRADE_LOCK_ENABLED + if lc.PermissiveCacheLoading && os.Getenv("OADP_UPGRADE_LOCK_ENABLED") == "" { + return nil, errors.New("must have set OADP_UPGRADE_LOCK_ENABLED when connecting to BSL with permissive cache loading") } return &lc, nil diff --git a/tests/end_to_end_test/acl_test.go b/tests/end_to_end_test/acl_test.go index 548c92e65d2..d844d6175a5 100644 --- a/tests/end_to_end_test/acl_test.go +++ b/tests/end_to_end_test/acl_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/api_server_repository_test.go b/tests/end_to_end_test/api_server_repository_test.go index cc6b458b88b..64858bd734b 100644 --- a/tests/end_to_end_test/api_server_repository_test.go +++ b/tests/end_to_end_test/api_server_repository_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/auto_update_test.go b/tests/end_to_end_test/auto_update_test.go index 0f38f830374..5c59234c196 100644 --- a/tests/end_to_end_test/auto_update_test.go +++ b/tests/end_to_end_test/auto_update_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/compression_test.go b/tests/end_to_end_test/compression_test.go index 99d32082104..1228376eb43 100644 --- a/tests/end_to_end_test/compression_test.go +++ b/tests/end_to_end_test/compression_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/content_info_test.go b/tests/end_to_end_test/content_info_test.go index 15291a37db2..5187e934533 100644 --- a/tests/end_to_end_test/content_info_test.go +++ b/tests/end_to_end_test/content_info_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/diff_test.go b/tests/end_to_end_test/diff_test.go index b847c7c8819..0c0b718a0e0 100644 --- a/tests/end_to_end_test/diff_test.go +++ b/tests/end_to_end_test/diff_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/ecc_test.go b/tests/end_to_end_test/ecc_test.go index 8f095ca2ff7..571f820fcf8 100644 --- a/tests/end_to_end_test/ecc_test.go +++ b/tests/end_to_end_test/ecc_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/index_optimize_test.go b/tests/end_to_end_test/index_optimize_test.go index 1721ef7a7e5..d9143b1b24b 100644 --- a/tests/end_to_end_test/index_optimize_test.go +++ b/tests/end_to_end_test/index_optimize_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/index_recover_test.go b/tests/end_to_end_test/index_recover_test.go index f2c520454df..adae43be502 100644 --- a/tests/end_to_end_test/index_recover_test.go +++ b/tests/end_to_end_test/index_recover_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/maintenance_test.go b/tests/end_to_end_test/maintenance_test.go index 4cd5a2b7d86..0f8fc7b01c3 100644 --- a/tests/end_to_end_test/maintenance_test.go +++ b/tests/end_to_end_test/maintenance_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/policy_test.go b/tests/end_to_end_test/policy_test.go index 95ff7cada97..96184bd5cc9 100644 --- a/tests/end_to_end_test/policy_test.go +++ b/tests/end_to_end_test/policy_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/repository_connect_test.go b/tests/end_to_end_test/repository_connect_test.go index 0294347974f..bb6d00b3b2d 100644 --- a/tests/end_to_end_test/repository_connect_test.go +++ b/tests/end_to_end_test/repository_connect_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/repository_repair_test.go b/tests/end_to_end_test/repository_repair_test.go index 79923928a27..c30acc32ecf 100644 --- a/tests/end_to_end_test/repository_repair_test.go +++ b/tests/end_to_end_test/repository_repair_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/repository_set_client_test.go b/tests/end_to_end_test/repository_set_client_test.go index b9e2e709a75..0f299b3b360 100644 --- a/tests/end_to_end_test/repository_set_client_test.go +++ b/tests/end_to_end_test/repository_set_client_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/repository_sync_test.go b/tests/end_to_end_test/repository_sync_test.go index 6be0a9643bf..4252983a463 100644 --- a/tests/end_to_end_test/repository_sync_test.go +++ b/tests/end_to_end_test/repository_sync_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/restore_fail_test.go b/tests/end_to_end_test/restore_fail_test.go index 07fd8c15a46..d1ad4a3e07c 100644 --- a/tests/end_to_end_test/restore_fail_test.go +++ b/tests/end_to_end_test/restore_fail_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/server_repo_logs_test.go b/tests/end_to_end_test/server_repo_logs_test.go index 163b741622a..62569a7886a 100644 --- a/tests/end_to_end_test/server_repo_logs_test.go +++ b/tests/end_to_end_test/server_repo_logs_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/server_start_test.go b/tests/end_to_end_test/server_start_test.go index 1cdb7fe02d3..7dd09515b69 100644 --- a/tests/end_to_end_test/server_start_test.go +++ b/tests/end_to_end_test/server_start_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_actions_test.go b/tests/end_to_end_test/snapshot_actions_test.go index 2420cada283..423ad398846 100644 --- a/tests/end_to_end_test/snapshot_actions_test.go +++ b/tests/end_to_end_test/snapshot_actions_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_copy_move_history_test.go b/tests/end_to_end_test/snapshot_copy_move_history_test.go index 8e850d72133..197791ffee2 100644 --- a/tests/end_to_end_test/snapshot_copy_move_history_test.go +++ b/tests/end_to_end_test/snapshot_copy_move_history_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_create_test.go b/tests/end_to_end_test/snapshot_create_test.go index 968325dfdd1..22eb3f8dbc7 100644 --- a/tests/end_to_end_test/snapshot_create_test.go +++ b/tests/end_to_end_test/snapshot_create_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_delete_test.go b/tests/end_to_end_test/snapshot_delete_test.go index 0af2034c899..da079324354 100644 --- a/tests/end_to_end_test/snapshot_delete_test.go +++ b/tests/end_to_end_test/snapshot_delete_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_fail_test.go b/tests/end_to_end_test/snapshot_fail_test.go index 33b734eba72..80240a59375 100644 --- a/tests/end_to_end_test/snapshot_fail_test.go +++ b/tests/end_to_end_test/snapshot_fail_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_gc_test.go b/tests/end_to_end_test/snapshot_gc_test.go index eab0bddc3a5..aaf9ab37747 100644 --- a/tests/end_to_end_test/snapshot_gc_test.go +++ b/tests/end_to_end_test/snapshot_gc_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_migrate_test.go b/tests/end_to_end_test/snapshot_migrate_test.go index 78cf4df8e25..bb38c363da7 100644 --- a/tests/end_to_end_test/snapshot_migrate_test.go +++ b/tests/end_to_end_test/snapshot_migrate_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/snapshot_verify_test.go b/tests/end_to_end_test/snapshot_verify_test.go index 27147d5128a..4f69e384b9f 100644 --- a/tests/end_to_end_test/snapshot_verify_test.go +++ b/tests/end_to_end_test/snapshot_verify_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/end_to_end_test/suite_test.go b/tests/end_to_end_test/suite_test.go index a031e8ac57f..3ad1d404032 100644 --- a/tests/end_to_end_test/suite_test.go +++ b/tests/end_to_end_test/suite_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endtoend_test import ( diff --git a/tests/endurance_test/endurance_test.go b/tests/endurance_test/endurance_test.go index d3fe9cbb6d2..1e40bfe0b28 100644 --- a/tests/endurance_test/endurance_test.go +++ b/tests/endurance_test/endurance_test.go @@ -1,3 +1,5 @@ +//go:build !oadp + package endurance_test import ( diff --git a/tests/testenv/cli_test_env.go b/tests/testenv/cli_test_env.go index 0d4c19664cb..14ce7aa7c64 100644 --- a/tests/testenv/cli_test_env.go +++ b/tests/testenv/cli_test_env.go @@ -106,7 +106,8 @@ func NewCLITest(tb testing.TB, repoCreateFlags []string, runner CLIRunner) *CLIT fixedArgs: fixedArgs, DefaultRepositoryCreateFlags: formatFlags, Environment: map[string]string{ - "KOPIA_PASSWORD": TestRepoPassword, + // OADP: Changed from KOPIA_PASSWORD to BSLS_PASSWORD + "BSLS_PASSWORD": TestRepoPassword, }, Runner: runner, } @@ -282,9 +283,26 @@ func (e *CLITest) RunAndVerifyOutputLineCount(tb testing.TB, wantLines int, args func (e *CLITest) cmdArgs(args []string) []string { var suffix []string + // OADP: Translate old command names to new ones for test compatibility. + // This allows existing tests to work without modification. + translatedArgs := make([]string, len(args)) + + for i, arg := range args { + switch arg { + case "repo", "repository": + translatedArgs[i] = "bsl" + case "snapshot", "snap": + translatedArgs[i] = "backup" + default: + translatedArgs[i] = arg + } + } + + args = translatedArgs + // detect repository creation and override DefaultRepositoryCreateFlags for best // performance on the current platform. - if len(args) >= 2 && (args[0] == "repo" && args[1] == "create") { + if len(args) >= 2 && (args[0] == "bsl" && args[1] == "create") { suffix = e.DefaultRepositoryCreateFlags } diff --git a/tools/cli2md/cli2md.go b/tools/cli2md/cli2md.go index a6e63c34053..ff39f8cd05b 100644 --- a/tools/cli2md/cli2md.go +++ b/tools/cli2md/cli2md.go @@ -311,8 +311,8 @@ hide_summary: true } } - fmt.Fprintf(f, "```shell\n$ kopia %v%v%v\n```\n\n", cmd.FullCommand, flagSummary.String(), argSummary.String()) //nolint:errcheck - fmt.Fprintf(f, "%v\n\n", escapeFlags(cmd.Help)) //nolint:errcheck + fmt.Fprintf(f, "```shell\n$ %v %v%v%v\n```\n\n", cli.AppName, cmd.FullCommand, flagSummary, argSummary) //nolint:errcheck + fmt.Fprintf(f, "%v\n\n", escapeFlags(cmd.Help)) //nolint:errcheck emitFlags(f, cmd.Flags) emitArgs(f, cmd.Args) @@ -328,7 +328,7 @@ func main() { _ = os.RemoveAll(filepath.Join(*baseDir, commonSection)) _ = os.RemoveAll(filepath.Join(*baseDir, advancedSection)) - kingpinApp := kingpin.New("kopia", "Kopia - Fast And Secure Open-Source Backup").Author("http://kopia.github.io/") + kingpinApp := kingpin.New(cli.AppName, cli.AppLongDescription).Author(cli.AppAuthor) cli.NewApp().Attach(kingpinApp) app := kingpinApp.Model()