Skip to content

Add SSH deployment support with release management - #1170

Draft
Soner (shyim) wants to merge 1 commit into
mainfrom
claude/shopware-cli-deploy-command-umiqxz
Draft

Add SSH deployment support with release management#1170
Soner (shyim) wants to merge 1 commit into
mainfrom
claude/shopware-cli-deploy-command-umiqxz

Conversation

@shyim

Copy link
Copy Markdown
Member

Summary

This PR adds comprehensive SSH-based deployment capabilities to the CLI, enabling users to deploy Shopware projects to remote servers using a releases/shared directory layout with atomic symlink switching, similar to Deployer (deployer.org).

Key Changes

New Deployment Infrastructure

  • internal/deployment/ - New package providing deployment abstraction:
    • deployment.go - Core Deployer interface and types (Release, HostReleases, Options)
    • ssh_deployer.go - SSH-based deployer implementing releases/shared/current symlink pattern
    • ssh_connection.go - SSH connection wrapper using system ssh client
    • connection.go - Connection interface for testability
    • archive.go - Project archiving with configurable exclusions
    • Comprehensive test coverage for all deployment scenarios

SSH Command Building

  • internal/sshcmd/ - New package for SSH client command construction:
    • sshcmd.go - Builds SSH arguments from environment config with ControlMaster multiplexing support
    • Handles authentication (keys, passwords, SSH agent), host key verification, and connection pooling
    • Shares multiplexed connections between deployments and remote command execution

Remote Command Execution

  • internal/executor/ssh.go - New SSH executor for running commands on deployed releases:
    • Executes console, composer, PHP, and npm commands on the remote current symlink
    • Supports TTY allocation for interactive commands
    • Integrates with SSH multiplexing for efficient connection reuse

Configuration Schema

  • internal/shop/config.go - Extended EnvironmentConfig with:
    • EnvironmentSSH - SSH connection settings (host, port, user, authentication, known_hosts, ControlMaster control)
    • EnvironmentDeployment - Deployment configuration (path, keep_releases, shared files/dirs, hooks)
    • EnvironmentDeploymentHooks - Build, pre-switch, and post-switch hooks
    • Multi-host support for deployments across multiple servers

CLI Commands

  • cmd/project/project_deploy.go - Main deploy command
  • cmd/project/project_deploy_releases.go - List releases on target
  • cmd/project/project_deploy_rollback.go - Rollback to previous release
  • cmd/project/project_console.go - Enhanced to support SSH environments with flag stripping

Utilities

  • internal/shell/quote.go - POSIX shell quoting for safe command construction
  • internal/executor/factory.go - Extended to support SSH executor type

Notable Implementation Details

  • Atomic Symlink Switching: Uses ln -sfn + mv -fT for atomic current symlink updates, preventing partial deployments
  • Shared Path Management: Automatically seeds shared directories/files from first release, then maintains symlinks across deployments
  • Multi-Host Deployments: Uploads happen in parallel, but hooks and symlink switching are sequential to prevent concurrent database operations
  • Connection Multiplexing: All SSH operations share one ControlMaster connection per host for efficiency
  • Deployment Helper Integration: Auto-detects and runs shopware-deployment-helper when present and no pre-switch hooks are configured
  • Release Cleanup: Configurable retention (default 5 releases) with automatic cleanup of old releases
  • Bad Release Marking: Rollbacks mark the previous release as "bad" to skip it in future rollbacks
  • Graceful Degradation: Cleanup failures are logged as warnings but don't abort deployments

Configuration Example

environments:
  production:
    type: ssh
    ssh:
      host: shop.example.com
      port: 2222
      user: deploy
      identity_file: ~/.ssh/id_ed25519
    deployment:
      path: /var/www/shopware
      keep_releases: 3
      hooks:
        build:
          - shopware-cli project ci .
        pre_switch:
          - vendor/bin/shopware-deployment-helper run
        post

https://claude.ai/code/session_01Ev2G1VYZ3kkAzJ3cCeGZtZ

