diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 91b811f..2161d9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,30 @@ jobs: provenance: true sbom: true - - name: Build and push Docker image (stage-release-all-in-one) + - name: Build and push Docker image (stage-release-all-in-one-noavx - non-AVX) + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + platforms: | + linux/arm64 + linux/amd64 + target: stage-release-all-in-one-noavx + tags: | + ghcr.io/${{ github.repository }}:non-avx + ${{ steps.meta.outputs.tags }}-non-avx + labels: ${{ steps.meta.outputs.labels }} + build-args: | + VERSION=${{ fromJSON(steps.goreleaser.outputs.metadata).version }} + COMMIT=${{ fromJSON(steps.goreleaser.outputs.metadata).commit }} + COMMIT_DATE=${{ fromJSON(steps.goreleaser.outputs.metadata).date }} + cache-from: type=gha + cache-to: type=gha,mode=max + provenance: true + sbom: true + + - name: Build and push Docker image (stage-release-all-in-one - AVX) uses: docker/build-push-action@v6 with: context: . diff --git a/Dockerfile b/Dockerfile index 693ebe1..bd12abf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,25 +49,72 @@ ENTRYPOINT ["/usr/local/bin/any-sync-bundle"] CMD ["start-bundle"] # -# Stage: stage-release-all-in-one +# Stage: stage-release-all-in-one (with AVX support) # FROM docker.io/redis/redis-stack-server:7.4.0-v7 AS stage-release-all-in-one +# MongoDB version configuration +ARG MONGODB_VERSION_AVX=8.0 + +# Bundle network ports +EXPOSE 33010 +EXPOSE 33020/udp + +VOLUME /data + +# Install prerequisites and MongoDB (AVX version) +RUN DEBIAN_FRONTEND=noninteractive \ + && apt-get update && apt-get install -y --no-install-recommends \ + gnupg \ + curl \ + ca-certificates \ + # Setup MongoDB AVX version repository (requires AVX) + && curl -fsSL https://pgp.mongodb.com/server-${MONGODB_VERSION_AVX}.asc | gpg -o /usr/share/keyrings/mongodb-server-${MONGODB_VERSION_AVX}.gpg --dearmor \ + && echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-${MONGODB_VERSION_AVX}.gpg ] http://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/${MONGODB_VERSION_AVX} multiverse" | tee /etc/apt/sources.list.d/mongodb-org-${MONGODB_VERSION_AVX}.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends mongodb-org-server \ + # Remove unnecessary packages + && apt-get remove -y gnupg curl python3-pip \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf \ + /var/lib/apt/lists/* \ + /tmp/* \ + /var/tmp/* \ + /usr/share/keyrings/mongodb-server-*.gpg \ + /etc/apt/sources.list.d/mongodb-org-*.list + +COPY --from=stage-bin /bin/any-sync-bundle /usr/local/bin/any-sync-bundle + +ENTRYPOINT ["/usr/local/bin/any-sync-bundle"] +CMD ["start-all-in-one"] + +# +# Stage: stage-release-all-in-one-noavx (without AVX support) +# +# Use Ubuntu 20.04 (focal) as base since MongoDB 4.4 only supports up to Ubuntu 20.04 +FROM ubuntu:20.04 AS stage-release-all-in-one-noavx + # Bundle network ports EXPOSE 33010 EXPOSE 33020/udp VOLUME /data -# Install prerequisites and MongoDB +# Install prerequisites, Redis Stack Server, and MongoDB 4.4 RUN DEBIAN_FRONTEND=noninteractive \ && apt-get update && apt-get install -y --no-install-recommends \ gnupg \ curl \ ca-certificates \ - # Install MongoDB - && curl -fsSL https://pgp.mongodb.com/server-8.0.asc | gpg -o /usr/share/keyrings/mongodb-server-8.0.gpg --dearmor \ - && echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] http://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/8.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-8.0.list \ + # Install Redis Stack Server + && curl -fsSL https://packages.redis.io/gpg | gpg --batch --yes --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb focal main" | tee /etc/apt/sources.list.d/redis.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends redis-stack-server \ + # Setup MongoDB 4.4 repository (no AVX required - officially supported on Ubuntu 20.04) + && curl -fsSL https://pgp.mongodb.com/server-4.4.asc | gpg --batch --yes -o /usr/share/keyrings/mongodb-server-4.4.gpg --dearmor \ + && echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-4.4.gpg ] http://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list \ && apt-get update \ && apt-get install -y --no-install-recommends mongodb-org-server \ # Remove unnecessary packages @@ -78,8 +125,10 @@ RUN DEBIAN_FRONTEND=noninteractive \ /var/lib/apt/lists/* \ /tmp/* \ /var/tmp/* \ - /usr/share/keyrings/mongodb-server-8.0.gpg \ - /etc/apt/sources.list.d/mongodb-org-8.0.list + /usr/share/keyrings/mongodb-server-*.gpg \ + /usr/share/keyrings/redis-archive-keyring.gpg \ + /etc/apt/sources.list.d/mongodb-org-*.list \ + /etc/apt/sources.list.d/redis.list COPY --from=stage-bin /bin/any-sync-bundle /usr/local/bin/any-sync-bundle diff --git a/README.md b/README.md index 8b701ce..047e090 100644 --- a/README.md +++ b/README.md @@ -29,13 +29,21 @@ - **Both** (comma-separated) for flexibility: `sync.example.com,192.168.1.100` ```sh +# For AVX-capable CPUs (most modern CPUs - 2011+) docker run -d \ -e ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS="192.168.100.9" \ -p 33010:33010 \ -p 33020:33020/udp \ -v $(pwd)/data:/data \ - --restart unless-stopped \ - ghcr.io/grishy/any-sync-bundle:1.2.1-2025-12-10 + ghcr.io/grishy/any-sync-bundle:1.1.3-2025-12-01 + +# For non-AVX CPUs (older hardware) +docker run \ + -e ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS="192.168.100.9" \ + -p 33010:33010 \ + -p 33020:33020/udp \ + -v $(pwd)/data:/data \ + ghcr.io/grishy/any-sync-bundle:1.1.3-2025-12-01-non-avx ``` After the first run, import `./data/client-config.yml` into Anytype apps. @@ -44,7 +52,15 @@ After the first run, import `./data/client-config.yml` into Anytype apps. ## Overview -### Key Features +- **โœ… Bundle (all-in-one container)**: Bundled with MongoDB and Redis built in. + - **AVX version** (default): Uses MongoDB 8.0+ for modern CPUs with AVX support. Tag: `latest` or version tag (e.g., `v1.1.3-2025-12-01`). + - **Non-AVX version**: Uses MongoDB 4.4 for older CPUs without AVX support. Tag: `non-avx` or version tag with `-non-avx` suffix (e.g., `v1.1.3-2025-12-01-non-avx`). +- **โœ… Bundle (solo bundle / container)**: A variant without MongoDB and Redis. You can use your own instances. Tag: `minimal` or version tag with `-minimal` suffix. +- **๐Ÿงถ Custom Light version[\*](#light-version-not-in-development)**: Not in development currently. + +> **๐Ÿ’ก Which image to use?** Most modern CPUs (2011+) support AVX. Use the default `latest` tag. For older hardware or if you encounter AVX-related errors, use the `non-avx` tag. + +## Key features - **Easy to start**: A single command to launch the server - **All-in-one option**: All services in a single container or in separate binaries @@ -87,16 +103,116 @@ Format: `v[bundle-version]-[anytype-compatibility-date]` | `ghcr.io/grishy/any-sync-bundle:1.2.1-2025-12-10` | All-in-one (embedded MongoDB/Redis) | | `ghcr.io/grishy/any-sync-bundle:1.2.1-2025-12-10-minimal` | Minimal (external MongoDB/Redis) | -Latest tags (`:latest`, `:minimal`) are available, but explicit version tags are recommended. +**Available tags:** +- `latest` / `v1.1.3-2025-12-01` - AVX version (MongoDB 8.0+) for modern CPUs +- `non-avx` / `v1.1.3-2025-12-01-non-avx` - Non-AVX version (MongoDB 4.4) for older CPUs +- `minimal` / `v1.1.3-2025-12-01-minimal` - Bundle only, external MongoDB/Redis required + +Using an explicit release tag keeps upgrades deliberate (my recommendation). ### Docker Compose (Recommended) -| File | Description | -| ---------------------- | -------------------------------------------- | -| `compose.aio.yml` | All-in-one with embedded MongoDB/Redis | -| `compose.external.yml` | Bundle + external MongoDB + Redis containers | -| `compose.s3.yml` | Bundle + MinIO for S3 storage | -| `compose.traefik.yml` | With Traefik reverse proxy | +1. Container (all-in-one with embedded MongoDB/Redis) + + **AVX version (recommended for most users):** + ```sh + docker run -d \ + -e ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS="192.168.100.9" \ + -p 33010:33010 \ + -p 33020:33020/udp \ + -v $(pwd)/data:/data \ + --restart unless-stopped \ + --name any-sync-bundle-aio \ + ghcr.io/grishy/any-sync-bundle:1.1.3-2025-12-01 + ``` + + **Non-AVX version (for older CPUs):** + ```sh + docker run -d \ + -e ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS="192.168.100.9" \ + -p 33010:33010 \ + -p 33020:33020/udp \ + -v $(pwd)/data:/data \ + --restart unless-stopped \ + --name any-sync-bundle-aio \ + ghcr.io/grishy/any-sync-bundle:1.1.3-2025-12-01-non-avx + ``` + +2. Container (solo bundle, external MongoDB/Redis) + ```sh + docker run -d \ + -e ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS="192.168.100.9" \ + -e ANY_SYNC_BUNDLE_INIT_MONGO_URI="mongodb://user:pass@mongo:27017/" \ + -e ANY_SYNC_BUNDLE_INIT_REDIS_URI="redis://redis:6379/" \ + -p 33010:33010 \ + -p 33020:33020/udp \ + -v $(pwd)/data:/data \ + --restart unless-stopped \ + --name any-sync-bundle \ + ghcr.io/grishy/any-sync-bundle:1.1.3-2025-12-01-minimal + ``` + +### Docker Compose + +- All-in-one image only: + ```sh + docker compose -f compose.aio.yml up -d + ``` +- Bundle + external MongoDB + Redis: + ```sh + docker compose -f compose.external.yml up -d + ``` +- With Traefik reverse proxy (TCP 33010 + UDP 33020): + ```sh + docker compose -f compose.traefik.yml up -d + ``` + Edit `ANY_SYNC_BUNDLE_INIT_EXTERNAL_ADDRS` in the compose file before starting. + +### Without container (binary) + +1. Download the binary from the [Release page](https://github.com/grishy/any-sync-bundle/releases) +2. Start as below (replace IP and URIs as needed): + + ```sh + ./any-sync-bundle start-bundle \ + --initial-external-addrs "192.168.100.9" \ + --initial-mongo-uri "mongodb://127.0.0.1:27017/" \ + --initial-redis-uri "redis://127.0.0.1:6379/" \ + --initial-storage ./data/storage + ``` + + systemd example: + + ```ini + [Unit] + Description=Any Sync Bundle + After=network-online.target + Wants=network-online.target + + [Service] + WorkingDirectory=/opt/any-sync-bundle + ExecStart=/opt/any-sync-bundle/any-sync-bundle start-bundle \ + --initial-external-addrs "example.local,192.168.100.9" \ + --initial-mongo-uri "mongodb://127.0.0.1:27017/?replicaSet=rs0" \ + --initial-redis-uri "redis://127.0.0.1:6379/" \ + --initial-storage /opt/any-sync-bundle/data/storage + Restart=on-failure + RestartSec=5 + + [Install] + WantedBy=multi-user.target + ``` + +## Building from Source + +### Traditional Go Build + +**Prerequisites:** + +- Go 1.25.2 or later +- Docker (optional, for testing with containers) + +**Build:** ```sh # Pick one as example: diff --git a/cmd/start.go b/cmd/start.go index 51da171..f71147a 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -285,7 +285,27 @@ func newInfraProcess(ctx context.Context, name, bin string, args ...string) (*in done := make(chan error, 1) go func() { - done <- cmd.Wait() + err := cmd.Wait() + // Log process exit for debugging (especially for MongoDB AVX errors) + if err != nil { + fields := []zap.Field{ + zap.String("process", name), + zap.String("error_msg", err.Error()), + zap.Error(err), + } + + if exitErr, ok := err.(*exec.ExitError); ok { + fields = append(fields, zap.Int("exit_code", exitErr.ExitCode())) + } + + log.Debug("process exited with error", fields...) + + // Check if this is an AVX-related error for MongoDB + if name == "mongo" && isAVXRelatedError(err) { + printAVXErrorMessage(err, "process exited") + } + } + done <- err }() go streamPipe(name, stdout) @@ -360,6 +380,62 @@ func streamPipe(name string, reader io.Reader) { } } +// isAVXRelatedError checks if an error is related to AVX (illegal instruction) +func isAVXRelatedError(err error) bool { + if err == nil { + return false + } + + // Check for exit code 132 (SIGILL - Illegal Instruction) + if exitErr, ok := err.(*exec.ExitError); ok { + if exitErr.ExitCode() == 132 { + return true + } + } + + // Check error message for AVX-related keywords + errMsg := strings.ToLower(err.Error()) + avxKeywords := []string{ + "avx", + "illegal instruction", + "sigill", + "code 132", + "exit code 132", + "avx-related", + } + for _, keyword := range avxKeywords { + if strings.Contains(errMsg, keyword) { + return true + } + } + return false +} + +// printAVXErrorMessage displays a formatted AVX error message to the user +func printAVXErrorMessage(err error, context string) { + exitCode := "" + if exitErr, ok := err.(*exec.ExitError); ok { + exitCode = fmt.Sprintf(" (exit code %d)", exitErr.ExitCode()) + } + + _, _ = fmt.Fprint(os.Stderr, "\n") + _, _ = fmt.Fprint(os.Stderr, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n") + _, _ = fmt.Fprint(os.Stderr, "โš ๏ธ MONGODB AVX ERROR DETECTED โš ๏ธ\n") + _, _ = fmt.Fprint(os.Stderr, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n") + _, _ = fmt.Fprintf(os.Stderr, "MongoDB failed to start%s (%s)\n", exitCode, context) + _, _ = fmt.Fprint(os.Stderr, "\n") + _, _ = fmt.Fprint(os.Stderr, "This is an AVX-related error. Your CPU does not support AVX\n") + _, _ = fmt.Fprint(os.Stderr, "instructions required by MongoDB. The process was killed by the\n") + _, _ = fmt.Fprint(os.Stderr, "kernel with SIGILL (Illegal Instruction) before it could write\n") + _, _ = fmt.Fprint(os.Stderr, "any logs.\n") + _, _ = fmt.Fprint(os.Stderr, "\n") + _, _ = fmt.Fprint(os.Stderr, "SOLUTION: Use the 'non-avx' image tag:\n") + _, _ = fmt.Fprint(os.Stderr, " ghcr.io/grishy/any-sync-bundle:non-avx\n") + _, _ = fmt.Fprint(os.Stderr, "\n") + _, _ = fmt.Fprint(os.Stderr, "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n") + _, _ = fmt.Fprint(os.Stderr, "\n") +} + // waitForTCPReady polls the address until a TCP connection succeeds or timeout is reached. func waitForTCPReady(addr string, timeout time.Duration) error { ctx, cancel := context.WithTimeout(context.Background(), timeout)