Change headless from flag to subpackage

This commit is contained in:
FIGBERT 2023-08-06 22:01:40 -07:00
parent edda1a956a
commit 704fc53db1
No known key found for this signature in database
GPG key ID: 67F1598D607A844B
6 changed files with 154 additions and 92 deletions

136
headless/headless.go Normal file
View file

@ -0,0 +1,136 @@
package headless
import (
"errors"
"fmt"
"os"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/crypto/ssss"
"maunium.net/go/gomuks/initialize"
"maunium.net/go/gomuks/ui"
)
type HeadlessConfig struct {
OutputDir,
MxID, MxPassword,
KeyPath, KeyPassword,
RecoveryPhrase string
}
func HeadlessInit(conf HeadlessConfig) error {
// setup package dir
os.Setenv("GOMUKS_ROOT", conf.OutputDir)
// init boilerplate
configDir, dataDir, cacheDir, downloadDir, err := initDirs()
if err != nil {
return err
}
gmx := initialize.NewGomuks(ui.NewGomuksUI, configDir, dataDir, cacheDir, downloadDir)
err = gmx.StartHeadless()
if err != nil {
return err
}
// login section
if err = gmx.Matrix().Login(conf.MxID, conf.MxPassword); err != nil {
return err
}
// key import
data, err := os.ReadFile(conf.KeyPath)
if err != nil {
return err
}
mach := gmx.Matrix().Crypto().(*crypto.OlmMachine)
_, _, err = mach.ImportKeys(conf.KeyPassword, data)
if err != nil {
return fmt.Errorf("Failed to import sessions: %v", err)
}
// verify (fetch)
key, err := getSSSS(mach, conf.MxPassword, conf.RecoveryPhrase)
if err != nil {
return err
}
err = mach.FetchCrossSigningKeysFromSSSS(key)
if err != nil {
return fmt.Errorf("Error fetching cross-signing keys: %v", err)
}
// verify (sign)
if mach.CrossSigningKeys == nil {
return fmt.Errorf("Cross-signing keys not cached")
}
err = mach.SignOwnDevice(mach.OwnIdentity())
if err != nil {
return fmt.Errorf("Failed to self-sign: %v", err)
}
// sync
// how?
// this does too much: gmx.Matrix().Start()
return nil
}
func initDirs() (string, string, string, string, error) {
config, err := initialize.UserConfigDir()
if err != nil {
return "", "", "", "", fmt.Errorf("Failed to get config directory: %v", err)
}
data, err := initialize.UserDataDir()
if err != nil {
return "", "", "", "", fmt.Errorf("Failed to get data directory: %v", err)
}
cache, err := initialize.UserCacheDir()
if err != nil {
return "", "", "", "", fmt.Errorf("Failed to get cache directory: %v", err)
}
download, err := initialize.UserDownloadDir()
if err != nil {
return "", "", "", "", fmt.Errorf("Failed to get download directory: %v", err)
}
return config, data, cache, download, nil
}
func getSSSS(mach *crypto.OlmMachine, password, recoveryPhrase string) (*ssss.Key, error) {
_, keyData, err := mach.SSSS.GetDefaultKeyData()
if err != nil {
if errors.Is(err, mautrix.MNotFound) {
return nil, fmt.Errorf("SSSS not set up, use `!ssss generate --set-default` first")
} else {
return nil, fmt.Errorf("Failed to fetch default SSSS key data: %v", err)
}
}
var key *ssss.Key
if keyData.Passphrase != nil && keyData.Passphrase.Algorithm == ssss.PassphraseAlgorithmPBKDF2 {
key, err = keyData.VerifyPassphrase(password)
if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
return nil, fmt.Errorf("Incorrect passphrase")
}
} else {
key, err = keyData.VerifyRecoveryKey(recoveryPhrase)
if errors.Is(err, ssss.ErrInvalidRecoveryKey) {
return nil, fmt.Errorf("Malformed recovery key")
} else if errors.Is(err, ssss.ErrIncorrectSSSSKey) {
return nil, fmt.Errorf("Incorrect recovery key")
}
}
// All the errors should already be handled above, this is just for backup
if err != nil {
return nil, fmt.Errorf("Failed to get SSSS key: %v", err)
}
return key, nil
}

View file

