Compare commits

...

78 Commits

Author SHA1 Message Date
Andras Bacsai bd65345df8 Merge pull request #51 from YaRissi/fix/service-update-env
fix: update service env command
2026-03-19 22:03:20 +01:00
Andras Bacsai c94e147639 feat(env): enforce minimum version requirement for updates 2026-03-19 22:00:40 +01:00
Andras Bacsai 0ea34284ef fix(env): require key and value flags for updating variables
- Changed app and service env update commands to accept only the resource UUID instead of separate env UUID argument
- Made --key and --value required flags for identifying and updating environment variables
- Removed UUID field from EnvironmentVariableUpdateRequest model
- Updated validation to explicitly require --key and --value instead of "at least one field"
- Changed ServiceEnvBulkUpdateResponse from struct with message to slice of ServiceEnvironmentVariable
- Updated BulkUpdateEnvs return type from pointer to non-pointer
- Updated tests and documentation to reflect new command interface
2026-03-19 21:57:11 +01:00
YaRissi a93872ee16 fix lint 2025-12-19 18:49:13 +01:00
YaRissi 1bc1a601a8 fix update env command 2025-12-19 18:42:42 +01:00
github-actions[bot] 4ad94e2d65 chore: bump version to v1.4.0 2025-12-12 13:04:47 +00:00
Andras Bacsai faa8186301 Merge pull request #47 from coollabsio/project-app-create
feat: add create commands for applications, projects, and services
2025-12-12 14:02:09 +01:00
Andras Bacsai 1eba511544 fix: use assert instead of require in HTTP handlers
Replace require.NoError with assert.NoError inside HTTP handler
functions to fix testifylint go-require violations. Using require
in HTTP handlers can cause unpredictable test behavior since
t.FailNow() only exits the current goroutine (the handler), not
the main test goroutine.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-11 10:21:33 +01:00
Andras Bacsai 541f633edc feat: add create commands for applications, projects, and services
Add comprehensive create functionality for three main resource types:
- Applications: public, private (GitHub App & deploy key), Dockerfile, Docker image
- Projects: simple project creation with optional description
- Services: one-click service deployment with 80+ service types

Includes full service layer implementation with 15+ test cases covering success and error scenarios. Also fixed EnvironmentUUID handling in service creation requests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 18:41:31 +01:00
github-actions[bot] 0f23b029f0 chore: bump version to v1.3.0 2025-12-05 12:43:09 +00:00
github-actions[bot] 780b3674c7 chore: bump version to v1.2 2025-12-05 12:38:45 +00:00
Andras Bacsai 77adbfaebc Merge pull request #46 from coollabsio/update-check-every-cmd
Check for CLI updates on every command
2025-12-05 13:35:52 +01:00
Andras Bacsai 9215fd537e feat: check for CLI updates on every command
Remove the 10-minute check interval so update notifications appear on every command execution. Silent error handling prevents network issues from interrupting commands. Updated message format is more concise and shows the available version.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 13:35:05 +01:00
github-actions[bot] 26c0925854 chore: bump version to v1.1 2025-12-05 12:04:57 +00:00
Andras Bacsai 1f1b187ed2 Merge pull request #45 from coollabsio/add-runtime-env-flag
Add runtime env flag and improve service env handling
2025-12-05 13:01:46 +01:00
Andras Bacsai 4af598c213 fix: use is_buildtime JSON tag to match Coolify API response
The Coolify API returns `is_buildtime` (without underscore between
build and time) in responses. Updated service tests to use the correct
field name.

