mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
feat: Atomic update mcp image
This commit is contained in:
+5
-3
@@ -10,6 +10,8 @@ ARG COMMIT_HASH=unknown
|
|||||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH:-amd64} \
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH:-amd64} \
|
||||||
go build -trimpath -ldflags "-s -w -X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH}" -o /out/mcp ./cmd/mcp
|
go build -trimpath -ldflags "-s -w -X github.com/memohai/memoh/internal/version.CommitHash=${COMMIT_HASH}" -o /out/mcp ./cmd/mcp
|
||||||
|
|
||||||
FROM busybox:latest
|
FROM alpine:latest
|
||||||
COPY --from=build /out/mcp /mcp
|
RUN apk add --no-cache grep
|
||||||
ENTRYPOINT ["/mcp"]
|
WORKDIR /app
|
||||||
|
COPY --from=build /out/mcp /app/mcp
|
||||||
|
ENTRYPOINT ["/app/mcp"]
|
||||||
|
|||||||
+22
-3
@@ -2,8 +2,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/memohai/memoh/internal/logger"
|
"github.com/memohai/memoh/internal/logger"
|
||||||
"github.com/memohai/memoh/internal/mcp"
|
"github.com/memohai/memoh/internal/mcp"
|
||||||
@@ -12,13 +16,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||||
|
defer stop()
|
||||||
|
|
||||||
server := gomcp.NewServer(
|
server := gomcp.NewServer(
|
||||||
&gomcp.Implementation{Name: "memoh-mcp", Version: version.GetInfo()},
|
&gomcp.Implementation{Name: "memoh-mcp", Version: version.GetInfo()},
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
mcp.RegisterTools(server)
|
mcp.RegisterTools(server)
|
||||||
if err := server.Run(context.Background(), &gomcp.StdioTransport{}); err != nil {
|
err := server.Run(ctx, &gomcp.StdioTransport{})
|
||||||
logger.Error("mcp server failed", slog.Any("error", err))
|
if ctx.Err() != nil {
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
if err == nil {
|
||||||
|
logger.Warn("mcp server exited without error; waiting for shutdown signal")
|
||||||
|
<-ctx.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
logger.Warn("mcp stdio closed; waiting for shutdown signal")
|
||||||
|
<-ctx.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Error("mcp server failed", slog.Any("error", err))
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ socket_path = "/run/containerd/containerd.sock"
|
|||||||
namespace = "default"
|
namespace = "default"
|
||||||
|
|
||||||
[mcp]
|
[mcp]
|
||||||
busybox_image = "docker.io/library/busybox:latest"
|
busybox_image = "docker.io/library/memoh-mcp:dev"
|
||||||
snapshotter = "overlayfs"
|
snapshotter = "overlayfs"
|
||||||
data_root = "data"
|
data_root = "data"
|
||||||
data_mount = "/data"
|
data_mount = "/data"
|
||||||
|
|||||||
@@ -165,12 +165,20 @@ func (h *ContainerdHandler) CreateContainer(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
specOpts := []oci.SpecOpts{
|
specOpts := []oci.SpecOpts{
|
||||||
oci.WithMounts([]specs.Mount{{
|
oci.WithMounts([]specs.Mount{
|
||||||
Destination: dataMount,
|
{
|
||||||
Type: "bind",
|
Destination: dataMount,
|
||||||
Source: dataDir,
|
Type: "bind",
|
||||||
Options: []string{"rbind", "rw"},
|
Source: dataDir,
|
||||||
}}),
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/app",
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
}),
|
||||||
oci.WithProcessArgs("/bin/sh", "-lc", "sleep 2147483647"),
|
oci.WithProcessArgs("/bin/sh", "-lc", "sleep 2147483647"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-6
@@ -98,12 +98,20 @@ func (m *Manager) EnsureUser(ctx context.Context, userID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
specOpts := []oci.SpecOpts{
|
specOpts := []oci.SpecOpts{
|
||||||
oci.WithMounts([]specs.Mount{{
|
oci.WithMounts([]specs.Mount{
|
||||||
Destination: dataMount,
|
{
|
||||||
Type: "bind",
|
Destination: dataMount,
|
||||||
Source: dataDir,
|
Type: "bind",
|
||||||
Options: []string{"rbind", "rw"},
|
Source: dataDir,
|
||||||
}}),
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/app",
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = m.service.CreateContainer(ctx, ctr.CreateContainerRequest{
|
_, err = m.service.CreateContainer(ctx, ctr.CreateContainerRequest{
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
package mcp
|
package mcp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"mime"
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -109,6 +111,17 @@ type FSReadBase64Output struct {
|
|||||||
MimeType string `json:"mime_type" jsonschema:"detected mime type"`
|
MimeType string `json:"mime_type" jsonschema:"detected mime type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GrepInput struct {
|
||||||
|
Pattern string `json:"pattern" jsonschema:"grep pattern"`
|
||||||
|
Args []string `json:"args" jsonschema:"grep options (flags only)"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GrepOutput struct {
|
||||||
|
Stdout string `json:"stdout" jsonschema:"grep standard output"`
|
||||||
|
Stderr string `json:"stderr" jsonschema:"grep standard error"`
|
||||||
|
ExitCode int `json:"exit_code" jsonschema:"grep exit code"`
|
||||||
|
}
|
||||||
|
|
||||||
func RegisterTools(server *sdkmcp.Server) {
|
func RegisterTools(server *sdkmcp.Server) {
|
||||||
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "echo", Description: "echo input text"}, echoTool)
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "echo", Description: "echo input text"}, echoTool)
|
||||||
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.read", Description: "read file content"}, fsReadTool)
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.read", Description: "read file content"}, fsReadTool)
|
||||||
@@ -120,6 +133,7 @@ func RegisterTools(server *sdkmcp.Server) {
|
|||||||
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.apply_patch", Description: "apply unified diff patch"}, fsApplyPatchTool)
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.apply_patch", Description: "apply unified diff patch"}, fsApplyPatchTool)
|
||||||
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.mkdir", Description: "create directory (mkdir -p)"}, fsMkdirTool)
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.mkdir", Description: "create directory (mkdir -p)"}, fsMkdirTool)
|
||||||
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.rename", Description: "rename/move file or directory"}, fsRenameTool)
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "fs.rename", Description: "rename/move file or directory"}, fsRenameTool)
|
||||||
|
sdkmcp.AddTool(server, &sdkmcp.Tool{Name: "grep", Description: "grep within /data using GNU grep"}, grepTool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func echoTool(ctx context.Context, req *sdkmcp.CallToolRequest, input EchoInput) (
|
func echoTool(ctx context.Context, req *sdkmcp.CallToolRequest, input EchoInput) (
|
||||||
@@ -196,6 +210,48 @@ func fsReadBase64Tool(ctx context.Context, req *sdkmcp.CallToolRequest, input FS
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func grepTool(ctx context.Context, req *sdkmcp.CallToolRequest, input GrepInput) (
|
||||||
|
*sdkmcp.CallToolResult,
|
||||||
|
GrepOutput,
|
||||||
|
error,
|
||||||
|
) {
|
||||||
|
if strings.TrimSpace(input.Pattern) == "" {
|
||||||
|
return nil, GrepOutput{}, fmt.Errorf("pattern is required")
|
||||||
|
}
|
||||||
|
if stat, err := os.Stat("/data"); err != nil || !stat.IsDir() {
|
||||||
|
return nil, GrepOutput{}, fmt.Errorf("/data is not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := append([]string{}, input.Args...)
|
||||||
|
args = append(args, input.Pattern, ".")
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "grep", args...)
|
||||||
|
cmd.Dir = "/data"
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
exitCode := 0
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
|
exitCode = exitErr.ExitCode()
|
||||||
|
if exitCode != 1 {
|
||||||
|
return nil, GrepOutput{}, fmt.Errorf("grep failed: %s", strings.TrimSpace(stderr.String()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, GrepOutput{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, GrepOutput{
|
||||||
|
Stdout: stdout.String(),
|
||||||
|
Stderr: stderr.String(),
|
||||||
|
ExitCode: exitCode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func fsWriteTool(ctx context.Context, req *sdkmcp.CallToolRequest, input FSWriteInput) (
|
func fsWriteTool(ctx context.Context, req *sdkmcp.CallToolRequest, input FSWriteInput) (
|
||||||
*sdkmcp.CallToolResult,
|
*sdkmcp.CallToolResult,
|
||||||
FSWriteOutput,
|
FSWriteOutput,
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/v2/pkg/oci"
|
||||||
"github.com/containerd/errdefs"
|
"github.com/containerd/errdefs"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
|
||||||
"github.com/memohai/memoh/internal/config"
|
"github.com/memohai/memoh/internal/config"
|
||||||
|
|
||||||
@@ -65,12 +67,39 @@ func (m *Manager) CreateVersion(ctx context.Context, userID string) (*VersionInf
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataDir, err := m.ensureUserDir(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dataMount := m.cfg.DataMount
|
||||||
|
if dataMount == "" {
|
||||||
|
dataMount = config.DefaultDataMount
|
||||||
|
}
|
||||||
|
|
||||||
|
specOpts := []oci.SpecOpts{
|
||||||
|
oci.WithMounts([]specs.Mount{
|
||||||
|
{
|
||||||
|
Destination: dataMount,
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/app",
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
_, err = m.service.CreateContainerFromSnapshot(ctx, ctr.CreateContainerRequest{
|
_, err = m.service.CreateContainerFromSnapshot(ctx, ctr.CreateContainerRequest{
|
||||||
ID: containerID,
|
ID: containerID,
|
||||||
ImageRef: info.Image,
|
ImageRef: info.Image,
|
||||||
SnapshotID: activeSnapshotID,
|
SnapshotID: activeSnapshotID,
|
||||||
Snapshotter: info.Snapshotter,
|
Snapshotter: info.Snapshotter,
|
||||||
Labels: info.Labels,
|
Labels: info.Labels,
|
||||||
|
SpecOpts: specOpts,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -165,12 +194,38 @@ func (m *Manager) RollbackVersion(ctx context.Context, userID string, version in
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dataDir, err := m.ensureUserDir(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dataMount := m.cfg.DataMount
|
||||||
|
if dataMount == "" {
|
||||||
|
dataMount = config.DefaultDataMount
|
||||||
|
}
|
||||||
|
specOpts := []oci.SpecOpts{
|
||||||
|
oci.WithMounts([]specs.Mount{
|
||||||
|
{
|
||||||
|
Destination: dataMount,
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Destination: "/app",
|
||||||
|
Type: "bind",
|
||||||
|
Source: dataDir,
|
||||||
|
Options: []string{"rbind", "rw"},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
_, err = m.service.CreateContainerFromSnapshot(ctx, ctr.CreateContainerRequest{
|
_, err = m.service.CreateContainerFromSnapshot(ctx, ctr.CreateContainerRequest{
|
||||||
ID: containerID,
|
ID: containerID,
|
||||||
ImageRef: info.Image,
|
ImageRef: info.Image,
|
||||||
SnapshotID: activeSnapshotID,
|
SnapshotID: activeSnapshotID,
|
||||||
Snapshotter: info.Snapshotter,
|
Snapshotter: info.Snapshotter,
|
||||||
Labels: info.Labels,
|
Labels: info.Labels,
|
||||||
|
SpecOpts: specOpts,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ node = "25"
|
|||||||
bun = "latest"
|
bun = "latest"
|
||||||
# pnpm for workspace management
|
# pnpm for workspace management
|
||||||
pnpm = "10"
|
pnpm = "10"
|
||||||
|
# Lima for macOS
|
||||||
|
lima = { version = "latest", platform = "darwin" }
|
||||||
|
|
||||||
[task_config]
|
[task_config]
|
||||||
dir = "{{cwd}}"
|
dir = "{{cwd}}"
|
||||||
@@ -24,6 +26,24 @@ run = "pnpm install"
|
|||||||
description = "Install Go dependencies"
|
description = "Install Go dependencies"
|
||||||
run = "go mod download"
|
run = "go mod download"
|
||||||
|
|
||||||
|
[tasks.lima-up]
|
||||||
|
run = """
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl start default
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks.lima-down]
|
||||||
|
run = """
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl stop default
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
[tasks.containerd-install]
|
||||||
|
description = "Install containerd or verify in Lima"
|
||||||
|
run = "scripts/containerd-install.sh"
|
||||||
|
|
||||||
[tasks.swagger-generate]
|
[tasks.swagger-generate]
|
||||||
description = "Generate Swagger documentation"
|
description = "Generate Swagger documentation"
|
||||||
run = "cd internal/handlers && go generate"
|
run = "cd internal/handlers && go generate"
|
||||||
@@ -45,6 +65,18 @@ description = "Install CLI"
|
|||||||
depends = ["//:pnpm-install"]
|
depends = ["//:pnpm-install"]
|
||||||
run = "cd packages/cli && npm install -g"
|
run = "cd packages/cli && npm install -g"
|
||||||
|
|
||||||
|
[tasks.mcp-image-up]
|
||||||
|
description = "Build MCP container image"
|
||||||
|
run = "scripts/mcp-image-up.sh"
|
||||||
|
|
||||||
|
[tasks.mcp-image-down]
|
||||||
|
description = "Remove MCP container image"
|
||||||
|
run = "scripts/mcp-image-down.sh"
|
||||||
|
|
||||||
|
[tasks.compile-mcp]
|
||||||
|
description = "Build MCP binary into /app and signal container"
|
||||||
|
run = "scripts/compile-mcp.sh"
|
||||||
|
|
||||||
[tasks.dev]
|
[tasks.dev]
|
||||||
description = "Start development environment"
|
description = "Start development environment"
|
||||||
depends = [
|
depends = [
|
||||||
|
|||||||
Executable
+38
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APP_DIR=${APP_DIR:-/app}
|
||||||
|
BIN_NAME=${BIN_NAME:-mcp}
|
||||||
|
STOP_SIGNAL=${STOP_SIGNAL:-TERM}
|
||||||
|
CONTAINER_NAME=${CONTAINER_NAME:-}
|
||||||
|
TARGET_OS=${TARGET_OS:-linux}
|
||||||
|
TARGET_ARCH=${TARGET_ARCH:-}
|
||||||
|
|
||||||
|
if [ -z "$TARGET_ARCH" ]; then
|
||||||
|
case "$(uname -m)" in
|
||||||
|
arm64|aarch64)
|
||||||
|
TARGET_ARCH=arm64
|
||||||
|
;;
|
||||||
|
x86_64|amd64)
|
||||||
|
TARGET_ARCH=amd64
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
TARGET_ARCH=amd64
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$APP_DIR"
|
||||||
|
|
||||||
|
GOOS="$TARGET_OS" GOARCH="$TARGET_ARCH" go build -trimpath -ldflags "-s -w" -o "${APP_DIR}/${BIN_NAME}.new" ./cmd/mcp
|
||||||
|
mv -f "${APP_DIR}/${BIN_NAME}.new" "${APP_DIR}/${BIN_NAME}"
|
||||||
|
|
||||||
|
if [ -n "$CONTAINER_NAME" ]; then
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl shell default -- nerdctl kill -s "$STOP_SIGNAL" "$CONTAINER_NAME"
|
||||||
|
else
|
||||||
|
nerdctl kill -s "$STOP_SIGNAL" "$CONTAINER_NAME"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "CONTAINER_NAME is empty; skip sending stop signal."
|
||||||
|
fi
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl start default
|
||||||
|
limactl shell default -- sudo containerd --version
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v containerd >/dev/null 2>&1 && command -v nerdctl >/dev/null 2>&1; then
|
||||||
|
containerd --version
|
||||||
|
nerdctl --version
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y containerd nerdctl
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
sudo dnf install -y containerd nerdctl
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
sudo yum install -y containerd nerdctl
|
||||||
|
elif command -v apk >/dev/null 2>&1; then
|
||||||
|
sudo apk add --no-cache containerd nerdctl
|
||||||
|
else
|
||||||
|
echo "No supported package manager found. Install containerd manually."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
containerd --version
|
||||||
|
nerdctl --version
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE="memoh-mcp:dev"
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl shell default -- nerdctl rmi -f "$IMAGE"
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v nerdctl >/dev/null 2>&1; then
|
||||||
|
echo "nerdctl not found. Install nerdctl to remove images."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nerdctl rmi -f "$IMAGE"
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IMAGE="memoh-mcp:dev"
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = "Darwin" ]; then
|
||||||
|
limactl shell default -- nerdctl build -f cmd/mcp/Dockerfile -t "$IMAGE" .
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v nerdctl >/dev/null 2>&1; then
|
||||||
|
echo "nerdctl not found. Install nerdctl to build images."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
nerdctl build -f cmd/mcp/Dockerfile -t "$IMAGE" .
|
||||||
Reference in New Issue
Block a user