Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: .
Expand Down
63 changes: 56 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
136 changes: 126 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down
78 changes: 77 additions & 1 deletion cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down