Compare commits
5 Commits
fff7789dfb
...
fix/20-spl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de38b03508 | ||
| 811c210b09 | |||
|
|
5ca64a37ce | ||
| 118bca1151 | |||
|
|
85729d9181 |
43
Dockerfile
43
Dockerfile
@@ -1,23 +1,38 @@
|
|||||||
# Build stage
|
# Lint stage — fast feedback on formatting and lint issues
|
||||||
# golang:1.24-alpine, 2026-02-25
|
# golangci/golangci-lint:v2.10.1, 2026-03-01
|
||||||
FROM golang:1.24-alpine@sha256:8bee1901f1e530bfb4a7850aa7a479d17ae3a18beb6e09064ed54cfd245b7191 AS builder
|
FROM golangci/golangci-lint@sha256:ea84d14c2fef724411be7dc45e09e6ef721d748315252b02df19a7e3113ee763 AS lint
|
||||||
|
|
||||||
|
# Install CGO dependencies needed for static analysis of vips/libheif code
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
libvips-dev \
|
||||||
|
libheif-dev \
|
||||||
|
pkg-config \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN make fmt-check
|
||||||
|
RUN make lint
|
||||||
|
|
||||||
|
# Build stage — tests and compilation
|
||||||
|
# golang:1.25.4-alpine, 2026-02-25
|
||||||
|
FROM golang:1.25.4-alpine@sha256:d3f0cf7723f3429e3f9ed846243970b20a2de7bae6a5b66fc5914e228d831bbb AS builder
|
||||||
|
|
||||||
ARG VERSION=dev
|
ARG VERSION=dev
|
||||||
|
|
||||||
|
# Force BuildKit to run the lint stage by creating a stage dependency
|
||||||
|
COPY --from=lint /src/go.sum /dev/null
|
||||||
|
|
||||||
# Install build dependencies for CGO image libraries
|
# Install build dependencies for CGO image libraries
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
build-base \
|
build-base \
|
||||||
vips-dev \
|
vips-dev \
|
||||||
libheif-dev \
|
libheif-dev \
|
||||||
pkgconfig \
|
pkgconfig
|
||||||
curl
|
|
||||||
|
|
||||||
# golangci-lint v2.10.1, 2026-02-25
|
|
||||||
RUN curl -sSfL https://github.com/golangci/golangci-lint/releases/download/v2.10.1/golangci-lint-2.10.1-linux-amd64.tar.gz -o /tmp/golangci-lint.tar.gz && \
|
|
||||||
echo "dfa775874cf0561b404a02a8f4481fc69b28091da95aa697259820d429b09c99 /tmp/golangci-lint.tar.gz" | sha256sum -c - && \
|
|
||||||
tar -xzf /tmp/golangci-lint.tar.gz -C /tmp && \
|
|
||||||
mv /tmp/golangci-lint-2.10.1-linux-amd64/golangci-lint /usr/local/bin/ && \
|
|
||||||
rm -rf /tmp/golangci-lint*
|
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
@@ -28,8 +43,8 @@ RUN GOTOOLCHAIN=auto go mod download
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Run all checks (fmt-check, lint, test)
|
# Run tests
|
||||||
RUN make check
|
RUN make test
|
||||||
|
|
||||||
# Build with CGO enabled
|
# Build with CGO enabled
|
||||||
RUN CGO_ENABLED=1 GOTOOLCHAIN=auto go build -ldflags "-X main.Version=${VERSION}" -o /pixad ./cmd/pixad
|
RUN CGO_ENABLED=1 GOTOOLCHAIN=auto go build -ldflags "-X main.Version=${VERSION}" -o /pixad ./cmd/pixad
|
||||||
|
|||||||
@@ -132,7 +132,9 @@ func loadConfigFile(log *slog.Logger, appName string) (*smartconfig.Config, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range configPaths {
|
for _, path := range configPaths {
|
||||||
if _, statErr := os.Stat(path); statErr == nil {
|
cleanPath := filepath.Clean(path)
|
||||||
|
//nolint:gosec // G703: paths are hardcoded config locations
|
||||||
|
if _, statErr := os.Stat(cleanPath); statErr == nil {
|
||||||
sc, err := smartconfig.NewFromConfigPath(path)
|
sc, err := smartconfig.NewFromConfigPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("failed to parse config file", "path", path, "error", err)
|
log.Warn("failed to parse config file", "path", path, "error", err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
|
neturl "net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -158,11 +159,18 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*FetchResult, erro
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
parsedURL, err := neturl.Parse(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
return nil, fmt.Errorf("failed to parse URL: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodGet,
|
||||||
|
URL: parsedURL,
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
req.Header.Set("User-Agent", f.config.UserAgent)
|
req.Header.Set("User-Agent", f.config.UserAgent)
|
||||||
req.Header.Set("Accept", strings.Join(f.config.AllowedContentTypes, ", "))
|
req.Header.Set("Accept", strings.Join(f.config.AllowedContentTypes, ", "))
|
||||||
|
|
||||||
@@ -180,6 +188,7 @@ func (f *HTTPFetcher) Fetch(ctx context.Context, url string) (*FetchResult, erro
|
|||||||
|
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
|
|
||||||
|
//nolint:gosec // G704: URL validated by validateURL() above
|
||||||
resp, err := f.client.Do(req)
|
resp, err := f.client.Do(req)
|
||||||
|
|
||||||
fetchDuration := time.Since(startTime)
|
fetchDuration := time.Since(startTime)
|
||||||
|
|||||||
@@ -103,7 +103,8 @@ func (s *ContentStorage) Store(r io.Reader) (hash ContentHash, size int64, err e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atomic rename
|
// Atomic rename
|
||||||
if err := os.Rename(tmpPath, path); err != nil {
|
//nolint:gosec // G703: paths from internal SHA256 hashes
|
||||||
|
if err := os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)); err != nil {
|
||||||
return "", 0, fmt.Errorf("failed to rename temp file: %w", err)
|
return "", 0, fmt.Errorf("failed to rename temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,10 +174,10 @@ func (s *ContentStorage) Exists(hash ContentHash) bool {
|
|||||||
func (s *ContentStorage) hashToPath(hash ContentHash) string {
|
func (s *ContentStorage) hashToPath(hash ContentHash) string {
|
||||||
h := string(hash)
|
h := string(hash)
|
||||||
if len(h) < MinHashLength {
|
if len(h) < MinHashLength {
|
||||||
return filepath.Join(s.baseDir, h)
|
return filepath.Clean(filepath.Join(s.baseDir, h))
|
||||||
}
|
}
|
||||||
|
|
||||||
return filepath.Join(s.baseDir, h[0:2], h[2:4], h)
|
return filepath.Clean(filepath.Join(s.baseDir, h[0:2], h[2:4], h))
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetadataStorage handles JSON metadata file storage.
|
// MetadataStorage handles JSON metadata file storage.
|
||||||
@@ -252,7 +253,8 @@ func (s *MetadataStorage) Store(host string, pathHash PathHash, meta *SourceMeta
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atomic rename
|
// Atomic rename
|
||||||
if err := os.Rename(tmpPath, path); err != nil {
|
//nolint:gosec // G703: paths from internal SHA256 hashes
|
||||||
|
if err := os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)); err != nil {
|
||||||
return fmt.Errorf("failed to rename temp file: %w", err)
|
return fmt.Errorf("failed to rename temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +304,7 @@ func (s *MetadataStorage) Exists(host string, pathHash PathHash) bool {
|
|||||||
|
|
||||||
// metaPath returns the file path for metadata: <basedir>/<host>/<path_hash>.json
|
// metaPath returns the file path for metadata: <basedir>/<host>/<path_hash>.json
|
||||||
func (s *MetadataStorage) metaPath(host string, pathHash PathHash) string {
|
func (s *MetadataStorage) metaPath(host string, pathHash PathHash) string {
|
||||||
return filepath.Join(s.baseDir, host, string(pathHash)+".json")
|
return filepath.Clean(filepath.Join(s.baseDir, host, string(pathHash)+".json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashPath computes the SHA256 hash of a path string.
|
// HashPath computes the SHA256 hash of a path string.
|
||||||
@@ -395,7 +397,8 @@ func (s *VariantStorage) Store(key VariantKey, r io.Reader, contentType string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Atomic rename content
|
// Atomic rename content
|
||||||
if err := os.Rename(tmpPath, path); err != nil {
|
//nolint:gosec // G703: paths from internal SHA256 hashes
|
||||||
|
if err := os.Rename(filepath.Clean(tmpPath), filepath.Clean(path)); err != nil {
|
||||||
return 0, fmt.Errorf("failed to rename temp file: %w", err)
|
return 0, fmt.Errorf("failed to rename temp file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user