diff --git a/.github/workflows/release.docker.yml b/.github/workflows/release.docker.yml index e8889e1..d0fc89e 100644 --- a/.github/workflows/release.docker.yml +++ b/.github/workflows/release.docker.yml @@ -15,7 +15,8 @@ jobs: docker: runs-on: ubuntu-latest env: - PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7" + # CGO cross-compilation supported platforms + PLATFORMS: "linux/amd64,linux/arm64" VERSION: "${{ github.event_name == 'release' && github.event.release.name || github.sha }}" steps: diff --git a/Dockerfile b/Dockerfile index ac9a882..7d4b736 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,41 @@ FROM golang:1.24 AS builder +# Install build dependencies for VFS extension +RUN apt-get update && apt-get install -y gcc libc6-dev && rm -rf /var/lib/apt/lists/* + WORKDIR /src/litestream COPY . . ARG LITESTREAM_VERSION=latest +# Build litestream binary RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg \ go build -ldflags "-s -w -X 'main.Version=${LITESTREAM_VERSION}' -extldflags '-static'" -tags osusergo,netgo,sqlite_omit_load_extension -o /usr/local/bin/litestream ./cmd/litestream +# Build VFS loadable extension +RUN --mount=type=cache,target=/root/.cache/go-build \ + --mount=type=cache,target=/go/pkg \ + mkdir -p dist && \ + CGO_ENABLED=1 go build \ + -tags "vfs,SQLITE3VFS_LOADABLE_EXT" \ + -buildmode=c-archive \ + -o dist/litestream-vfs.a ./cmd/litestream-vfs && \ + mv dist/litestream-vfs.h src/litestream-vfs.h && \ + gcc -DSQLITE3VFS_LOADABLE_EXT -g -fPIC -shared \ + -o dist/litestream-vfs.so \ + src/litestream-vfs.c \ + dist/litestream-vfs.a \ + -lpthread -ldl -lm + +FROM debian:bookworm-slim + +RUN apt-get update && \ + apt-get install -y ca-certificates sqlite3 && \ + rm -rf /var/lib/apt/lists/* -FROM alpine:3.20 COPY --from=builder /usr/local/bin/litestream /usr/local/bin/litestream +COPY --from=builder /src/litestream/dist/litestream-vfs.so /usr/local/lib/litestream-vfs.so + ENTRYPOINT ["/usr/local/bin/litestream"] CMD [] diff --git a/cmd/litestream-vfs/main.go b/cmd/litestream-vfs/main.go index 2d9501f..fd4f3cf 100644 --- a/cmd/litestream-vfs/main.go +++ b/cmd/litestream-vfs/main.go @@ -13,7 +13,7 @@ import "C" import ( "context" - "log" + "fmt" "log/slog" "os" "strings" @@ -38,19 +38,23 @@ import ( func main() {} //export LitestreamVFSRegister -func LitestreamVFSRegister() { +func LitestreamVFSRegister() *C.char { var client litestream.ReplicaClient var err error replicaURL := os.Getenv("LITESTREAM_REPLICA_URL") + if replicaURL == "" { + return C.CString("LITESTREAM_REPLICA_URL environment variable required") + } + client, err = litestream.NewReplicaClientFromURL(replicaURL) if err != nil { - log.Fatalf("failed to create replica client from URL: %s", err) + return C.CString(fmt.Sprintf("failed to create replica client: %s", err)) } // Initialize the client. if err := client.Init(context.Background()); err != nil { - log.Fatalf("failed to initialize litestream replica client: %s", err) + return C.CString(fmt.Sprintf("failed to initialize replica client: %s", err)) } var level slog.Level @@ -71,7 +75,7 @@ func LitestreamVFSRegister() { if s := os.Getenv("LITESTREAM_SYNC_INTERVAL"); s != "" { d, err := time.ParseDuration(s) if err != nil { - log.Fatalf("invalid LITESTREAM_SYNC_INTERVAL: %s", err) + return C.CString(fmt.Sprintf("invalid LITESTREAM_SYNC_INTERVAL: %s", err)) } vfs.WriteSyncInterval = d } @@ -82,8 +86,10 @@ func LitestreamVFSRegister() { } if err := sqlite3vfs.RegisterVFS("litestream", vfs); err != nil { - log.Fatalf("failed to register litestream vfs: %s", err) + return C.CString(fmt.Sprintf("failed to register VFS: %s", err)) } + + return nil } //export GoLitestreamRegisterConnection diff --git a/src/litestream-vfs.c b/src/litestream-vfs.c index 911ee98..6438d0d 100644 --- a/src/litestream-vfs.c +++ b/src/litestream-vfs.c @@ -9,6 +9,7 @@ extern const sqlite3_api_routines *sqlite3_api; /* Go function declarations */ +extern char* LitestreamVFSRegister(void); extern char* GoLitestreamRegisterConnection(void* db, sqlite3_uint64 file_id); extern char* GoLitestreamUnregisterConnection(void* db); extern char* GoLitestreamSetTime(void* db, char* timestamp); @@ -31,8 +32,13 @@ int sqlite3_litestreamvfs_init(sqlite3 *db, char **pzErrMsg, const sqlite3_api_r int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); - /* call into Go to register the VFS */ - LitestreamVFSRegister(); + /* Call into Go to register the VFS and check for errors. */ + char* err = LitestreamVFSRegister(); + if (err != NULL) { + *pzErrMsg = sqlite3_mprintf("%s", err); + free(err); + return SQLITE_ERROR; + } /* Register SQL functions for new connections. */ rc = sqlite3_auto_extension((void (*)(void))litestream_auto_extension);