@ -43,14 +43,14 @@ type Gomuks struct {
// NewGomuks creates a new Gomuks instance with everything initialized, // NewGomuks creates a new Gomuks instance with everything initialized,
// but does not start it. // but does not start it.
func NewGomuks(uiProvider ifc.UIProvider, configDir, dataDir, cacheDir, downloadDir string, isHeadless bool) *Gomuks { func NewGomuks(uiProvider ifc.UIProvider, configDir, dataDir, cacheDir, downloadDir string) *Gomuks {
gmx := &Gomuks{ gmx := &Gomuks{
stop: make(chan bool, 1), stop: make(chan bool, 1),
} }
gmx.config = config.NewConfig(configDir, dataDir, cacheDir, downloadDir) gmx.config = config.NewConfig(configDir, dataDir, cacheDir, downloadDir)
gmx.ui = uiProvider(gmx) gmx.ui = uiProvider(gmx)
gmx.matrix = matrix.NewContainer(gmx, isHeadless) gmx.matrix = matrix.NewContainer(gmx)
gmx.config.LoadAll() gmx.config.LoadAll()
gmx.ui.Init() gmx.ui.Init()
@ -103,24 +103,6 @@ func (gmx *Gomuks) internalStop(save bool) {
if save { if save {
gmx.Save() gmx.Save()
} }
if gmx.matrix.IsHeadless() {
fmt.Println("🚚📦📦 gomuks is ready to go 🚚📦📦")
fmt.Println("⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒⇒")
fmt.Println()
fmt.Println("1.")
fmt.Println("copy your new `transfer` folder to the target")
fmt.Println("location (perhaps a shiny new beepberry) with")
fmt.Println("cp, rsync, or equivalent.")
fmt.Println()
fmt.Println("2.")
fmt.Println("set the GOMUKS_ROOT environment variable to match")
fmt.Println("the new location of the directory and edit the")
fmt.Println("config file to reflect the changes.")
fmt.Println()
fmt.Println("recommended reading:")
fmt.Println("https://docs.mau.fi/gomuks/faq.html#where-does-gomuks-store-data")
fmt.Println("https://beepy.sqfmi.com")
}
debug.Print("Exiting process") debug.Print("Exiting process")
os.Exit(0) os.Exit(0)
} }
@ -130,7 +112,7 @@ func (gmx *Gomuks) internalStop(save bool) {
// If the tview app returns an error, it will be passed into panic(), which // If the tview app returns an error, it will be passed into panic(), which
// will be recovered as specified in Recover(). // will be recovered as specified in Recover().
func (gmx *Gomuks) Start() { func (gmx *Gomuks) Start() {
err := gmx.matrix.InitClient(true) err := gmx.StartHeadless()
if err != nil { if err != nil {
if errors.Is(err, matrix.ErrServerOutdated) { if errors.Is(err, matrix.ErrServerOutdated) {
_, _ = fmt.Fprintln(os.Stderr, strings.Replace(err.Error(), "homeserver", gmx.config.HS, 1)) _, _ = fmt.Fprintln(os.Stderr, strings.Replace(err.Error(), "homeserver", gmx.config.HS, 1))
@ -161,6 +143,10 @@ func (gmx *Gomuks) Start() {
} }
} }
func (gmx *Gomuks) StartHeadless() error {
return gmx.matrix.InitClient(true)
}
// Matrix returns the MatrixContainer instance. // Matrix returns the MatrixContainer instance.
func (gmx *Gomuks) Matrix() ifc.MatrixContainer { func (gmx *Gomuks) Matrix() ifc.MatrixContainer {
return gmx.matrix return gmx.matrix

View file

@ -45,7 +45,6 @@ type MatrixContainer interface {
Preferences() *config.UserPreferences Preferences() *config.UserPreferences
InitClient(isStartup bool) error InitClient(isStartup bool) error
Initialized() bool Initialized() bool
IsHeadless() bool
Start() Start()
Stop() Stop()

27
main.go
View file

@ -19,7 +19,6 @@ package main
import ( import (
"fmt" "fmt"
"os" "os"
"path/filepath"
"runtime" "runtime"
"strings" "strings"
"time" "time"
@ -72,7 +71,6 @@ var wantVersion = flag.MakeFull("v", "version", "Show the version of gomuks", "f
var clearCache = flag.MakeFull("c", "clear-cache", "Clear the cache directory instead of starting", "false").Bool() var clearCache = flag.MakeFull("c", "clear-cache", "Clear the cache directory instead of starting", "false").Bool()
var skipVersionCheck = flag.MakeFull("s", "skip-version-check", "Skip the homeserver version checks at startup and login", "false").Bool() var skipVersionCheck = flag.MakeFull("s", "skip-version-check", "Skip the homeserver version checks at startup and login", "false").Bool()
var clearData = flag.Make().LongKey("clear-all-data").Usage("Clear all data instead of starting").Default("false").Bool() var clearData = flag.Make().LongKey("clear-all-data").Usage("Clear all data instead of starting").Default("false").Bool()
var logInForTransfer = flag.Make().LongKey("log-in-for-transfer").Usage("Log in and generate packaged data for transfer").Default("false").Bool()
var wantHelp, _ = flag.MakeHelpFlag() var wantHelp, _ = flag.MakeHelpFlag()
func main() { func main() {
@ -91,27 +89,6 @@ func main() {
fmt.Println(VersionString) fmt.Println(VersionString)
return return
} }
if *logInForTransfer {
if currentDir, err := os.Getwd(); err == nil {
pack := filepath.Join(currentDir, "transfer")
if _, err := os.Stat(pack); err == nil {
fmt.Println("with the --log-in-for-transfer flag, gomuks packs your data up into")
fmt.Println("the transfer/ directory so you can move it around easily. please make")
fmt.Println("sure there is nothing there already, and then run it again.")
os.Exit(1)
}
keys := filepath.Join(currentDir, "keys.txt")
if _, err := os.Stat(keys); err != nil {
fmt.Println("with the --log-in-for-transfer flag, gomuks packs your data up so")
fmt.Println("you can move it around easily. please export your existing client")
fmt.Println("keys to the file keys.txt, and then run gomuks again.")
os.Exit(1)
}
os.Setenv("GOMUKS_ROOT", pack)
}
}
debugDir := os.Getenv("DEBUG_DIR") debugDir := os.Getenv("DEBUG_DIR")
if len(debugDir) > 0 { if len(debugDir) > 0 {
@ -155,7 +132,7 @@ func main() {
debug.Print("Download directory:", downloadDir) debug.Print("Download directory:", downloadDir)
matrix.SkipVersionCheck = *skipVersionCheck matrix.SkipVersionCheck = *skipVersionCheck
gmx := initialize.NewGomuks(MainUIProvider, configDir, dataDir, cacheDir, downloadDir, *logInForTransfer) gmx := initialize.NewGomuks(MainUIProvider, configDir, dataDir, cacheDir, downloadDir)
if *clearCache { if *clearCache {
debug.Print("Clearing cache as requested by CLI flag") debug.Print("Clearing cache as requested by CLI flag")
@ -169,8 +146,6 @@ func main() {
_ = os.RemoveAll(gmx.Config().Dir) _ = os.RemoveAll(gmx.Config().Dir)
fmt.Printf("Cleared cache at %s, data at %s and config at %s\n", gmx.Config().CacheDir, gmx.Config().DataDir, gmx.Config().Dir) fmt.Printf("Cleared cache at %s, data at %s and config at %s\n", gmx.Config().CacheDir, gmx.Config().DataDir, gmx.Config().Dir)
return return
} else if *logInForTransfer {
debug.Print("Initializing in headless mode as requested by CLI flag")
} }
gmx.Start() gmx.Start()

View file

@ -47,7 +47,6 @@ import (
"maunium.net/go/gomuks/lib/open" "maunium.net/go/gomuks/lib/open"
"maunium.net/go/gomuks/matrix/muksevt" "maunium.net/go/gomuks/matrix/muksevt"
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
"maunium.net/go/gomuks/ui"
) )
// Container is a wrapper for a mautrix Client and some other stuff. // Container is a wrapper for a mautrix Client and some other stuff.
@ -61,20 +60,18 @@ type Container struct {
ui ifc.GomuksUI ui ifc.GomuksUI
config *config.Config config *config.Config
history *HistoryManager history *HistoryManager
running, running bool
headless bool stop chan bool
stop chan bool
typing int64 typing int64
} }
// NewContainer creates a new Container for the given Gomuks instance. // NewContainer creates a new Container for the given Gomuks instance.
func NewContainer(gmx ifc.Gomuks, isHeadless bool) *Container { func NewContainer(gmx ifc.Gomuks) *Container {
c := &Container{ c := &Container{
config: gmx.Config(), config: gmx.Config(),
ui: gmx.UI(), ui: gmx.UI(),
gmx: gmx, gmx: gmx,
headless: isHeadless,
} }
return c return c
@ -85,10 +82,6 @@ func (c *Container) Client() *mautrix.Client {
return c.client return c.client
} }
func (c *Container) IsHeadless() bool {
return c.headless
}
type mxLogger struct{} type mxLogger struct{}
func (log mxLogger) Debugfln(message string, args ...interface{}) { func (log mxLogger) Debugfln(message string, args ...interface{}) {
@ -395,11 +388,6 @@ func (c *Container) OnLogin() {
c.client.Store = c.config c.client.Store = c.config
if c.headless {
debug.Print("Importing keys...")
c.RunCommand("/import keys.txt")
}
debug.Print("Initializing syncer") debug.Print("Initializing syncer")
c.syncer = NewGomuksSyncer(c.config.Rooms) c.syncer = NewGomuksSyncer(c.config.Rooms)
if c.crypto != nil { if c.crypto != nil {
@ -438,12 +426,6 @@ func (c *Container) OnLogin() {
c.syncer.Progress.Close() c.syncer.Progress.Close()
c.syncer.Progress = StubSyncingModal{} c.syncer.Progress = StubSyncingModal{}
c.syncer.FirstDoneCallback = nil c.syncer.FirstDoneCallback = nil
if c.headless {
c.RunCommand("/cs fetch")
c.RunCommand("/cs self-sign")
c.config.Preferences.DisplayMode = config.DisplayModeModern
c.gmx.Stop(true)
}
} }
} }
c.syncer.InitDoneCallback = func() { c.syncer.InitDoneCallback = func() {
@ -457,11 +439,9 @@ func (c *Container) OnLogin() {
c.config.Rooms.ForceClean() c.config.Rooms.ForceClean()
debug.Print("Saving all data") debug.Print("Saving all data")
c.config.SaveAll() c.config.SaveAll()
if !c.headless { debug.Print("Adding rooms to UI")
debug.Print("Adding rooms to UI") c.ui.MainView().SetRooms(c.config.Rooms)
c.ui.MainView().SetRooms(c.config.Rooms) c.ui.Render()
c.ui.Render()
}
// The initial sync can be a bit heavy, so we force run the GC here // The initial sync can be a bit heavy, so we force run the GC here
// after cleaning up rooms from memory above. // after cleaning up rooms from memory above.
debug.Print("Running GC") debug.Print("Running GC")
@ -471,9 +451,7 @@ func (c *Container) OnLogin() {
c.client.Syncer = c.syncer c.client.Syncer = c.syncer
debug.Print("Setting existing rooms") debug.Print("Setting existing rooms")
if !c.headless { c.ui.MainView().SetRooms(c.config.Rooms)
c.ui.MainView().SetRooms(c.config.Rooms)
}
debug.Print("OnLogin() done.") debug.Print("OnLogin() done.")
} }
@ -513,14 +491,6 @@ func (c *Container) Start() {
} }
} }
func (c *Container) RunCommand(text string) {
if view, ok := c.ui.MainView().(*ui.MainView); ok {
if cmd := view.CmdProcessor().ParseCommand(nil, text); cmd != nil {
view.CmdProcessor().HandleCommand(cmd)
}
}
}
func (c *Container) HandlePreferences(source mautrix.EventSource, evt *event.Event) { func (c *Container) HandlePreferences(source mautrix.EventSource, evt *event.Event) {
if source&mautrix.EventSourceAccountData == 0 { if source&mautrix.EventSourceAccountData == 0 {
return return

View file

@ -494,10 +494,6 @@ func (rstr *RosterView) Draw(screen mauview.Screen) {
} }
func (rstr *RosterView) OnKeyEvent(event mauview.KeyEvent) bool { func (rstr *RosterView) OnKeyEvent(event mauview.KeyEvent) bool {
if rstr.parent.matrix.IsHeadless() {
return false
}
kb := config.Keybind{ kb := config.Keybind{
Key: event.Key(), Key: event.Key(),
Ch: event.Rune(), Ch: event.Rune(),