refactor(deploy)!: TUI
This commit is contained in:
37 files changed
+3622
-1303
No files matched your search
@@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"felis.lolicon.best/internal/cfsetup"
|
||||
"felis.lolicon.best/internal/config"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
const podSetupConfigPath = "/etc/felis/felis.pod.toml"
|
||||
const cloudflaredFelisUnit = "/etc/systemd/system/cloudflared-felis.service"
|
||||
|
||||
func applyCloudflareEdge(ctx context.Context, result *cfsetup.Result, panelHost, adminHost, cloudflaredBin string) error {
|
||||
if result == nil || result.AccessAud == "" {
|
||||
return fmt.Errorf("edge result did not include an Access audience")
|
||||
}
|
||||
if adminHost == "" {
|
||||
return fmt.Errorf("admin hostname is required")
|
||||
}
|
||||
for _, path := range []string{hostSetupConfigPath, podSetupConfigPath} {
|
||||
if err := updateAuthConfig(path, panelHost, adminHost, result.AccessAud); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := applyFelisConfigSecret(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := kubectl(ctx, "-n", "felis", "rollout", "restart", "deployment/felis-api"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := installCloudflaredService(ctx, cloudflaredBin, result.ConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return kubectl(ctx, "-n", "felis", "rollout", "status", "deployment/felis-api", "--timeout=180s")
|
||||
}
|
||||
|
||||
func updateAuthConfig(path, panelHost, adminHost, aud string) error {
|
||||
cfg, err := config.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if panelHost != "" {
|
||||
cfg.Auth.PanelHostname = panelHost
|
||||
}
|
||||
cfg.Auth.AdminHostname = adminHost
|
||||
cfg.Auth.AccessJWTAud = aud
|
||||
return writeConfig(path, cfg)
|
||||
}
|
||||
|
||||
func writeConfig(path string, cfg *config.Config) error {
|
||||
tmp, err := os.CreateTemp(filepath.Dir(path), ".felis-*.toml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpPath := tmp.Name()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
if err := toml.NewEncoder(tmp).Encode(cfg); err != nil {
|
||||
_ = tmp.Close()
|
||||
return err
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpPath, path)
|
||||
}
|
||||
|
||||
func applyFelisConfigSecret(ctx context.Context) error {
|
||||
out, err := kubectlOutput(ctx,
|
||||
"-n", "felis", "create", "secret", "generic", "felis-config",
|
||||
"--from-file=felis.toml="+podSetupConfigPath,
|
||||
"--dry-run=client", "-o", "yaml",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return kubectlWithInput(ctx, out, "apply", "-f", "-")
|
||||
}
|
||||
|
||||
func installCloudflaredService(ctx context.Context, cloudflaredBin, configPath string) error {
|
||||
if cloudflaredBin == "" {
|
||||
return fmt.Errorf("cloudflared binary path is empty")
|
||||
}
|
||||
if configPath == "" {
|
||||
return fmt.Errorf("cloudflared config path is empty")
|
||||
}
|
||||
unit := fmt.Sprintf(`[Unit]
|
||||
Description=Felis Cloudflare Tunnel
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=%s --config %s tunnel run
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, cloudflaredBin, configPath)
|
||||
if err := os.WriteFile(cloudflaredFelisUnit, []byte(unit), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := systemctl(ctx, "daemon-reload"); err != nil {
|
||||
return err
|
||||
}
|
||||
return systemctl(ctx, "enable", "--now", "cloudflared-felis.service")
|
||||
}
|
||||
|
||||
func kubectl(ctx context.Context, args ...string) error {
|
||||
_, err := kubectlOutput(ctx, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func kubectlWithInput(ctx context.Context, input []byte, args ...string) error {
|
||||
_, err := runK3sKubectl(ctx, input, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func kubectlOutput(ctx context.Context, args ...string) ([]byte, error) {
|
||||
return runK3sKubectl(ctx, nil, args...)
|
||||
}
|
||||
|
||||
func runK3sKubectl(ctx context.Context, input []byte, args ...string) ([]byte, error) {
|
||||
fullArgs := append([]string{"kubectl"}, args...)
|
||||
cmd := exec.CommandContext(ctx, "k3s", fullArgs...)
|
||||
cmd.Env = append(os.Environ(), "KUBECONFIG="+hostBootstrapKubeconfigPath)
|
||||
if input != nil {
|
||||
cmd.Stdin = bytes.NewReader(input)
|
||||
}
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("k3s %v: %w: %s", fullArgs, err, string(out))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func systemctl(ctx context.Context, args ...string) error {
|
||||
cmd := exec.CommandContext(ctx, "systemctl", args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("systemctl %v: %w: %s", args, err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user