@lasomethingsomething

Copy link
Copy Markdown
Contributor

This is on hold until the Upgrade wizard is delivered, while also discussing this work with the PaaS team.

Base automatically changed from next to main July 13, 2026 08:48
@lasomethingsomething

somethings (lasomethingsomething) commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Nail down the interface first. The goal is to have shared commands that work across both PaaS and SSH, so we need to answer a few questions before implementation:

  • Which relevant commands does paas-cli expose? (https://github.com/shopware/paas-cli/tree/main/cmd/application)
  • How does each command behave, and what output does the user see?
  • We already know roughly where PaaS and on-prem diverge (local plugin changes are similar; the split starts on the DevOps side where code is pushed, how the application is updated, when the PaaS API is called). We should write this down per command so the user can ideally just run deploy without caring about the target. This affects the Shopware CLI design.

From Renaud Hager (@renaudhager): It depends on what you want to deploy:

  • For PaaS, it's the same. They don't use SSH to deploy things. On the experience flow, the deployment is still the same. Basically they have two items around upgrades/deployment:
  • you can update an application: CLI asks for application name, commit SHA. When you give SHA and hits enter, it triggers backend events A) clone repo, commit SHA checked, build a container, push image. Triggers another workflow that creates a deployment that includes the new Docker image and push that to K8s. Customer with paas-CLI can either 1) do application update (old flow) or 2) trigger application build or 3) application deployment / any successful build they want.

Adds "shopware-cli project deploy" building on the existing environments
abstraction: environments get a new "ssh" type with connection settings
and a deployment section (path, keep_releases, shared files/dirs, hooks).

Deployment methods are pluggable through a Deployer interface in
internal/deployment so SFTP/PaaS backends can be added later with the
same CLI surface. The SSH implementation uses a Deployer-style layout:
releases are uploaded as tarball streams into releases/<timestamp>,
shared files and directories are symlinked from shared/, the current
symlink is switched atomically and old releases are pruned. When no
pre_switch hooks are configured the Shopware Deployment Helper is run
automatically when present.

"project deploy rollback [release]" switches back to a previous release
and marks the rolled-back-from release as bad so later rollbacks skip
it, and "project deploy releases" lists the releases on the target.

Multi-host support: environments can list additional servers under
ssh.hosts, each inheriting unset connection settings from the ssh block.
Deploys upload to all hosts in parallel, run hooks host after host so
database work like migrations never executes concurrently, and switch
the current symlink everywhere only after every host finished its
pre-switch phase. A failure on any host before the switch aborts the
deployment with the running release untouched everywhere. Rollback
validates the target release exists on every host before switching any.

Remote command execution: ssh environments implement the Executor
interface, so "project console", "project clear-cache", "project worker"
and "project dump" run on the deployment target with the same CLI
surface as local and docker environments. Commands execute inside the
currently deployed release on the primary host, with environment
variables inlined and arguments shell-quoted. Because "project console"
disables cobra flag parsing, shopware-cli's own flags (--env/-e,
--project-config) are extracted before the console command name so
Symfony's own --env flag stays usable; shell completion strips them too.

Deployments and remote commands share one multiplexed connection through
the new internal/sshcmd package, which sets ControlMaster=auto with a
shared ControlPath and ControlPersist=60. The first command opens a
master connection per host and later commands reuse it, skipping the
TCP, key exchange and authentication handshake. Using the system ssh
client also means deployments honor the user's ssh_config, SSH agent and
ProxyJump settings.

Squashed from 10 commits originally developed on the "next" line and
replayed onto main; the intermediate states did not apply individually.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyim
Soner (shyim) force-pushed the claude/shopware-cli-deploy-command-umiqxz branch from 9329243 to eb5bf53 Compare July 30, 2026 07:13
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.12807% with 300 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.21%. Comparing base (b7c4df3) to head (eb5bf53).

