publish

package module
v0.0.0-...-cb69bf3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 16, 2026 License: MIT Imports: 23 Imported by: 0

README

pub — Application Deployment Tool

Deploy applications to remote Docker hosts over SSH.

Requirements

  • ssh, scp — for remote access
  • sshpass — only if the profile uses password authentication

Installation

make build
# or directly:
go build -o pub ./cmd/pub/...

Usage

pub run         [--pub-dir .pub] [--no-build] [--no-sync] [profile]
pub check
pub init        [--pub-dir .pub]
pub status      [--pub-dir .pub]
pub sample-config
pub version
Run Arguments
Argument Description
profile Profile name (default: default). Resolved as <pub-dir>/profiles/<profile>/
--pub-dir Publish directory root (default: .pub). Profiles live under <pub-dir>/profiles/
--no-build Skip the local build step
--no-sync Skip shipping files to the server (reuse existing remote files)
Subcommands
Command Description
run Build and publish the application to a remote server
check Verify that all required tools are installed
init Create a new profile from the embedded config template
status Show the deployment status of a profile on the remote server
sample-config Print the sample config.yaml template to stdout
Profiles

Each deployment target is a profile directory under <pub-dir>/profiles/<name>/. Start by creating one with the sample config:

mkdir -p .pub/profiles/myapp
pub sample-config > .pub/profiles/myapp/config.yaml
# or use init to scaffold the whole directory:
pub init myapp

Profile directory layout:

.pub/
└── profiles/
    ├── default/
    │   ├── config.yaml           # Required: deployment settings
    │   ├── config.secrets.yaml   # Optional: secrets (git-ignored)
    │   ├── extra_compose.yaml    # Optional: extra compose overrides
    │   ├── Dockerfile            # Optional: auto-shipped in build mode
    │   └── hooks/                # Optional: hook scripts
    │       ├── pre_publish/
    │       ├── pre_build/
    │       ├── post_build/
    │       ├── pre_sync/
    │       ├── post_sync/
    │       ├── pre_down/
    │       ├── post_down/
    │       ├── pre_up/
    │       └── post_up/
    └── staging/
        └── config.yaml

Deployment Flow

  1. Profile resolution — loads config.yaml and deep-merges config.secrets.yaml
  2. First run check — determines whether this is a first publish (sets $FIRST_RUN=1)
  3. Build — runs the required build hook (e.g. make dist), skipped with --no-build
  4. Remote setup — ensures remote directories exist, resolves container user
  5. Compose rendering — generates compose.yaml from config, merges extra_compose.yaml
  6. File staging — ships all files in a single tar.gz over SSH stdin
  7. Sync — moves staged files into place (backing up changed destinations)
  8. Stop — stops the running app if updating
  9. Up — runs the required up hook (e.g. docker compose up -d)
  10. Verify — checks the deployed version via public_url or SSH tunnel

Hooks

Hooks are bash scripts that run at specific points in the deploy. Two hooks are required:

Hook Location Purpose
build local Build the application (e.g. make dist)
up remote Bring the stack up (e.g. docker compose up -d)

Optional hooks wrap each stage:

Hook Location Runs
pre_publish local Before anything
pre_build local Before the build hook
post_build local After the build hook
pre_sync remote Before moving staged files into place
post_sync remote After moving staged files into place
pre_down remote Before stopping the running app
post_down remote After stopping the running app
pre_up remote Before the up hook
post_up remote After the up hook
post_publish local After the deploy is verified
status remote Run by pub status; defaults to docker compose ps
Environment Variables

All hooks receive:

  • PROFILE, PROFILE_DIR, REPO_ROOT
  • REMOTE_DIR, REMOTE_HOST, REMOTE_USER
  • IMAGE, SERVICE_NAME
  • FIRST_RUN — set to 1 on the first deploy to a server

Hook scripts can be inline strings in config.yaml or .sh files under <profile>/hooks/<name>/.

License

MIT License. See LICENSE for details.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	CommitID   = "unknown"
	CommitTime = "unknown"
	BranchName = "unknown"
)

Build-time injected via -ldflags -X.

View Source
var SCRIPT_DIR string

