diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..6b901b91
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,122 @@
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+permissions:
+ contents: write
+
+jobs:
+ release:
+ runs-on: ubuntu-latest
+ environment: production
+
+ steps:
+ - name: Check out repository code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Go
+ uses: actions/setup-go@v6
+ with:
+ go-version-file: ./go.mod
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.4"
+ extensions: curl, openssl, phar, zlib
+ tools: composer:v2
+
+ - name: Get Composer cache directory
+ id: composer-cache
+ run: echo "dir=$(cd legacy && composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - name: Cache Composer dependencies
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-composer-${{ hashFiles('legacy/composer.lock') }}
+ restore-keys: ${{ runner.os }}-composer-
+
+ - name: Install legacy dependencies
+ run: cd legacy && composer install --no-interaction
+
+ - name: Get PHP version from Makefile
+ id: php-version
+ run: echo "version=$(grep '^PHP_VERSION' Makefile | cut -d= -f2 | tr -d ' ')" >> $GITHUB_OUTPUT
+
+ - name: Get tool versions from Makefile
+ id: tool-versions
+ run: |
+ echo "goreleaser=$(grep '^GORELEASER_VERSION' Makefile | cut -d= -f2 | tr -d ' ')" >> $GITHUB_OUTPUT
+ echo "repogen=$(grep '^REPOGEN_VERSION' Makefile | cut -d= -f2 | tr -d ' ')" >> $GITHUB_OUTPUT
+
+ - name: Cache PHP binaries
+ uses: actions/cache@v4
+ id: php-cache
+ with:
+ path: internal/legacy/archives/php_*
+ key: php-binaries-${{ steps.php-version.outputs.version }}
+
+ - name: Download all PHP binaries
+ if: steps.php-cache.outputs.cache-hit != 'true'
+ run: make download-php
+
+ - name: Write signing keys to files
+ id: signing-keys
+ run: |
+ KEY_DIR=$(mktemp -d)
+ echo "key_dir=$KEY_DIR" >> $GITHUB_OUTPUT
+ echo "${{ secrets.RSA_SIGNING_KEY }}" > "$KEY_DIR/rsa-signing-key.pem"
+ chmod 600 "$KEY_DIR/rsa-signing-key.pem"
+ openssl rsa -in "$KEY_DIR/rsa-signing-key.pem" -pubout -out "$KEY_DIR/rsa-signing-key.pub"
+ echo "${{ secrets.GPG_SIGNING_KEY }}" | base64 -d > "$KEY_DIR/gpg-signing-key.asc"
+ chmod 600 "$KEY_DIR/gpg-signing-key.asc"
+
+ - name: Cache Go binaries
+ uses: actions/cache@v4
+ with:
+ path: ~/go/bin
+ key: ${{ runner.os }}-go-bin-goreleaser-${{ steps.tool-versions.outputs.goreleaser }}-repogen-${{ steps.tool-versions.outputs.repogen }}
+
+ - name: Install Go tools
+ run: make goreleaser repogen
+
+ - name: Build release (tag)
+ if: startsWith(github.ref, 'refs/tags/')
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ RSA_SIGNING_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/rsa-signing-key.pem
+ GPG_SIGNING_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/gpg-signing-key.asc
+ CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
+ run: make release
+
+ - name: Build snapshot (branch)
+ if: "!startsWith(github.ref, 'refs/tags/')"
+ env:
+ RSA_SIGNING_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/rsa-signing-key.pem
+ GPG_SIGNING_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/gpg-signing-key.asc
+ run: make snapshot
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws-region: eu-west-1
+
+ - name: Upload packages to repository
+ env:
+ GPG_PRIVATE_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/gpg-signing-key.asc
+ RSA_PRIVATE_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/rsa-signing-key.pem
+ RSA_PUBLIC_KEY_FILE: ${{ steps.signing-keys.outputs.key_dir }}/rsa-signing-key.pub
+ run: bash ./repogen.sh
+
+ - name: Clean up signing keys
+ if: always()
+ run: |
+ rm -rf "${{ steps.signing-keys.outputs.key_dir }}"
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 0c0b86ae..2b8342c0 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -104,7 +104,7 @@ release:
### HomeBrew
```console
- brew update && brew upgrade platformsh/tap/upsun-cli
+ brew update && brew upgrade upsun/tap/upsun-cli
```
### Scoop
@@ -119,30 +119,6 @@ release:
curl -fsSL https://raw.githubusercontent.com/upsun/cli/main/installer.sh | bash
```
-
-
- Platform.sh compatibility
-
- ### HomeBrew
-
- ```console
- brew update && brew upgrade platformsh/tap/platformsh-cli
- ```
-
- ### Scoop
-
- ```console
- scoop update platform
- ```
-
- ### Bash installer
-
- ```console
- curl -fsSL https://raw.githubusercontent.com/upsun/cli/main/installer.sh | VENDOR=platformsh bash
- ```
-
-
-
archives:
- id: platform
name_template: "platform_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
@@ -171,7 +147,7 @@ archives:
brews:
- name: platformsh-cli
repository:
- owner: platformsh
+ owner: upsun
name: homebrew-tap
token: "{{ .Env.GITHUB_TOKEN }}"
ids:
@@ -201,7 +177,7 @@ brews:
- name: upsun-cli
repository:
- owner: platformsh
+ owner: upsun
name: homebrew-tap
token: "{{ .Env.GITHUB_TOKEN }}"
ids:
@@ -232,7 +208,7 @@ brews:
scoops:
- name: platform
repository:
- owner: platformsh
+ owner: upsun
name: homebrew-tap
token: "{{ .Env.GITHUB_TOKEN }}"
ids:
@@ -253,7 +229,7 @@ scoops:
- name: upsun
repository:
- owner: platformsh
+ owner: upsun
name: homebrew-tap
token: "{{ .Env.GITHUB_TOKEN }}"
ids:
@@ -280,6 +256,7 @@ nfpms:
maintainer: Antonis Kalipetis
license: MIT
vendor: Platform.sh
+ file_name_template: "platformsh-cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
ids:
- platform
formats:
@@ -291,6 +268,13 @@ nfpms:
dst: /etc/bash_completion.d/platform
- src: completion/zsh/_platform
dst: /usr/local/share/zsh/site-functions/_platform
+ apk:
+ signature:
+ key_file: "{{ with .Env.RSA_SIGNING_KEY_FILE }}{{ . }}{{ end }}"
+ key_name: repositories-upsun-com
+ rpm:
+ signature:
+ key_file: "{{ with .Env.GPG_SIGNING_KEY_FILE }}{{ . }}{{ end }}"
- id: upsun
homepage: https://docs.upsun.com/anchors/cli/
@@ -298,7 +282,8 @@ nfpms:
description: Upsun CLI
maintainer: Antonis Kalipetis
license: MIT
- vendor: Platform.sh
+ file_name_template: upsun-cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}
+ vendor: Upsun
ids:
- upsun
formats:
@@ -310,3 +295,10 @@ nfpms:
dst: /etc/bash_completion.d/upsun
- src: completion/zsh/_upsun
dst: /usr/local/share/zsh/site-functions/_upsun
+ apk:
+ signature:
+ key_file: "{{ with .Env.RSA_SIGNING_KEY_FILE }}{{ . }}{{ end }}"
+ key_name: repositories-upsun-com
+ rpm:
+ signature:
+ key_file: "{{ with .Env.GPG_SIGNING_KEY_FILE }}{{ . }}{{ end }}"
diff --git a/CLAUDE.md b/CLAUDE.md
index cc9d9db0..c9cea0d8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -74,7 +74,6 @@ The CLI operates as a wrapper around a legacy PHP CLI:
- `version.go`: Uses runtime/debug to get version info from VCS
- Supports vendorization through embedded YAML configs:
- `config_upsun.go`: Default (no build tags) - Upsun CLI
- - `config_platformsh.go`: Requires `-tags platformsh` - Platform.sh CLI
- `config_vendor.go`: Requires `-tags vendor` - Custom vendor CLI
- Config can be loaded from external files for testing/development
@@ -86,7 +85,7 @@ The CLI operates as a wrapper around a legacy PHP CLI:
- Copies PHP binary and phar to cache directory on first run
**API Client**: `internal/api/`
-- HTTP client for interacting with Platform.sh/Upsun API
+- HTTP client for interacting with Upsun API
- Handles authentication, organizations, and resource management
**Authentication**: `internal/auth/`
@@ -102,7 +101,6 @@ The CLI operates as a wrapper around a legacy PHP CLI:
**Multi-Vendor Support**:
- Uses Go build tags to compile different binaries:
- Default (no tags): Upsun CLI
- - `-tags platformsh`: Platform.sh CLI
- `-tags vendor`: Custom vendor CLI
- Configuration is embedded at compile time
- GoReleaser builds multiple variants (upsun, platform, vendor-specific)
diff --git a/Makefile b/Makefile
index 01c7e3a4..0a89d638 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,7 @@ VERSION := $(shell git describe --always)
# Tooling versions
GORELEASER_VERSION=v2.12.0
+REPOGEN_VERSION=v1.0.4
# PHP binaries are downloaded from cli-php-builds releases.
# See: https://github.com/upsun/cli-php-builds
@@ -74,13 +75,27 @@ php: internal/legacy/archives/php_$(GOOS)_$(GOARCH)
goreleaser:
command -v goreleaser >/dev/null || go install github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION)
+.PHONY: repogen
+repogen:
+ command -v repogen >/dev/null || go install github.com/ralt/repogen/cmd/repogen@$(REPOGEN_VERSION)
+
.PHONY: single
single: goreleaser internal/legacy/archives/platform.phar php ## Build a single target release
PHP_VERSION=$(PHP_VERSION) goreleaser build --single-target --id=$(GORELEASER_ID) --snapshot --clean
-.PHONY: snapshot ## Build a snapshot release
-snapshot: goreleaser internal/legacy/archives/platform.phar php
- PHP_VERSION=$(PHP_VERSION) goreleaser build --snapshot --clean
+.PHONY: snapshot
+snapshot: goreleaser internal/legacy/archives/platform.phar php internal/legacy/archives/cacert.pem ## Build a snapshot release
+ifndef RSA_SIGNING_KEY_FILE
+ $(error RSA_SIGNING_KEY_FILE is not set. Set it to the path of your RSA private key for APK signing, or use 'make snapshot-no-nfpm' to skip packaging.)
+endif
+ifndef GPG_SIGNING_KEY_FILE
+ $(error GPG_SIGNING_KEY_FILE is not set. Set it to the path of your GPG private key for RPM signing, or use 'make snapshot-no-nfpm' to skip packaging.)
+endif
+ PHP_VERSION=$(PHP_VERSION) goreleaser release --snapshot --clean --skip=publish,announce
+
+.PHONY: snapshot-no-nfpm
+snapshot-no-nfpm: goreleaser internal/legacy/archives/platform.phar php ## Build a snapshot release without package signing
+ PHP_VERSION=$(PHP_VERSION) goreleaser release --snapshot --clean --skip=publish,announce,nfpm
.PHONY: clean-phar
clean-phar: ## Clean up the legacy CLI phar
@@ -88,7 +103,13 @@ clean-phar: ## Clean up the legacy CLI phar
rm -rf legacy/vendor
.PHONY: release
-release: goreleaser clean-phar internal/legacy/archives/platform.phar php ## Create and publish a release
+release: goreleaser clean-phar internal/legacy/archives/platform.phar php internal/legacy/archives/cacert.pem ## Create and publish a release
+ifndef RSA_SIGNING_KEY_FILE
+ $(error RSA_SIGNING_KEY_FILE is not set. Set it to the path of your RSA private key for APK signing.)
+endif
+ifndef GPG_SIGNING_KEY_FILE
+ $(error GPG_SIGNING_KEY_FILE is not set. Set it to the path of your GPG private key for RPM signing.)
+endif
PHP_VERSION=$(PHP_VERSION) goreleaser release --clean
VERSION=$(VERSION) bash cloudsmith.sh
diff --git a/README.md b/README.md
index 8c91432d..77be0ee1 100644
--- a/README.md
+++ b/README.md
@@ -13,13 +13,13 @@ To install the CLI, use either [Homebrew](https://brew.sh/) (on Linux, macOS, or
### HomeBrew
```console
-brew install platformsh/tap/upsun-cli
+brew install upsun/tap/upsun-cli
```
### Scoop
```console
-scoop bucket add platformsh https://github.com/platformsh/homebrew-tap.git
+scoop bucket add upsun https://github.com/upsun/homebrew-tap.git
scoop install upsun
```
@@ -69,38 +69,43 @@ nix profile install nixpkgs#upsun
### Alpine
```console
-sudo apk add --no-cache bash
-curl -1sLf \
- 'https://dl.cloudsmith.io/public/platformsh/upsun-cli/setup.alpine.sh' \
- | sudo -E bash
-```
+# Add the signing key and repository
+sudo mkdir -p /etc/apk/keys
+sudo curl -fsSL -o /etc/apk/keys/repositories-upsun-com.rsa.pub https://repositories.upsun.com/alpine/repositories-upsun-com.rsa.pub
+echo "https://repositories.upsun.com/alpine" | sudo tee -a /etc/apk/repositories
-```console
-apk add upsun-cli
+# Install the CLI
+sudo apk add upsun-cli
```
### Ubuntu/Debian
```console
-apt-get update
-apt-get install -y apt-transport-https curl
-curl -1sLf \
- 'https://dl.cloudsmith.io/public/platformsh/upsun-cli/setup.deb.sh' \
- | sudo -E bash
-```
+# Add the signing key and repository
+sudo mkdir -p /etc/apt/keyrings
+sudo curl -fsSL https://repositories.upsun.com/gpg.key -o /etc/apt/keyrings/upsun.asc
+echo "deb [signed-by=/etc/apt/keyrings/upsun.asc] https://repositories.upsun.com/debian stable main" | sudo tee /etc/apt/sources.list.d/upsun.list
-```console
-apt-get install -y upsun-cli
+# Install the CLI
+sudo apt-get update
+sudo apt-get install -y upsun-cli
```
### CentOS/RHEL/Fedora
```console
-curl -1sLf \
- 'https://dl.cloudsmith.io/public/platformsh/upsun-cli/setup.rpm.sh' \
- | sudo -E bash
+# Add the repository
+sudo tee /etc/yum.repos.d/upsun.repo << 'EOF'
+[upsun]
+name=Upsun CLI
+baseurl=https://repositories.upsun.com/fedora/$releasever/$basearch
+enabled=1
+gpgcheck=1
+gpgkey=https://repositories.upsun.com/gpg.key
+EOF
-yum install -y upsun-cli
+# Install the CLI (use yum on older systems)
+sudo dnf install -y upsun-cli
```
### Manual installation
@@ -114,7 +119,7 @@ Upgrade using the same tool:
### HomeBrew
```console
-brew update && brew upgrade platformsh/tap/upsun-cli
+brew update && brew upgrade upsun/tap/upsun-cli
```
### Scoop
@@ -132,33 +137,19 @@ curl -fsSL https://raw.githubusercontent.com/upsun/cli/main/installer.sh | bash
### Alpine
```console
-apk upgrade upsun-cli
+sudo apk update && sudo apk upgrade upsun-cli
```
### Ubuntu/Debian
```console
-apt-get upgrade upsun-cli
+sudo apt-get update && sudo apt-get upgrade upsun-cli
```
### CentOS/RHEL/Fedora
```console
-yum upgrade -y upsun-cli
-```
-
-## Platform.sh compatibility
-
-For backwards compatibility with Platform.sh, a `platform` binary is also available:
-
-```console
-brew install platformsh/tap/platformsh-cli
-```
-
-Or with the bash installer:
-
-```console
-curl -fsSL https://raw.githubusercontent.com/upsun/cli/main/installer.sh | VENDOR=platformsh bash
+sudo dnf upgrade -y upsun-cli
```
## Building
@@ -182,15 +173,21 @@ Build a snapshot for a vendor:
make vendor-snapshot VENDOR_NAME='Vendor Name' VENDOR_BINARY='vendorcli'
```
-Create a release:
+## Creating a Release
-```console
-# First, create a new tag
-git tag -m 'Release v5.0.0' 'v5.0.0'
+Releases are automated via GitHub Actions. To create a new release:
-# Create a release (requires GITHUB_TOKEN)
-make release
-```
+1. Create and push a new tag:
+ ```console
+ git tag -m 'Release v5.0.0' 'v5.0.0'
+ git push origin v5.0.0
+ ```
+
+2. The [Release workflow](.github/workflows/release.yml) will automatically:
+ - Build binaries for all platforms
+ - Sign packages (APK, DEB, RPM)
+ - Create a GitHub release with all artifacts
+ - Update package repositories at repositories.upsun.com
## Licenses
diff --git a/installer.sh b/installer.sh
index c2bf7f07..56fdc470 100644
--- a/installer.sh
+++ b/installer.sh
@@ -1,7 +1,7 @@
-#!/bin/bash
+#!/bin/sh
# Upsun CLI installer
-set -euo pipefail
+set -eu
# Location of install log
: "${INSTALL_LOG:=/tmp/upsun-install-$(date '+%Y%m%d-%H%M%S').log}"
@@ -18,15 +18,12 @@ set -euo pipefail
: "${VERSION:=}"
# macOS specifics
-: "${BREW_TAP:=platformsh/tap}"
-: "${BREW_FORMULA:=platformsh/tap/upsun-cli}"
+: "${BREW_TAP:=upsun/tap}"
+: "${BREW_FORMULA:=upsun/tap/upsun-cli}"
# GitHub token check
: "${GITHUB_TOKEN:=}"
-# The vendor to install
-: "${VENDOR:=upsun}"
-
# CI specifics
: "${CI:=}"
: "${BUILD_NUMBER:=}"
@@ -34,8 +31,7 @@ set -euo pipefail
# global variables
binary="upsun"
-vendor_name="Upsun"
-cloudsmith_repository="upsun-cli"
+vendor_name="Upsun (formerly Platform.sh)"
cmd_shasum=""
cmd_sudo=""
dir_bin="/usr/bin"
@@ -48,14 +44,6 @@ package="upsun-cli"
docs_url="https://docs.upsun.com"
support_url="https://upsun.com/contact"
-if [ "$VENDOR" == "platformsh" ]; then
- BREW_FORMULA="platformsh/tap/platformsh-cli"
- binary="platform"
- vendor_name="Upsun (Platform.sh compatibility)"
- package="platformsh-cli"
- cloudsmith_repository="cli"
-fi
-
# create a log file where every output will be pipe to
pipe=/tmp/$binary-install-$$.tmp
mkfifo $pipe
@@ -64,7 +52,7 @@ exec 1>&-
exec 1>$pipe 2>&1
trap 'rm -f $pipe' EXIT
-function output {
+output() {
style_start=""
style_end=""
if [ "${2:-}" != "" ]; then
@@ -92,56 +80,77 @@ function output {
esac
fi
- builtin echo -e "${style_start}${1}${style_end}"
+ printf "%b%b%b\n" "${style_start}" "${1}" "${style_end}"
}
-function create_table_line() {
+create_table_line() {
local text="${1:-}"
local width="${2:-}"
+ local text_length
+ local i
+ local padding
+ local right_padding
if [ -z "$width" ]; then
# Calculate width based on text length, minimum 50
- local text_length=${#text}
- width=$((text_length < 50 ? 50 : text_length + 4))
+ text_length=${#text}
+ if [ $text_length -lt 50 ]; then
+ width=50
+ else
+ width=$((text_length + 4))
+ fi
fi
if [ "$text" = "border" ] || [ -z "$text" ]; then
# Border line
printf "+"
- for ((i=0; i/dev/null 2>&1; then
output " [*] cURL is installed" "success"
- if gh auth status >/dev/null 2>&1; then
- GITHUB_TOKEN="$(gh auth token)"
- if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
- GITHUB_TOKEN=""
- else
- output " [*] Using GitHub auth from the gh CLI" "success"
- fi
- elif [ ! -z "${GITHUB_TOKEN}" ]; then
- if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
- GITHUB_TOKEN=""
- else
- output " [*] Using GitHub auth from the GITHUB_TOKEN env variable" "success"
- fi
- fi
else
output " [ ] ERROR: cURL is required for installation" "error"
exit_with_error
fi
}
+setup_github_token() {
+ if gh auth status >/dev/null 2>&1; then
+ GITHUB_TOKEN="$(gh auth token)"
+ if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
+ GITHUB_TOKEN=""
+ else
+ output " [*] Using GitHub auth from the gh CLI" "success"
+ fi
+ elif [ ! -z "${GITHUB_TOKEN}" ]; then
+ if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
+ GITHUB_TOKEN=""
+ else
+ output " [*] Using GitHub auth from the GITHUB_TOKEN env variable" "success"
+ fi
+ fi
+}
+
# Check that Gzip is installed
-function check_gzip() {
+check_gzip() {
if command -v gzip >/dev/null 2>&1; then
output " [*] Gzip is installed" "success"
else
@@ -246,7 +259,17 @@ function check_gzip() {
fi
}
-function check_version() {
+# Check that ca-certificates is installed (Alpine)
+check_ca_certificates() {
+ if apk info -e ca-certificates >/dev/null 2>&1; then
+ output " [*] ca-certificates is installed" "success"
+ else
+ output " [ ] ERROR: ca-certificates is required for installation" "error"
+ exit_with_error
+ fi
+}
+
+check_version() {
if [ -z "${VERSION}" ]; then
version=$(curl -I https://github.com/upsun/cli/releases/latest 2>/dev/null | awk -F/ -v RS='\r\n' '/upsun.cli.releases.tag/ {printf "%s", $NF}')
output " [*] No version specified, using latest ($version)" "success"
@@ -257,7 +280,7 @@ function check_version() {
}
# Detect the kernel type
-function check_kernel() {
+check_kernel() {
kernel=$(uname -s 2>$pipe || /usr/bin/uname -s)
case ${kernel} in
"Linux"|"linux")
@@ -279,7 +302,7 @@ function check_kernel() {
output " [*] Your kernel (${kernel}) is supported" "success"
}
-function check_architecture() {
+check_architecture() {
# Detect architecture
machine=$(uname -m 2>$pipe || /usr/bin/uname -m)
case ${machine} in
@@ -301,7 +324,7 @@ function check_architecture() {
output " [*] Your architecture (${machine}) is supported" "success"
}
-function check_install_method() {
+check_install_method() {
if [ "linux" = "$kernel" ]; then
if [ -z "${INSTALL_METHOD}" ]; then
if is_ci; then
@@ -318,7 +341,7 @@ function check_install_method() {
INSTALL_METHOD="raw"
fi
fi
- elif [ "darwin" == $kernel ]; then
+ elif [ "darwin" = $kernel ]; then
if [ -z "${INSTALL_METHOD}" ]; then
if command -v brew > /dev/null 2>&1
then
@@ -330,21 +353,21 @@ function check_install_method() {
if [ "${INSTALL_METHOD}" = "raw" ]; then
machine="all"
fi
- elif [ "freebsd" == $kernel ]; then
+ elif [ "freebsd" = $kernel ]; then
INSTALL_METHOD="raw"
fi
output " [*] Using ${INSTALL_METHOD} install method" "success"
}
-function init_sudo() {
+init_sudo() {
if [ ! -z "${has_sudo}" ]; then
return
fi
has_sudo=false
# Are we running the installer as root?
- if [ "$(echo "$UID")" = "0" ]; then
+ if [ "$(id -u)" = "0" ]; then
has_sudo=true
cmd_sudo=''
@@ -357,7 +380,7 @@ function init_sudo() {
fi
}
-function call_root() {
+call_root() {
init_sudo
if ! ${has_sudo}; then
@@ -365,14 +388,21 @@ function call_root() {
exit_with_error
fi
- if $cmd_sudo sh -c "$1" 2>&1 | indent; then
+ local output_file
+ output_file=$(mktemp)
+ if $cmd_sudo sh -c "$1" >"$output_file" 2>&1; then
+ cat "$output_file" | indent
+ rm -f "$output_file"
return 0
+ else
+ local exit_code=$?
+ cat "$output_file" | indent
+ rm -f "$output_file"
+ return $exit_code
fi
-
- return 1
}
-function call_try_user() {
+call_try_user() {
if ! call_user "$1"; then
output " command failed, re-trying with sudo" "warning"
if ! call_root "$1"; then
@@ -382,11 +412,11 @@ function call_try_user() {
fi
}
-function call_user() {
+call_user() {
sh -c "$1" 2>&1 | indent
}
-function check_shasum() {
+check_shasum() {
if command -v sha1sum > /dev/null 2>&1; then
cmd_shasum="sha1sum"
output " [*] sha1sum is installed" "success"
@@ -399,7 +429,7 @@ function check_shasum() {
fi
}
-function check_directories() {
+check_directories() {
if [ ! -z "${INSTALL_DIR}" ]; then
dir_bin="${INSTALL_DIR}"
INSTALL_METHOD="raw"
@@ -437,7 +467,7 @@ function check_directories() {
output " Binary will be installed in ${dir_bin}"
}
-function install_homebrew() {
+install_homebrew() {
output "\nInstalling the $vendor_name brew tap" "heading"
if ! call_user "brew tap ${BREW_TAP}"; then
output " could not add tap to brew" "error"
@@ -460,12 +490,20 @@ function install_homebrew() {
fi
}
-function install_yum() {
+install_yum() {
output "\nSetting up the $vendor_name CLI yum repository" "heading"
- call_user "curl -1sLf 'https://dl.cloudsmith.io/public/platformsh/$cloudsmith_repository/setup.rpm.sh' > /tmp/setup.rpm.sh"
+ repo_file="/etc/yum.repos.d/upsun.repo"
+ repo_content="[upsun]
+name=Upsun CLI
+baseurl=https://repositories.upsun.com/fedora/\\\$releasever/\\\$basearch
+enabled=1
+gpgcheck=1
+gpgkey=https://repositories.upsun.com/gpg.key"
+
+ cmd="printf '${repo_content}' > '${repo_file}'"
- if ! call_root "bash -E /tmp/setup.rpm.sh"; then
+ if ! call_root "${cmd}"; then
output " could not setup the RPM repository the CLI" "error"
exit_with_error
fi
@@ -478,16 +516,35 @@ function install_yum() {
fi
}
-function install_apt() {
+install_apt() {
output "\nSetting up the $vendor_name CLI apt repository" "heading"
- call_user "curl -1sLf 'https://dl.cloudsmith.io/public/platformsh/$cloudsmith_repository/setup.deb.sh' > /tmp/setup.deb.sh"
+ # Install the GPG key
+ if ! call_root "mkdir -p /etc/apt/keyrings"; then
+ output " could not create keyrings directory" "error"
+ exit_with_error
+ fi
+
+ if ! call_root "curl -fsSL https://repositories.upsun.com/gpg.key -o /etc/apt/keyrings/upsun.asc"; then
+ output " could not download GPG key" "error"
+ exit_with_error
+ fi
- if ! call_root "bash -E /tmp/setup.deb.sh"; then
+ list_file="/etc/apt/sources.list.d/upsun.list"
+ list_content="deb [signed-by=/etc/apt/keyrings/upsun.asc] https://repositories.upsun.com/debian stable main"
+
+ cmd="printf '${list_content}' > '${list_file}'"
+
+ if ! call_root "${cmd}"; then
output " could not setup the APT repository the CLI" "error"
exit_with_error
fi
+ if ! call_root "apt-get update"; then
+ output " could not update apt cache" "error"
+ exit_with_error
+ fi
+
output "\nInstalling the $vendor_name CLI" "heading"
if ! call_root "apt-get install -y $package"; then
@@ -496,13 +553,24 @@ function install_apt() {
fi
}
-function install_apk() {
+install_apk() {
output "\nSetting up the $vendor_name CLI apk repository" "heading"
- call_user "curl -1sLf 'https://dl.cloudsmith.io/public/platformsh/$cloudsmith_repository/setup.alpine.sh' > /tmp/setup.alpine.sh"
+ # Repository configuration
+ local repo_url="https://repositories.upsun.com"
+ local rsa_key_name="repositories-upsun-com.rsa.pub"
- if ! call_root "bash -E /tmp/setup.alpine.sh"; then
- output " could not setup the APK repository the CLI" "error"
+ # Download and install the repository signing key
+ output " Installing repository signing key..."
+ if ! call_root "mkdir -p /etc/apk/keys && curl -fsSL -o /etc/apk/keys/${rsa_key_name} ${repo_url}/alpine/${rsa_key_name}"; then
+ output " could not install the signing key" "error"
+ exit_with_error
+ fi
+
+ # Add the repository (Alpine automatically appends /$arch to the URL)
+ output " Adding repository..."
+ if ! call_root "grep -qF '${repo_url}/alpine' /etc/apk/repositories 2>/dev/null || echo '${repo_url}/alpine' >> /etc/apk/repositories"; then
+ output " could not add the repository" "error"
exit_with_error
fi
@@ -514,17 +582,17 @@ function install_apk() {
fi
}
-function github_curl {
+github_curl() {
if [ -z "${GITHUB_TOKEN}" ]; then
- curl -fsSL -H "Accept: application/vnd.github+json" $1
+ curl -fsSL -H "Accept: application/vnd.github+json" "$1"
return $?
else
- curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${GITHUB_TOKEN}" $1
+ curl -fsSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${GITHUB_TOKEN}" "$1"
return $?
fi
}
-function is_ci {
+is_ci() {
if [ ! -z "${CI}" ]; then # GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
return 0
elif [ ! -z "${BUILD_NUMBER}" ]; then # Jenkins, TeamCity
@@ -536,7 +604,7 @@ function is_ci {
fi
}
-function install_raw() {
+install_raw() {
# Start downloading the right version
output "\nDownloading the $vendor_name CLI" "heading"
@@ -544,18 +612,18 @@ function install_raw() {
output " Downloading ${url}";
tmp_dir=$(mktemp -d)
tmp_name="$binary-"$(date +"%s")
- if ! github_curl $url > "${tmp_dir}/${tmp_name}.tgz"; then
+ if ! github_curl "$url" > "${tmp_dir}/${tmp_name}.tgz"; then
output " the download failed" "error"
exit_with_error
fi
output " Uncompressing archive"
- tar -C ${tmp_dir} -xzf "${tmp_dir}/${tmp_name}.tgz"
+ tar -C "${tmp_dir}" -xzf "${tmp_dir}/${tmp_name}.tgz"
output " Making the binary executable"
chmod 0755 "${tmp_dir}/$binary"
- if [ ! -d $dir_bin ]; then
+ if [ ! -d "$dir_bin" ]; then
output " Creating ${dir_bin} directory"
call_try_user "mkdir -p ${dir_bin}" "Failed to create the ${dir_bin} directory"
fi
@@ -564,7 +632,7 @@ function install_raw() {
call_try_user "mv '${tmp_dir}/$binary' '${dir_bin}/${binary}'" "Failed to move the binary ${binary}"
}
-function install() {
+install() {
if [ "homebrew" = "${INSTALL_METHOD}" ]; then
install_homebrew
elif [ "yum" = "${INSTALL_METHOD}" ]; then
@@ -583,8 +651,9 @@ check_kernel
check_architecture
check_install_method
if [ "raw" = "${INSTALL_METHOD}" ]; then
+ check_curl
if [ -z "${VERSION}" ]; then
- check_curl
+ setup_github_token
fi
check_version
check_gzip
@@ -592,6 +661,9 @@ if [ "raw" = "${INSTALL_METHOD}" ]; then
check_directories
elif [ "apt" = "${INSTALL_METHOD}" ] || [ "yum" = "${INSTALL_METHOD}" ] || [ "apk" = "${INSTALL_METHOD}" ]; then
check_curl
+ if [ "apk" = "${INSTALL_METHOD}" ]; then
+ check_ca_certificates
+ fi
fi
install
outro
diff --git a/internal/config/platformsh-cli.yaml b/internal/config/platformsh-cli.yaml
index 239fae91..dd7592e0 100644
--- a/internal/config/platformsh-cli.yaml
+++ b/internal/config/platformsh-cli.yaml
@@ -4,15 +4,15 @@
#
# These are settings for the 'platform' command, which is available for backwards compatibility.
wrapper:
- homebrew_tap: platformsh/tap/platformsh-cli
+ homebrew_tap: upsun/tap/platformsh-cli
github_repo: upsun/cli
application:
- name: 'Upsun CLI (Platform.sh compatibility)'
- slug: 'platformsh-cli'
- executable: 'platform'
- env_prefix: 'PLATFORMSH_CLI_'
- user_config_dir: '.platformsh'
+ name: "Upsun CLI (Platform.sh compatibility)"
+ slug: "platformsh-cli"
+ executable: "platform"
+ env_prefix: "PLATFORMSH_CLI_"
+ user_config_dir: ".platformsh"
mark_unwrapped_legacy: true
@@ -21,32 +21,32 @@ application:
- self:update
service:
- name: 'Upsun (formerly Platform.sh)'
- env_prefix: 'PLATFORM_'
+ name: "Upsun (formerly Platform.sh)"
+ env_prefix: "PLATFORM_"
- project_config_dir: '.platform'
- app_config_file: '.platform.app.yaml'
- project_config_flavor: 'platform'
+ project_config_dir: ".platform"
+ app_config_file: ".platform.app.yaml"
+ project_config_flavor: "platform"
- console_url: 'https://console.upsun.com'
+ console_url: "https://console.upsun.com"
- docs_url: 'https://docs.upsun.com'
- docs_search_url: 'https://docs.upsun.com/search.html?q={{ terms }}'
+ docs_url: "https://docs.upsun.com"
+ docs_search_url: "https://docs.upsun.com/search.html?q={{ terms }}"
- register_url: 'https://auth.upsun.com/register'
- reset_password_url: 'https://auth.upsun.com/reset-password'
+ register_url: "https://auth.upsun.com/register"
+ reset_password_url: "https://auth.upsun.com/reset-password"
- pricing_url: 'https://upsun.com/pricing'
+ pricing_url: "https://upsun.com/pricing"
- activity_type_list_url: 'https://docs.upsun.com/anchors/fixed/integrations/activity-scripts/type/'
+ activity_type_list_url: "https://docs.upsun.com/anchors/fixed/integrations/activity-scripts/type/"
- runtime_operations_help_url: 'https://docs.upsun.com/anchors/fixed/app/runtime-operations/'
+ runtime_operations_help_url: "https://docs.upsun.com/anchors/fixed/app/runtime-operations/"
api:
- base_url: 'https://api.upsun.com'
+ base_url: "https://api.upsun.com"
- auth_url: 'https://auth.upsun.com'
- oauth2_client_id: 'platform-cli'
+ auth_url: "https://auth.upsun.com"
+ oauth2_client_id: "platform-cli"
organization_types: [flexible, fixed]
default_organization_type: flexible
@@ -56,16 +56,17 @@ api:
metrics: true
teams: true
- vendor_filter: 'upsun'
+ vendor_filter: "upsun"
ssh:
- domain_wildcards: ['*.platform.sh']
+ domain_wildcards: ["*.platform.sh"]
detection:
- git_remote_name: 'platform'
- git_domain: 'platform.sh' # matches git.eu-5.platform.sh, etc.
- site_domains: ['platform.sh', 'platformsh.site', 'tst.site', 'upsunapp.com', 'upsun.app']
- cluster_header: 'X-Platform-Cluster'
+ git_remote_name: "platform"
+ git_domain: "platform.sh" # matches git.eu-5.platform.sh, etc.
+ site_domains:
+ ["platform.sh", "platformsh.site", "tst.site", "upsunapp.com", "upsun.app"]
+ cluster_header: "X-Platform-Cluster"
browser_login:
body: |
diff --git a/internal/config/schema.go b/internal/config/schema.go
index dd9fa84a..62e012cb 100644
--- a/internal/config/schema.go
+++ b/internal/config/schema.go
@@ -20,8 +20,8 @@ import (
type Config struct {
// Fields only used by to the Go wrapper.
Wrapper struct {
- HomebrewTap string `yaml:"homebrew_tap,omitempty"` // e.g. "platformsh/tap/platformsh-cli"
- GitHubRepo string `yaml:"github_repo,omitempty"` // e.g. "platformsh/cli"
+ HomebrewTap string `yaml:"homebrew_tap,omitempty"` // e.g. "upsun/tap/platformsh-cli"
+ GitHubRepo string `yaml:"github_repo,omitempty"` // e.g. "upsun/cli"
} `yaml:"wrapper,omitempty"`
Application struct {
diff --git a/internal/config/upsun-cli.yaml b/internal/config/upsun-cli.yaml
index 091a234e..5f756fac 100644
--- a/internal/config/upsun-cli.yaml
+++ b/internal/config/upsun-cli.yaml
@@ -2,7 +2,7 @@
#
# See ../internal/config/schema.go for the required keys
wrapper:
- homebrew_tap: platformsh/tap/upsun-cli
+ homebrew_tap: upsun/tap/upsun-cli
github_repo: upsun/cli
application:
@@ -74,7 +74,7 @@ detection:
git_remote_name: "upsun"
git_domain: "platform.sh" # matches git.eu-5.platform.sh, etc.
site_domains: ["platformsh.site", "tst.site", "upsunapp.com", "upsun.app"]
- cluster_header: 'X-Platform-Cluster'
+ cluster_header: "X-Platform-Cluster"
browser_login:
body: |
diff --git a/legacy/README.md b/legacy/README.md
index 4095704e..840fcc6a 100644
--- a/legacy/README.md
+++ b/legacy/README.md
@@ -7,7 +7,7 @@ To install the CLI, use either [Homebrew](https://brew.sh/) (on Linux, macOS, or
### HomeBrew
```console
-brew install platformsh/tap/platformsh-cli
+brew install upsun/tap/platformsh-cli
```
### Scoop
diff --git a/repogen.sh b/repogen.sh
new file mode 100755
index 00000000..7813a40e
--- /dev/null
+++ b/repogen.sh
@@ -0,0 +1,252 @@
+#!/bin/bash
+set -e
+
+# Parse arguments
+PACKAGE_MANAGER="${1:-all}" # debian, rpm, alpine, or all (default)
+
+if [[ ! "$PACKAGE_MANAGER" =~ ^(debian|rpm|alpine|all)$ ]]; then
+ echo "Usage: $0 [debian|rpm|alpine|all]"
+ echo " Default: all"
+ exit 1
+fi
+
+echo "Package manager filter: $PACKAGE_MANAGER"
+
+# Required environment variables:
+# - VERSION: Version of the CLI being released (auto-detected from dist/ if not set)
+# - AWS credentials: Configured via AWS CLI or environment
+# - GPG_PRIVATE_KEY_FILE: Path to GPG private key file for signing Debian/RPM packages
+# OR GPG_PRIVATE_KEY: Base64-encoded GPG private key (legacy)
+# - RSA_PRIVATE_KEY_FILE: Path to RSA private key file for signing Alpine packages
+# OR RSA_PRIVATE_KEY: RSA private key content (legacy)
+# - RSA_PUBLIC_KEY_FILE: Path to RSA public key file for Alpine users
+# OR RSA_PUBLIC_KEY: RSA public key content (legacy)
+
+# Auto-detect VERSION from dist/ if not set
+if [ -z "$VERSION" ]; then
+ # Use glob instead of ls to avoid parsing ls output
+ for deb_file in dist/*-cli_*_linux_*.deb; do
+ if [ -f "$deb_file" ]; then
+ VERSION=$(basename "$deb_file" | sed -E 's/.*-cli_(.+)_linux_.*/\1/')
+ break
+ fi
+ done
+ if [ -n "$VERSION" ]; then
+ echo "Auto-detected VERSION: $VERSION"
+ fi
+fi
+
+# Validate VERSION format (should be semver-like)
+if [ -n "$VERSION" ] && ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then
+ echo "Warning: VERSION '$VERSION' does not look like a valid semver format"
+fi
+
+# Check required environment variables
+MISSING_VARS=()
+
+if [ -z "$VERSION" ]; then
+ MISSING_VARS+=("VERSION")
+fi
+
+if [ -z "$GPG_PRIVATE_KEY_FILE" ] && [ -z "$GPG_PRIVATE_KEY" ]; then
+ MISSING_VARS+=("GPG_PRIVATE_KEY_FILE or GPG_PRIVATE_KEY")
+fi
+
+if [ -z "$RSA_PRIVATE_KEY_FILE" ] && [ -z "$RSA_PRIVATE_KEY" ]; then
+ MISSING_VARS+=("RSA_PRIVATE_KEY_FILE or RSA_PRIVATE_KEY")
+fi
+
+if [ -z "$RSA_PUBLIC_KEY_FILE" ] && [ -z "$RSA_PUBLIC_KEY" ]; then
+ MISSING_VARS+=("RSA_PUBLIC_KEY_FILE or RSA_PUBLIC_KEY")
+fi
+
+if [ ${#MISSING_VARS[@]} -ne 0 ]; then
+ echo "Error: The following required environment variables are not set:"
+ for var in "${MISSING_VARS[@]}"; do
+ echo " - $var"
+ done
+ exit 1
+fi
+
+# AWS bucket for package repositories
+export AWS_BUCKET="${AWS_BUCKET:-public-repogen-repositories-upsun-com}"
+
+# Set AWS region
+export AWS_DEFAULT_REGION="${AWS_REGION:-eu-west-1}"
+
+# Create temporary directories for repository work
+WORK_DIR=$(mktemp -d)
+
+# Use a temporary GNUPGHOME to avoid polluting the user's keyring
+export GNUPGHOME=$(mktemp -d)
+trap "rm -rf \"$WORK_DIR\" \"$GNUPGHOME\"" EXIT
+
+# Handle GPG key - from file or environment variable
+if [ -n "$GPG_PRIVATE_KEY_FILE" ]; then
+ GPG_KEY_FILE="$GPG_PRIVATE_KEY_FILE"
+else
+ GPG_KEY_FILE="${WORK_DIR}/gpg-private-key.asc"
+ echo "$GPG_PRIVATE_KEY" | base64 -d > "$GPG_KEY_FILE"
+fi
+
+# Import GPG key
+gpg --import "$GPG_KEY_FILE"
+
+# Handle RSA private key - from file or environment variable
+if [ -n "$RSA_PRIVATE_KEY_FILE" ]; then
+ RSA_KEY_FILE="$RSA_PRIVATE_KEY_FILE"
+else
+ RSA_KEY_FILE="${WORK_DIR}/rsa-private-key.pem"
+ echo "$RSA_PRIVATE_KEY" > "$RSA_KEY_FILE"
+fi
+
+# Handle RSA public key - from file or environment variable
+if [ -n "$RSA_PUBLIC_KEY_FILE" ]; then
+ RSA_PUB_KEY_FILE="$RSA_PUBLIC_KEY_FILE"
+else
+ RSA_PUB_KEY_FILE="${WORK_DIR}/repositories-upsun-com.rsa.pub"
+ echo "$RSA_PUBLIC_KEY" > "$RSA_PUB_KEY_FILE"
+fi
+
+# Packages are expected to be pre-built by goreleaser in the dist/ directory
+# goreleaser builds signed .deb, .rpm, and .apk packages via nfpm
+# Check that packages exist
+if [ ! -d "dist" ]; then
+ echo "Error: dist/ directory not found. Run 'goreleaser release' or 'make snapshot' first."
+ exit 1
+fi
+
+# Create shared staging directories for all packages
+# Both platformsh and upsun packages go in the same directories so they're
+# processed together and appear in the same repository indexes
+PACKAGES_DEB_DIR="${WORK_DIR}/packages-deb"
+PACKAGES_RPM_DIR="${WORK_DIR}/packages-rpm"
+PACKAGES_APK_DIR="${WORK_DIR}/packages-apk"
+REPO_DIR="${WORK_DIR}/repo"
+mkdir -p "$PACKAGES_DEB_DIR" "$PACKAGES_RPM_DIR" "$PACKAGES_APK_DIR" "$REPO_DIR"
+
+echo "=== Copying packages to staging directories ==="
+
+# Copy Platform.sh packages
+cp dist/platformsh-cli_${VERSION}_linux_amd64.deb "$PACKAGES_DEB_DIR/"
+cp dist/platformsh-cli_${VERSION}_linux_arm64.deb "$PACKAGES_DEB_DIR/"
+cp dist/platformsh-cli_${VERSION}_linux_amd64.apk "$PACKAGES_APK_DIR/"
+cp dist/platformsh-cli_${VERSION}_linux_arm64.apk "$PACKAGES_APK_DIR/"
+cp dist/platformsh-cli_${VERSION}_linux_amd64.rpm "$PACKAGES_RPM_DIR/"
+cp dist/platformsh-cli_${VERSION}_linux_arm64.rpm "$PACKAGES_RPM_DIR/"
+
+# Copy Upsun packages
+cp dist/upsun-cli_${VERSION}_linux_amd64.deb "$PACKAGES_DEB_DIR/"
+cp dist/upsun-cli_${VERSION}_linux_arm64.deb "$PACKAGES_DEB_DIR/"
+cp dist/upsun-cli_${VERSION}_linux_amd64.apk "$PACKAGES_APK_DIR/"
+cp dist/upsun-cli_${VERSION}_linux_arm64.apk "$PACKAGES_APK_DIR/"
+cp dist/upsun-cli_${VERSION}_linux_amd64.rpm "$PACKAGES_RPM_DIR/"
+cp dist/upsun-cli_${VERSION}_linux_arm64.rpm "$PACKAGES_RPM_DIR/"
+
+echo "Packages staged:"
+ls -la "$PACKAGES_DEB_DIR" "$PACKAGES_RPM_DIR" "$PACKAGES_APK_DIR" 2>/dev/null
+
+# Check that at least some packages were found
+pkg_count=$(find "$PACKAGES_DEB_DIR" "$PACKAGES_RPM_DIR" "$PACKAGES_APK_DIR" -type f 2>/dev/null | wc -l)
+if [ "$pkg_count" -eq 0 ]; then
+ echo "Error: No packages found in staging directories. Check that goreleaser produced the expected packages."
+ exit 1
+fi
+echo "Found $pkg_count package(s) to process."
+
+# --- Debian Repository ---
+if [[ "$PACKAGE_MANAGER" == "all" || "$PACKAGE_MANAGER" == "debian" ]]; then
+ echo "=== Processing Debian packages ==="
+ DEB_REPO_DIR="${REPO_DIR}/debian"
+ mkdir -p "$DEB_REPO_DIR"
+
+ # Sync Debian metadata from S3 (if exists)
+ aws s3 sync "s3://${AWS_BUCKET}/debian/dists" "${DEB_REPO_DIR}/dists" --delete 2>/dev/null || true
+
+ # Run repogen for Debian packages
+ repogen generate \
+ --input-dir "$PACKAGES_DEB_DIR" \
+ --output-dir "$DEB_REPO_DIR" \
+ --incremental \
+ --arch amd64,arm64 \
+ --codename stable \
+ --origin "Upsun" \
+ --label "Upsun CLI" \
+ --gpg-key "$GPG_KEY_FILE"
+
+ # Sync Debian back to S3
+ aws s3 sync "${DEB_REPO_DIR}" "s3://${AWS_BUCKET}/debian"
+ echo "=== Done processing Debian packages ==="
+fi
+
+# --- RPM Repository ---
+# Generates repos for multiple Fedora versions to support $releasever in yum/dnf configs
+if [[ "$PACKAGE_MANAGER" == "all" || "$PACKAGE_MANAGER" == "rpm" ]]; then
+ echo "=== Processing RPM packages ==="
+
+ # Fedora versions to support (configurable via environment variable)
+ FEDORA_VERSIONS="${FEDORA_VERSIONS:-40 41 42 43}"
+
+ for FEDORA_VER in $FEDORA_VERSIONS; do
+ echo "--- Processing Fedora $FEDORA_VER ---"
+
+ # Use a separate output directory per version to avoid repogen's ParseExistingMetadata
+ # scanning all version directories and detecting conflicts
+ RPM_REPO_DIR="${REPO_DIR}/fedora-${FEDORA_VER}"
+ mkdir -p "$RPM_REPO_DIR"
+
+ # Sync only repodata metadata from S3 (not packages)
+ for arch in x86_64 aarch64; do
+ mkdir -p "${RPM_REPO_DIR}/${FEDORA_VER}/${arch}/repodata"
+ aws s3 sync "s3://${AWS_BUCKET}/fedora/${FEDORA_VER}/${arch}/repodata" "${RPM_REPO_DIR}/${FEDORA_VER}/${arch}/repodata" --delete 2>/dev/null || true
+ done
+
+ # Run repogen for RPM packages with explicit version
+ # repogen will create $version/$arch/Packages/ and $version/$arch/repodata/ inside output-dir
+ repogen generate \
+ --input-dir "$PACKAGES_RPM_DIR" \
+ --output-dir "$RPM_REPO_DIR" \
+ --incremental \
+ --arch amd64,arm64 \
+ --origin "Upsun" \
+ --label "Upsun CLI" \
+ --version "$FEDORA_VER" \
+ --gpg-key "$GPG_KEY_FILE"
+
+ # Sync this Fedora version back to S3 (without --delete to preserve existing packages)
+ aws s3 sync "${RPM_REPO_DIR}/${FEDORA_VER}" "s3://${AWS_BUCKET}/fedora/${FEDORA_VER}"
+ done
+
+ echo "=== Done processing RPM packages ==="
+fi
+
+# --- Alpine Repository ---
+if [[ "$PACKAGE_MANAGER" == "all" || "$PACKAGE_MANAGER" == "alpine" ]]; then
+ echo "=== Processing Alpine packages ==="
+ APK_REPO_DIR="${REPO_DIR}/alpine"
+ mkdir -p "$APK_REPO_DIR"
+
+ # Sync Alpine metadata from S3 (if exists)
+ for arch in x86_64 aarch64; do
+ mkdir -p "${APK_REPO_DIR}/${arch}"
+ aws s3 cp "s3://${AWS_BUCKET}/alpine/${arch}/APKINDEX.tar.gz" "${APK_REPO_DIR}/${arch}/APKINDEX.tar.gz" 2>/dev/null || true
+ done
+
+ # Run repogen for Alpine packages with RSA signing
+ repogen generate \
+ --input-dir "$PACKAGES_APK_DIR" \
+ --output-dir "$APK_REPO_DIR" \
+ --incremental \
+ --arch amd64,arm64 \
+ --rsa-key "$RSA_KEY_FILE" \
+ --key-name "repositories-upsun-com"
+
+ # Copy RSA public key for users to download
+ cp "$RSA_PUB_KEY_FILE" "${APK_REPO_DIR}/"
+
+ # Sync Alpine back to S3
+ aws s3 sync "${APK_REPO_DIR}" "s3://${AWS_BUCKET}/alpine"
+ echo "=== Done processing Alpine packages ==="
+fi
+
+echo "All packages uploaded successfully!"