Initial implementation of mcdeploy deployment tool

Single Go binary with five commands:
- build: podman build locally with registry tags + git version
- push: podman push to MCR
- deploy: SSH pull/stop/rm/run on target node
- cert renew: issue TLS cert from Metacrypt via REST API
- status: show container status on a node

Config-driven via TOML service registry describing images,
Dockerfiles, container configs per node. Shells out to podman
for container operations and ssh for remote access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 00:01:15 -07:00
commit 8cd32cbb1c
12 changed files with 729 additions and 0 deletions

95
config.go Normal file
View File

@@ -0,0 +1,95 @@
package main
import (
"fmt"
"os"
"path/filepath"
toml "github.com/pelletier/go-toml/v2"
)
// Config is the top-level deployment configuration.
type Config struct {
Workspace string `toml:"workspace"`
Registry string `toml:"registry"`
MCDSL MCDSLConfig `toml:"mcdsl"`
Services []ServiceConfig `toml:"services"`
Nodes map[string]*NodeConfig `toml:"nodes"`
}
// MCDSLConfig points to the shared mcdsl library.
type MCDSLConfig struct {
Path string `toml:"path"` // relative to workspace
}
// ServiceConfig describes a deployable service.
type ServiceConfig struct {
Name string `toml:"name"`
Path string `toml:"path"` // relative to workspace
Images []string `toml:"images"`
Dockerfiles map[string]string `toml:"dockerfiles"` // image name -> Dockerfile path
UsesMCDSL bool `toml:"uses_mcdsl"`
}
// NodeConfig describes a deployment target machine.
type NodeConfig struct {
Host string `toml:"host"`
User string `toml:"user"`
Containers map[string]*ContainerConfig `toml:"containers"`
}
// ContainerConfig describes a container to run on a node.
type ContainerConfig struct {
Image string `toml:"image"`
Network string `toml:"network"`
User string `toml:"user"`
Volumes []string `toml:"volumes"`
Ports []string `toml:"ports"`
Restart string `toml:"restart"`
Cmd []string `toml:"cmd"`
}
// LoadConfig reads and parses a TOML config file.
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
var cfg Config
if err := toml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
// FindService looks up a service by name.
func (c *Config) FindService(name string) (*ServiceConfig, error) {
for i := range c.Services {
if c.Services[i].Name == name {
return &c.Services[i], nil
}
}
return nil, fmt.Errorf("service %q not found in config", name)
}
// FindNode looks up a node by name.
func (c *Config) FindNode(name string) (*NodeConfig, error) {
node, ok := c.Nodes[name]
if !ok {
return nil, fmt.Errorf("node %q not found in config", name)
}
return node, nil
}
// ServicePath returns the absolute path to a service directory.
func (c *Config) ServicePath(svc *ServiceConfig) string {
return filepath.Join(c.Workspace, svc.Path)
}
// ImageRef returns the full registry reference for an image
// (e.g. "mcr.svc.mcp.metacircular.net:8443/mc-proxy").
func (c *Config) ImageRef(image string) string {
return c.Registry + "/" + image
}