Also simplified application env get command by removing preview filter.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 12:58:18 +01:00
Andras Bacsai 6ca3b700ce fix: resolve lint errors for stuttering type names
Move ServiceBulkUpdateEnvsRequest and ServiceBulkUpdateEnvsResponse
to models package as ServiceEnvBulkUpdateRequest/Response to avoid
the "type name stutters" lint error from revive.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 12:00:41 +01:00
Andras Bacsai 8cf0b71ebf feat: add runtime env flag and improve service env handling
- Add --runtime flag to all env commands (create, update, sync) for both apps and services
- Make --runtime and --build-time flags default to true
- Remove is_preview field from service environment variables (services don't have preview)
- Create ServiceEnvironmentVariable model without preview support
- Wire up service env commands in service.go
- Add --preview filter option to app env list and get commands
- Add --all flag to app env list to show all variables (non-preview first)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:54:22 +01:00
github-actions[bot] 99a40bfa1d chore: bump version to v1.0.5 2025-11-27 08:20:33 +00:00
Andras Bacsai 188834fd6d Merge pull request #39 from YaRissi/fix/version
fix: update  release workflow
2025-11-27 09:17:39 +01:00
Andras Bacsai f9c3b9869a Merge pull request #43 from coollabsio/dependabot/go_modules/golang.org/x/crypto-0.45.0
chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0
2025-11-27 09:16:36 +01:00
dependabot[bot] 6044a2107e chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.43.0 to 0.45.0.
- [Commits](https://github.com/golang/crypto/compare/v0.43.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-version: 0.45.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-27 08:16:04 +00:00
Andras Bacsai 2f9dc6e8d7 Merge pull request #42 from coollabsio/fix-env-is-build-time-value
Fix is_buildtime JSON tag and add is_runtime, is_shared fields
2025-11-27 09:15:01 +01:00
Andras Bacsai 1e741309cb fix: correct is_buildtime JSON tag and add is_runtime, is_shared fields
Fixed critical bug where is_buildtime field was not unmarshaling from API
responses due to JSON tag mismatch (was expecting 'is_build_time' with
underscore but API returns 'is_buildtime' without underscore). Also added
missing is_runtime and is_shared fields that are present in API responses.

Added comprehensive tests for EnvironmentVariable model and service layer
to ensure proper marshaling/unmarshaling of all fields. Achieved 100%
coverage for EnvironmentVariable struct.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 09:11:35 +01:00
YaRissi 51f759c38f fix: improve error handling in Stop-WithError function and update git tag push command 2025-11-20 02:34:22 +01:00
Andras Bacsai 51e9ec5ec8 Update install.sh 2025-11-18 23:25:17 +01:00
Andras Bacsai e071fd81d4 Merge branch 'v4.x' into fix/version 2025-11-18 23:19:32 +01:00
Andras Bacsai cb0bbfc5cb Merge pull request #41 from ncryptedV1/fix/install-script-release-download-link
fix: leading 'v' for release filename of install script
2025-11-18 23:18:53 +01:00
ncryptedV1 87b6b8fdf7 fix: leading 'v' for release filename of install script 2025-11-18 17:24:25 +01:00
YaRissi 3dbe2507f4 fix deprecated builds tag 2025-11-16 16:39:08 +01:00
YaRissi 1e82217a50 feat: update installation instructions for Windows and add PowerShell script 2025-11-16 16:29:27 +01:00
YaRissi 6f33fa00f1 fix: readd version in binary name 2025-11-16 16:06:03 +01:00
YaRissi d7841b3b5a chore: readd version in binaryname 2025-11-16 15:59:41 +01:00
YaRissi 234f6e9ed6 fix: update binary name in installation script 2025-11-10 21:45:31 +01:00
YaRissi fa86ceb5cc fix: update filename format in download function for consistency 2025-11-10 21:37:22 +01:00
YaRissi 646bf9de36 fix: update version tagging logic in release workflow 2025-11-10 21:07:02 +01:00
github-actions[bot] be29a6e05d chore: bump version to v1.0.4 2025-11-10 13:24:40 +00:00
Andras Bacsai 63a882107a Merge pull request #37 from coollabsio/add-deployment-logs-cli
Add deployment management commands for improved user experience
2025-11-10 14:21:49 +01:00
Andras Bacsai 08cd3b8ac7 fix: resolve nilerr linting error in deployment logs pretty-print logic
Restructure JSON pretty-print logic to check for success instead of
failure, avoiding nilerr linting violation while maintaining fallback
behavior. The change ensures proper error handling patterns without
hiding potential bugs.
2025-11-10 14:19:29 +01:00
Andras Bacsai 06f191e9ba Merge branch 'v4.x' into add-deployment-logs-cli 2025-11-10 14:18:09 +01:00
Andras Bacsai 7a19a02c02 fix: resolve remaining nilerr linting errors
- Explicitly ignore errors where we have intentional fallback behavior
- Use blank identifier (_) for errors we don't need to check
- Restructure error checking to avoid nilerr pattern
- All errors are properly handled or explicitly ignored
2025-11-10 13:55:10 +01:00
Andras Bacsai 806a6b9716 fix: resolve linting errors (errcheck, nilerr, revive, gci)
- Add error checking for w.Write calls in tests
- Rename unused http.Request parameters to _ in test handlers
- Fix nilerr errors by using different variable names to avoid shadowing
- Fix gci import grouping (stdlib, external, internal)
2025-11-10 13:48:58 +01:00
Andras Bacsai a18d751ad4 style: fix gofmt formatting in deployment_test.go 2025-11-10 13:44:42 +01:00
Andras Bacsai 9283717821 fix: add --format flag support and filter hidden logs in JSON output
Add proper support for --format flag (json/pretty/table) in deployment logs:
- Respect global --format flag for output formatting
- Filter hidden logs by default in JSON/pretty formats
- Only show debug logs when --debuglogs flag is present
- Apply --lines limit only to table format (JSON shows complete data)

Changes:
- Add GetLogsByDeploymentWithFormat() and GetLogsByApplicationWithFormat()
- Filter log entries with "hidden": true unless --debuglogs is set
- Return raw JSON for --format json
- Return pretty-printed JSON for --format pretty
- Return human-readable text for --format table (default)
2025-11-10 13:26:29 +01:00
Andras Bacsai a44c712163 feat: add deployment logs commands with human-readable output
Add new commands to query and display deployment logs:
- coolify app deployments list <app-uuid>: List all deployments for an application
- coolify app deployments logs <app-uuid> [deployment-uuid>]: Get deployment logs

Features:
- Retrieves latest deployment logs by default or specific deployment by UUID
- Parses JSON log format into human-readable line-by-line output
- Supports --lines flag to limit output to last N lines
- Supports --follow flag for real-time log streaming
- Supports --debuglogs flag to show hidden debug commands
- Uses API pagination (skip/take) for efficient deployment fetching
- Sorts deployments by timestamp to ensure latest is selected

Implementation:
- Add DeploymentsListResponse to handle paginated API responses
- Add log parsing utilities to format JSON logs as plain text
- Add comprehensive test coverage for deployment service methods
- Update README with new command documentation
2025-11-10 13:18:15 +01:00
Andras Bacsai 7c42af6203 Merge pull request #36 from coollabsio/fix/remove-file-check
Remove gzip file validation check
2025-11-10 12:34:29 +01:00
Andras Bacsai ed2dbd4947 Merge pull request #34 from YaRissi/version-inject
feat: Version inject
2025-11-10 12:33:57 +01:00
Laurence Jones f1301c1dbd Remove gzip file validation check
fix #35 

Removed basic check for gzip compressed file, since tar will error if not valid type anyways.
2025-11-07 08:08:31 +00:00
YaRissi 8a58cdd72f fix: update documentation to reflect new version retrieval location 2025-10-29 22:07:58 +01:00
Yassir Elmarissi 5ca4c3a8e3 Merge branch 'v4.x' into version-inject 2025-10-29 15:33:27 +01:00
Andras Bacsai 89cd744696 Merge pull request #33 from YaRissi/ci
feat: testing ci with linter
2025-10-29 15:22:39 +01:00
Andras Bacsai 694b4f8e32 Merge pull request #31 from YaRissi/go-install
feat: installable via go
2025-10-29 15:18:10 +01:00
Yassir Elmarissi ef15d013da fix: update regex for version injection
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-29 00:14:51 +01:00
YaRissi 9f5a44be04 fix: comment 2025-10-28 23:51:16 +01:00
YaRissi 1078a9d3ca fix: improve instance validation and error handling in context switching 2025-10-28 23:42:04 +01:00
YaRissi 9cef9ebee7 fix: improve error handling in commands 2025-10-28 23:27:03 +01:00
YaRissi 51f33cfc5e reactor: change context.Background zu cmd.Context 2025-10-28 23:15:02 +01:00
YaRissi 559d4e2709 chore: update commit message for version bump in release workflow 2025-10-21 01:02:43 +02:00
YaRissi 94e237e2b1 fix: specify branch reference in action and fix readme 2025-10-21 00:57:52 +02:00
YaRissi 3dd0dfdce1 fix: stage version file before committing 2025-10-21 00:34:10 +02:00
YaRissi aac44a0ddb fix: remove parallelism setting in goreleaser configuration 2025-10-21 00:28:22 +02:00
YaRissi a4c96d2803 fix: correct regex for version update in checker.go 2025-10-21 00:23:43 +02:00
YaRissi dc9670992a feat: implement version update automation 2025-10-21 00:13:55 +02:00
YaRissi c369689131 add coverage to test command 2025-10-18 13:50:45 +02:00
YaRissi dcf0b39c1b fix: golangci-lint version 2025-10-18 13:46:46 +02:00
YaRissi 14a3f00c57 refactor: update command handlers to use RunE for error handling 2025-10-18 13:37:31 +02:00
YaRissi 86f77716ee fix: all found problems by the linter 2025-10-18 13:13:35 +02:00
YaRissi ef91ed987e feat: testing ci with linter and pull request template 2025-10-18 13:12:21 +02:00
Yassir Elmarissi d8cf7a5986 docs: update installation section to emphasize the recommended install script 2025-10-17 21:32:44 +00:00
YaRissi d30b0b90de fix: run commands paths 2025-10-17 23:08:13 +02:00
YaRissi 4a8a659090 fix: update build commands and paths in configuration and documentation 2025-10-17 22:57:05 +02:00
Andras Bacsai aa1bda4063 Merge pull request #32 from coollabsio/andrasbacsai/env-sync-analysis
Add flags to env sync command
2025-10-17 22:29:43 +02:00
Andras Bacsai 69f2a7ac1f Changes auto-committed by Conductor 2025-10-17 22:29:14 +02:00
Yassir Elmarissi ad185a42ee update goreleaser 2025-10-17 14:58:05 +00:00
Yassir Elmarissi 22e34fb72e add dist to gitignore 2025-10-17 11:32:39 +00:00
Yassir Elmarissi c5b0ad4218 feat: installable via go and update gorealeser 2025-10-17 11:29:04 +00:00
Andras Bacsai 1a6fa9e397 Merge pull request #29 from coollabsio/andrasbacsai/parallel-goreleaser-builds
Enable parallel GoReleaser builds
2025-10-17 11:41:30 +02:00
Andras Bacsai b767468b29 Changes auto-committed by Conductor 2025-10-17 11:40:59 +02:00
150 changed files with 4924 additions and 867 deletions
+2 -2
View File
@@ -5,14 +5,14 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./coolify"
cmd = "go build -o ./coolify ."
cmd = "go build -o ./coolify ./coolify"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", ".git", ".conductor"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = "echo 'Build complete. Binary: ./coolify'"
full_bin = "echo 'Build complete. Binary: ./coolify/coolify'"
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
+7
View File
@@ -0,0 +1,7 @@
## Changes
-
## Issues & Discussions
- fix #
+34 -5
View File
@@ -10,18 +10,47 @@ jobs:
release-cli:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: stable
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: ${{ env.GITHUB_REF_NAME }}
args: release --clean
workdir: ./
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
update-version:
needs: [release-cli]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: v4.x
fetch-depth: 0
- name: Update version file
run: |
TAG=${GITHUB_REF#refs/tags/}
echo "Updating version to $TAG"
sed -i "s/^\tversion = \".*\"/\tversion = \"$TAG\"/" internal/version/checker.go
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add internal/version/checker.go
git commit -m "chore: bump version to $TAG"
git push origin v4.x
# Move the tag to point to the new commit with updated version
git tag -d "$TAG" || true
git tag "$TAG"
git push origin "refs/tags/$TAG" --force
+55
View File
@@ -0,0 +1,55 @@
name: Testing CLI
on:
push:
branches: ["v4.x"]
pull_request:
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Run gofmt
run: diff -u <(echo -n) <(gofmt -d -s .)
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8
with:
version: v2.5.0 # pin version for consistency
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Run tests
run: go test -v -race -cover ./...
go-mod-tidy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Run go mod tidy
run: go mod tidy
- name: Check uncommitted changes
run: git diff --exit-code
- if: failure()
run: echo "::error::Check failed, please run 'go mod tidy' and commit the changes."
+2 -1
View File
@@ -1,11 +1,12 @@
coolify-cli
coolify
/coolify
config.json
.claude
# Generated documentation (can be regenerated)
man/
docs/cli/
dist/
# Test coverage
coverage.out
+75
View File
@@ -0,0 +1,75 @@
version: "2"
run:
timeout: 5m
linters:
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- contextcheck
- durationcheck
- errchkjson
- errorlint
- exhaustive
- gocheckcompilerdirectives
- gochecksumtype
- gocritic
- gomoddirectives
- gomodguard
- gosec
- gosmopolitan
- loggercheck
- makezero
- musttag
- nilerr
- nilnesserr
- noctx
- protogetter
- reassign
- recvcheck
- revive
- rowserrcheck
- spancheck
- sqlclosecheck
- testifylint
- unparam
- zerologlint
settings:
exhaustive:
default-signifies-exhaustive: true
staticcheck:
checks: ["all", "-ST1005", "-S1016"]
gosec:
excludes:
- G115
gosmopolitan:
allow-time-local: true
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- std-error-handling
formatters:
enable:
- gci
- goimports
settings:
gci:
sections:
- standard
- default
- prefix(github.com/coollabsio)
exclusions:
generated: lax
+27 -2
View File
@@ -1,8 +1,19 @@
version: 2
before:
hooks:
- go mod tidy
builds:
- binary: coolify
- id: coolify
binary: coolify
flags:
- -trimpath
ldflags:
- -s
- -w
- -X github.com/coollabsio/coolify-cli/internal/version.version={{ .Version }}
main: ./coolify/main.go
goos:
- darwin
- linux
@@ -11,4 +22,18 @@ builds:
- amd64
- arm64
env:
- CGO_ENABLED=0
- CGO_ENABLED=0
checksum:
name_template: checksums.txt
algorithm: sha256
archives:
- id: coolify-archive
ids:
- coolify
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
format_overrides:
- goos: windows
formats: [zip]
+4 -1
View File
@@ -550,7 +550,10 @@ c.httpClient = &http.Client{
```bash
# Local build
go build -o coolify .
go build -o coolify ./coolify
# Install locally
go install ./coolify
# Multi-platform release
goreleaser release --clean
+7 -6
View File
@@ -9,6 +9,7 @@ This is a CLI tool for interacting with the Coolify API, built with Go using the
### API Specification
This CLI is a client for the Coolify API. The API specification is defined in the OpenAPI schema:
- **Source**: https://github.com/coollabsio/coolify/blob/v4.x/openapi.json
- **Raw JSON**: https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/v4.x/openapi.json
- **Base Path**: `/api/v1/`
- **Authentication**: Bearer token (API tokens from Coolify dashboard at `/security/api-tokens`)
@@ -21,7 +22,7 @@ All commands in this CLI are wrappers around API endpoints defined in the OpenAP
### Command Structure
The codebase follows Cobra's command pattern with a root command and subcommands:
- Entry point: `main.go` calls `cmd.Execute()`
- Entry point: `coolify/main.go` calls `cmd.Execute()`
- Root command: `cmd/root.go` - contains core utilities (HTTP client, authentication, version checking, config management)
- Subcommands: Each command is in its own file in `cmd/`:
- `context.go` - manage Coolify context (add, remove, list, set default/token)
@@ -62,23 +63,23 @@ Three output modes supported via `--format` flag:
### Build
```bash
go build -o coolify .
go build -o coolify ./coolify
```
### Run locally
```bash
go run main.go [command]
go run ./coolify [command]
```
### Test a command
```bash
go run main.go instances list
go run main.go servers list --debug
go run ./coolify context list
go run ./coolify servers list --debug
```
### Install locally
```bash
go install
go install ./coolify
```
### Run tests
+7 -7
View File
@@ -37,7 +37,7 @@ git clone https://github.com/YOUR_USERNAME/coolify-cli.git
cd coolify-cli
# Build the CLI
go build -o coolify .
go build -o coolify ./coolify
# Install locally
go install
@@ -47,14 +47,14 @@ go install
```bash
# Run without installing
go run main.go [command]
go run ./coolify [command]
# Example commands
go run main.go context list
go run main.go server list --debug
go run ./coolify context list
go run ./coolify server list --debug
# With flags
go run main.go server list --format json --debug
go run ./coolify server list --format json --debug
```
### Project Structure
@@ -177,7 +177,7 @@ func NewListCommand() *cobra.Command {
Use: "list",
Short: "List all myfeature resources",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
@@ -330,7 +330,7 @@ func TestMyFeatureService_List(t *testing.T) {
client := api.NewClient(server.URL, "test-token")
svc := NewMyFeatureService(client)
items, err := svc.List(context.Background())
items, err := svc.List(cmd.Context())
require.NoError(t, err)
assert.Len(t, items, 2)
+10 -28
View File
@@ -10,28 +10,7 @@ This guide explains the release process for the Coolify CLI.
## Release Process
### 1. Update Version Number
Edit `cmd/root.go` and update the `CliVersion` variable:
```go
var CliVersion = "1.x.x" // Change to your new version
```
**Version Format:** Use semantic versioning: `MAJOR.MINOR.PATCH` (e.g., `1.2.3`)
- **MAJOR**: Breaking changes
- **MINOR**: New features (backwards compatible)
- **PATCH**: Bug fixes (backwards compatible)
### 2. Commit and Push Version Change
```bash
git add cmd/root.go
git commit -m "chore: bump version to 1.x.x"
git push origin v4.x
```
### 3. Create a GitHub Release
### 1. Create a GitHub Release
1. Go to https://github.com/coollabsio/coolify-cli/releases/new
2. Click "Choose a tag" and create a new tag:
@@ -56,7 +35,7 @@ git push origin v4.x
```
5. Click "Publish release"
### 4. Automated Build Process
### 2. Automated Build Process
Once you publish the release:
@@ -65,12 +44,14 @@ Once you publish the release:
- **Linux**: amd64, arm64
- **macOS (Darwin)**: amd64, arm64
- **Windows**: amd64, arm64
3. Binaries are automatically uploaded to the release
4. The release becomes available at:
3. Goreleaser injects the version from the tag into the binaries
4. Binaries are automatically uploaded to the release
5. The release becomes available at:
- GitHub: `https://github.com/coollabsio/coolify-cli/releases/tag/v1.x.x`
- Install script: `curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash`
- `go install`: `go install github.com/coollabsio/coolify-cli/coolify@v1.x.x`
### 5. Verify the Release
### 3. Verify the Release
After the workflow completes (usually 2-5 minutes):
@@ -128,10 +109,11 @@ After creating a release:
The release process uses these configuration files:
- `.goreleaser.yml` - GoReleaser configuration (build matrix, archives, etc.)
- `.goreleaser.yml` - GoReleaser configuration (build matrix, archives, etc.) - points to `/coolify` as entry point
- `.github/workflows/release-cli.yml` - GitHub Actions workflow
- `scripts/install.sh` - User-facing install script
- `cmd/root.go` - Contains `CliVersion` variable (line 22)
- `internal/version/checker.go` - Contains `GetVersion()` function that returns the current version
- `coolify/main.go` - Binary entry point for `go install` support
## Notes
+70 -4
View File
@@ -2,12 +2,45 @@
## Installation
### Install script (recommended)
#### Linux/macOS
```bash
curl -fsSL https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.sh | bash
```
It will install the CLI in `/usr/local/bin/coolify` and the configuration file in `~/.config/coolify/config.json`
#### Windows (PowerShell)
```powershell
irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
```
It will install the CLI in `%ProgramFiles%\Coolify\coolify.exe` and the configuration file in `%USERPROFILE%\.config\coolify\config.json`
For user installation (no admin rights required):
```powershell
$env:COOLIFY_USER_INSTALL=1; irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
```
For a specific version:
```powershell
$env:COOLIFY_VERSION='v1.0.0'; irm https://raw.githubusercontent.com/coollabsio/coolify-cli/main/scripts/install.ps1 | iex
```
### Using `go install`
```bash
go install github.com/coollabsio/coolify-cli/coolify@latest
```
This will install the `coolify` binary in your `$GOPATH/bin` directory (usually `~/go/bin`). Make sure this directory is in your `$PATH`.
### Using the install script
## Getting Started
1. Get a `<token>` from your Coolify dashboard (Cloud or self-hosted) at `/security/api-tokens`
@@ -116,10 +149,30 @@ Commands can use `server` or `servers` interchangeably.
- `--build-time` - Available at build time
- `--is-literal` - Treat value as literal (don't interpolate variables)
- `--is-multiline` - Value is multiline
- `coolify app env update <app_uuid> <env_uuid>` - Update an environment variable
- `coolify app env update <app_uuid>` - Update an environment variable
- `--key <key>` - Variable key (required)
- `--value <value>` - Variable value (required)
- `--preview` - Available in preview deployments
- `--build-time` - Available at build time
- `--is-literal` - Treat value as literal (don't interpolate variables)
- `--is-multiline` - Value is multiline
- `--runtime` - Available at runtime
- `coolify app env delete <app_uuid> <env_uuid>` - Delete an environment variable
- `coolify app env sync <app_uuid>` - Sync environment variables from a .env file
- `--file <path>` - Path to .env file (required)
- `--build-time` - Make all variables available at build time
- `--preview` - Make all variables available in preview deployments
- `--is-literal` - Treat all values as literal (don't interpolate variables)
- **Behavior**: Updates existing variables, creates missing ones. Does NOT delete variables not in the file.
#### Application Deployments
- `coolify app deployments list <app-uuid>` - List all deployments for an application
- `coolify app deployments logs <app-uuid> [deployment-uuid]` - Get deployment logs (formatted as human-readable text)
- If only `app-uuid` is provided: retrieves logs from the **latest/most recent deployment only**
- If `deployment-uuid` is also provided: retrieves logs for that **specific deployment**
- `-n, --lines <n>` - Number of log lines to display (default: 0 = all lines)
- `-f, --follow` - Follow log output in real-time (like tail -f)
- `--debuglogs` - Show debug logs (includes hidden commands and internal operations)
### Databases
- `coolify database list` - List all databases
@@ -186,10 +239,20 @@ Commands can use `server` or `servers` interchangeably.
- `coolify service env get <service_uuid> <env_uuid_or_key>` - Get a specific environment variable
- `coolify service env create <service_uuid>` - Create a new environment variable
- Same flags as application environment variables
- `coolify service env update <service_uuid> <env_uuid>` - Update an environment variable
- `coolify service env update <service_uuid>` - Update an environment variable
- `--key <key>` - Variable key (required)
- `--value <value>` - Variable value (required)
- `--build-time` - Available at build time
- `--is-literal` - Treat value as literal (don't interpolate variables)
- `--is-multiline` - Value is multiline
- `--runtime` - Available at runtime
- `coolify service env delete <service_uuid> <env_uuid>` - Delete an environment variable
- `coolify service env sync <service_uuid>` - Sync environment variables from a .env file
- `--file <path>` - Path to .env file (required)
- `--build-time` - Make all variables available at build time
- `--preview` - Make all variables available in preview deployments
- `--is-literal` - Treat all values as literal (don't interpolate variables)
- **Behavior**: Updates existing variables, creates missing ones. Does NOT delete variables not in the file.
### Deployments
- `coolify deploy uuid <uuid>` - Deploy a resource by UUID
@@ -295,7 +358,10 @@ coolify app logs <uuid>
# Environment variables
coolify app env list <uuid>
coolify app env create <uuid> --key API_KEY --value secret123
# Sync from .env file (updates existing, creates new, keeps others unchanged)
coolify app env sync <uuid> --file .env
coolify app env sync <uuid> --file .env.production --build-time --preview
```
### Database Management
@@ -446,7 +512,7 @@ This CLI follows a clean architecture with:
```bash
# Build
go build -o coolify .
go build -o coolify ./coolify
# Run tests
go test ./...
@@ -455,7 +521,7 @@ go test ./...
go test -cover ./...
# Install locally
go install
go install ./coolify
```
## Contributing
+3
View File
@@ -3,6 +3,7 @@ package application
import (
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/cmd/application/create"
"github.com/coollabsio/coolify-cli/cmd/application/env"
)
@@ -18,12 +19,14 @@ func NewAppCommand() *cobra.Command {
// Add main subcommands
cmd.AddCommand(NewListCommand())
cmd.AddCommand(NewGetCommand())
cmd.AddCommand(create.NewCreateCommand())
cmd.AddCommand(NewUpdateCommand())
cmd.AddCommand(NewDeleteCommand())
cmd.AddCommand(NewStartCommand())
cmd.AddCommand(NewStopCommand())
cmd.AddCommand(NewRestartCommand())
cmd.AddCommand(NewLogsCommand())
cmd.AddCommand(NewDeploymentsCommand())
// Add env subcommand with its children
envCmd := &cobra.Command{
+38
View File
@@ -0,0 +1,38 @@
package create
import "github.com/spf13/cobra"
// NewCreateCommand creates the create parent command with all subcommands
func NewCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a new application",
Long: `Create a new application from various sources.
Available source types:
public Create from a public git repository
github Create from a private repository using GitHub App
deploy-key Create from a private repository using SSH deploy key
dockerfile Create from a custom Dockerfile
dockerimage Create from a pre-built Docker image
Examples:
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--git-repository "https://github.com/user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--github-app-uuid <uuid> --git-repository "user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--docker-registry-image-name "nginx:latest" --ports-exposes 80`,
}
// Add all create subcommands
cmd.AddCommand(NewPublicCommand())
cmd.AddCommand(NewGitHubCommand())
cmd.AddCommand(NewDeployKeyCommand())
cmd.AddCommand(NewDockerfileCommand())
cmd.AddCommand(NewDockerImageCommand())
return cmd
}
+152
View File
@@ -0,0 +1,152 @@
package create
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewDeployKeyCommand returns the create deploy-key application command
func NewDeployKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "deploy-key",
Short: "Create an application from a private repository using SSH deploy key",
Long: `Create a new application from a private git repository using SSH deploy key authentication.
Use 'coolify privatekeys list' to find your private key UUID.
Examples:
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--private-key-uuid <uuid> --git-repository "git@github.com:owner/repo.git" --git-branch main \
--build-pack nixpacks --ports-exposes 3000
coolify app create deploy-key --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--private-key-uuid <uuid> --git-repository "git@gitlab.com:owner/repo.git" --git-branch main \
--build-pack dockerfile --ports-exposes 8080 --instant-deploy`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get required flags
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
privateKeyUUID, _ := cmd.Flags().GetString("private-key-uuid")
gitRepository, _ := cmd.Flags().GetString("git-repository")
gitBranch, _ := cmd.Flags().GetString("git-branch")
buildPack, _ := cmd.Flags().GetString("build-pack")
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
// Validate required fields
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if privateKeyUUID == "" {
return fmt.Errorf("--private-key-uuid is required")
}
if gitRepository == "" || gitBranch == "" {
return fmt.Errorf("--git-repository and --git-branch are required")
}
if buildPack == "" || portsExposes == "" {
return fmt.Errorf("--build-pack and --ports-exposes are required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ApplicationCreateDeployKeyRequest{
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
PrivateKeyUUID: privateKeyUUID,
GitRepository: gitRepository,
GitBranch: gitBranch,
BuildPack: buildPack,
PortsExposes: portsExposes,
}
if environmentName != "" {
req.EnvironmentName = &environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Optional fields
setOptionalStringFlag(cmd, "name", &req.Name)
setOptionalStringFlag(cmd, "description", &req.Description)
setOptionalStringFlag(cmd, "domains", &req.Domains)
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
appSvc := service.NewApplicationService(client)
app, err := appSvc.CreateDeployKey(ctx, req)
if err != nil {
return fmt.Errorf("failed to create application: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(app)
},
}
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
cmd.Flags().String("private-key-uuid", "", "Private key UUID (required)")
cmd.Flags().String("git-repository", "", "Git repository SSH URL, e.g., 'git@github.com:owner/repo.git' (required)")
cmd.Flags().String("git-branch", "", "Git branch (required)")
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
// Optional flags
cmd.Flags().String("name", "", "Application name")
cmd.Flags().String("description", "", "Application description")
cmd.Flags().String("domains", "", "Domain(s) for the application")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().String("build-command", "", "Custom build command")
cmd.Flags().String("start-command", "", "Custom start command")
cmd.Flags().String("install-command", "", "Custom install command")
cmd.Flags().String("base-directory", "", "Base directory for the application")
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
cmd.Flags().String("limits-cpus", "", "CPU limit")
cmd.Flags().String("limits-memory", "", "Memory limit")
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
cmd.Flags().String("health-check-path", "", "Health check path")
return cmd
}
+120
View File
@@ -0,0 +1,120 @@
package create
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewDockerfileCommand returns the create dockerfile application command
func NewDockerfileCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "dockerfile",
Short: "Create an application from a custom Dockerfile",
Long: `Create a new application from a custom Dockerfile content.
Examples:
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--dockerfile "FROM node:18\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"npm\", \"start\"]"
coolify app create dockerfile --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--dockerfile "$(cat Dockerfile)" --ports-exposes 3000 --instant-deploy`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get required flags
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
dockerfile, _ := cmd.Flags().GetString("dockerfile")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
// Validate required fields
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if dockerfile == "" {
return fmt.Errorf("--dockerfile is required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ApplicationCreateDockerfileRequest{
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
Dockerfile: dockerfile,
}
if environmentName != "" {
req.EnvironmentName = &environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Optional fields
setOptionalStringFlag(cmd, "name", &req.Name)
setOptionalStringFlag(cmd, "description", &req.Description)
setOptionalStringFlag(cmd, "domains", &req.Domains)
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
setOptionalStringFlag(cmd, "ports-exposes", &req.PortsExposes)
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
appSvc := service.NewApplicationService(client)
app, err := appSvc.CreateDockerfile(ctx, req)
if err != nil {
return fmt.Errorf("failed to create application: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(app)
},
}
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
cmd.Flags().String("dockerfile", "", "Dockerfile content (required)")
// Optional flags
cmd.Flags().String("name", "", "Application name")
cmd.Flags().String("description", "", "Application description")
cmd.Flags().String("domains", "", "Domain(s) for the application")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080'")
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
cmd.Flags().String("limits-cpus", "", "CPU limit")
cmd.Flags().String("limits-memory", "", "Memory limit")
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
cmd.Flags().String("health-check-path", "", "Health check path")
return cmd
}
+127
View File
@@ -0,0 +1,127 @@
package create
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewDockerImageCommand returns the create dockerimage application command
func NewDockerImageCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "dockerimage",
Short: "Create an application from a pre-built Docker image",
Long: `Create a new application from a pre-built Docker image from a registry.
Examples:
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--docker-registry-image-name "nginx:latest" --ports-exposes 80
coolify app create dockerimage --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--docker-registry-image-name "ghcr.io/myorg/myapp" --docker-registry-image-tag "v1.0.0" \
--ports-exposes 3000 --domains "myapp.example.com" --instant-deploy`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get required flags
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
dockerRegistryImageName, _ := cmd.Flags().GetString("docker-registry-image-name")
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
// Validate required fields
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if dockerRegistryImageName == "" {
return fmt.Errorf("--docker-registry-image-name is required")
}
if portsExposes == "" {
return fmt.Errorf("--ports-exposes is required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ApplicationCreateDockerImageRequest{
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
DockerRegistryImageName: dockerRegistryImageName,
PortsExposes: portsExposes,
}
if environmentName != "" {
req.EnvironmentName = &environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Optional fields
setOptionalStringFlag(cmd, "name", &req.Name)
setOptionalStringFlag(cmd, "description", &req.Description)
setOptionalStringFlag(cmd, "domains", &req.Domains)
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
setOptionalStringFlag(cmd, "docker-registry-image-tag", &req.DockerRegistryImageTag)
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
appSvc := service.NewApplicationService(client)
app, err := appSvc.CreateDockerImage(ctx, req)
if err != nil {
return fmt.Errorf("failed to create application: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(app)
},
}
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
cmd.Flags().String("docker-registry-image-name", "", "Docker image name from registry (required)")
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '80' or '80,443' (required)")
// Optional flags
cmd.Flags().String("name", "", "Application name")
cmd.Flags().String("description", "", "Application description")
cmd.Flags().String("domains", "", "Domain(s) for the application")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().String("docker-registry-image-tag", "", "Docker image tag (defaults to 'latest')")
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
cmd.Flags().String("limits-cpus", "", "CPU limit")
cmd.Flags().String("limits-memory", "", "Memory limit")
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
cmd.Flags().String("health-check-path", "", "Health check path")
return cmd
}
+153
View File
@@ -0,0 +1,153 @@
package create
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewGitHubCommand returns the create github application command
func NewGitHubCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "github",
Short: "Create an application from a private repository using GitHub App",
Long: `Create a new application from a private git repository using GitHub App authentication.
Use 'coolify github list' to find your GitHub App UUID.
Use 'coolify github repos <app-uuid>' to list accessible repositories.
Examples:
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--github-app-uuid <uuid> --git-repository "owner/repo" --git-branch main \
--build-pack nixpacks --ports-exposes 3000
coolify app create github --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--github-app-uuid <uuid> --git-repository "owner/repo" --git-branch main \
--build-pack dockerfile --ports-exposes 8080 --instant-deploy`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get required flags
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
gitHubAppUUID, _ := cmd.Flags().GetString("github-app-uuid")
gitRepository, _ := cmd.Flags().GetString("git-repository")
gitBranch, _ := cmd.Flags().GetString("git-branch")
buildPack, _ := cmd.Flags().GetString("build-pack")
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
// Validate required fields
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if gitHubAppUUID == "" {
return fmt.Errorf("--github-app-uuid is required")
}
if gitRepository == "" || gitBranch == "" {
return fmt.Errorf("--git-repository and --git-branch are required")
}
if buildPack == "" || portsExposes == "" {
return fmt.Errorf("--build-pack and --ports-exposes are required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ApplicationCreateGitHubAppRequest{
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
GitHubAppUUID: gitHubAppUUID,
GitRepository: gitRepository,
GitBranch: gitBranch,
BuildPack: buildPack,
PortsExposes: portsExposes,
}
if environmentName != "" {
req.EnvironmentName = &environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Optional fields
setOptionalStringFlag(cmd, "name", &req.Name)
setOptionalStringFlag(cmd, "description", &req.Description)
setOptionalStringFlag(cmd, "domains", &req.Domains)
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
appSvc := service.NewApplicationService(client)
app, err := appSvc.CreateGitHubApp(ctx, req)
if err != nil {
return fmt.Errorf("failed to create application: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(app)
},
}
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
cmd.Flags().String("github-app-uuid", "", "GitHub App UUID (required)")
cmd.Flags().String("git-repository", "", "Git repository in format 'owner/repo' (required)")
cmd.Flags().String("git-branch", "", "Git branch (required)")
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
// Optional flags
cmd.Flags().String("name", "", "Application name")
cmd.Flags().String("description", "", "Application description")
cmd.Flags().String("domains", "", "Domain(s) for the application")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().String("build-command", "", "Custom build command")
cmd.Flags().String("start-command", "", "Custom start command")
cmd.Flags().String("install-command", "", "Custom install command")
cmd.Flags().String("base-directory", "", "Base directory for the application")
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
cmd.Flags().String("limits-cpus", "", "CPU limit")
cmd.Flags().String("limits-memory", "", "Memory limit")
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
cmd.Flags().String("health-check-path", "", "Health check path")
return cmd
}
+158
View File
@@ -0,0 +1,158 @@
package create
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewPublicCommand returns the create public application command
func NewPublicCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "public",
Short: "Create an application from a public git repository",
Long: `Create a new application from a public git repository.
Examples:
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--git-repository "https://github.com/user/repo" --git-branch main --build-pack nixpacks --ports-exposes 3000
coolify app create public --server-uuid <uuid> --project-uuid <uuid> --environment-name production \
--git-repository "https://github.com/user/repo" --git-branch main --build-pack dockerfile --ports-exposes 8080 \
--instant-deploy --domains "myapp.example.com"`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get required flags
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
gitRepository, _ := cmd.Flags().GetString("git-repository")
gitBranch, _ := cmd.Flags().GetString("git-branch")
buildPack, _ := cmd.Flags().GetString("build-pack")
portsExposes, _ := cmd.Flags().GetString("ports-exposes")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
// Validate required fields
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if gitRepository == "" || gitBranch == "" {
return fmt.Errorf("--git-repository and --git-branch are required")
}
if buildPack == "" || portsExposes == "" {
return fmt.Errorf("--build-pack and --ports-exposes are required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ApplicationCreatePublicRequest{
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
GitRepository: gitRepository,
GitBranch: gitBranch,
BuildPack: buildPack,
PortsExposes: portsExposes,
}
if environmentName != "" {
req.EnvironmentName = &environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Optional fields
setOptionalStringFlag(cmd, "name", &req.Name)
setOptionalStringFlag(cmd, "description", &req.Description)
setOptionalStringFlag(cmd, "domains", &req.Domains)
setOptionalStringFlag(cmd, "git-commit-sha", &req.GitCommitSHA)
setOptionalStringFlag(cmd, "destination-uuid", &req.DestinationUUID)
setOptionalStringFlag(cmd, "build-command", &req.BuildCommand)
setOptionalStringFlag(cmd, "start-command", &req.StartCommand)
setOptionalStringFlag(cmd, "install-command", &req.InstallCommand)
setOptionalStringFlag(cmd, "base-directory", &req.BaseDirectory)
setOptionalStringFlag(cmd, "publish-directory", &req.PublishDirectory)
setOptionalStringFlag(cmd, "ports-mappings", &req.PortsMappings)
setOptionalStringFlag(cmd, "limits-cpus", &req.LimitsCPUs)
setOptionalStringFlag(cmd, "limits-memory", &req.LimitsMemory)
setOptionalBoolFlag(cmd, "instant-deploy", &req.InstantDeploy)
setOptionalBoolFlag(cmd, "health-check-enabled", &req.HealthCheckEnabled)
setOptionalStringFlag(cmd, "health-check-path", &req.HealthCheckPath)
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
appSvc := service.NewApplicationService(client)
app, err := appSvc.CreatePublic(ctx, req)
if err != nil {
return fmt.Errorf("failed to create application: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(app)
},
}
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
cmd.Flags().String("git-repository", "", "Git repository URL (required)")
cmd.Flags().String("git-branch", "", "Git branch (required)")
cmd.Flags().String("build-pack", "", "Build pack: nixpacks, static, dockerfile, dockercompose (required)")
cmd.Flags().String("ports-exposes", "", "Exposed ports, e.g., '3000' or '3000,8080' (required)")
// Optional flags
cmd.Flags().String("name", "", "Application name")
cmd.Flags().String("description", "", "Application description")
cmd.Flags().String("domains", "", "Domain(s) for the application")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("git-commit-sha", "", "Specific commit SHA to deploy")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().String("build-command", "", "Custom build command")
cmd.Flags().String("start-command", "", "Custom start command")
cmd.Flags().String("install-command", "", "Custom install command")
cmd.Flags().String("base-directory", "", "Base directory for the application")
cmd.Flags().String("publish-directory", "", "Publish directory for static builds")
cmd.Flags().String("ports-mappings", "", "Port mappings (host:container)")
cmd.Flags().String("limits-cpus", "", "CPU limit")
cmd.Flags().String("limits-memory", "", "Memory limit")
cmd.Flags().Bool("health-check-enabled", false, "Enable health checks")
cmd.Flags().String("health-check-path", "", "Health check path")
return cmd
}
// Helper functions for optional flags
func setOptionalStringFlag(cmd *cobra.Command, flagName string, target **string) {
if cmd.Flags().Changed(flagName) {
val, _ := cmd.Flags().GetString(flagName)
*target = &val
}
}
func setOptionalBoolFlag(cmd *cobra.Command, flagName string, target **bool) {
if cmd.Flags().Changed(flagName) {
val, _ := cmd.Flags().GetBool(flagName)
*target = &val
}
}
+8 -4
View File
@@ -1,12 +1,12 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewDeleteCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete an application. This action cannot be undone.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
force, _ := cmd.Flags().GetBool("force")
@@ -24,7 +24,11 @@ func NewDeleteCommand() *cobra.Command {
if !force {
var response string
fmt.Printf("Are you sure you want to delete application %s? This cannot be undone. (yes/no): ", uuid)
fmt.Scanln(&response)
_, err := fmt.Scanln(&response)
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
if response != "yes" && response != "y" {
fmt.Println("Delete cancelled.")
+182
View File
@@ -0,0 +1,182 @@
package application
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
func NewDeploymentsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "deployments",
Short: "Deployment related commands for an application",
Long: `Manage deployments for a specific application. List deployments or view deployment logs.`,
}
cmd.AddCommand(NewListDeploymentsCommand())
cmd.AddCommand(NewLogsDeploymentsCommand())
return cmd
}
func NewListDeploymentsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "list <app-uuid>",
Short: "List all deployments for an application",
Long: `Retrieve a list of all deployments for a specific application.`,
Args: cli.ExactArgs(1, "<app-uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
deploySvc := service.NewDeploymentService(client)
deployments, err := deploySvc.ListByApplication(ctx, appUUID)
if err != nil {
return fmt.Errorf("failed to list deployments: %w", err)
}
format, _ := cmd.Flags().GetString("format")
formatter, err := output.NewFormatter(format, output.Options{})
if err != nil {
return err
}
return formatter.Format(deployments)
},
}
return cmd
}
func NewLogsDeploymentsCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "logs <app-uuid> [deployment-uuid]",
Short: "Get deployment logs for an application",
Long: `Retrieve deployment logs for a specific application or deployment.
If only app-uuid is provided, retrieves logs from the latest deployment.
If deployment-uuid is also provided, retrieves logs for that specific deployment.
Use --follow to continuously stream new logs.`,
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
appUUID := args[0]
var deploymentUUID string
if len(args) == 2 {
deploymentUUID = args[1]
}
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
lines, _ := cmd.Flags().GetInt("lines")
follow, _ := cmd.Flags().GetBool("follow")
debugLogs, _ := cmd.Flags().GetBool("debuglogs")
format, _ := cmd.Flags().GetString("format")
deploySvc := service.NewDeploymentService(client)
// Function to get logs based on whether we have a deployment UUID
// Returns raw or formatted based on format flag
getLogs := func() (string, error) {
if deploymentUUID != "" {
return deploySvc.GetLogsByDeploymentWithFormat(ctx, deploymentUUID, debugLogs, format)
}
// Get logs from the latest deployment
// Use take=1 internally to efficiently fetch only the most recent deployment
return deploySvc.GetLogsByApplicationWithFormat(ctx, appUUID, 1, debugLogs, format)
}
if !follow {
logs, err := getLogs()
if err != nil {
return fmt.Errorf("failed to get deployment logs: %w", err)
}
// Apply line limit if specified (only for text output)
if lines > 0 && format == "table" {
logs = limitLogLines(logs, lines)
}
fmt.Print(logs)
return nil
}
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
lastLogs := ""
logs, err := getLogs()
if err != nil {
return fmt.Errorf("failed to get deployment logs: %w", err)
}
fmt.Print(logs)
lastLogs = logs
for {
select {
case <-sigChan:
fmt.Println("\nStopping log follow...")
return nil
case <-ticker.C:
logs, err := getLogs()
if err != nil {
continue
}
if logs != lastLogs {
if len(logs) > len(lastLogs) && strings.HasPrefix(logs, lastLogs) {
fmt.Print(logs[len(lastLogs):])
} else {
fmt.Print(logs)
}
lastLogs = logs
}
}
}
},
}
cmd.Flags().IntP("lines", "n", 0, "Number of log lines to display (0 = all)")
cmd.Flags().BoolP("follow", "f", false, "Follow log output (like tail -f)")
cmd.Flags().Bool("debuglogs", false, "Show debug logs (includes hidden commands and internal operations)")
return cmd
}
// limitLogLines limits the output to the last N lines
func limitLogLines(logs string, n int) string {
if n <= 0 {
return logs
}
// Trim trailing newline to avoid empty element at the end
logs = strings.TrimRight(logs, "\n")
lines := strings.Split(logs, "\n")
// If we have fewer lines than requested, return all
if len(lines) <= n {
return logs + "\n"
}
// Get the last N lines
lastLines := lines[len(lines)-n:]
return strings.Join(lastLines, "\n") + "\n"
}
+12 -7
View File
@@ -1,13 +1,13 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewCreateEnvCommand() *cobra.Command {
@@ -15,10 +15,10 @@ func NewCreateEnvCommand() *cobra.Command {
Use: "create <app_uuid>",
Short: "Create an environment variable for an application",
Long: `Create a new environment variable for a specific application. Use --key and --value flags to specify the variable.`,
Args: cli.ExactArgs(1, "<uuid>"),
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
uuid := args[0]
ctx := cmd.Context()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
if err != nil {
@@ -31,6 +31,7 @@ func NewCreateEnvCommand() *cobra.Command {
isPreview, _ := cmd.Flags().GetBool("preview")
isLiteral, _ := cmd.Flags().GetBool("is-literal")
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
isRuntime, _ := cmd.Flags().GetBool("runtime")
if key == "" {
return fmt.Errorf("--key is required")
@@ -56,9 +57,12 @@ func NewCreateEnvCommand() *cobra.Command {
if cmd.Flags().Changed("is-multiline") {
req.IsMultiline = &isMultiline
}
if cmd.Flags().Changed("runtime") {
req.IsRuntime = &isRuntime
}
appSvc := service.NewApplicationService(client)
env, err := appSvc.CreateEnv(ctx, uuid, req)
env, err := appSvc.CreateEnv(ctx, appUUID, req)
if err != nil {
return fmt.Errorf("failed to create environment variable: %w", err)
}
@@ -71,9 +75,10 @@ func NewCreateEnvCommand() *cobra.Command {
cmd.Flags().String("key", "", "Environment variable key (required)")
cmd.Flags().String("value", "", "Environment variable value (required)")
cmd.Flags().Bool("build-time", false, "Available at build time")
cmd.Flags().Bool("build-time", true, "Available at build time (default: true)")
cmd.Flags().Bool("preview", false, "Available in preview deployments")
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
return cmd
}
+8 -4
View File
@@ -1,12 +1,12 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewDeleteEnvCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewDeleteEnvCommand() *cobra.Command {
Long: `Delete an environment variable from an application. First UUID is the application, second is the specific environment variable to delete.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
envUUID := args[1]
@@ -31,7 +31,11 @@ func NewDeleteEnvCommand() *cobra.Command {
if !force {
var response string
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
fmt.Scanln(&response)
_, err := fmt.Scanln(&response)
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
if response != "yes" && response != "y" {
fmt.Println("Delete cancelled.")
+11 -7
View File
@@ -1,25 +1,25 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewGetEnvCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "get <app_uuid> <env_uuid_or_key>",
Short: "Get environment variable details",
Long: `Get detailed information about a specific environment variable by UUID or key name.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
Args: cli.ExactArgs(2, "<app_uuid> <env_uuid_or_key>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
envUUID := args[1]
envUUIDOrKey := args[1]
client, err := cli.GetAPIClient(cmd)
if err != nil {
@@ -27,7 +27,9 @@ func NewGetEnvCommand() *cobra.Command {
}
appSvc := service.NewApplicationService(client)
env, err := appSvc.GetEnv(ctx, appUUID, envUUID)
// First try to get by the identifier directly
env, err := appSvc.GetEnv(ctx, appUUID, envUUIDOrKey)
if err != nil {
return fmt.Errorf("failed to get environment variable: %w", err)
}
@@ -53,4 +55,6 @@ func NewGetEnvCommand() *cobra.Command {
return formatter.Format(env)
},
}
return cmd
}
+35 -5
View File
@@ -1,23 +1,25 @@
package env
import (
"context"
"fmt"
"sort"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListEnvCommand() *cobra.Command {
return &cobra.Command{
cmd := &cobra.Command{
Use: "list <app_uuid>",
Short: "List all environment variables for an application",
Long: `List all environment variables for a specific application.`,
Long: `List all environment variables for a specific application. By default, only non-preview environment variables are shown. Use --preview to show preview environment variables instead, or --all to show all variables (non-preview first, then preview).`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -31,6 +33,29 @@ func NewListEnvCommand() *cobra.Command {
return fmt.Errorf("failed to list environment variables: %w", err)
}
// Filter by preview/all flags
showAll, _ := cmd.Flags().GetBool("all")
showPreview, _ := cmd.Flags().GetBool("preview")
if showAll {
// Sort: non-preview first, then preview
sort.SliceStable(envs, func(i, j int) bool {
if envs[i].IsPreview != envs[j].IsPreview {
return !envs[i].IsPreview // non-preview (false) comes before preview (true)
}
return false // maintain original order within groups
})
} else {
// Filter by preview flag
var filtered []models.EnvironmentVariable
for _, env := range envs {
if env.IsPreview == showPreview {
filtered = append(filtered, env)
}
}
envs = filtered
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
@@ -54,4 +79,9 @@ func NewListEnvCommand() *cobra.Command {
return formatter.Format(envs)
},
}
cmd.Flags().Bool("preview", false, "Show preview environment variables instead of regular ones")
cmd.Flags().Bool("all", false, "Show all environment variables (non-preview first, then preview)")
return cmd
}
+9 -4
View File
@@ -1,15 +1,15 @@
package env
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/parser"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewSyncEnvCommand() *cobra.Command {
@@ -24,7 +24,7 @@ func NewSyncEnvCommand() *cobra.Command {
Example: coolify app env sync abc123 --file .env.production`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -40,6 +40,7 @@ Example: coolify app env sync abc123 --file .env.production`,
isBuildTime, _ := cmd.Flags().GetBool("build-time")
isPreview, _ := cmd.Flags().GetBool("preview")
isLiteral, _ := cmd.Flags().GetBool("is-literal")
isRuntime, _ := cmd.Flags().GetBool("runtime")
// Parse the .env file
envVars, err := parser.ParseEnvFile(filePath)
@@ -87,6 +88,9 @@ Example: coolify app env sync abc123 --file .env.production`,
if cmd.Flags().Changed("is-literal") {
req.IsLiteral = &isLiteral
}
if cmd.Flags().Changed("runtime") {
req.IsRuntime = &isRuntime
}
// Auto-detect multiline values
if strings.Contains(envVar.Value, "\n") {
@@ -147,8 +151,9 @@ Example: coolify app env sync abc123 --file .env.production`,
}
syncEnvCmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
syncEnvCmd.Flags().Bool("build-time", false, "Make all variables available at build time")
syncEnvCmd.Flags().Bool("build-time", true, "Make all variables available at build time (default: true)")
syncEnvCmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
syncEnvCmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
syncEnvCmd.Flags().Bool("runtime", true, "Make all variables available at runtime (default: true)")
return syncEnvCmd
}
+22 -12
View File
@@ -1,35 +1,37 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewUpdateEnvCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <app_uuid> <env_uuid>",
Use: "update <app_uuid>",
Short: "Update an environment variable",
Long: `Update an existing environment variable. First UUID is the application, second is the specific environment variable to update.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
Long: `Update an existing environment variable. UUID is the application.`,
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
envUUID := args[1]
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
req := &models.EnvironmentVariableUpdateRequest{
UUID: envUUID,
// Check minimum version requirement
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.469"); err != nil {
return err
}
req := &models.EnvironmentVariableUpdateRequest{}
if cmd.Flags().Changed("key") {
key, _ := cmd.Flags().GetString("key")
req.Key = &key
@@ -54,9 +56,16 @@ func NewUpdateEnvCommand() *cobra.Command {
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
req.IsMultiline = &isMultiline
}
if cmd.Flags().Changed("runtime") {
isRuntime, _ := cmd.Flags().GetBool("runtime")
req.IsRuntime = &isRuntime
}
if req.Key == nil && req.Value == nil && req.IsBuildTime == nil && req.IsPreview == nil && req.IsLiteral == nil && req.IsMultiline == nil {
return fmt.Errorf("at least one field must be provided to update")
if req.Key == nil {
return fmt.Errorf("--key is required")
}
if req.Value == nil {
return fmt.Errorf("--value is required")
}
appSvc := service.NewApplicationService(client)
@@ -72,9 +81,10 @@ func NewUpdateEnvCommand() *cobra.Command {
cmd.Flags().String("key", "", "New environment variable key")
cmd.Flags().String("value", "", "New environment variable value")
cmd.Flags().Bool("build-time", false, "Available at build time")
cmd.Flags().Bool("build-time", true, "Available at build time (default: true)")
cmd.Flags().Bool("preview", false, "Available in preview deployments")
cmd.Flags().Bool("is-literal", false, "Treat value as literal")
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
return cmd
}
+3 -3
View File
@@ -1,13 +1,13 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewGetCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
Long: `Retrieve detailed information about a specific application.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,14 +1,14 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListCommand() *cobra.Command {
@@ -16,8 +16,8 @@ func NewListCommand() *cobra.Command {
Use: "list",
Short: "List all applications",
Long: `List all applications in Coolify.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+3 -3
View File
@@ -1,7 +1,6 @@
package application
import (
"context"
"fmt"
"os"
"os/signal"
@@ -9,9 +8,10 @@ import (
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewLogsCommand() *cobra.Command {
@@ -21,7 +21,7 @@ func NewLogsCommand() *cobra.Command {
Long: `Retrieve logs for an application. Use --follow to continuously stream new logs.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewRestartCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewRestartCommand() *cobra.Command {
Long: `Restart a running application.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewStartCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewStartCommand() *cobra.Command {
Long: `Start an application (initiates a deployment).`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewStopCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewStopCommand() *cobra.Command {
Long: `Stop a running application.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,14 +1,14 @@
package application
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewUpdateCommand() *cobra.Command {
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
Long: `Update configuration for a specific application. Only specified fields will be updated.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+2 -1
View File
@@ -4,8 +4,9 @@ import (
"fmt"
"os"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
)
func NewCompletionsCommand() *cobra.Command {
+3 -2
View File
@@ -3,8 +3,9 @@ package config
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/config"
)
// NewConfigCommand creates the config command
@@ -13,7 +14,7 @@ func NewConfigCommand() *cobra.Command {
Use: "config",
Short: "Show configuration file location",
Long: "Display the path to the Coolify CLI configuration file",
Run: func(cmd *cobra.Command, args []string) {
Run: func(_ *cobra.Command, _ []string) {
fmt.Println(config.Path())
},
}
+11 -7
View File
@@ -3,10 +3,11 @@ package context
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/config"
)
// NewAddCommand creates the add command
@@ -16,7 +17,7 @@ func NewAddCommand() *cobra.Command {
Example: `context add myserver https://coolify.example.com your-api-token`,
Args: cli.ExactArgs(3, "<context_name> <url> <token>"),
Short: "Add a new context",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
host := args[1]
token := args[2]
@@ -44,12 +45,14 @@ func NewAddCommand() *cobra.Command {
fmt.Printf("%s already exists. Force overwriting.\n", name)
}
viper.Set("instances", instances)
viper.WriteConfig()
return
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
return nil
}
fmt.Printf("%s already exists.\n", name)
fmt.Println("\nNote: Use --force to force overwrite.")
return
return nil
}
}
@@ -77,8 +80,9 @@ func NewAddCommand() *cobra.Command {
viper.Set("instances", instances)
if err := viper.WriteConfig(); err != nil {
fmt.Printf("failed to write config: %v\n", err)
return fmt.Errorf("failed to write config: %w", err)
}
return nil
},
}
+12 -7
View File
@@ -2,11 +2,12 @@ package context
import (
"fmt"
"slices"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"slices"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewDeleteCommand creates the delete command
@@ -17,7 +18,7 @@ func NewDeleteCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<context_name>"),
Short: "Delete a context",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(_ *cobra.Command, args []string) error {
Name := args[0]
instances := viper.Get("instances").([]interface{})
for i, instance := range instances {
@@ -25,13 +26,17 @@ func NewDeleteCommand() *cobra.Command {
if instanceMap["name"] == Name {
instances = slices.Delete(instances, i, i+1)
viper.Set("instances", instances)
viper.WriteConfig()
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
if instanceMap["default"] == true {
if len(instances) > 0 {
instances[0].(map[string]interface{})["default"] = true
viper.Set("instances", instances)
viper.WriteConfig()
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to write config: %w", err)
}
newDefaultName := instances[0].(map[string]interface{})["name"]
fmt.Printf("Context '%s' deleted. '%s' is now the default context.\n", Name, newDefaultName)
} else {
@@ -40,10 +45,10 @@ func NewDeleteCommand() *cobra.Command {
} else {
fmt.Printf("Context '%s' deleted.\n", Name)
}
return
return nil
}
}
fmt.Printf("Context '%s' not found.\n", Name)
return fmt.Errorf("context '%s' not found", Name)
},
}
}
+3 -2
View File
@@ -3,11 +3,12 @@ package context
import (
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// NewGetCommand creates the get command
+4 -3
View File
@@ -1,10 +1,11 @@
package context
import (
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/coollabsio/coolify-cli/internal/output"
)
// NewListCommand creates the list command
@@ -12,7 +13,7 @@ func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all configured contexts",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
// Get instances from viper (returns []interface{})
instancesRaw := viper.Get("instances")
if instancesRaw == nil {
+23 -11
View File
@@ -3,9 +3,10 @@ package context
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewSetTokenCommand creates the set-token command
@@ -18,13 +19,20 @@ func NewSetDefaultCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
name := args[0]
instances := viper.Get("instances").([]interface{})
raw := viper.Get("instances")
instances, ok := raw.([]interface{})
if !ok {
return fmt.Errorf("invalid instances configuration")
}
// Check if instance exists
var found bool
for _, instance := range instances {
instanceMap := instance.(map[string]interface{})
if instanceMap["name"] == name {
instanceMap, ok := instance.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid instance configuration")
}
if val, ok := instanceMap["name"].(string); ok && val == name {
found = true
instanceMap["default"] = true
}
@@ -32,13 +40,17 @@ func NewSetDefaultCommand() *cobra.Command {
if !found {
return fmt.Errorf("Context '%s' not found", name)
} else {
// Only unset other defaults if we found the target instance
for _, instance := range instances {
instanceMap := instance.(map[string]interface{})
if instanceMap["name"] != name {
instanceMap["default"] = false
}
}
// Only unset other defaults if we found the target instance
for _, instance := range instances {
instanceMap, ok := instance.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid instance configuration")
}
if val, ok := instanceMap["name"].(string); ok && val != name {
instanceMap["default"] = false
}
}
+8 -5
View File
@@ -3,9 +3,10 @@ package context
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewSetTokenCommand creates the set-token command
@@ -15,7 +16,7 @@ func NewSetTokenCommand() *cobra.Command {
Example: `context set-token myserver your-new-api-token`,
Args: cli.ExactArgs(2, "<context_name> <token>"),
Short: "Update the API token for a context",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(_ *cobra.Command, args []string) error {
name := args[0]
token := args[1]
var found interface{}
@@ -27,8 +28,7 @@ func NewSetTokenCommand() *cobra.Command {
}
}
if found == nil {
fmt.Printf("Context '%s' not found.\n", name)
return
return fmt.Errorf("context '%s' not found", name)
}
instances := viper.Get("instances").([]interface{})
for _, instance := range instances {
@@ -38,8 +38,11 @@ func NewSetTokenCommand() *cobra.Command {
}
}
viper.Set("instances", instances)
viper.WriteConfig()
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to update token for context '%s': %w", name, err)
}
fmt.Printf("Token updated for context '%s'.\n", name)
return nil
},
}
}
+9 -13
View File
@@ -3,9 +3,10 @@ package context
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewUpdateCommand creates the update command
@@ -15,7 +16,7 @@ func NewUpdateCommand() *cobra.Command {
Example: `context update myserver --name newname --url https://new.coolify.com --token newtoken`,
Args: cli.ExactArgs(1, "<context_name>"),
Short: "Update a context's properties (name, URL, token)",
Run: func(cmd *cobra.Command, args []string) {
RunE: func(cmd *cobra.Command, args []string) error {
oldName := args[0]
instances := viper.Get("instances").([]interface{})
@@ -26,9 +27,7 @@ func NewUpdateCommand() *cobra.Command {
// Check if at least one flag is provided
if newName == "" && newURL == "" && newToken == "" {
fmt.Println("Error: At least one of --name, --url, or --token must be provided")
fmt.Println("\nUsage: coolify context update <context_name> [--name <new_name>] [--url <new_url>] [--token <new_token>]")
return
return fmt.Errorf("at least one of --name, --url, or --token must be provided")
}
// Find the context
@@ -44,8 +43,7 @@ func NewUpdateCommand() *cobra.Command {
}
if !found {
fmt.Printf("Context '%s' not found.\n", oldName)
return
return fmt.Errorf("context '%s' not found", oldName)
}
// If renaming, check if new name already exists
@@ -53,8 +51,7 @@ func NewUpdateCommand() *cobra.Command {
for _, instance := range instances {
instanceMap := instance.(map[string]interface{})
if instanceMap["name"] == newName {
fmt.Printf("Error: Context with name '%s' already exists.\n", newName)
return
return fmt.Errorf("context with name '%s' already exists", newName)
}
}
contextToUpdate["name"] = newName
@@ -72,10 +69,8 @@ func NewUpdateCommand() *cobra.Command {
// Save changes
viper.Set("instances", instances)
err := viper.WriteConfig()
if err != nil {
fmt.Printf("Error saving config: %v\n", err)
return
if err := viper.WriteConfig(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
// Use the new name if renamed, otherwise use old name
@@ -84,6 +79,7 @@ func NewUpdateCommand() *cobra.Command {
finalName = newName
}
fmt.Printf("Context '%s' updated successfully.\n", finalName)
return nil
},
}
+19 -7
View File
@@ -3,9 +3,10 @@ package context
import (
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewUseCommand creates the use command
@@ -15,15 +16,22 @@ func NewUseCommand() *cobra.Command {
Example: `context use myserver`,
Args: cli.ExactArgs(1, "<context_name>"),
Short: "Switch to a different context (set as default)",
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(_ *cobra.Command, args []string) error {
name := args[0]
instances := viper.Get("instances").([]interface{})
raw := viper.Get("instances")
instances, ok := raw.([]interface{})
if !ok {
return fmt.Errorf("invalid instances configuration")
}
// Check if instance exists
var found bool
for _, instance := range instances {
instanceMap := instance.(map[string]interface{})
if instanceMap["name"] == name {
instanceMap, ok := instance.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid instance configuration")
}
if val, ok := instanceMap["name"].(string); ok && val == name {
found = true
break
}
@@ -35,8 +43,12 @@ func NewUseCommand() *cobra.Command {
// Update default
for _, instance := range instances {
instanceMap := instance.(map[string]interface{})
if instanceMap["name"] == name {
instanceMap, ok := instance.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid instance configuration")
}
if val, ok := instanceMap["name"].(string); ok && val == name {
instanceMap["default"] = true
} else {
delete(instanceMap, "default")
+4 -4
View File
@@ -1,11 +1,11 @@
package context
import (
"context"
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewVerifyCommand creates the verify command for contexts
@@ -15,8 +15,8 @@ func NewVerifyCommand() *cobra.Command {
Short: "Verify current context connection and authentication",
Long: `Verify that the current context is properly configured by testing the connection
to the Coolify instance and validating the API token.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get API client - this will use the current default context
client, err := cli.GetAPIClient(cmd)
+9 -8
View File
@@ -7,9 +7,10 @@ import (
"net/http/httptest"
"testing"
"github.com/coollabsio/coolify-cli/internal/api"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coollabsio/coolify-cli/internal/api"
)
// TestVerifyCommand_APIIntegration tests the verify logic using the API client directly
@@ -21,7 +22,7 @@ func TestVerifyCommand_APIIntegration(t *testing.T) {
assert.Equal(t, "/api/v1/version", r.URL.Path)
assert.Equal(t, "Bearer test-token", r.Header.Get("Authorization"))
w.WriteHeader(http.StatusOK)
w.Write([]byte("4.0.0-beta.383"))
_, _ = w.Write([]byte("4.0.0-beta.383"))
}))
defer server.Close()
@@ -36,9 +37,9 @@ func TestVerifyCommand_APIIntegration(t *testing.T) {
t.Run("unauthorized - invalid token", func(t *testing.T) {
// Create a test HTTP server that returns 401
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]string{
_ = json.NewEncoder(w).Encode(map[string]string{
"message": "Invalid token",
})
}))
@@ -55,9 +56,9 @@ func TestVerifyCommand_APIIntegration(t *testing.T) {
t.Run("server error", func(t *testing.T) {
// Create a test HTTP server that returns 500
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(map[string]string{
_ = json.NewEncoder(w).Encode(map[string]string{
"error": "Internal server error",
})
}))
@@ -76,9 +77,9 @@ func TestVerifyCommand_APIIntegration(t *testing.T) {
t.Run("not found", func(t *testing.T) {
// Create a test HTTP server that returns 404
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]string{
_ = json.NewEncoder(w).Encode(map[string]string{
"message": "Endpoint not found",
})
}))
+4 -4
View File
@@ -1,11 +1,11 @@
package context
import (
"context"
"fmt"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
)
// NewVersionCommand creates the version command for contexts
@@ -13,8 +13,8 @@ func NewVersionCommand() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Get current context's Coolify version",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,14 +1,14 @@
package backup
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewCreateCommand creates a new database
@@ -21,7 +21,7 @@ func NewCreateCommand() *cobra.Command {
Example: coolify database backup create abc123 --frequency "0 0 * * *" --enabled`,
Args: cli.ExactArgs(1, "<database_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -2,14 +2,14 @@ package backup
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewDeleteExecutionCommand lists all databases
@@ -20,7 +20,7 @@ func NewDeleteExecutionCommand() *cobra.Command {
Long: `Delete a specific backup execution and optionally from S3. First UUID is the database, second is the backup configuration, third is the specific execution.`,
Args: cli.ExactArgs(3, "<database_uuid> <backup_uuid> <execution_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
backupUUID := args[1]
executionUUID := args[2]
+3 -3
View File
@@ -2,14 +2,14 @@ package backup
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewDeleteCommand deletes a database
@@ -20,7 +20,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete a backup configuration and optionally all its executions from S3. First UUID is the database, second is the specific backup configuration.`,
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
backupUUID := args[1]
+3 -3
View File
@@ -1,13 +1,13 @@
package backup
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewExecutionCommand lists all databases
@@ -18,7 +18,7 @@ func NewExecutionCommand() *cobra.Command {
Long: `List all executions for a backup configuration. First UUID is the database, second is the specific backup configuration.`,
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
backupUUID := args[1]
+3 -3
View File
@@ -1,13 +1,13 @@
package backup
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand lists all databases
@@ -18,7 +18,7 @@ func NewListCommand() *cobra.Command {
Long: `List all backup configurations for a specific database.`,
Args: cli.ExactArgs(1, "<database_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,13 +1,13 @@
package backup
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewTriggerCommand triggers a database backup
@@ -18,7 +18,7 @@ func NewTriggerCommand() *cobra.Command {
Long: `Trigger an immediate backup for a specific backup configuration. First UUID is the database, second is the specific backup configuration to trigger.`,
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
backupUUID := args[1]
+3 -3
View File
@@ -1,13 +1,13 @@
package backup
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewUpdateCommand updates a database
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
Long: `Update a backup configuration settings (frequency, retention, S3, etc.). First UUID is the database, second is the specific backup configuration.`,
Args: cli.ExactArgs(2, "<database_uuid> <backup_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbUUID := args[0]
backupUUID := args[1]
+4 -4
View File
@@ -1,15 +1,15 @@
package database
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewCreateCommand() *cobra.Command {
@@ -25,7 +25,7 @@ Examples:
coolify databases create mysql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --name="My MySQL"`,
Args: cli.ExactArgs(1, "<type>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
dbType := args[0]
validTypes := []string{"postgresql", "mysql", "mariadb", "mongodb", "redis", "keydb", "clickhouse", "dragonfly"}
isValid := false
@@ -116,7 +116,7 @@ Examples:
}
if cmd.Flags().Changed("postgres-db") {
db, _ := cmd.Flags().GetString("postgres-db")
req.PostgresDb = &db
req.PostgresDB = &db
}
}
+3 -3
View File
@@ -2,14 +2,14 @@ package database
import (
"bufio"
"context"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewDeleteCommand deletes a database
@@ -20,7 +20,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete a database and optionally clean up its configurations, volumes, and networks.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
force, _ := cmd.Flags().GetBool("force")
+3 -3
View File
@@ -1,13 +1,13 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewGetCommand gets database details
@@ -18,7 +18,7 @@ func NewGetCommand() *cobra.Command {
Long: `Get detailed information about a specific database by UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,13 +1,13 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand lists all databases
@@ -16,8 +16,8 @@ func NewListCommand() *cobra.Command {
Use: "list",
Short: "List all databases",
Long: `List all databases in Coolify.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+3 -3
View File
@@ -1,12 +1,12 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewRestartCommand restarts a database
@@ -17,7 +17,7 @@ func NewRestartCommand() *cobra.Command {
Long: `Restart a database by UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewStartCommand starts a database
@@ -17,7 +17,7 @@ func NewStartCommand() *cobra.Command {
Long: `Start a database by UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewStopCommand stops a database
@@ -17,7 +17,7 @@ func NewStopCommand() *cobra.Command {
Long: `Stop a database by UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,13 +1,13 @@
package database
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewUpdateCommand updates a database
@@ -18,7 +18,7 @@ func NewUpdateCommand() *cobra.Command {
Long: `Update a database's configuration by UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
req := &models.DatabaseUpdateRequest{}
+3 -3
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewBatchCommand deploys multiple resources by name
@@ -20,7 +20,7 @@ Provide resource names as comma-separated values.
Example: coolify deploy batch app1,app2,app3`,
Args: cli.ExactArgs(1, "<names>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
namesStr := args[0]
client, err := cli.GetAPIClient(cmd)
+16 -7
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewCancelCommand cancels a deployment
@@ -18,7 +18,7 @@ func NewCancelCommand() *cobra.Command {
Long: `Cancel an in-progress deployment. This will stop the deployment process and clean up any temporary resources.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -31,13 +31,18 @@ func NewCancelCommand() *cobra.Command {
return err
}
force, _ := cmd.Flags().GetBool("force")
force, err := cmd.Flags().GetBool("force")
if err != nil {
return fmt.Errorf("failed to parse force flag: %w", err)
}
// Prompt for confirmation unless --force is used
if !force {
var response string
fmt.Printf("Are you sure you want to cancel deployment %s? (yes/no): ", uuid)
fmt.Scanln(&response)
var response string
if _, err := fmt.Scanln(&response); err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
if response != "yes" && response != "y" {
fmt.Println("Cancel aborted.")
@@ -51,7 +56,11 @@ func NewCancelCommand() *cobra.Command {
return fmt.Errorf("failed to cancel deployment: %w", err)
}
format, _ := cmd.Flags().GetString("format")
format, err := cmd.Flags().GetString("format")
if err != nil {
return fmt.Errorf("failed to get format flag: %w", err)
}
formatter, err := output.NewFormatter(format, output.Options{})
if err != nil {
return fmt.Errorf("failed to create formatter: %w", err)
+3 -3
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewGetCommand gets deployment details
@@ -18,7 +18,7 @@ func NewGetCommand() *cobra.Command {
Long: `Get detailed information about a specific deployment by its UUID.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand lists all deployments
@@ -16,8 +16,8 @@ func NewListCommand() *cobra.Command {
Use: "list",
Short: "List all deployments",
Long: `List all currently running deployments across all resources.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+3 -3
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewNameCommand deploys a resource by name
@@ -17,7 +17,7 @@ func NewNameCommand() *cobra.Command {
Short: "Deploy by resource name",
Args: cli.ExactArgs(1, "<resource_name>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
name := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,13 +1,13 @@
package deployment
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// ResultDisplay represents a deploy result for table display
@@ -23,7 +23,7 @@ func NewUUIDCommand() *cobra.Command {
Short: "Deploy by uuid",
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -23,11 +23,11 @@ var manCmd = &cobra.Command{
The man pages will be written to the specified directory (default: ./man).`,
Example: ` coolify docs man
coolify docs man --output-dir=/usr/local/share/man/man1`,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
outputDir, _ := cmd.Flags().GetString("output-dir")
// Create output directory if it doesn't exist
if err := os.MkdirAll(outputDir, 0755); err != nil {
if err := os.MkdirAll(outputDir, 0750); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
@@ -65,11 +65,11 @@ var markdownCmd = &cobra.Command{
The markdown files will be written to the specified directory (default: ./docs).`,
Example: ` coolify docs markdown
coolify docs markdown --output-dir=./documentation`,
RunE: func(cmd *cobra.Command, args []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
outputDir, _ := cmd.Flags().GetString("output-dir")
// Create output directory if it doesn't exist
if err := os.MkdirAll(outputDir, 0755); err != nil {
if err := os.MkdirAll(outputDir, 0750); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
+3 -3
View File
@@ -1,13 +1,13 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListBranchesCommand() *cobra.Command {
@@ -19,7 +19,7 @@ func NewListBranchesCommand() *cobra.Command {
Example: coolify github branches abc-123-def owner/repository`,
Args: cli.ExactArgs(2, "<app_uuid> <owner/repo>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
// Parse owner/repo
+12 -12
View File
@@ -1,14 +1,14 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewCreateCommand() *cobra.Command {
@@ -20,8 +20,8 @@ func NewCreateCommand() *cobra.Command {
Required flags: --name, --api-url, --html-url, --app-id, --installation-id, --client-id, --client-secret, --private-key-uuid
Example: coolify github create --name "My GitHub App" --api-url "https://api.github.com" --html-url "https://github.com" --app-id 123456 --installation-id 789012 --client-id "Iv1.abc123" --client-secret "secret123" --private-key-uuid "abc-123-def-456"`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
@@ -100,14 +100,14 @@ Example: coolify github create --name "My GitHub App" --api-url "https://api.git
createCmd.Flags().String("private-key-uuid", "", "UUID of existing private key (required)")
createCmd.Flags().Bool("system-wide", false, "Is this app system-wide (cloud only)")
createCmd.MarkFlagRequired("name")
createCmd.MarkFlagRequired("api-url")
createCmd.MarkFlagRequired("html-url")
createCmd.MarkFlagRequired("app-id")
createCmd.MarkFlagRequired("installation-id")
createCmd.MarkFlagRequired("client-id")
createCmd.MarkFlagRequired("client-secret")
createCmd.MarkFlagRequired("private-key-uuid")
_ = createCmd.MarkFlagRequired("name")
_ = createCmd.MarkFlagRequired("api-url")
_ = createCmd.MarkFlagRequired("html-url")
_ = createCmd.MarkFlagRequired("app-id")
_ = createCmd.MarkFlagRequired("installation-id")
_ = createCmd.MarkFlagRequired("client-id")
_ = createCmd.MarkFlagRequired("client-secret")
_ = createCmd.MarkFlagRequired("private-key-uuid")
return createCmd
}
+8 -4
View File
@@ -1,12 +1,12 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewDeleteCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete a GitHub App integration. The app must not be used by any applications.`,
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -30,7 +30,11 @@ func NewDeleteCommand() *cobra.Command {
if !force {
var response string
fmt.Printf("Are you sure you want to delete GitHub App %s? This cannot be undone. (yes/no): ", appUUID)
fmt.Scanln(&response)
_, err := fmt.Scanln(&response)
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
if response != "yes" && response != "y" {
fmt.Println("Delete cancelled.")
+3 -3
View File
@@ -1,13 +1,13 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewGetCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
Long: `Get detailed information about a specific GitHub App integration.`,
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,13 +1,13 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListCommand() *cobra.Command {
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
Use: "list",
Short: "List all GitHub App integrations",
Long: `List all GitHub App integrations configured in Coolify.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+3 -3
View File
@@ -1,13 +1,13 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListRepositoriesCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewListRepositoriesCommand() *cobra.Command {
Long: `List all repositories that are accessible by the specified GitHub App.`,
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,13 +1,13 @@
package github
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewUpdateCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewUpdateCommand() *cobra.Command {
Long: `Update an existing GitHub App integration. Provide the app UUID and the fields you want to update.`,
Args: cli.ExactArgs(1, "<app_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
appUUID := args[0]
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,14 +1,14 @@
package privatekeys
import (
"context"
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewCreateCommand creates the create command
@@ -19,7 +19,7 @@ func NewCreateCommand() *cobra.Command {
Args: cli.ExactArgs(2, "<key_name> <private_key_or_file>"),
Short: "Add a private key",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
name := args[0]
privateKeyInput := args[1]
+3 -3
View File
@@ -1,12 +1,12 @@
package privatekeys
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewDeleteCommand creates the delete command
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<uuid>"),
Short: "Remove a private key",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,13 +1,13 @@
package privatekeys
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand creates the list command
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all private keys",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+70
View File
@@ -0,0 +1,70 @@
package project
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// NewCreateCommand returns the create project command
func NewCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Create a new project",
Long: `Create a new project in Coolify.
Examples:
coolify project create --name "My Project"
coolify project create --name "My Project" --description "A description"`,
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
name, _ := cmd.Flags().GetString("name")
if name == "" {
return fmt.Errorf("--name is required")
}
req := &models.ProjectCreateRequest{
Name: name,
}
if cmd.Flags().Changed("description") {
desc, _ := cmd.Flags().GetString("description")
req.Description = &desc
}
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
projectSvc := service.NewProjectService(client)
project, err := projectSvc.Create(ctx, req)
if err != nil {
return fmt.Errorf("failed to create project: %w", err)
}
format, _ := cmd.Flags().GetString("format")
showSensitive, _ := cmd.Flags().GetBool("show-sensitive")
formatter, err := output.NewFormatter(format, output.Options{
ShowSensitive: showSensitive,
})
if err != nil {
return err
}
return formatter.Format(project)
},
}
cmd.Flags().String("name", "", "Project name (required)")
cmd.Flags().String("description", "", "Project description")
return cmd
}
+4 -4
View File
@@ -1,13 +1,13 @@
package project
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// EnvironmentRow represents an environment for display
@@ -23,7 +23,7 @@ func NewGetCommand() *cobra.Command {
Short: "Get a project by uuid",
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -55,7 +55,7 @@ func NewGetCommand() *cobra.Command {
var rows []EnvironmentRow
// If the project has environments, expand them
if project.Environments != nil && len(project.Environments) > 0 {
if len(project.Environments) > 0 {
for _, env := range project.Environments {
rows = append(rows, EnvironmentRow{
UUID: env.UUID,
+4 -4
View File
@@ -1,13 +1,13 @@
package project
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// ListRow represents a project for list display (without environments)
@@ -22,8 +22,8 @@ func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all projects",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+1
View File
@@ -14,6 +14,7 @@ func NewProjectCommand() *cobra.Command {
// Add all project subcommands
cmd.AddCommand(NewListCommand())
cmd.AddCommand(NewGetCommand())
cmd.AddCommand(NewCreateCommand())
return cmd
}
+4 -4
View File
@@ -1,13 +1,13 @@
package resources
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand returns the list projects command
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all resources",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
client, err := cli.GetAPIClient(cmd)
if err != nil {
+16 -32
View File
@@ -1,10 +1,13 @@
package cmd
import (
"errors"
"fmt"
"log"
"os"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/coollabsio/coolify-cli/cmd/application"
"github.com/coollabsio/coolify-cli/cmd/completion"
@@ -23,9 +26,6 @@ import (
cliversion "github.com/coollabsio/coolify-cli/cmd/version"
"github.com/coollabsio/coolify-cli/internal/config"
"github.com/coollabsio/coolify-cli/internal/version"
compareVersion "github.com/hashicorp/go-version"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// Legacy global variables - kept for backward compatibility during migration
@@ -39,10 +39,9 @@ var (
Debug bool
ShowSensitive bool
Format string
JsonMode bool
JSONMode bool
PrettyMode bool
SetDefaultInstance bool
w = tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug)
)
var rootCmd = &cobra.Command{
@@ -51,7 +50,7 @@ var rootCmd = &cobra.Command{
Long: `A CLI tool to interact with Coolify API.`,
SilenceUsage: true, // Don't show usage on errors
SilenceErrors: false, // Still print errors
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
return nil
},
}
@@ -60,7 +59,7 @@ var rootCmd = &cobra.Command{
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(0)
os.Exit(1)
}
}
@@ -68,10 +67,10 @@ func init() {
rootCmd = &cobra.Command{
Use: "coolify",
Short: "Coolify CLI",
Long: fmt.Sprintf("A CLI tool to interact with Coolify API.\nVersion: %s", version.CliVersion),
Long: fmt.Sprintf("A CLI tool to interact with Coolify API.\nVersion: %s", version.GetVersion()),
SilenceUsage: true, // Don't show usage on errors
SilenceErrors: false, // Still print errors
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
return nil
},
}
@@ -112,11 +111,14 @@ func initConfig() {
// Ensure config directory exists
configDir := config.Path()[:len(config.Path())-len("/config.json")]
if _, err := os.Stat(configDir); os.IsNotExist(err) {
os.MkdirAll(configDir, 0755)
if err := os.MkdirAll(configDir, 0750); err != nil {
log.Printf("Failed to create config directory: %v\n", err)
}
}
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
var notFoundErr viper.ConfigFileNotFoundError
if errors.As(err, &notFoundErr) {
log.Println("Config file not found. Creating a new one at", config.Path())
if err := config.CreateDefault(); err != nil {
log.Printf("Failed to create default config: %v\n", err)
@@ -141,24 +143,6 @@ func initConfig() {
// They are loaded on-demand by getAPIClient() based on --instance or default instance
// This allows --instance flag to work correctly
// Check for updates
latestVersionStr, err := version.CheckLatestVersionOfCli(Debug)
if err != nil {
if Debug {
log.Println("Failed to check for updates:", err)
}
}
// Compare versions properly using semantic versioning
if latestVersionStr != "" {
latestVersion, err := compareVersion.NewVersion(latestVersionStr)
if err == nil {
currentVersion, err := compareVersion.NewVersion(version.CliVersion)
if err == nil && latestVersion.GreaterThan(currentVersion) {
if Debug {
log.Printf("New version of Coolify CLI is available: %s\n", latestVersionStr)
}
}
}
}
// Check for updates (errors are handled silently inside the function)
_, _ = version.CheckLatestVersionOfCli(Debug)
}
+5 -5
View File
@@ -1,13 +1,13 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewAddCommand creates the add command
@@ -17,7 +17,7 @@ func NewAddCommand() *cobra.Command {
Args: cli.ExactArgs(3, "<server_name> <ip_address> <private_key_uuid>"),
Short: "Add a server",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
@@ -28,7 +28,7 @@ func NewAddCommand() *cobra.Command {
// Parse arguments and flags
name := args[0]
ip := args[1]
privateKeyUuid := args[2]
privateKeyUUID := args[2]
port, _ := cmd.Flags().GetInt("port")
user, _ := cmd.Flags().GetString("user")
validate, _ := cmd.Flags().GetBool("validate")
@@ -39,7 +39,7 @@ func NewAddCommand() *cobra.Command {
IP: ip,
Port: port,
User: user,
PrivateKeyUUID: privateKeyUuid,
PrivateKeyUUID: privateKeyUUID,
InstantValidate: validate,
}
+3 -3
View File
@@ -1,13 +1,13 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewGetCommand creates the get command
@@ -18,7 +18,7 @@ func NewGetDomainsCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<uuid>"),
Short: "Get server domains by uuid",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,13 +1,13 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewGetCommand creates the get command
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<uuid>"),
Short: "Get server details by uuid",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+4 -4
View File
@@ -1,13 +1,13 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewListCommand creates the list command
@@ -15,8 +15,8 @@ func NewListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List all servers",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewRemoveCommand creates the remove command
@@ -16,7 +16,7 @@ func NewRemoveCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<uuid>"),
Short: "Remove a server",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+3 -3
View File
@@ -1,12 +1,12 @@
package server
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewValidateCommand creates the validate command
@@ -16,7 +16,7 @@ func NewValidateCommand() *cobra.Command {
Args: cli.ExactArgs(1, "<uuid>"),
Short: "Validate a server",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
// Get API client
client, err := cli.GetAPIClient(cmd)
+252
View File
@@ -0,0 +1,252 @@
package service
import (
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
)
// validServiceTypes contains all supported one-click service types
var validServiceTypes = []string{
"activepieces",
"appsmith",
"appwrite",
"authentik",
"babybuddy",
"budge",
"changedetection",
"chatwoot",
"classicpress-with-mariadb",
"classicpress-with-mysql",
"classicpress-without-database",
"cloudflared",
"code-server",
"dashboard",
"directus",
"directus-with-postgresql",
"docker-registry",
"docuseal",
"docuseal-with-postgres",
"dokuwiki",
"duplicati",
"emby",
"embystat",
"fider",
"filebrowser",
"firefly",
"formbricks",
"ghost",
"gitea",
"gitea-with-mariadb",
"gitea-with-mysql",
"gitea-with-postgresql",
"glance",
"glances",
"glitchtip",
"grafana",
"grafana-with-postgresql",
"grocy",
"heimdall",
"homepage",
"jellyfin",
"kuzzle",
"listmonk",
"logto",
"mediawiki",
"meilisearch",
"metabase",
"metube",
"minio",
"moodle",
"n8n",
"n8n-with-postgresql",
"next-image-transformation",
"nextcloud",
"nocodb",
"odoo",
"openblocks",
"pairdrop",
"penpot",
"phpmyadmin",
"pocketbase",
"posthog",
"reactive-resume",
"rocketchat",
"shlink",
"slash",
"snapdrop",
"statusnook",
"stirling-pdf",
"supabase",
"syncthing",
"tolgee",
"trigger",
"trigger-with-external-database",
"twenty",
"umami",
"unleash-with-postgresql",
"unleash-without-database",
"uptime-kuma",
"vaultwarden",
"vikunja",
"weblate",
"whoogle",
"wordpress-with-mariadb",
"wordpress-with-mysql",
"wordpress-without-database",
}
func NewCreateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "create <type>",
Short: "Create a new one-click service",
Long: `Create a new one-click service of the specified type.
Use 'coolify service create --list-types' to see all available service types.
Examples:
coolify service create wordpress-with-mysql --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production
coolify service create ghost --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --name="My Blog"
coolify service create n8n --server-uuid=<uuid> --project-uuid=<uuid> --environment-name=production --instant-deploy
Popular service types:
- wordpress-with-mysql, wordpress-with-mariadb, wordpress-without-database
- ghost, plausible, umami, uptime-kuma
- n8n, n8n-with-postgresql
- nextcloud, gitea, minio
- grafana, metabase, nocodb
- supabase, pocketbase, appwrite`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
// Handle --list-types flag
listTypes, _ := cmd.Flags().GetBool("list-types")
if listTypes {
fmt.Println("Available one-click service types:")
fmt.Println()
for _, t := range validServiceTypes {
fmt.Printf(" %s\n", t)
}
return nil
}
// Require type argument if not listing
if len(args) == 0 {
return fmt.Errorf("service type is required. Use --list-types to see available types")
}
serviceType := args[0]
// Validate service type
isValid := false
for _, t := range validServiceTypes {
if t == serviceType {
isValid = true
break
}
}
if !isValid {
return fmt.Errorf("invalid service type '%s'. Use --list-types to see available types", serviceType)
}
serverUUID, _ := cmd.Flags().GetString("server-uuid")
projectUUID, _ := cmd.Flags().GetString("project-uuid")
environmentName, _ := cmd.Flags().GetString("environment-name")
environmentUUID, _ := cmd.Flags().GetString("environment-uuid")
if serverUUID == "" || projectUUID == "" {
return fmt.Errorf("--server-uuid and --project-uuid are required")
}
if environmentName == "" && environmentUUID == "" {
return fmt.Errorf("either --environment-name or --environment-uuid must be provided")
}
req := &models.ServiceCreateRequest{
Type: serviceType,
ServerUUID: serverUUID,
ProjectUUID: projectUUID,
}
if environmentName != "" {
req.EnvironmentName = environmentName
}
if environmentUUID != "" {
req.EnvironmentUUID = &environmentUUID
}
// Handle optional flags
if cmd.Flags().Changed("name") {
name, _ := cmd.Flags().GetString("name")
req.Name = &name
}
if cmd.Flags().Changed("description") {
desc, _ := cmd.Flags().GetString("description")
req.Description = &desc
}
if cmd.Flags().Changed("destination-uuid") {
dest, _ := cmd.Flags().GetString("destination-uuid")
req.Destination = &dest
}
if cmd.Flags().Changed("instant-deploy") {
instant, _ := cmd.Flags().GetBool("instant-deploy")
req.InstantDeploy = &instant
}
if cmd.Flags().Changed("docker-compose") {
compose, _ := cmd.Flags().GetString("docker-compose")
req.DockerCompose = &compose
}
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
svc := service.NewService(client)
result, err := svc.Create(ctx, req)
if err != nil {
return fmt.Errorf("failed to create service: %w", err)
}
format, _ := cmd.Flags().GetString("format")
formatter, err := output.NewFormatter(format, output.Options{})
if err != nil {
return fmt.Errorf("failed to create formatter: %w", err)
}
return formatter.Format(result)
},
}
// List types flag
cmd.Flags().Bool("list-types", false, "List all available service types")
// Required flags
cmd.Flags().String("server-uuid", "", "Server UUID (required)")
cmd.Flags().String("project-uuid", "", "Project UUID (required)")
cmd.Flags().String("environment-name", "", "Environment name")
cmd.Flags().String("environment-uuid", "", "Environment UUID")
// Optional flags
cmd.Flags().String("name", "", "Service name")
cmd.Flags().String("description", "", "Service description")
cmd.Flags().String("destination-uuid", "", "Destination UUID if server has multiple destinations")
cmd.Flags().Bool("instant-deploy", false, "Deploy immediately after creation")
cmd.Flags().String("docker-compose", "", "Custom Docker Compose content (for advanced customization)")
// Add completion for service type positional argument
cmd.ValidArgsFunction = func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
return validServiceTypes, cobra.ShellCompDirectiveNoFileComp
}
return nil, cobra.ShellCompDirectiveNoFileComp
}
return cmd
}
+9 -5
View File
@@ -1,12 +1,12 @@
package service
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewDeleteCommand deletes a service
@@ -17,7 +17,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete a service and optionally clean up its configurations, volumes, and networks.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -35,7 +35,11 @@ func NewDeleteCommand() *cobra.Command {
if !force {
var response string
fmt.Printf("Are you sure you want to delete this service? (yes/no): ")
fmt.Scanln(&response)
_, err := fmt.Scanln(&response)
if err != nil {
return fmt.Errorf("failed to read confirmation: %w", err)
}
if response != "yes" && response != "y" {
fmt.Println("Delete cancelled.")
@@ -43,7 +47,7 @@ func NewDeleteCommand() *cobra.Command {
}
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
err = serviceSvc.Delete(ctx, uuid, deleteConfigurations, deleteVolumes, dockerCleanup, deleteConnectedNetworks)
if err != nil {
return fmt.Errorf("failed to delete service: %w", err)
+11 -11
View File
@@ -1,13 +1,13 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewCreateCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewCreateCommand() *cobra.Command {
Long: `Create a new environment variable for a specific service. Use --key and --value flags to specify the variable.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -28,9 +28,9 @@ func NewCreateCommand() *cobra.Command {
key, _ := cmd.Flags().GetString("key")
value, _ := cmd.Flags().GetString("value")
isBuildTime, _ := cmd.Flags().GetBool("build-time")
isPreview, _ := cmd.Flags().GetBool("preview")
isLiteral, _ := cmd.Flags().GetBool("is-literal")
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
isRuntime, _ := cmd.Flags().GetBool("runtime")
if key == "" {
return fmt.Errorf("--key is required")
@@ -39,7 +39,7 @@ func NewCreateCommand() *cobra.Command {
return fmt.Errorf("--value is required")
}
req := &models.EnvironmentVariableCreateRequest{
req := &models.ServiceEnvironmentVariableCreateRequest{
Key: key,
Value: value,
}
@@ -48,17 +48,17 @@ func NewCreateCommand() *cobra.Command {
if cmd.Flags().Changed("build-time") {
req.IsBuildTime = &isBuildTime
}
if cmd.Flags().Changed("preview") {
req.IsPreview = &isPreview
}
if cmd.Flags().Changed("is-literal") {
req.IsLiteral = &isLiteral
}
if cmd.Flags().Changed("is-multiline") {
req.IsMultiline = &isMultiline
}
if cmd.Flags().Changed("runtime") {
req.IsRuntime = &isRuntime
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
env, err := serviceSvc.CreateEnv(ctx, uuid, req)
if err != nil {
return fmt.Errorf("failed to create environment variable: %w", err)
@@ -71,10 +71,10 @@ func NewCreateCommand() *cobra.Command {
cmd.Flags().String("key", "", "Environment variable key (required)")
cmd.Flags().String("value", "", "Environment variable value (required)")
cmd.Flags().Bool("build-time", false, "Available at build time")
cmd.Flags().Bool("preview", false, "Available in preview deployments")
cmd.Flags().Bool("build-time", true, "Available at build time (default: true)")
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
return cmd
}
+5 -5
View File
@@ -1,12 +1,12 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewDeleteCommand() *cobra.Command {
@@ -16,7 +16,7 @@ func NewDeleteCommand() *cobra.Command {
Long: `Delete an environment variable from a service. First UUID is the service, second is the specific environment variable to delete.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
serviceUUID := args[0]
envUUID := args[1]
@@ -31,7 +31,7 @@ func NewDeleteCommand() *cobra.Command {
if !force {
var response string
fmt.Printf("Are you sure you want to delete this environment variable? (yes/no): ")
fmt.Scanln(&response)
_, _ = fmt.Scanln(&response)
if response != "yes" && response != "y" {
fmt.Println("Delete cancelled.")
@@ -39,7 +39,7 @@ func NewDeleteCommand() *cobra.Command {
}
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
err = serviceSvc.DeleteEnv(ctx, serviceUUID, envUUID)
if err != nil {
return fmt.Errorf("failed to delete environment variable: %w", err)
+4 -4
View File
@@ -1,13 +1,13 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewGetCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewGetCommand() *cobra.Command {
Long: `Get detailed information about a specific environment variable. First UUID is the service, second is the environment variable UUID or key name.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
serviceUUID := args[0]
envUUID := args[1]
@@ -26,7 +26,7 @@ func NewGetCommand() *cobra.Command {
return fmt.Errorf("failed to get API client: %w", err)
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
env, err := serviceSvc.GetEnv(ctx, serviceUUID, envUUID)
if err != nil {
return fmt.Errorf("failed to get environment variable: %w", err)
+4 -4
View File
@@ -1,13 +1,13 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewListCommand() *cobra.Command {
@@ -17,7 +17,7 @@ func NewListCommand() *cobra.Command {
Long: `List all environment variables for a specific service.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -25,7 +25,7 @@ func NewListCommand() *cobra.Command {
return fmt.Errorf("failed to get API client: %w", err)
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
envs, err := serviceSvc.ListEnvs(ctx, uuid)
if err != nil {
return fmt.Errorf("failed to list environment variables: %w", err)
+15 -15
View File
@@ -1,15 +1,15 @@
package env
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/parser"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewSyncCommand() *cobra.Command {
@@ -24,7 +24,7 @@ func NewSyncCommand() *cobra.Command {
Example: coolify service env sync abc123 --file .env.production`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -38,8 +38,8 @@ Example: coolify service env sync abc123 --file .env.production`,
}
isBuildTime, _ := cmd.Flags().GetBool("build-time")
isPreview, _ := cmd.Flags().GetBool("preview")
isLiteral, _ := cmd.Flags().GetBool("is-literal")
isRuntime, _ := cmd.Flags().GetBool("runtime")
// Parse the .env file
envVars, err := parser.ParseEnvFile(filePath)
@@ -55,24 +55,24 @@ Example: coolify service env sync abc123 --file .env.production`,
fmt.Printf("Found %d environment variables in file. Syncing...\n", len(envVars))
// Fetch existing environment variables
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
existingEnvs, err := serviceSvc.ListEnvs(ctx, uuid)
if err != nil {
return fmt.Errorf("failed to list existing environment variables: %w", err)
}
// Build a map of existing env vars by key
existingMap := make(map[string]models.EnvironmentVariable)
existingMap := make(map[string]models.ServiceEnvironmentVariable)
for _, env := range existingEnvs {
existingMap[env.Key] = env
}
// Separate into updates and creates
var toUpdate []models.EnvironmentVariableCreateRequest
var toCreate []models.EnvironmentVariableCreateRequest
var toUpdate []models.ServiceEnvironmentVariableCreateRequest
var toCreate []models.ServiceEnvironmentVariableCreateRequest
for _, envVar := range envVars {
req := models.EnvironmentVariableCreateRequest{
req := models.ServiceEnvironmentVariableCreateRequest{
Key: envVar.Key,
Value: envVar.Value,
}
@@ -81,12 +81,12 @@ Example: coolify service env sync abc123 --file .env.production`,
if cmd.Flags().Changed("build-time") {
req.IsBuildTime = &isBuildTime
}
if cmd.Flags().Changed("preview") {
req.IsPreview = &isPreview
}
if cmd.Flags().Changed("is-literal") {
req.IsLiteral = &isLiteral
}
if cmd.Flags().Changed("runtime") {
req.IsRuntime = &isRuntime
}
// Auto-detect multiline values
if strings.Contains(envVar.Value, "\n") {
@@ -108,7 +108,7 @@ Example: coolify service env sync abc123 --file .env.production`,
// Perform bulk update if there are vars to update
if len(toUpdate) > 0 {
fmt.Printf("Updating %d existing variables...\n", len(toUpdate))
bulkReq := &service.BulkUpdateEnvsRequest{
bulkReq := &models.ServiceEnvBulkUpdateRequest{
Data: toUpdate,
}
_, err := serviceSvc.BulkUpdateEnvs(ctx, uuid, bulkReq)
@@ -147,9 +147,9 @@ Example: coolify service env sync abc123 --file .env.production`,
}
cmd.Flags().StringP("file", "f", "", "Path to .env file (required)")
cmd.Flags().Bool("build-time", false, "Make all variables available at build time")
cmd.Flags().Bool("preview", false, "Make all variables available in preview deployments")
cmd.Flags().Bool("build-time", true, "Make all variables available at build time (default: true)")
cmd.Flags().Bool("is-literal", false, "Treat all values as literal (don't interpolate variables)")
cmd.Flags().Bool("runtime", true, "Make all variables available at runtime (default: true)")
return cmd
}
+24 -20
View File
@@ -1,35 +1,37 @@
package env
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/models"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
func NewUpdateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <service_uuid> <env_uuid>",
Use: "update <service_uuid>",
Short: "Update an environment variable",
Long: `Update an existing environment variable. First UUID is the service, second is the specific environment variable to update.`,
Args: cli.ExactArgs(2, "<uuid1> <uuid2>"),
Long: `Update an existing environment variable. UUID is the service.`,
Args: cli.ExactArgs(1, "<service_uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
serviceUUID := args[0]
envUUID := args[1]
client, err := cli.GetAPIClient(cmd)
if err != nil {
return fmt.Errorf("failed to get API client: %w", err)
}
req := &models.EnvironmentVariableUpdateRequest{
UUID: envUUID,
// Check minimum version requirement
if err := cli.CheckMinimumVersion(ctx, client, "4.0.0-beta.469"); err != nil {
return err
}
req := &models.ServiceEnvironmentVariableUpdateRequest{}
// Only set fields that were provided
if cmd.Flags().Changed("key") {
key, _ := cmd.Flags().GetString("key")
@@ -43,10 +45,6 @@ func NewUpdateCommand() *cobra.Command {
isBuildTime, _ := cmd.Flags().GetBool("build-time")
req.IsBuildTime = &isBuildTime
}
if cmd.Flags().Changed("preview") {
isPreview, _ := cmd.Flags().GetBool("preview")
req.IsPreview = &isPreview
}
if cmd.Flags().Changed("is-literal") {
isLiteral, _ := cmd.Flags().GetBool("is-literal")
req.IsLiteral = &isLiteral
@@ -55,13 +53,19 @@ func NewUpdateCommand() *cobra.Command {
isMultiline, _ := cmd.Flags().GetBool("is-multiline")
req.IsMultiline = &isMultiline
}
// Check if at least one field is being updated
if req.Key == nil && req.Value == nil && req.IsBuildTime == nil && req.IsPreview == nil && req.IsLiteral == nil && req.IsMultiline == nil {
return fmt.Errorf("at least one field must be provided to update (--key, --value, --build-time, --preview, --is-literal, or --is-multiline)")
if cmd.Flags().Changed("runtime") {
isRuntime, _ := cmd.Flags().GetBool("runtime")
req.IsRuntime = &isRuntime
}
serviceSvc := service.NewServiceService(client)
if req.Key == nil {
return fmt.Errorf("--key is required")
}
if req.Value == nil {
return fmt.Errorf("--value is required")
}
serviceSvc := service.NewService(client)
env, err := serviceSvc.UpdateEnv(ctx, serviceUUID, req)
if err != nil {
return fmt.Errorf("failed to update environment variable: %w", err)
@@ -74,10 +78,10 @@ func NewUpdateCommand() *cobra.Command {
cmd.Flags().String("key", "", "New environment variable key")
cmd.Flags().String("value", "", "New environment variable value")
cmd.Flags().Bool("build-time", false, "Available at build time")
cmd.Flags().Bool("preview", false, "Available in preview deployments")
cmd.Flags().Bool("build-time", true, "Available at build time (default: true)")
cmd.Flags().Bool("is-literal", false, "Treat value as literal (don't interpolate variables)")
cmd.Flags().Bool("is-multiline", false, "Value is multiline")
cmd.Flags().Bool("runtime", true, "Available at runtime (default: true)")
return cmd
}
+4 -4
View File
@@ -1,13 +1,13 @@
package service
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/coollabsio/coolify-cli/internal/cli"
"github.com/coollabsio/coolify-cli/internal/output"
"github.com/coollabsio/coolify-cli/internal/service"
"github.com/spf13/cobra"
)
// NewGetCommand gets service details
@@ -18,7 +18,7 @@ func NewGetCommand() *cobra.Command {
Long: `Get detailed information about a specific service.`,
Args: cli.ExactArgs(1, "<uuid>"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
uuid := args[0]
client, err := cli.GetAPIClient(cmd)
@@ -26,7 +26,7 @@ func NewGetCommand() *cobra.Command {
return fmt.Errorf("failed to get API client: %w", err)
}
serviceSvc := service.NewServiceService(client)
serviceSvc := service.NewService(client)
svc, err := serviceSvc.Get(ctx, uuid)
if err != nil {
return fmt.Errorf("failed to get service: %w", err)

Some files were not shown because too many files have changed in this diff Show More