diff --git a/docker/README.md b/docker/README.md index 9d6e26770d..5efd700251 100644 --- a/docker/README.md +++ b/docker/README.md @@ -15,7 +15,7 @@ Additions, amendments and contributions for additional workflows are most welcom The easiest way to launch a new environment is to use Docker Compose. -``` +```sh cd docker docker compose up ``` @@ -35,7 +35,7 @@ The following environment variables are available when using Docker Compose: | Environment Variable | Default | | -------------------- |----------| -| `OS_VERSION` | bullseye | +| `OS_VERSION` | trixie | | `WEB_HTTP_PORT` | 8080 | | `BACKEND_HOST` | backend | | `BACKEND_PORT` | 6868 | @@ -46,9 +46,10 @@ The following environment variables are available when using Docker Compose: **Examples:** -Run Debian "bullseye": -``` -OS_VERSION=bullseye docker compose up +Run Debian "trixie": + +```sh +OS_VERSION=trixie docker compose up ``` The Docker entrypoint generates an ephemeral session key for local development. @@ -56,7 +57,7 @@ Set `SESSION_KEY` yourself if browser sessions must survive a container restart. Run the Go web-client test suite with: -``` +```sh docker compose --profile webui-tests run --rm go-test ``` @@ -66,9 +67,8 @@ When using Docker Compose the following volumes will be mounted automatically: | Local Path | Container Path | | ----------------------- | ------------------------ | -| docker/volumes/images/ | /home/pi/images/ | -| docker/volumes/config/ | /home/pi/.config/piscsi/ | - +| docker/volumes/images/ | /var/lib/piscsi/images/ | +| docker/volumes/config/ | /var/lib/piscsi/config/ | ## How To @@ -77,7 +77,7 @@ When using Docker Compose the following volumes will be mounted automatically: You should rebuild the container images after checking out a different version of PiSCSI or making changes to the Go web client or its runtime dependencies. -``` +```sh docker compose up --build ``` @@ -85,7 +85,7 @@ docker compose up --build Run the following command, replacing `[CONTAINER]` with `backend` or `web`. -``` +```sh docker compose exec [CONTAINER] bash ``` @@ -98,7 +98,8 @@ The web binary embeds templates and static assets, so rebuild and restart the `web` service after editing Go web-client files. **Example:** -``` + +```yaml services: go-test: volumes: @@ -110,6 +111,6 @@ services: This can be useful for testing, but there are some caveats, e.g. the PiSCSI and the web UI will be accessing separate `images` directories. -``` +```sh BACKEND_HOST=foo BACKEND_PASSWORD=bar docker compose up ``` diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index dd72413f9a..f9ba162a9d 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,6 +1,6 @@ ARG DEBIAN_FRONTEND=noninteractive -FROM debian:bullseye AS build +FROM debian:trixie AS build RUN apt-get update && apt-get install --assume-yes --no-install-recommends sudo RUN groupadd pi \ && useradd --create-home --shell /bin/bash -g pi pi \ @@ -15,14 +15,14 @@ COPY --chown=pi:pi doc doc COPY --chown=pi:pi proto proto RUN ./easyinstall.sh --run_choice=15 --cores=`nproc` -FROM debian:bullseye-slim AS runner +FROM debian:trixie-slim AS runner USER root WORKDIR /home/pi COPY --from=build /home/pi/piscsi/cpp/bin/* /usr/local/bin/ COPY docker/backend/piscsi_wrapper.sh /usr/local/bin/piscsi_wrapper.sh RUN chmod +x /usr/local/bin/* -RUN mkdir -p /home/pi/images +RUN mkdir -p /var/lib/piscsi/images /var/lib/piscsi/config RUN apt-get update \ && apt-get install --no-install-recommends --assume-yes libpcap-dev libprotobuf-dev \ @@ -31,7 +31,7 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* EXPOSE 6868 -ENTRYPOINT ["/usr/local/bin/piscsi_wrapper.sh", "-r", "7", "-F", "/home/pi/images"] +ENTRYPOINT ["/usr/local/bin/piscsi_wrapper.sh", "-r", "7", "-F", "/var/lib/piscsi/images"] CMD ["-L", "trace"] HEALTHCHECK --interval=5m --timeout=1s CMD scsictl -v diff --git a/docker/backend/piscsi_wrapper.sh b/docker/backend/piscsi_wrapper.sh index 0fab4dbea8..81d3a662a9 100644 --- a/docker/backend/piscsi_wrapper.sh +++ b/docker/backend/piscsi_wrapper.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash if [[ $BACKEND_PASSWORD ]]; then - TOKEN_FILE="/home/pi/.config/piscsi/piscsi_secret" - mkdir -p /home/pi/.config/piscsi || true + TOKEN_FILE="/var/lib/piscsi/config/piscsi_secret" + mkdir -p /var/lib/piscsi/config || true echo $BACKEND_PASSWORD > $TOKEN_FILE chmod 700 $TOKEN_FILE /usr/local/bin/piscsi "$@" -P $TOKEN_FILE diff --git a/docker/docker-compose.ci.yml b/docker/docker-compose.ci.yml index 8d3e2ca85e..37f00fc098 100644 --- a/docker/docker-compose.ci.yml +++ b/docker/docker-compose.ci.yml @@ -8,7 +8,7 @@ services: dockerfile: docker/backend/Dockerfile init: true volumes: - - ./volumes/images:/home/pi/images:delegated + - ./volumes/images:/var/lib/piscsi/images:delegated healthcheck: interval: 5s start_period: 5s @@ -27,18 +27,18 @@ services: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=bullseye + - OS_VERSION=trixie volumes: - - ./volumes/images:/home/pi/images:delegated + - ./volumes/images:/var/lib/piscsi/images:delegated user: "${PISCSI_DOCKER_UID:-1000}:${PISCSI_DOCKER_GID:-1000}" init: true environment: PISCSI_HOST: mock-backend PISCSI_PORT: 6868 SERVER_PORT: 8080 - BASE_DIR: /home/pi/images - SHARED_DIR: /home/pi/shared_files - CONFIG_DIR: /home/pi/.config/piscsi + BASE_DIR: /var/lib/piscsi/images + SHARED_DIR: /var/lib/piscsi/shared + CONFIG_DIR: /var/lib/piscsi/config healthcheck: interval: 5s start_period: 5s diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 97676d86fb..19bbfafb25 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -7,8 +7,8 @@ services: context: .. dockerfile: docker/backend/Dockerfile volumes: - - ./volumes/images:/home/pi/images:delegated - - ./volumes/config:/home/pi/.config/piscsi:delegated + - ./volumes/images:/var/lib/piscsi/images:delegated + - ./volumes/config:/var/lib/piscsi/config:delegated ports: - "127.0.0.1:${BACKEND_PORT:-6868}:6868" environment: @@ -27,10 +27,10 @@ services: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=${OS_VERSION:-bullseye} + - OS_VERSION=${OS_VERSION:-trixie} volumes: - - ./volumes/images:/home/pi/images:delegated - - ./volumes/config:/home/pi/.config/piscsi:delegated + - ./volumes/images:/var/lib/piscsi/images:delegated + - ./volumes/config:/var/lib/piscsi/config:delegated ports: - "127.0.0.1:${WEB_HTTP_PORT:-8080}:8080" environment: @@ -38,9 +38,9 @@ services: PISCSI_PORT: ${BACKEND_PORT:-6868} PISCSI_TOKEN: ${BACKEND_PASSWORD:-} SERVER_PORT: 8080 - BASE_DIR: /home/pi/images - SHARED_DIR: /home/pi/shared_files - CONFIG_DIR: /home/pi/.config/piscsi + BASE_DIR: /var/lib/piscsi/images + SHARED_DIR: /var/lib/piscsi/shared + CONFIG_DIR: /var/lib/piscsi/config SESSION_KEY: ${SESSION_KEY:-} user: "${PISCSI_DOCKER_UID:-1000}:${PISCSI_DOCKER_GID:-1000}" init: true diff --git a/docker/go-test/Dockerfile b/docker/go-test/Dockerfile index 6e39339b2b..e9895a052c 100644 --- a/docker/go-test/Dockerfile +++ b/docker/go-test/Dockerfile @@ -1,6 +1,6 @@ ARG GO_VERSION=1.25.13 -FROM golang:${GO_VERSION}-bookworm +FROM golang:${GO_VERSION}-trixie WORKDIR /src diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index 3978a16068..13dfa2ab0d 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,7 +1,7 @@ ARG GO_VERSION=1.25.13 -ARG OS_VERSION=bullseye +ARG OS_VERSION=trixie -FROM golang:${GO_VERSION}-bookworm AS build +FROM golang:${GO_VERSION}-trixie AS build WORKDIR /src @@ -31,7 +31,6 @@ RUN apt-get update \ disktype \ dosfstools \ genisoimage \ - hfsutils \ kpartx \ man2html \ openssl \ @@ -43,22 +42,19 @@ RUN apt-get update \ RUN groupadd --system pi \ && useradd --system --create-home --home-dir /home/pi --shell /usr/sbin/nologin --gid pi pi \ && install -d --owner=pi --group=pi --mode=0777 \ - /home/pi/images \ - /home/pi/shared_files \ - /home/pi/.config/piscsi \ - && install -d --owner=pi --group=pi \ - /home/pi/web/templates \ - /home/pi/web/static + /var/lib/piscsi/images \ + /var/lib/piscsi/shared \ + /var/lib/piscsi/config \ + && install -d --mode=0755 /opt/piscsi-web COPY --from=build /out/piscsi-web /usr/local/bin/piscsi-web COPY --from=build /out/mock-piscsi /usr/local/bin/mock-piscsi -COPY --from=build /src/go/piscsi-web/drive_properties.json /home/pi/drive_properties.json +COPY --from=build /src/go/piscsi-web/drive_properties.json /opt/piscsi-web/drive_properties.json COPY docker/web/web_start_wrapper.sh /usr/local/bin/web_start_wrapper.sh -RUN chown pi:pi /home/pi/drive_properties.json \ - && chmod 0755 /usr/local/bin/piscsi-web /usr/local/bin/mock-piscsi /usr/local/bin/web_start_wrapper.sh +RUN chmod 0755 /usr/local/bin/piscsi-web /usr/local/bin/mock-piscsi /usr/local/bin/web_start_wrapper.sh USER pi -WORKDIR /home/pi +WORKDIR /opt/piscsi-web EXPOSE 8080 ENTRYPOINT ["/usr/local/bin/web_start_wrapper.sh"] diff --git a/go/Makefile b/go/Makefile index ff4bc057cf..3a210bf843 100644 --- a/go/Makefile +++ b/go/Makefile @@ -59,21 +59,21 @@ mock: proto @echo "Run with: ./$(MOCK_BINARY)" build-linux-arm64: proto - @echo "Building for ARM64 (Raspberry Pi 4/5)..." + @echo "Building for ARM64..." CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ $(GOBUILD) $(WEB_TAG_FLAGS) $(WEB_LDFLAGS) -o $(BINARY_NAME)-arm64 ./piscsi-web/cmd/piscsi-web @echo "✅ ARM64 binary created" @ls -lh $(BINARY_NAME)-arm64 build-linux-armv7: proto - @echo "Building for ARMv7 (Raspberry Pi 2/3)..." + @echo "Building for ARMv7..." CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 \ $(GOBUILD) $(WEB_TAG_FLAGS) $(WEB_LDFLAGS) -o $(BINARY_NAME)-armv7 ./piscsi-web/cmd/piscsi-web @echo "✅ ARMv7 binary created" @ls -lh $(BINARY_NAME)-armv7 build-linux-arm64-static: proto - @echo "Building static ARM64 binary (Raspberry Pi 4/5)..." + @echo "Building static ARM64 binary..." CGO_ENABLED=0 GOOS=linux GOARCH=arm64 \ $(GOBUILD) $(STATIC_WEB_TAG_FLAGS) $(STATIC_WEB_LDFLAGS) -o $(BINARY_NAME)-arm64-static ./piscsi-web/cmd/piscsi-web @echo "✅ Static ARM64 binary created: $(BINARY_NAME)-arm64-static" @@ -81,7 +81,7 @@ build-linux-arm64-static: proto @ls -lh $(BINARY_NAME)-arm64-static build-linux-armv7-static: proto - @echo "Building static ARMv7 binary (Raspberry Pi 2/3)..." + @echo "Building static ARMv7 binary..." CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 \ $(GOBUILD) $(STATIC_WEB_TAG_FLAGS) $(STATIC_WEB_LDFLAGS) -o $(BINARY_NAME)-armv7-static ./piscsi-web/cmd/piscsi-web diff --git a/go/piscsi-web/internal/server/handlers.go b/go/piscsi-web/internal/server/handlers.go index 81262729e1..3922f6a318 100644 --- a/go/piscsi-web/internal/server/handlers.go +++ b/go/piscsi-web/internal/server/handlers.go @@ -186,6 +186,7 @@ func (s *Server) handleIndex(c *gin.Context) { data["ShowUnits"] = showUnits data["Files"] = files data["FilesBySubdir"] = filesBySubdir + data["SharedFiles"] = sharedFiles(s.config.SharedDir) data["ImageDir"] = s.config.BaseDir data["ImageDirExists"] = imageDirExists data["ImageRootDir"] = s.config.BaseDir @@ -1308,7 +1309,7 @@ func (s *Server) handleFilesUpload(c *gin.Context) { s.logger.Info("File uploaded", "filename", filename, "size", fileSize, "destination", filepath.Dir(fullPath)) s.respond(c, ResponseOptions{ - Message: "File uploaded successfully", + Message: fmt.Sprintf("File %q uploaded successfully to %q", filename, filepath.Dir(fullPath)), RedirectURL: "/upload", }) } @@ -3128,6 +3129,24 @@ func transferSubdirectories(root string) []string { return subdirectories } +// sharedFiles returns the visible regular files directly in the shared +// directory. These are available as sources when creating CD-ROM images. +func sharedFiles(root string) []string { + entries, err := os.ReadDir(root) + if err != nil { + return []string{} + } + + files := make([]string, 0, len(entries)) + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), ".") || !entry.Type().IsRegular() { + continue + } + files = append(files, entry.Name()) + } + return files +} + // displays disk image information func (s *Server) handleFilesDiskinfo(c *gin.Context) { fileName := c.PostForm("file_name") @@ -3502,9 +3521,9 @@ func (s *Server) handleFilesCreateISO(c *gin.Context) { isoPath, err = resolvePathWithin(s.config.BaseDir, fileName+".iso") } } else { - sourcePath, err = resolvePathWithin(s.config.BaseDir, localFile) + sourcePath, err = resolvePathWithin(s.config.SharedDir, localFile) if err == nil { - isoPath, err = resolvePathWithin(s.config.BaseDir, localFile+".iso") + isoPath, err = resolvePathWithin(s.config.BaseDir, filepath.Base(localFile)+".iso") } } if err != nil { diff --git a/go/piscsi-web/internal/server/handlers_image_test.go b/go/piscsi-web/internal/server/handlers_image_test.go index c6c47c5338..af0d1911e4 100644 --- a/go/piscsi-web/internal/server/handlers_image_test.go +++ b/go/piscsi-web/internal/server/handlers_image_test.go @@ -175,14 +175,18 @@ func TestHandleFilesCreateISOLocalFile(t *testing.T) { gin.SetMode(gin.TestMode) root := t.TempDir() imageDir := filepath.Join(root, "images") + sharedDir := filepath.Join(root, "shared") binDir := filepath.Join(root, "bin") if err := os.MkdirAll(imageDir, 0o755); err != nil { t.Fatal(err) } + if err := os.MkdirAll(sharedDir, 0o755); err != nil { + t.Fatal(err) + } if err := os.MkdirAll(binDir, 0o755); err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(imageDir, "software.hds"), []byte("software"), 0o644); err != nil { + if err := os.WriteFile(filepath.Join(sharedDir, "software.hds"), []byte("software"), 0o644); err != nil { t.Fatal(err) } fakeGenisoimage := "#!/bin/sh\nwhile [ \"$1\" != \"-o\" ]; do shift; done\nshift\n: > \"$1\"\n" @@ -192,7 +196,7 @@ func TestHandleFilesCreateISOLocalFile(t *testing.T) { t.Setenv("PATH", binDir+string(os.PathListSeparator)+os.Getenv("PATH")) server := &Server{ - config: &config.Config{BaseDir: imageDir, TemplatesDir: filepath.Join(root, "web", "templates")}, + config: &config.Config{BaseDir: imageDir, SharedDir: sharedDir, TemplatesDir: filepath.Join(root, "web", "templates")}, logger: slog.New(slog.NewTextHandler(io.Discard, nil)), sessionStore: sessions.NewCookieStore([]byte("test-secret-key")), } @@ -217,6 +221,27 @@ func TestHandleFilesCreateISOLocalFile(t *testing.T) { } } +func TestSharedFilesListsVisibleRegularFiles(t *testing.T) { + sharedDir := t.TempDir() + for name, content := range map[string][]byte{ + "document.txt": []byte("document"), + ".hidden": []byte("hidden"), + "archive.zip": []byte("archive"), + } { + if err := os.WriteFile(filepath.Join(sharedDir, name), content, 0o644); err != nil { + t.Fatal(err) + } + } + if err := os.Mkdir(filepath.Join(sharedDir, "folder"), 0o755); err != nil { + t.Fatal(err) + } + + want := []string{"archive.zip", "document.txt"} + if got := sharedFiles(sharedDir); !reflect.DeepEqual(got, want) { + t.Fatalf("sharedFiles() = %v, want %v", got, want) + } +} + func TestGetImageFilesUsesLocalDirectoryAndDaemonMapping(t *testing.T) { imageDir := t.TempDir() nestedDir := filepath.Join(imageDir, "nested") diff --git a/go/piscsi-web/internal/server/handlers_upload_test.go b/go/piscsi-web/internal/server/handlers_upload_test.go index 6999ac2c7c..ff8204b79b 100644 --- a/go/piscsi-web/internal/server/handlers_upload_test.go +++ b/go/piscsi-web/internal/server/handlers_upload_test.go @@ -155,7 +155,7 @@ func TestHandleFilesUploadReturnsJSONForProgressRequest(t *testing.T) { if contentType := recorder.Header().Get("Content-Type"); !strings.HasPrefix(contentType, "application/json") { t.Fatalf("Content-Type = %q, want JSON", contentType) } - if !strings.Contains(recorder.Body.String(), `"message":"File uploaded successfully"`) { + if !strings.Contains(recorder.Body.String(), `"message":"File \"disk.hda\" uploaded successfully to \"`+imageDir+`\""`) { t.Fatalf("response = %s", recorder.Body.String()) } if _, err := os.Stat(filepath.Join(imageDir, "disk.hda")); err != nil { diff --git a/go/piscsi-web/scripts/build-all.sh b/go/piscsi-web/scripts/build-all.sh index 0f59066205..3c98bba3fa 100755 --- a/go/piscsi-web/scripts/build-all.sh +++ b/go/piscsi-web/scripts/build-all.sh @@ -20,13 +20,13 @@ echo "🔨 Building for native platform (x86_64)..." make build echo "" -# Build for Raspberry Pi ARM64 (Pi 4/5) -echo "🔨 Building for ARM64 (Raspberry Pi 4/5)..." +# Build for Raspberry Pi ARM64 (Pi 3/4/5) +echo "🔨 Building for ARM64..." make build-linux-arm64 echo "" -# Build for Raspberry Pi ARMv7 (Pi 2/3) -echo "🔨 Building for ARMv7 (Raspberry Pi 2/3)..." +# Build for Raspberry Pi ARMv7 (Pi 2/early 3) +echo "🔨 Building for ARMv7..." make build-linux-armv7 echo "" diff --git a/go/piscsi-web/web/templates/index.html b/go/piscsi-web/web/templates/index.html index 9ea61eb672..1aa0859a4f 100644 --- a/go/piscsi-web/web/templates/index.html +++ b/go/piscsi-web/web/templates/index.html @@ -279,6 +279,12 @@ {{if .InUse}} In use + {{if not .IsArchive}} +
+ + +
+ {{end}} {{else}} {{if .IsArchive}} {{if .ArchiveContents}} @@ -471,7 +477,7 @@ @@ -572,10 +578,10 @@
- + diff --git a/go/piscsi-web/web/templates/upload.html b/go/piscsi-web/web/templates/upload.html index 437c736d36..e97afffa2d 100644 --- a/go/piscsi-web/web/templates/upload.html +++ b/go/piscsi-web/web/templates/upload.html @@ -5,6 +5,12 @@

Upload File from Local Computer

+

Select the destination directory for the uploaded file according to its purpose:

+
    +
  • images are disk images to be mounted as a SCSI storage device
  • +
  • shared is for files to be shared with the host through network file sharing or used to create an ISO
  • +
  • config is meant for PiSCSI configuration files
  • +