Files with missing lines Patch % Lines
internal/deployment/ssh_deployer.go 78.43% 55 Missing ⚠️
internal/sshcmd/dialer.go 0.00% 40 Missing ⚠️
internal/executor/ssh.go 68.51% 34 Missing ⚠️
internal/sshcmd/sshcmd.go 60.60% 26 Missing ⚠️
cmd/project/project_deploy_releases.go 7.69% 24 Missing ⚠️
cmd/project/project_deploy.go 11.53% 23 Missing ⚠️
internal/deployment/ssh_connection.go 0.00% 23 Missing ⚠️
cmd/project/project_console.go 56.66% 13 Missing ⚠️
internal/deployment/archive.go 75.00% 13 Missing ⚠️
cmd/project/project_dump.go 7.69% 12 Missing ⚠️
... and 7 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1170      +/-   ##
==========================================
+ Coverage   53.94%   54.21%   +0.27%     
==========================================
  Files         300      312      +12     
  Lines       23345    24027     +682     
==========================================
+ Hits        12594    13027     +433     
- Misses      10723    10972     +249     
  Partials       28       28              
Flag Coverage Δ
go-test 54.21% <59.12%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lasomethingsomething

somethings (lasomethingsomething) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

TL;DR: Aligning to PaaS Native's deployment experience. Users should not need to know whether a plugin, binary, backend, or other execution mechanism is handling the deployment.

Command structure and semantics

Deployment should be shopware-cli project deploy (confirmed with PaaS). Where the concepts between Shopware CLI and PaaS Native workflows match, we aim for the same command structure:

project deploy
project deploy create
project deploy list
project deploy get
project deploy logs
project deploy rollback # TBD: no equivalent PaaS command today

Note for broader context: PR #1237 already establishes shopware-cli project upgrade to mean changing the Shopware project to a newer version.

Delegation model

The architectural proposal is for Shopware CLI to act as a wrapper around the paas-cli-as-plugin, with Shopware CLI providing a shared command surface and UX without taking ownership of, or reimplementing, any PaaS operations. The paas-cli plugin will own the PaaS-specific behavior. paas-cli can output JSON, which could help Shopware CLI consume it reliably.

Native PaaS-specific actions could still exist under a command family with passed-through arguments. Benefit: limits coordination overhead, because DX Tools and PaaS/SaaS teams can work behind a shared CLI interface instead of blocking each other.

For a PaaS target, project deploy will delegate to the "paas-cli-as-plugin", which remains responsible for triggering the existing PaaS workflows. Shopware CLI will resolve the environment and delegate to the appropriate SSH or PaaS implementation.

Initially, this should provide the equivalent experience of sw-paas application update: select a commit, build it, create a deployment, follow the infrastructure and migration phases, and show the result.

For an SSH target, project deploy will use the implementation from this PR to prepare the project, transfer it, create a release, run hooks, switch the active release, and show the result.

Shared deployment experience

The shared flow should be:

select target → select code/version → build if required → deploy → follow progress → show result

The Shopware CLI and TUI should use consistent progress language where possible, while preserving the existing TUI experience. Back-end differences should be explained when they affect configuration, user decisions, or troubleshooting:

Phase PaaS plugin SSH
Source Platform fetches a Git commit CLI transfers the project
Build PaaS builds a container image Build hooks run locally or remotely
Deploy Platform deployment Release and symlink switch
Recovery Redeploy an earlier build Activate an earlier release
Logs Build and migration logs Hook and remote command output

Before finalizing the PaaS integration, confirm ownership boundaries between Shopware CLI and paas-cli-as-plugin, version compatibility, and how required plugin or PaaS CLI updates and deprecations are communicated. Users need clear info about paas-cli version changes.

Related operational questions

Relevant to the deployment experience but remain separate design decisions:

  • Should production deployments support or require maintenance/read-only mode, and how do we guarantee it is disabled again after failures?
  • What access model should apply when customers change Shopware administration credentials?

cc Sandor Akszenovics (@axasanya) Renaud Hager (@renaudhager) PTAL and share any concerns, comments, or feedback you have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants