Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
EASYPANEL_ENDPOINT=https://panel.example.com
EASYPANEL_API_KEY=your-easypanel-api-key
# Optional Go duration. Deploy calls can block while images pull (default: 5m).
EASYPANEL_HTTP_TIMEOUT=5m
GRPC_PORT=50051
GRPC_AUTH_TOKENS=client-token-1,client-token-2
DEFAULT_DOMAIN=xyz.easypanel.host
Expand Down
28 changes: 20 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ cp .env.example .env
|----------|----------|-------------|
| `EASYPANEL_ENDPOINT` | Yes | Easypanel panel URL (e.g. `https://panel.example.com`) |
| `EASYPANEL_API_KEY` | Yes | Easypanel API key |
| `EASYPANEL_HTTP_TIMEOUT` | No | Panel request timeout as Go duration (default `5m`; deploy calls block while images pull) |
| `GRPC_PORT` | No | gRPC listen port (default: `50051`) |
| `GRPC_AUTH_TOKENS` | Yes* | Comma-separated bearer tokens for gRPC clients |
| `DEFAULT_DOMAIN` | Yes* | Easypanel wildcard domain (e.g. `xyz.easypanel.host`) |
Expand Down Expand Up @@ -105,12 +106,20 @@ export EASYPANEL_ENDPOINT EASYPANEL_API_KEY
./deploy-everything services restart <project> <service> <type>
./deploy-everything services inspect <project> <service> <type>

# Environment
./deploy-everything services set-env <project> <service> <type> "KEY1=val1\nKEY2=val2"
# Environment (saves config, then automatically redeploys app/compose)
./deploy-everything services set-env <project> <service> <type> "KEY1=val1" "KEY2=val2"

# Compose
./deploy-everything services compose-set <project> <service> <docker-compose.yml>
./deploy-everything services compose-deploy <project> <service>
# App sources (all save, then automatically redeploy)
./deploy-everything services set-git <project> <service> <repo> <ref> [--path /]
./deploy-everything services set-github <project> <service> <owner> <repo> <ref> [--path /]
./deploy-everything services set-dockerfile <project> <service> '<Dockerfile content>'

# Compose sources (save, then automatically redeploy)
./deploy-everything services set-source-inline <project> <service> docker-compose.yml '<compose content>'
./deploy-everything services set-source-git-compose <project> <service> <repo> <ref> docker-compose.yml [--root-path /]

# Logs
./deploy-everything services logs <project> <service> [--type app|compose] [--compose-service web] [--limit 200]

# WordPress
./deploy-everything wordpress create <project> <service>
Expand All @@ -121,8 +130,11 @@ export EASYPANEL_ENDPOINT EASYPANEL_API_KEY

```bash
./deploy-everything domains list <project> <service>
./deploy-everything domains create <project> <service> <host> [--port 80] [--https]
./deploy-everything domains delete <id>
./deploy-everything domains create <project> <service> <host> [--type app|compose] [--compose-service web] [--port 80] [--https]
./deploy-everything domains delete <project> <service> <type> <domain-id>

# Domain create/delete automatically redeploys the target app/compose service.
# Easypanel applies domain routing only while generating deployment config.
```

### Other Commands
Expand Down Expand Up @@ -176,7 +188,7 @@ Tokens are configured via `GRPC_AUTH_TOKENS` (comma-separated for multi-tenant).
| `RestartService` | Restart service |
| `StopService` | Stop service |
| `StartService` | Start service |
| `GetLogs` | Fetch service logs |
| `GetLogs` | Fetch app/compose logs with structured entries and flat text |
| `ScaleService` | Set replica count |
| `UpdateResources` | Set CPU/memory limits |

Expand Down
86 changes: 66 additions & 20 deletions cmd/domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ func init() {
domainsCmd.AddCommand(domainsListCmd)
domainsCmd.AddCommand(domainsCreateCmd)
domainsCmd.AddCommand(domainsDeleteCmd)

domainsCreateCmd.Flags().Int("port", 80, "Container port to route to")
domainsCreateCmd.Flags().Bool("https", true, "Serve over HTTPS")
domainsCreateCmd.Flags().Bool("wildcard", false, "Treat host as a wildcard domain")
domainsCreateCmd.Flags().String("path", "/", "Request path to route")
domainsCreateCmd.Flags().String("certificate-resolver", "letsencrypt", "Traefik certificate resolver")
domainsCreateCmd.Flags().String("compose-service", "", "Route to an internal compose service")
domainsCreateCmd.Flags().String("type", "app", "Service type to redeploy: app or compose")
}

var domainsListCmd = &cobra.Command{
Expand Down Expand Up @@ -59,47 +67,85 @@ var domainsListCmd = &cobra.Command{

var domainsCreateCmd = &cobra.Command{
Use: "create [project] [service] [host]",
Short: "Create domain pointing to service (port 80)",
Short: "Create domain pointing to service",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
c := newPanelClient()
port, _ := cmd.Flags().GetInt("port")
https, _ := cmd.Flags().GetBool("https")
wildcard, _ := cmd.Flags().GetBool("wildcard")
path, _ := cmd.Flags().GetString("path")
resolver, _ := cmd.Flags().GetString("certificate-resolver")
composeService, _ := cmd.Flags().GetString("compose-service")
svcType, _ := cmd.Flags().GetString("type")
if composeService != "" {
svcType = "compose"
}
if svcType != "app" && svcType != "compose" {
return fmt.Errorf("domain redeploy type must be app or compose, got %q", svcType)
}

destination := map[string]any{
"protocol": "http",
"port": port,
"path": path,
"projectName": args[0],
"serviceName": args[1],
}
if composeService != "" {
destination["composeService"] = composeService
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

input := map[string]any{
"id": args[2],
"https": true,
"https": https,
"host": args[2],
"path": "/",
"path": path,
"middlewares": []string{},
"certificateResolver": "letsencrypt",
"destinationType": "service",
"serviceDestination": map[string]any{
"protocol": "http",
"port": 80,
"path": "/",
"projectName": args[0],
"serviceName": args[1],
},
"certificateResolver": resolver,
// Panel schema requires wildcard; omitting it fails validation.
"wildcard": wildcard,
"destinationType": "service",
"serviceDestination": destination,
}
err := c.call(context.Background(), "/api/trpc/domains.createDomain", input, nil)
if err != nil {
return err
}
fmt.Printf("Domain created: %s -> %s/%s\n", args[2], args[0], args[1])
if err := redeployAfterConfig(c, args[0], args[1], svcType); err != nil {
return fmt.Errorf("apply domain: %w", err)
}
fmt.Printf("Domain created and deployed: %s -> %s/%s\n", args[2], args[0], args[1])
return nil
},
}

var domainsDeleteCmd = &cobra.Command{
Use: "delete [domain-id]",
Short: "Delete a domain",
Args: cobra.ExactArgs(1),
Use: "delete [project] [service] [type] [domain-id]",
Short: "Delete a domain and redeploy its app or compose service",
Args: cobra.ExactArgs(4),
RunE: func(cmd *cobra.Command, args []string) error {
svcType := args[2]
if svcType != "app" && svcType != "compose" {
return fmt.Errorf("domain redeploy type must be app or compose, got %q", svcType)
}

c := newPanelClient()
input := map[string]string{"id": args[0]}
err := c.call(context.Background(), "/api/trpc/domains.deleteDomain", input, nil)
if err != nil {
input := map[string]string{"id": args[3]}
if err := c.call(context.Background(), "/api/trpc/domains.deleteDomain", input, nil); err != nil {
return err
}
fmt.Printf("Deleted domain: %s\n", args[0])
var deployErr error
for attempt := 0; attempt < 2; attempt++ {
deployErr = redeployAfterConfig(c, args[0], args[1], svcType)
if deployErr == nil {
break
}
}
if deployErr != nil {
return fmt.Errorf("domain %s was deleted, but routing redeploy failed after 2 attempts: %w; run `services deploy %s %s %s` manually", args[3], deployErr, args[0], args[1], svcType)
}
fmt.Printf("Deleted domain and deployed: %s\n", args[3])
return nil
},
}
10 changes: 9 additions & 1 deletion cmd/panel_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"time"
)

Expand All @@ -18,11 +19,18 @@ type panelClient struct {
}

func newPanelClient() *panelClient {
timeout := 5 * time.Minute
if raw := os.Getenv("EASYPANEL_HTTP_TIMEOUT"); raw != "" {
if parsed, err := time.ParseDuration(raw); err == nil && parsed > 0 {
timeout = parsed
}
}
return &panelClient{
baseURL: getEndpoint(),
token: getAPIKey(),
httpClient: &http.Client{
Timeout: 30 * time.Second,
// deployService blocks while images pull/start; 30s was too short.
Timeout: timeout,
},
}
}
Expand Down
Loading