SCRIPT_DIR is the repo root directory.

Functions

func CheckSSHPass

func CheckSSHPass(profileDir string) (bool, error)

CheckSSHPass checks whether the profile config at profileDir has a non-empty password field. It mimics the bash grep used by publish.sh.

func CheckTools

func CheckTools(tools ...string) []string

CheckTools returns the list of missing tool names from the given list.

func DBServiceCompose

func DBServiceCompose(s ResolvedDBService) map[string]any

DBServiceCompose builds the compose service definition for a DB service.

func DBServiceDSN

func DBServiceDSN(s ResolvedDBService) string

DBServiceDSN returns the DATABASE_URL for a resolved DB service.

func DeepMerge

func DeepMerge(base, extra map[string]any) map[string]any

DeepMerge recursively merges extra into base (extra wins). Nested maps are merged; lists and scalars are replaced. List-merge markers (leading/trailing "+") are supported.

func DockerfileShortcut

func DockerfileShortcut(cfg *Config, profileDir string, image string) string

DockerfileShortcut expands the app Dockerfile shortcut. Returns a sync_files entry ("LOCAL:REMOTE") or empty string.

func ExpandPlaceholders

func ExpandPlaceholders(v any, mapping map[string]string) any

ExpandPlaceholders recursively replaces placeholder substrings in strings within value (a string, map, or slice). Dict keys are left untouched.

func ExpandServicePlaceholders

func ExpandServicePlaceholders(services map[string]any) map[string]any

ExpandServicePlaceholders expands $NAME and $VOLS in every service definition.

func ManagedServiceNames

func ManagedServiceNames(cfg *Config, serviceName string) map[string]bool

ManagedServiceNames returns the compose service keys the publisher owns.

func MergeRemoteCompose

func MergeRemoteCompose(remote *Remote, composeRemote string, generated map[string]any, managedNames map[string]bool) map[string]any

MergeRemoteCompose updates an existing remote compose in place.

func NewCheckCommand

func NewCheckCommand() *cc.Command

NewCheckCommand returns the "check" subcommand.

func NewCommand

func NewCommand() *cc.Command

NewCommand returns the root pub command — a dispatcher with subcommands.

func NewInitCommand

func NewInitCommand() *cc.Command

NewInitCommand returns the "init" subcommand that scaffolds a new profile.

func NewRunCommand

func NewRunCommand() *cc.Command

NewRunCommand returns the "run" subcommand.

func NewSampleConfigCommand

func NewSampleConfigCommand() *cc.Command

NewSampleConfigCommand returns a subcommand that prints the embedded config.example.yaml to stdout.

func NewStatusCommand

func NewStatusCommand() *cc.Command

NewStatusCommand returns the "status" subcommand that checks remote status.

func NewVersionCommand

func NewVersionCommand() *cc.Command

NewVersionCommand returns the "version" subcommand.

func ParseSyncEntry

func ParseSyncEntry(entry string, profileDir string) (localPath, remoteRel string, err error)

ParseSyncEntry parses a sync_files entry "[BKP:]LOCAL_PATH[:REMOTE_PATH]".

func Publish

func Publish(profile, pubDir string, noBuild, noSync bool) error

Publish runs the full publish flow.

func RenderCompose

func RenderCompose(cfg *Config, image, serviceName, appUser string, extra map[string]any) map[string]any

RenderCompose builds the remote compose file as a nested map.

func ResolveProfile

func ResolveProfile(pubDir, profile string) (profileDir string, err error)

ResolveProfile checks that the profile exists under pubDir/profiles/<name> and returns the profile directory path.

func SetProfile

func SetProfile(name string)

SetProfile sets the active profile name shown in log lines.

func VersionString

func VersionString() string

VersionString returns a single-line version string suitable for --version flag or logging.

Types

type AppConfig

type AppConfig struct {
	Port          any            `yaml:"port"`
	ServiceName   string         `yaml:"service_name,omitempty"`
	User          string         `yaml:"user,omitempty"`
	Build         any            `yaml:"build,omitempty"`
	Dockerfile    string         `yaml:"dockerfile,omitempty"`
	Tunnel        bool           `yaml:"tunnel,omitempty"`
	PublicURL     string         `yaml:"public_url,omitempty"`
	Volumes       []string       `yaml:"volumes,omitempty"`
	Compose       map[string]any `yaml:"compose,omitempty"`
	ContainerName string         `yaml:"container_name,omitempty"`
}

