mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-19 18:13:41 -05:00
Change headless from flag to subpackage
This commit is contained in:
parent
edda1a956a
commit
704fc53db1
6 changed files with 154 additions and 92 deletions
136
headless/headless.go
Normal file
136
headless/headless.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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
27
main.go
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue