Files
Gittea-plugin/cmd/root.go
Spencer e88f33071e Add newproject command and fix template bugs
- Add newproject, ssh, templates commands for full project scaffolding
- Add homelab SSH, Caddy, port, and proxy_host fields to Config
- Fix dotEnvExample: use container-internal port (3000/8000/8080) not host port
- Fix caddySnippet: use configurable proxy_host instead of localhost
- Fix dockerComposeTemplate: add build: path so image-less deploys work
- Commit gtea.exe binary
2026-05-29 12:37:14 -04:00

119 lines
2.8 KiB
Go

package cmd
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
// Config holds Gitea connection details stored in ~/.gtea.json
type Config struct {
URL string `json:"url"`
Token string `json:"token"`
Username string `json:"username"`
// Homelab / beepc
SSHHost string `json:"ssh_host"`
SSHUser string `json:"ssh_user"`
ProjectsDir string `json:"projects_dir"`
HomelabDir string `json:"homelab_dir"`
ProxyHost string `json:"proxy_host"` // IP/hostname Caddy on VPS uses to reach beepc
// Caddy reverse proxy
CaddyConfDir string `json:"caddy_conf_dir"`
CaddyReloadCmd string `json:"caddy_reload_cmd"`
// Project defaults
Domain string `json:"domain"`
PortStart int `json:"port_range_start"`
}
var rootCmd = &cobra.Command{
Use: "gtea",
Short: "Gitea project and repo manager",
Long: "gtea — create Gitea repos, commit, push, and pull without leaving the terminal.",
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(commitCmd)
rootCmd.AddCommand(pushCmd)
rootCmd.AddCommand(pullCmd)
rootCmd.AddCommand(statusCmd)
}
func configPath() string {
home, _ := os.UserHomeDir()
return filepath.Join(home, ".gtea.json")
}
func loadConfig() (*Config, error) {
data, err := os.ReadFile(configPath())
if err != nil {
return nil, fmt.Errorf("no config found — run 'gtea config' first")
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}
if cfg.URL == "" || cfg.Token == "" || cfg.Username == "" {
return nil, fmt.Errorf("incomplete config — run 'gtea config' to fill in missing fields")
}
return &cfg, nil
}
func saveConfig(cfg *Config) error {
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(configPath(), data, 0600)
}
// runGit runs a git command in dir (use "." for current directory).
func runGit(dir string, args ...string) error {
c := exec.Command("git", args...)
c.Dir = dir
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if err := c.Run(); err != nil {
return fmt.Errorf("git %s: %w", strings.Join(args, " "), err)
}
return nil
}
// currentBranch returns the name of the currently checked-out branch.
func currentBranch() (string, error) {
out, err := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD").Output()
if err != nil {
return "", err
}
return string(bytes.TrimSpace(out)), nil
}
// authCloneURL injects token credentials into an HTTPS clone URL.
func authCloneURL(cfg *Config, cloneURL string) string {
if strings.HasPrefix(cloneURL, "https://") {
return strings.Replace(
cloneURL,
"https://",
fmt.Sprintf("https://%s:%s@", cfg.Username, cfg.Token),
1,
)
}
return cloneURL
}