type Config

type Config struct {
	SSH             SSHConfig         `yaml:"ssh"`
	Remote          RemoteConfig      `yaml:"remote"`
	App             AppConfig         `yaml:"app"`
	Image           *ImageConfig      `yaml:"image,omitempty"`
	Hooks           map[string]string `yaml:"hooks,omitempty"`
	SyncFiles       []string          `yaml:"sync_files,omitempty"`
	Networks        map[string]any    `yaml:"networks,omitempty"`
	PostgresService *DBServiceConfig  `yaml:"postgres_service,omitempty"`
	MySQLService    *DBServiceConfig  `yaml:"mysql_service,omitempty"`
}

func LoadConfig

func LoadConfig(profileDir string) (*Config, error)

LoadConfig reads config.yaml from the profile directory and deep-merges config.secrets.yaml if present.

type DBServiceConfig

type DBServiceConfig struct {
	Enabled     bool   `yaml:"enabled"`
	ServiceName string `yaml:"service_name,omitempty"`
	Up          *bool  `yaml:"up,omitempty"`
	AppDBURL    bool   `yaml:"app_database_url,omitempty"`
	User        string `yaml:"user,omitempty"`
	Password    string `yaml:"password,omitempty"`
	DB          string `yaml:"db,omitempty"`
}

type ImageConfig

type ImageConfig struct {
	Name string `yaml:"name"`
	Tag  string `yaml:"tag"`
}

type PortMapping

type PortMapping struct {
	Entry    string
	HostPort string
}

PortMapping holds the compose ports entry and the published host port.

func AppPortMapping

func AppPortMapping(port any) PortMapping

AppPortMapping interprets app.port for the compose ports and tunnel.

type Remote

type Remote struct {
	Host     string
	Port     string
	User     string
	Key      string
	Password string
	// contains filtered or unexported fields
}

Remote wraps SSH/SCP operations against a configured host.

func NewRemote

func NewRemote(cfg SSHConfig) *Remote

NewRemote creates a Remote from the SSH config.

func (*Remote) Capture

func (r *Remote) Capture(remoteCmd string) string

Capture runs a command on the remote and returns its trimmed stdout.

func (*Remote) Exists

func (r *Remote) Exists(remotePath string) bool

Exists checks whether a path exists on the remote.

func (*Remote) Run

func (r *Remote) Run(remoteCmd string) error

Run executes a command on the remote host.

func (*Remote) RunScript

func (r *Remote) RunScript(remoteDir, script string) error

RunScript pipes a bash script over SSH stdin into remoteDir.

func (*Remote) Tunnel

func (r *Remote) Tunnel(localPort, remotePort int, remoteHost ...string) (func(), error)

Tunnel opens an SSH local forward and returns a cleanup function. The forward maps 127.0.0.1:localPort to remoteHost:remotePort.

func (*Remote) Upload

func (r *Remote) Upload(localPath, remotePath string) error

Upload copies a local file to the remote host via SCP.

type RemoteConfig

type RemoteConfig struct {
	Dir string `yaml:"dir"`
}

type ResolvedDBService

type ResolvedDBService struct {
	Name     string
	Up       bool
	AppDBURL bool
	Kind     string
	Image    string
	DataPath string
	User     string
	Password string
	DB       string
}

ResolvedDBService holds the resolved settings for a bundled database service.

func ResolveDBServices

func ResolveDBServices(cfg *Config) []ResolvedDBService

ResolveDBServices reads the postgres_service / mysql_service sections.

type SSHConfig

type SSHConfig struct {
	Host     string `yaml:"host"`
	Port     string `yaml:"port,omitempty"`
	User     string `yaml:"user,omitempty"`
	Key      string `yaml:"key,omitempty"`
	Password string `yaml:"password,omitempty"`
}

Directories

Path Synopsis
cmd
pub command