refactor: seperate application into seperate files and add killForwarding

This commit is contained in:
Patryk Hegenberg 2025-01-03 12:50:04 +01:00
parent fec43ea77d
commit a7ea6f41a7
7 changed files with 481 additions and 357 deletions

357
main.go
View file

@ -1,367 +1,10 @@
package main
import (
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
)
type Config struct {
SSHUser string `mapstructure:"SSH_USER"`
SSHHost string `mapstructure:"SSH_HOST"`
JumpUser string `mapstructure:"JUMP_USER"`
JumpHost string `mapstructure:"JUMP_HOST"`
WorkstationHost string `mapstructure:"WORKSTATION_HOST"`
WorkstationUser string `mapstructure:"WORKSTATION_USER"`
WorkstationMac string `mapstructure:"WORKSTATION_MAC"`
RDPUser string `mapstructure:"RDP_USER"`
RDPPassword string `mapstructure:"RDP_PASSWORD"`
WorkstationIP string `mapstructure:"WORKSTATION_IP"`
SSHPort int `mapstructure:"SSH_PORT"`
}
type Flags struct {
ShowWeek bool
ShowMonth bool
ShowExport bool
}
type App struct {
cfg Config
flags Flags
}
func NewApp() (*App, error) {
cfg, err := loadConfig()
if err != nil {
return nil, fmt.Errorf("error loading config: %w", err)
}
return &App{
cfg: cfg,
}, nil
}
func loadConfig() (Config, error) {
var cfg Config
configPath, err := os.UserConfigDir()
if err != nil {
return cfg, err
}
workConfigPath := filepath.Join(configPath, "work")
configFile := filepath.Join(workConfigPath, "config.toml")
viper.SetConfigFile(configFile)
viper.SetConfigType("toml")
viper.AddConfigPath(".")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
return cfg, fmt.Errorf("error reading config file: %w", err)
}
if err := viper.UnmarshalKey("default", &cfg); err != nil {
return cfg, fmt.Errorf("error decoding config: %w", err)
}
return cfg, nil
}
func (a *App) connect() {
tw := NewTimeWarrior()
tw.StartWork()
// a.runCommand("timew", "start", "work")
a.wakeWorkstation()
sshClient, err := ssh.Dial("tcp", a.cfg.SSHHost+":"+fmt.Sprintf("%v", a.cfg.SSHPort), a.makeSSHClient())
if err != nil {
log.Fatal("Failed to dial: ", err)
}
defer sshClient.Close()
session, err := sshClient.NewSession()
if err != nil {
log.Fatal("Failed to create session: ", err)
}
defer session.Close()
go forwardPort(sshClient, "2048", a.cfg.WorkstationIP, "22")
go forwardPort(sshClient, "6000", a.cfg.WorkstationIP, "3389")
a.connectToWorkstation()
}
func forwardPort(sshConn *ssh.Client, localPort, remoteHost, remotePort string) {
listener, err := net.Listen("tcp", "127.0.0.1:"+localPort)
if err != nil {
log.Printf("Fehler beim Öffnen des lokalen Ports %s: %v", localPort, err)
return
}
defer listener.Close()
for {
localConn, err := listener.Accept()
if err != nil {
log.Printf("Fehler beim Akzeptieren der Verbindung: %v", err)
continue
}
remoteConn, err := sshConn.Dial("tcp", remoteHost+":"+remotePort)
if err != nil {
log.Printf("Fehler beim Verbinden zum Remote-Host %s:%s: %v", remoteHost, remotePort, err)
localConn.Close()
continue
}
go copyConn(localConn, remoteConn)
go copyConn(remoteConn, localConn)
}
}
func copyConn(dst, src net.Conn) {
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
}
func (a *App) makeSSHClient() *ssh.ClientConfig {
keypath := os.ExpandEnv("$HOME/.ssh/hegenberg")
keyBytes, err := os.ReadFile(keypath)
if err != nil {
log.Fatalf("Failed to read private key: %s", err)
}
key, err := ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(a.cfg.RDPPassword))
if err != nil {
log.Fatalf("Failed to parse private key: %s", err)
}
return &ssh.ClientConfig{
User: a.cfg.SSHUser,
Auth: []ssh.AuthMethod{
ssh.PublicKeys(key),
},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: 5 * time.Second,
}
}
func (a *App) runCommand(name string, args ...string) {
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
err := cmd.Run()
if err != nil {
fmt.Println("Error:", err)
}
}
func (a *App) wakeWorkstation() {
sshCommand := fmt.Sprintf("ssh -tt -p %s %s@%s ssh -tt %s@%s \"wakeonlan %s && exit\"",
fmt.Sprintf("%v", a.cfg.SSHPort),
a.cfg.SSHUser,
a.cfg.SSHHost,
a.cfg.JumpUser,
a.cfg.JumpHost,
a.cfg.WorkstationMac)
args := strings.Split(sshCommand, " ")
log.Println(args)
a.runCommand("ssh", args[1:]...)
}
func (a *App) connectToJump() {
sshCommand := fmt.Sprintf("ssh -tt -L 2048:%s:22 %s@%s",
a.cfg.WorkstationHost,
a.cfg.SSHUser,
a.cfg.SSHHost)
args := strings.Split(sshCommand, " ")
a.runCommand("ssh", args[1:]...)
}
func (a *App) connectToWorkstation() {
sshCommand := fmt.Sprintf("ssh -tt -L 6000:%s:3389 -p 2048 %s@127.0.0.1",
a.cfg.WorkstationHost,
a.cfg.SSHUser)
args := strings.Split(sshCommand, " ")
a.runCommand("ssh", args[1:]...)
}
func (a *App) startRDPConnection() {
rdpCommand := fmt.Sprintf("xfreerdp /u:%s /p:%s /v:127.0.0.1:6000 /size:3000x1350",
a.cfg.RDPUser,
a.cfg.RDPPassword)
a.runCommand("bash", "-c", rdpCommand)
}
func (a *App) makeChoice() {
var choice string
tw := NewTimeWarrior()
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("What would you like to do?").
Options(
huh.NewOption("Start", "Start"),
huh.NewOption("Stop Work", "stop Work"),
huh.NewOption("Show Week Summary", "show week summary"),
huh.NewOption("Show Month Summary", "show month summary"),
huh.NewOption("Start Break", "start break"),
huh.NewOption("Stop Break", "stop break"),
huh.NewOption("Export Timetable", "export"),
huh.NewOption("Wake Workstation", "wake workstation"),
huh.NewOption("Connect to Jump", "connect to jump"),
huh.NewOption("Connect to Workstation", "connect to workstation"),
huh.NewOption("Start RDP Connection", "start rdp connection"),
).
Value(&choice),
),
)
err := form.Run()
if err != nil {
fmt.Println("Error:", err)
return
}
switch choice {
case "Start":
a.connect()
case "stop Work":
tw.StopWork()
case "start break":
tw.StartBreak()
case "stop break":
tw.StopBreak()
case "show week summary":
tw.ShowSummary(":week")
case "show month summary":
tw.ShowSummary(":month")
case "wake workstation":
a.wakeWorkstation()
case "connect to jump":
a.connectToJump()
case "connect to workstation":
a.connectToWorkstation()
case "start rdp connection":
a.startRDPConnection()
case "export":
tw.ExportSummary()
}
}
type TimeWarrior struct{}
func NewTimeWarrior() *TimeWarrior {
return &TimeWarrior{}
}
func (t *TimeWarrior) runCommand(args ...string) error {
cmd := exec.Command("timew", args...)
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
return cmd.Run()
}
func (t *TimeWarrior) StartWork() error {
return t.runCommand("start", "work")
}
func (t *TimeWarrior) StopWork() error {
return t.runCommand("stop", "work")
}
func (t *TimeWarrior) StartBreak() error {
return t.runCommand("track", "break")
}
func (t *TimeWarrior) StopBreak() error {
return t.runCommand("track", "work")
}
func (t *TimeWarrior) ShowSummary(period string) error {
return t.runCommand("summary", period, "work")
}
func (t *TimeWarrior) ExportSummary() error {
fmt.Println("Export Timetable")
return nil
}
func (a *App) setupCommands() *cobra.Command {
rootCmd := &cobra.Command{
Use: "work",
Short: "Fast work interactions",
Long: `A CLI tool to perform basic work cli tasks.`,
}
rootCmd.AddCommand(a.startCommand())
rootCmd.AddCommand(a.stopCommand())
rootCmd.AddCommand(a.showCommand())
return rootCmd
}
func (a *App) startCommand() *cobra.Command {
return &cobra.Command{
Use: "start",
Short: "start work",
Long: "command to start the work day",
Run: func(cmd *cobra.Command, args []string) {
a.connect()
},
}
}
func (a *App) stopCommand() *cobra.Command {
return &cobra.Command{
Use: "stop",
Short: "stop work",
Long: "command to stop the work day",
Run: func(cmd *cobra.Command, args []string) {
a.runCommand("timew", "stop", "work")
os.Exit(0)
},
}
}
func (a *App) showCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "show timetracking",
Long: "show different timetracking",
Run: func(cmd *cobra.Command, args []string) {
if a.flags.ShowExport {
fmt.Println("Exporting timetable")
}
if a.flags.ShowWeek {
a.runCommand("timew", "summary", ":week", "work")
}
if a.flags.ShowMonth {
a.runCommand("timew", "summary", ":month", "work")
}
os.Exit(0)
},
}
cmd.Flags().BoolVarP(&a.flags.ShowWeek, "week", "w", false, "show timewarrior week summary")
cmd.Flags().BoolVarP(&a.flags.ShowMonth, "month", "m", false, "show timewarrior month summary")
cmd.Flags().BoolVarP(&a.flags.ShowExport, "export", "e", false, "export timewarrior timetable")
return cmd
}
func main() {
app, err := NewApp()
if err != nil {