mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
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>
This commit is contained in:
+89
-13
@@ -1,8 +1,9 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM golang:1.25-alpine AS builder
|
||||
|
||||
# ---- 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 ./
|
||||
@@ -14,7 +15,6 @@ COPY . .
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT_HASH=unknown
|
||||
ARG BUILD_TIME=unknown
|
||||
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -39,11 +39,79 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
-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
|
||||
|
||||
RUN apk add --no-cache ca-certificates tzdata wget nerdctl cni-plugins iptables \
|
||||
# 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) \
|
||||
@@ -58,7 +126,7 @@ RUN apk add --no-cache ca-certificates tzdata wget nerdctl cni-plugins iptables
|
||||
' "bridge": "cni0",' \
|
||||
' "isGateway": true,' \
|
||||
' "ipMasq": true,' \
|
||||
' "promiscMode": true,' \
|
||||
' "hairpinMode": true,' \
|
||||
' "ipam": {' \
|
||||
' "type": "host-local",' \
|
||||
' "ranges": [[' \
|
||||
@@ -76,16 +144,24 @@ RUN apk add --no-cache ca-certificates tzdata wget nerdctl cni-plugins iptables
|
||||
' ]' \
|
||||
'}' > /etc/cni/net.d/10-memoh.conflist
|
||||
|
||||
COPY --from=builder /build/memoh-server /app/memoh-server
|
||||
COPY --from=builder /build/spec /app/spec
|
||||
# MCP image for containerd import
|
||||
COPY --from=oci-exporter /out/memoh-mcp.tar /opt/images/memoh-mcp.tar
|
||||
|
||||
RUN mkdir -p /opt/memoh/data
|
||||
# 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=5s --retries=3 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:8080/health \
|
||||
|| wget --no-verbose --tries=1 --spider http://server:8080/health \
|
||||
|| exit 1
|
||||
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
|
||||
|
||||
CMD ["/app/memoh-server"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
@@ -25,7 +25,7 @@ FROM nginx:alpine
|
||||
|
||||
COPY --from=builder /build/packages/web/dist /usr/share/nginx/html
|
||||
|
||||
COPY docker/config/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 8082
|
||||
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
## Service configuration
|
||||
[log]
|
||||
level = "info"
|
||||
format = "text"
|
||||
|
||||
[server]
|
||||
addr = "server:8080"
|
||||
|
||||
## Admin
|
||||
[admin]
|
||||
username = "admin"
|
||||
password = "admin123"
|
||||
email = "admin@memoh.local"
|
||||
|
||||
## Auth configuration
|
||||
[auth]
|
||||
jwt_secret = "YZq8kXrW5dFpNt9mLxQvHbRjKsMnOePw"
|
||||
jwt_expires_in = "168h"
|
||||
|
||||
## Docker configuration
|
||||
[containerd]
|
||||
socket_path = "/run/containerd/containerd.sock"
|
||||
namespace = "default"
|
||||
|
||||
[mcp]
|
||||
image = "docker.io/library/memoh-mcp:latest"
|
||||
snapshotter = "overlayfs"
|
||||
data_root = "/opt/memoh/data"
|
||||
data_mount = "/data"
|
||||
|
||||
## Postgres configuration
|
||||
[postgres]
|
||||
host = "postgres"
|
||||
port = 5432
|
||||
user = "memoh"
|
||||
password = "memoh123"
|
||||
database = "memoh"
|
||||
sslmode = "disable"
|
||||
|
||||
## Qdrant configuration
|
||||
[qdrant]
|
||||
base_url = "http://qdrant:6334"
|
||||
api_key = ""
|
||||
collection = "memory"
|
||||
timeout_seconds = 10
|
||||
|
||||
## Agent Gateway
|
||||
[agent_gateway]
|
||||
host = "agent"
|
||||
port = 8081
|
||||
server_addr = "server:8080"
|
||||
|
||||
## Web
|
||||
[web]
|
||||
host = "127.0.0.1"
|
||||
port = 8082
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
MCP_IMAGE="${MCP_IMAGE:-docker.io/library/memoh-mcp:latest}"
|
||||
|
||||
# ---- Setup cgroup v2 delegation for nested containerd ----
|
||||
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||
echo "Setting up cgroup v2 delegation..."
|
||||
mkdir -p /sys/fs/cgroup/init
|
||||
# Move existing processes out of root cgroup to allow subtree control
|
||||
while read -r pid; do
|
||||
echo "$pid" > /sys/fs/cgroup/init/cgroup.procs 2>/dev/null || true
|
||||
done < /sys/fs/cgroup/cgroup.procs
|
||||
# Enable all available controllers for subtree delegation
|
||||
sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \
|
||||
> /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ---- Start containerd in background ----
|
||||
mkdir -p /run/containerd
|
||||
containerd &
|
||||
CONTAINERD_PID=$!
|
||||
|
||||
echo "Waiting for containerd..."
|
||||
for i in $(seq 1 30); do
|
||||
if ctr version >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
if ! ctr version >/dev/null 2>&1; then
|
||||
echo "ERROR: containerd not responsive after 30s"
|
||||
exit 1
|
||||
fi
|
||||
echo "containerd is running (pid $CONTAINERD_PID)"
|
||||
|
||||
# ---- Import MCP image if not already present ----
|
||||
if ! ctr -n default images check "name==${MCP_IMAGE}" 2>/dev/null | grep -q "${MCP_IMAGE}"; then
|
||||
echo "Importing MCP image into containerd..."
|
||||
for tar in /opt/images/*.tar; do
|
||||
if [ -f "$tar" ]; then
|
||||
ctr -n default images import --all-platforms "$tar" 2>&1 || true
|
||||
fi
|
||||
done
|
||||
if ctr -n default images check "name==${MCP_IMAGE}" 2>/dev/null | grep -q "${MCP_IMAGE}"; then
|
||||
echo "MCP image ready: ${MCP_IMAGE}"
|
||||
else
|
||||
echo "WARNING: MCP image not available after import, will try pull at runtime"
|
||||
fi
|
||||
else
|
||||
echo "MCP image already present: ${MCP_IMAGE}"
|
||||
fi
|
||||
|
||||
echo "containerd is ready, starting memoh-server..."
|
||||
|
||||
# ---- Start server (foreground, trap signals for graceful shutdown) ----
|
||||
trap 'echo "Shutting down..."; kill $SERVER_PID 2>/dev/null; kill $CONTAINERD_PID 2>/dev/null; wait' TERM INT
|
||||
|
||||
/app/memoh-server serve &
|
||||
SERVER_PID=$!
|
||||
|
||||
wait $SERVER_PID
|
||||
EXIT_CODE=$?
|
||||
|
||||
kill $CONTAINERD_PID 2>/dev/null || true
|
||||
wait $CONTAINERD_PID 2>/dev/null || true
|
||||
|
||||
exit $EXIT_CODE
|
||||
Reference in New Issue
Block a user