2018-10-23 18:52:13 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-10-13 21:22:48 +00:00
|
|
|
"crypto/rand"
|
2018-11-22 00:01:17 +00:00
|
|
|
libapp "cwtch.im/cwtch/app"
|
2020-10-09 18:09:43 +00:00
|
|
|
"cwtch.im/cwtch/event"
|
2019-06-21 21:58:40 +00:00
|
|
|
"cwtch.im/cwtch/event/bridge"
|
2020-10-26 20:29:58 +00:00
|
|
|
"cwtch.im/cwtch/peer"
|
2020-10-29 23:12:04 +00:00
|
|
|
"cwtch.im/ui/go/features/servers"
|
2019-11-08 21:34:12 +00:00
|
|
|
"cwtch.im/ui/go/handlers"
|
2020-10-21 23:58:57 +00:00
|
|
|
os2 "cwtch.im/ui/go/os"
|
2018-11-28 22:14:02 +00:00
|
|
|
"cwtch.im/ui/go/the"
|
2019-11-08 21:34:12 +00:00
|
|
|
"cwtch.im/ui/go/ui"
|
2020-10-13 21:22:48 +00:00
|
|
|
"encoding/base64"
|
2019-03-25 19:10:46 +00:00
|
|
|
"flag"
|
2020-02-11 23:39:42 +00:00
|
|
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
|
|
|
"git.openprivacy.ca/openprivacy/log"
|
2019-08-02 04:45:58 +00:00
|
|
|
"github.com/therecipe/qt/androidextras"
|
2018-11-28 22:14:02 +00:00
|
|
|
"github.com/therecipe/qt/core"
|
2019-03-18 23:52:46 +00:00
|
|
|
"github.com/therecipe/qt/gui"
|
2019-01-28 22:00:46 +00:00
|
|
|
"github.com/therecipe/qt/network"
|
|
|
|
"github.com/therecipe/qt/qml"
|
2018-10-25 00:13:03 +00:00
|
|
|
"github.com/therecipe/qt/quickcontrols2"
|
2020-10-13 21:22:48 +00:00
|
|
|
mrand "math/rand"
|
2018-10-25 00:13:03 +00:00
|
|
|
"os"
|
2019-03-18 23:52:46 +00:00
|
|
|
"os/user"
|
2020-10-28 23:58:15 +00:00
|
|
|
"path"
|
2019-02-14 18:57:44 +00:00
|
|
|
"path/filepath"
|
2019-03-18 23:52:46 +00:00
|
|
|
"runtime"
|
2020-10-13 21:22:48 +00:00
|
|
|
"time"
|
2018-10-28 02:49:14 +00:00
|
|
|
)
|
2018-10-23 18:52:13 +00:00
|
|
|
|
2019-04-08 21:01:53 +00:00
|
|
|
const androidBaseDir = "/data/data/ca.openprivacy.cwtch.ui/"
|
2019-02-19 21:32:52 +00:00
|
|
|
|
2019-03-27 19:57:11 +00:00
|
|
|
var (
|
|
|
|
buildVer string
|
|
|
|
buildDate string
|
|
|
|
)
|
|
|
|
|
2018-11-22 00:01:17 +00:00
|
|
|
func init() {
|
|
|
|
// make go-defined types available in qml
|
2019-11-08 21:34:12 +00:00
|
|
|
ui.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher")
|
2018-10-23 18:52:13 +00:00
|
|
|
}
|
|
|
|
|
2018-11-22 00:01:17 +00:00
|
|
|
func main() {
|
2020-11-20 19:06:41 +00:00
|
|
|
|
2020-11-19 20:27:56 +00:00
|
|
|
if os.Getenv("CWTCH_FOLDER") != "" {
|
|
|
|
the.CwtchDir = os.Getenv("CWTCH_FOLDER")
|
|
|
|
} else if runtime.GOOS == "android" {
|
|
|
|
the.CwtchDir = filepath.Join(androidBaseDir, "files")
|
|
|
|
} else {
|
|
|
|
usr, err := user.Current()
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("\nerror: could not load current user: %v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
the.CwtchDir = filepath.Join(usr.HomeDir, ".cwtch")
|
|
|
|
}
|
|
|
|
|
2020-10-23 01:01:48 +00:00
|
|
|
// suppress event.NewMessageFromPeer so we can handle it ourselves
|
2020-10-09 18:09:43 +00:00
|
|
|
peer.DefaultEventsToHandle = []event.Type{
|
|
|
|
event.EncryptedGroupMessage,
|
|
|
|
event.PeerAcknowledgement,
|
|
|
|
event.NewGroupInvite,
|
|
|
|
event.PeerError,
|
|
|
|
event.SendMessageToGroupError,
|
|
|
|
event.NewGetValMessageFromPeer,
|
|
|
|
}
|
|
|
|
|
2020-11-19 20:27:56 +00:00
|
|
|
logfileDefault := "cwtch_log.txt"
|
2020-10-28 18:13:20 +00:00
|
|
|
|
|
|
|
flagDebug := flag.Bool("debug", false, "turn on extra logging. WARNING: THIS MAY EXPOSE PRIVATE INFORMATION IN CONSOLE OUTPUT!")
|
2020-11-19 20:27:56 +00:00
|
|
|
flagLogFile := flag.Bool("logfile", false, "instead of console output, log to $HOME/.cwtch/"+logfileDefault)
|
2020-10-28 18:13:20 +00:00
|
|
|
flagLocal := flag.Bool("local", false, "load user interface from the local folder \"qml\" instead of the built-in UI")
|
|
|
|
flagService := flag.Bool("service", false, "run this process as an android service")
|
|
|
|
flagClientUI := flag.Bool("clientui", false, "start the UI as a client of a service app instead of a full app")
|
|
|
|
flag.Parse()
|
|
|
|
|
2020-11-19 20:27:56 +00:00
|
|
|
if *flagLogFile {
|
|
|
|
filelogger, err := log.NewFile(log.LevelInfo, filepath.Join(the.CwtchDir, logfileDefault))
|
2019-08-12 21:23:50 +00:00
|
|
|
if err == nil {
|
|
|
|
log.SetStd(filelogger)
|
|
|
|
}
|
|
|
|
}
|
2019-02-14 02:42:13 +00:00
|
|
|
|
2019-03-25 19:10:46 +00:00
|
|
|
if *flagDebug {
|
2019-02-14 02:42:13 +00:00
|
|
|
log.SetLevel(log.LevelDebug)
|
|
|
|
} else {
|
|
|
|
log.SetLevel(log.LevelInfo)
|
|
|
|
}
|
|
|
|
|
2019-06-21 21:58:40 +00:00
|
|
|
// TESTING
|
2019-09-25 21:23:20 +00:00
|
|
|
if buildVer == "" {
|
|
|
|
log.SetLevel(log.LevelDebug)
|
|
|
|
}
|
2020-05-01 23:53:44 +00:00
|
|
|
log.ExcludeFromPattern("connection/connection")
|
2019-07-06 00:50:03 +00:00
|
|
|
//log.ExcludeFromPattern("outbound/3dhauthchannel")
|
2020-11-20 19:42:29 +00:00
|
|
|
log.ExcludeFromPattern("event/eventmanager")
|
2020-05-01 23:53:44 +00:00
|
|
|
log.ExcludeFromPattern("service.go")
|
|
|
|
log.ExcludeFromPattern("tor/BaseOnionService.go")
|
|
|
|
log.ExcludeFromPattern("applications/auth.go")
|
2020-09-21 21:31:45 +00:00
|
|
|
//log.ExcludeFromPattern("connections/engine.go")
|
2019-06-21 21:58:40 +00:00
|
|
|
|
2020-11-19 20:27:56 +00:00
|
|
|
log.Infoln("ui main()")
|
2019-09-25 21:23:20 +00:00
|
|
|
|
|
|
|
if buildVer == "" && os.Getenv("CWTCH_FOLDER") == "" {
|
|
|
|
log.Infoln("Development build: using dev directory for dev profiles")
|
2020-10-21 23:58:57 +00:00
|
|
|
the.CwtchDir = filepath.Join(the.CwtchDir, "dev")
|
2019-09-25 21:23:20 +00:00
|
|
|
}
|
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
the.ACN = nil
|
2019-06-21 21:58:40 +00:00
|
|
|
the.Peer = nil
|
|
|
|
the.IPCBridge = nil
|
|
|
|
the.CwtchApp = nil
|
|
|
|
the.CwtchService = nil
|
|
|
|
os.MkdirAll(the.CwtchDir, 0700)
|
2020-11-23 20:50:06 +00:00
|
|
|
os.MkdirAll(path.Join(the.CwtchDir, "tor"), 0700)
|
2019-05-09 21:45:02 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
if *flagService {
|
|
|
|
mainService()
|
|
|
|
} else {
|
2019-09-10 23:36:45 +00:00
|
|
|
clientUI := *flagClientUI || runtime.GOOS == "android"
|
|
|
|
mainUi(*flagLocal, clientUI)
|
2019-05-13 21:01:08 +00:00
|
|
|
}
|
2019-05-09 21:45:02 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
if the.ACN != nil {
|
|
|
|
the.ACN.Close()
|
|
|
|
}
|
|
|
|
}
|
2019-05-09 21:45:02 +00:00
|
|
|
|
2019-08-02 04:45:58 +00:00
|
|
|
// QRunnable is a shim for QAndroidService and QGuiApplication
|
|
|
|
type QRunnable interface {
|
|
|
|
Exec() int
|
|
|
|
}
|
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
func mainService() {
|
|
|
|
log.Infoln("I am the service")
|
|
|
|
log.Infoln("Starting a cwtch app...")
|
2019-07-06 00:50:03 +00:00
|
|
|
go loadNetworkingAndFiles(nil, true, false)
|
2019-08-02 04:45:58 +00:00
|
|
|
var app QRunnable
|
|
|
|
if runtime.GOOS == "android" {
|
|
|
|
log.Infoln("Making QAndroidService...")
|
|
|
|
app = androidextras.NewQAndroidService(len(os.Args), os.Args)
|
|
|
|
} else {
|
|
|
|
log.Infoln("Making QGuiApplication...")
|
|
|
|
app = gui.NewQGuiApplication(len(os.Args), os.Args)
|
|
|
|
}
|
2019-09-26 23:36:35 +00:00
|
|
|
|
2019-06-21 21:58:40 +00:00
|
|
|
log.Infoln("Cwtch Service starting app.Exec")
|
|
|
|
app.Exec()
|
2019-05-13 21:01:08 +00:00
|
|
|
}
|
2019-05-09 21:45:02 +00:00
|
|
|
|
2019-07-06 00:50:03 +00:00
|
|
|
func mainUi(flagLocal bool, flagClientUI bool) {
|
2019-09-10 23:36:45 +00:00
|
|
|
log.Infof("I am the UI (client:%v)\n", flagClientUI)
|
2019-05-09 21:45:02 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
app := gui.NewQGuiApplication(len(os.Args), os.Args)
|
|
|
|
// our globals
|
2020-10-21 23:58:57 +00:00
|
|
|
err := ui.InitGlobalSettingsFile(filepath.Join(the.CwtchDir, "global"), the.AppPassword)
|
2020-05-26 18:21:27 +00:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not access global ui config: %v\n", err)
|
|
|
|
os.Exit(-1)
|
|
|
|
}
|
2020-11-25 00:45:50 +00:00
|
|
|
|
2019-11-08 21:34:12 +00:00
|
|
|
gcd := ui.NewGrandCentralDispatcher(nil)
|
2019-05-13 21:01:08 +00:00
|
|
|
gcd.SetOs(runtime.GOOS)
|
2020-01-22 23:48:12 +00:00
|
|
|
dir := core.QCoreApplication_ApplicationDirPath()
|
|
|
|
log.Infof("core.QCoreApplication_ApplicationDirPath(): %v\n", dir)
|
2020-01-10 21:02:33 +00:00
|
|
|
if runtime.GOOS == "android" {
|
|
|
|
gcd.SetAssetPath("assets:/")
|
|
|
|
} else {
|
2020-01-22 23:48:12 +00:00
|
|
|
// all of these access are QML based, and QML takes URIs which use forward slashes and translates them to local OS sperators
|
|
|
|
// also windows paths need to be like /c:/PATH
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
dir = "/" + dir
|
|
|
|
}
|
2020-10-28 23:58:15 +00:00
|
|
|
// QML uses '/' regardless of platform (so we use path.Join here not filepath.Join)
|
|
|
|
gcd.SetAssetPath("file://" + path.Join(dir, "assets") + "/")
|
2020-01-10 21:02:33 +00:00
|
|
|
}
|
2020-10-28 23:58:15 +00:00
|
|
|
log.Infof("gcd.assetPath = '%v'\n", gcd.AssetPath())
|
2020-01-10 21:02:33 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
if buildVer != "" {
|
|
|
|
gcd.SetVersion(buildVer)
|
|
|
|
gcd.SetBuildDate(buildDate)
|
|
|
|
} else {
|
|
|
|
gcd.SetVersion("development")
|
|
|
|
gcd.SetBuildDate("now")
|
2019-05-09 21:45:02 +00:00
|
|
|
}
|
2019-04-16 21:11:29 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
// this is to load local qml files quickly when developing
|
|
|
|
var qmlSource *core.QUrl
|
|
|
|
if flagLocal {
|
|
|
|
qmlSource = core.QUrl_FromLocalFile("./qml/main.qml")
|
|
|
|
} else {
|
|
|
|
qmlSource = core.NewQUrl3("qrc:/qml/main.qml", 0)
|
2019-04-16 21:11:29 +00:00
|
|
|
}
|
2018-10-23 18:52:13 +00:00
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
app.SetWindowIcon(gui.NewQIcon5(":/qml/images/cwtch-icon.png"))
|
|
|
|
|
|
|
|
// load english first so it becomes the default in case we don't have a .ts for the user's locale, or if it contains unfinished strings
|
|
|
|
translator := core.NewQTranslator(nil)
|
|
|
|
translator.Load("translation_en", ":/i18n/", "", "")
|
|
|
|
core.QCoreApplication_InstallTranslator(translator)
|
2020-05-20 20:14:20 +00:00
|
|
|
opaqueTranslator := core.NewQTranslator(nil)
|
|
|
|
opaqueTranslator.Load("translation_en", ":/qml/opaque/i18n/", "", "")
|
|
|
|
core.QCoreApplication_InstallTranslator(opaqueTranslator)
|
2019-05-13 21:01:08 +00:00
|
|
|
|
|
|
|
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true)
|
|
|
|
quickcontrols2.QQuickStyle_SetStyle("Universe")
|
|
|
|
engine := qml.NewQQmlApplicationEngine(nil)
|
|
|
|
gcd.QMLEngine = engine
|
2020-05-26 18:21:27 +00:00
|
|
|
gcd.SetLocale(gcd.GlobalSettings.Locale)
|
2019-05-13 21:01:08 +00:00
|
|
|
|
|
|
|
// prevent qt from initiating network connections (possible deanon attempts!)
|
|
|
|
factory := qml.NewQQmlNetworkAccessManagerFactory()
|
|
|
|
factory.ConnectCreate(func(parent *core.QObject) *network.QNetworkAccessManager {
|
|
|
|
nam := network.NewQNetworkAccessManager(parent)
|
|
|
|
nam.SetNetworkAccessible(network.QNetworkAccessManager__NotAccessible)
|
|
|
|
proxy := network.NewQNetworkProxy()
|
|
|
|
proxy.SetHostName("0.0.0.0")
|
|
|
|
nam.SetProxy(proxy)
|
|
|
|
return nam
|
|
|
|
})
|
|
|
|
engine.SetNetworkAccessManagerFactory(factory)
|
|
|
|
engine.RootContext().SetContextProperty("gcd", gcd)
|
2020-10-09 18:09:43 +00:00
|
|
|
gcd.TimelineInterface = ui.NewMessageModel(nil)
|
|
|
|
engine.RootContext().SetContextProperty("mm", gcd.TimelineInterface)
|
2019-05-13 21:01:08 +00:00
|
|
|
|
2020-11-25 00:45:50 +00:00
|
|
|
|
|
|
|
engine.RootContext().SetContextProperty("androidCwtchActivity", gcd.AndroidCwtchActivity)
|
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
|
|
|
|
engine.Load(qmlSource)
|
|
|
|
|
2019-07-06 00:50:03 +00:00
|
|
|
go loadNetworkingAndFiles(gcd, false, flagClientUI)
|
2019-05-13 21:01:08 +00:00
|
|
|
|
2019-06-21 21:58:40 +00:00
|
|
|
log.Infoln("Cwtch App starting app.Exec")
|
2019-05-13 21:01:08 +00:00
|
|
|
app.Exec()
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadACN() {
|
2019-02-14 02:53:36 +00:00
|
|
|
torpath := "tor"
|
|
|
|
if runtime.GOOS == "android" {
|
2020-10-21 23:58:57 +00:00
|
|
|
torpath = filepath.Join(androidBaseDir, "lib/libtor.so")
|
2019-08-10 00:31:22 +00:00
|
|
|
} else if runtime.GOOS == "windows" {
|
|
|
|
ex, err := os.Executable()
|
|
|
|
if err != nil {
|
|
|
|
ex = ""
|
|
|
|
}
|
|
|
|
exPath := filepath.Dir(ex)
|
2020-10-21 23:58:57 +00:00
|
|
|
torpath = filepath.Join(exPath, "tor-0.3.5.7", "Tor", "tor.exe")
|
2019-02-14 18:57:44 +00:00
|
|
|
} else {
|
|
|
|
dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
2020-10-21 23:58:57 +00:00
|
|
|
if _, err := os.Stat(filepath.Join(dir, "tor")); os.IsNotExist(err) {
|
|
|
|
if _, err := os.Stat(filepath.Join(dir, "deploy", "linux", "tor")); os.IsNotExist(err) {
|
2019-08-10 00:31:22 +00:00
|
|
|
log.Warnln("Cannot find bundled Tor")
|
2019-02-14 18:57:44 +00:00
|
|
|
} else {
|
2020-10-21 23:58:57 +00:00
|
|
|
torpath = filepath.Join(dir, "deploy", "linux", "tor")
|
2019-02-14 18:57:44 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-10-21 23:58:57 +00:00
|
|
|
torpath = filepath.Join(dir, "tor")
|
2019-02-14 18:57:44 +00:00
|
|
|
}
|
2019-02-14 02:53:36 +00:00
|
|
|
}
|
2020-09-21 21:31:45 +00:00
|
|
|
|
2020-10-13 21:22:48 +00:00
|
|
|
// generate a random socks and control port (not real random...these are port numbers...)
|
|
|
|
mrand.Seed(int64(time.Now().Nanosecond()))
|
|
|
|
port := mrand.Intn(1000) + 9600
|
|
|
|
controlPort := port + 1
|
2020-09-21 21:31:45 +00:00
|
|
|
|
2020-10-13 21:22:48 +00:00
|
|
|
// generate a random password (actually random, stored in memory, for the control port)
|
|
|
|
key := make([]byte, 64)
|
|
|
|
_, err := rand.Read(key)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate torrc on the fly
|
|
|
|
// TODO if we have been configured for it, use system tor (like orbot) - we need a way to config this in the UI first
|
2020-10-21 23:58:57 +00:00
|
|
|
tor.NewTorrc().WithSocksPort(port).WithOnionTrafficOnly().WithControlPort(controlPort).WithHashedPassword(base64.StdEncoding.EncodeToString(key)).Build(filepath.Join(the.CwtchDir, "tor", "torrc"))
|
2020-10-13 21:22:48 +00:00
|
|
|
the.ACN, err = tor.NewTorACNWithAuth(the.CwtchDir, torpath, controlPort, tor.HashedPasswordAuthenticator{base64.StdEncoding.EncodeToString(key)})
|
2020-09-21 21:31:45 +00:00
|
|
|
|
2020-10-13 21:22:48 +00:00
|
|
|
if err != nil {
|
|
|
|
// TODO: turn into UI error: status panel?
|
|
|
|
log.Errorf("Could not start Tor: %v", err)
|
|
|
|
os.Exit(1)
|
2019-02-04 22:34:16 +00:00
|
|
|
}
|
2019-05-13 21:01:08 +00:00
|
|
|
}
|
|
|
|
|
2019-11-08 21:34:12 +00:00
|
|
|
func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, clientUI bool) {
|
2019-07-06 00:50:03 +00:00
|
|
|
if service || clientUI || runtime.GOOS == "android" {
|
2020-10-21 23:58:57 +00:00
|
|
|
clientIn := filepath.Join(the.CwtchDir, "clientIn")
|
|
|
|
serviceIn := filepath.Join(the.CwtchDir, "serviceIn")
|
2019-06-21 21:58:40 +00:00
|
|
|
if service {
|
|
|
|
loadACN()
|
2019-07-23 20:40:54 +00:00
|
|
|
serviceBridge := bridge.NewPipeBridgeService(serviceIn, clientIn)
|
2019-06-21 21:58:40 +00:00
|
|
|
log.Infoln("Creating New App Service")
|
|
|
|
the.CwtchService = libapp.NewAppService(the.ACN, the.CwtchDir, serviceBridge)
|
|
|
|
} else {
|
2019-07-10 20:38:56 +00:00
|
|
|
clientBridge := bridge.NewPipeBridgeClient(clientIn, serviceIn)
|
2019-06-21 21:58:40 +00:00
|
|
|
log.Infoln("Creating New App Client")
|
|
|
|
the.CwtchApp = libapp.NewAppClient(the.CwtchDir, clientBridge)
|
|
|
|
}
|
|
|
|
} else {
|
2020-10-21 23:58:57 +00:00
|
|
|
if gcd.GlobalSettings.PreviousPid != -1 {
|
|
|
|
// Todo: Check if there is an older version of the ACN running...
|
|
|
|
log.Debugf("checking to see if we shutdown the previous ACN (%v)", gcd.GlobalSettings.PreviousPid)
|
|
|
|
|
|
|
|
os2.CheckProcessAndKill(uint64(gcd.GlobalSettings.PreviousPid), "tor")
|
|
|
|
}
|
|
|
|
|
2019-05-13 21:01:08 +00:00
|
|
|
loadACN()
|
2020-11-13 20:13:53 +00:00
|
|
|
pid, err := the.ACN.GetPID()
|
2020-10-21 23:58:57 +00:00
|
|
|
if err == nil {
|
|
|
|
gcd.GlobalSettings.PreviousPid = int64(pid)
|
|
|
|
ui.WriteGlobalSettings(gcd.GlobalSettings)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
log.Errorf("error fetching pid from ACN %v", err)
|
|
|
|
}
|
|
|
|
|
2019-06-21 21:58:40 +00:00
|
|
|
log.Infoln("Creating New App")
|
|
|
|
the.CwtchApp = libapp.NewApp(the.ACN, the.CwtchDir)
|
2019-05-13 21:01:08 +00:00
|
|
|
}
|
2019-02-04 22:34:16 +00:00
|
|
|
|
2019-06-21 21:58:40 +00:00
|
|
|
if !service {
|
|
|
|
the.AppBus = the.CwtchApp.GetPrimaryBus()
|
2019-07-24 20:51:20 +00:00
|
|
|
subscribed := make(chan bool)
|
2019-10-22 18:55:16 +00:00
|
|
|
go handlers.App(gcd, subscribed, clientUI)
|
2020-11-04 21:41:10 +00:00
|
|
|
go servers.LaunchServiceManager(gcd, the.ACN, path.Join(the.CwtchDir, "servers"))
|
2019-07-24 20:51:20 +00:00
|
|
|
<-subscribed
|
2019-07-23 20:40:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !service && !clientUI {
|
|
|
|
the.CwtchApp.LoadProfiles(the.AppPassword)
|
2019-06-21 21:58:40 +00:00
|
|
|
}
|
2018-11-28 22:14:02 +00:00
|
|
|
}
|