Files
Memoh/docker/Dockerfile.server
T
BBQ bc374fe8cd refactor: content-addressed assets, cross-channel multimodal, infra simplification (#63)
* refactor(attachment): multimodal attachment refactor with snapshot schema and storage layer

- Add snapshot schema migration (0008) and update init/versions/snapshots
- Add internal/attachment and internal/channel normalize for unified attachment handling
- Move containerfs provider from internal/media to internal/storage
- Update agent types, channel adapters (Telegram/Feishu), inbound and handlers
- Add containerd snapshot lineage and local_channel tests
- Regenerate sqlc, swagger and SDK

* refactor(media): content-addressed asset system with unified naming

- Replace asset_id foreign key with content_hash as sole identifier
  for bot_history_message_assets (pure soft-link model)
- Remove mime, size_bytes, storage_key from DB; derive at read time
  via media.Resolve from actual storage
- Merge migrations 0008/0009 into single 0008; keep 0001 as canonical schema
- Add Docker initdb script for deterministic migration execution order
- Fix cross-channel real-time image display (Telegram → WebUI SSE)
- Fix message disappearing on refresh (null assets fallback)
- Fix file icon instead of image preview (mime derivation from storage)
- Unify AssetID → ContentHash naming across Go, Agent, and Frontend
- Change storage key prefix from 4-char to 2-char for directory sharding
- Add server-entrypoint.sh for Docker deployment migration handling

* refactor(infra): embedded migrations, Docker simplification, and config consolidation

- Embed SQL migrations into Go binary, removing shell-based migration scripts
- Consolidate config files into conf/ directory (app.example.toml, app.docker.toml, app.dev.toml)
- Simplify Docker setup: remove initdb.d scripts, streamline nginx config and entrypoint
- Remove legacy CLI, feishu-echo commands, and obsolete incremental migration files
- Update install script and docs to require sudo for one-click install
- Add mise tasks for dev environment orchestration

* chore: recover migrations

---------

Co-authored-by: Acbox <acbox0328@gmail.com>
2026-02-19 00:20:27 +08:00

168 lines
5.7 KiB
Docker

# syntax=docker/dockerfile:1
# ---- Stage 1: Build server binary ----
FROM golang:1.25-alpine AS server-builder
WORKDIR /build
RUN apk add --no-cache git make
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG VERSION=dev
ARG COMMIT_HASH=unknown
ARG BUILD_TIME=unknown
ARG TARGETOS
ARG TARGETARCH
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
set -eux; \
build_os="${TARGETOS:-linux}"; \
build_arch="${TARGETARCH:-$(uname -m)}"; \
case "$build_arch" in \
x86_64) build_arch="amd64" ;; \
aarch64) build_arch="arm64" ;; \
esac; \
case "$build_arch" in \
amd64|arm64) ;; \
*) echo "unsupported TARGETARCH: $build_arch (only amd64/arm64)"; exit 1 ;; \
esac; \
CGO_ENABLED=0 GOOS="$build_os" GOARCH="$build_arch" \
go build -trimpath \
-ldflags "-s -w \
-X github.com/memohai/memoh/internal/version.Version=${VERSION} \
-X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH} \
-X github.com/memohai/memoh/internal/version.BuildTime=${BUILD_TIME}" \
-o memoh-server ./cmd/agent/main.go
# ---- Stage 2: Build MCP binary ----
FROM golang:1.25-alpine AS mcp-builder
WORKDIR /src
RUN apk add --no-cache ca-certificates git
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETARCH=amd64
ARG COMMIT_HASH=unknown
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} \
go build -trimpath \
-ldflags "-s -w -X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH}" \
-o /out/mcp ./cmd/mcp
# ---- Stage 3: Assemble MCP image rootfs ----
FROM alpine:latest AS mcp-rootfs
RUN apk add --no-cache grep curl bash
RUN apk add --no-cache nodejs npm
RUN apk add --no-cache python3 && \
curl -LsSf https://astral.sh/uv/install.sh | sh && \
ln -sf /root/.local/bin/uv /usr/local/bin/uv && \
ln -sf /root/.local/bin/uvx /usr/local/bin/uvx
COPY --from=mcp-builder /out/mcp /opt/mcp
COPY cmd/mcp/template /opt/mcp-template
RUN printf '#!/bin/sh\n\
[ -e /app/mcp ] || { mkdir -p /app; [ -f /opt/mcp ] && cp -a /opt/mcp /app/mcp 2>/dev/null || true; }\n\
if [ -x /app/mcp ]; then exec /app/mcp "$@"; fi\n\
exec /opt/mcp "$@"\n' > /opt/entrypoint.sh && chmod +x /opt/entrypoint.sh
RUN tar -cf /tmp/rootfs.tar \
--exclude='./proc' --exclude='./sys' --exclude='./dev' \
--exclude='./tmp' --exclude='./run' \
-C / .
# ---- Stage 4: Package rootfs as OCI image tar ----
FROM alpine:latest AS oci-exporter
COPY --from=mcp-rootfs /tmp/rootfs.tar /tmp/layer.tar
ARG MCP_IMAGE_TAG=docker.io/library/memoh-mcp:latest
RUN set -e \
&& LAYER_SHA=$(sha256sum /tmp/layer.tar | awk '{print $1}') \
&& LAYER_SIZE=$(wc -c < /tmp/layer.tar) \
&& mkdir -p "/tmp/image/${LAYER_SHA}" /out \
&& mv /tmp/layer.tar "/tmp/image/${LAYER_SHA}/layer.tar" \
&& printf '{"architecture":"amd64","os":"linux","created":"1970-01-01T00:00:00Z","config":{"Entrypoint":["/opt/entrypoint.sh"],"WorkingDir":"/app","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:%s"]},"history":[{"created":"1970-01-01T00:00:00Z","comment":"memoh-mcp image"}]}' \
"${LAYER_SHA}" > /tmp/config.json \
&& CONFIG_SHA=$(sha256sum /tmp/config.json | awk '{print $1}') \
&& mv /tmp/config.json "/tmp/image/${CONFIG_SHA}.json" \
&& printf '[{"Config":"%s.json","RepoTags":["%s"],"Layers":["%s/layer.tar"]}]' \
"${CONFIG_SHA}" "${MCP_IMAGE_TAG}" "${LAYER_SHA}" > /tmp/image/manifest.json \
&& cd /tmp/image && tar -cf /out/memoh-mcp.tar manifest.json "${CONFIG_SHA}.json" "${LAYER_SHA}/"
# ---- Stage 5: Final runtime (containerd + server + CNI) ----
FROM alpine:latest
WORKDIR /app
# containerd runtime
RUN apk add --no-cache containerd containerd-ctr
# CNI plugins + iptables (for MCP container networking)
RUN apk add --no-cache ca-certificates tzdata wget cni-plugins iptables \
&& mkdir -p /opt/cni/bin \
&& (cp -a /usr/lib/cni/. /opt/cni/bin/ 2>/dev/null || true) \
&& (cp -a /usr/libexec/cni/. /opt/cni/bin/ 2>/dev/null || true) \
&& mkdir -p /etc/cni/net.d /var/lib/cni \
&& printf '%s\n' \
'{' \
' "cniVersion": "1.0.0",' \
' "name": "memoh-cni",' \
' "plugins": [' \
' {' \
' "type": "bridge",' \
' "bridge": "cni0",' \
' "isGateway": true,' \
' "ipMasq": true,' \
' "hairpinMode": true,' \
' "ipam": {' \
' "type": "host-local",' \
' "ranges": [[' \
' { "subnet": "10.88.0.0/16" }' \
' ]],' \
' "routes": [' \
' { "dst": "0.0.0.0/0" }' \
' ]' \
' }' \
' },' \
' {' \
' "type": "portmap",' \
' "capabilities": { "portMappings": true }' \
' }' \
' ]' \
'}' > /etc/cni/net.d/10-memoh.conflist
# MCP image for containerd import
COPY --from=oci-exporter /out/memoh-mcp.tar /opt/images/memoh-mcp.tar
# Server binary and spec
COPY --from=server-builder /build/memoh-server /app/memoh-server
COPY --from=server-builder /build/spec /app/spec
# Entrypoint: start containerd, then server
COPY docker/server-entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN mkdir -p /opt/memoh/data /run/containerd /var/lib/containerd
VOLUME ["/var/lib/containerd", "/opt/memoh/data"]
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health || exit 1
ENTRYPOINT ["/entrypoint.sh"]