Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CLI program #1260

Merged
merged 19 commits into from
Dec 16, 2024
Merged
80 changes: 80 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"context"
"os"
"os/signal"
"sync"
"syscall"
"time"

"github.com/getlantern/golog"
"github.com/getlantern/lantern-client/desktop/app"

"github.com/pterm/pterm"
)

var (
log = golog.LoggerFor("lantern")
)

type cliClient struct {
app *app.App
mu sync.Mutex
}

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-signalChan
pterm.Warning.Println("Received shutdown signal.")
cancel()
}()

client := &cliClient{}
client.start(ctx)
defer client.stop()

<-ctx.Done()
}

func (client *cliClient) start(ctx context.Context) {
client.mu.Lock()
defer client.mu.Unlock()
if client.app != nil {
pterm.Warning.Println("Lantern is already running")
return
}

// create new instance of Lantern app
app, err := app.NewApp()
if err != nil {
pterm.Error.Printf("Unable to initialize app: %v", err)
return
}
client.app = app

// Run Lantern in the background
go app.Run(ctx)
}

func (client *cliClient) stop() {
client.mu.Lock()
defer client.mu.Unlock()
if client.app == nil {
// Lantern is not running, no cleanup needed
return
}

pterm.Info.Println("Stopping Lantern...")
client.app.Exit(nil)
client.app = nil

// small delay to give Lantern time to cleanup
time.Sleep(1 * time.Second)
pterm.Success.Println("Lantern stopped successfully.")
}
158 changes: 77 additions & 81 deletions desktop/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,17 @@ type App struct {
}

// NewApp creates a new desktop app that initializes the app and acts as a moderator between all desktop components.
func NewApp() *App {
func NewApp() (*App, error) {
// initialize app config and flags based on environment variables
flags, err := initializeAppConfig()
if err != nil {
log.Fatalf("failed to initialize app config: %w", err)
return nil, fmt.Errorf("failed to initialize app config: %w", err)
}
return NewAppWithFlags(flags, flags.ConfigDir)
}

// NewAppWithFlags creates a new instance of App initialized with the given flags and configDir
func NewAppWithFlags(flags flashlight.Flags, configDir string) *App {
func NewAppWithFlags(flags flashlight.Flags, configDir string) (*App, error) {
if configDir == "" {
log.Debug("Config directory is empty, using default location")
configDir = appdir.General(common.DefaultAppName)
Expand Down Expand Up @@ -141,10 +141,10 @@ func NewAppWithFlags(flags flashlight.Flags, configDir string) *App {
app.translations.Set(os.DirFS("locale/translation"))

if e := app.configService.StartService(app.ws); e != nil {
app.Exit(fmt.Errorf("unable to register config service: %q", e))
return nil, fmt.Errorf("unable to register config service: %q", e)
}

return app
return app, nil
}

// Run starts the app.
Expand All @@ -156,88 +156,84 @@ func (app *App) Run(ctx context.Context) {
}
}()

// Run below in separate goroutine as config.Init() can potentially block when Lantern runs
// for the first time. User can still quit Lantern through systray menu when it happens.
go func() {
log.Debug(app.Flags)
userConfig := func() common.UserConfig {
return settings.UserConfig(app.Settings())
}
proClient := proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), userConfig)
authClient := auth.NewClient(fmt.Sprintf("https://%s", common.DFBaseUrl), userConfig)
log.Debug(app.Flags)
userConfig := func() common.UserConfig {
return settings.UserConfig(app.Settings())
}
proClient := proclient.NewClient(fmt.Sprintf("https://%s", common.ProAPIHost), userConfig)
authClient := auth.NewClient(fmt.Sprintf("https://%s", common.DFBaseUrl), userConfig)

app.mu.Lock()
app.proClient = proClient
app.authClient = authClient
app.mu.Unlock()
app.mu.Lock()
app.proClient = proClient
app.authClient = authClient
app.mu.Unlock()

settings := app.Settings()
settings := app.Settings()

if app.Flags.ProxyAll {
// If proxyall flag was supplied, force proxying of all
settings.SetProxyAll(true)
}
if app.Flags.ProxyAll {
// If proxyall flag was supplied, force proxying of all
settings.SetProxyAll(true)
}

listenAddr := app.Flags.Addr
if listenAddr == "" {
listenAddr = settings.GetAddr()
}
if listenAddr == "" {
listenAddr = defaultHTTPProxyAddress
}
listenAddr := app.Flags.Addr
if listenAddr == "" {
listenAddr = settings.GetAddr()
}
if listenAddr == "" {
listenAddr = defaultHTTPProxyAddress
}

socksAddr := app.Flags.SocksAddr
if socksAddr == "" {
socksAddr = settings.GetSOCKSAddr()
}
if socksAddr == "" {
socksAddr = defaultSOCKSProxyAddress
}
socksAddr := app.Flags.SocksAddr
if socksAddr == "" {
socksAddr = settings.GetSOCKSAddr()
}
if socksAddr == "" {
socksAddr = defaultSOCKSProxyAddress
}

if app.Flags.Timeout > 0 {
go func() {
time.AfterFunc(app.Flags.Timeout, func() {
app.Exit(errors.New("No succeeding proxy got after running for %v, global config fetched: %v, proxies fetched: %v",
app.Flags.Timeout, app.fetchedGlobalConfig.Load(), app.fetchedProxiesConfig.Load()))
})
}()
}
var err error
app.flashlight, err = flashlight.New(
common.DefaultAppName,
common.ApplicationVersion,
common.RevisionDate,
app.configDir,
app.Flags.VPN,
func() bool { return settings.GetDisconnected() }, // check whether we're disconnected
settings.GetProxyAll,
func() bool { return false }, // on desktop, we do not allow private hosts
settings.IsAutoReport,
app.Flags.AsMap(),
settings,
app.statsTracker,
app.IsPro,
settings.GetLanguage,
func(addr string) (string, error) { return addr, nil }, // no dnsgrab reverse lookups on desktop
// Dummy analytics function
func(category, action, label string) {},
flashlight.WithOnConfig(app.onConfigUpdate),
flashlight.WithOnProxies(app.onProxiesUpdate),
flashlight.WithOnSucceedingProxy(app.onSucceedingProxy),
)
if err != nil {
app.Exit(err)
return
}
app.beforeStart(ctx, listenAddr)

app.flashlight.Run(
listenAddr,
socksAddr,
app.afterStart,
func(err error) { _ = app.Exit(err) },
)
}()
if app.Flags.Timeout > 0 {
go func() {
time.AfterFunc(app.Flags.Timeout, func() {
app.Exit(errors.New("No succeeding proxy got after running for %v, global config fetched: %v, proxies fetched: %v",
app.Flags.Timeout, app.fetchedGlobalConfig.Load(), app.fetchedProxiesConfig.Load()))
})
}()
}
var err error
app.flashlight, err = flashlight.New(
common.DefaultAppName,
common.ApplicationVersion,
common.RevisionDate,
app.configDir,
app.Flags.VPN,
func() bool { return settings.GetDisconnected() }, // check whether we're disconnected
settings.GetProxyAll,
func() bool { return false }, // on desktop, we do not allow private hosts
settings.IsAutoReport,
app.Flags.AsMap(),
settings,
app.statsTracker,
app.IsPro,
settings.GetLanguage,
func(addr string) (string, error) { return addr, nil }, // no dnsgrab reverse lookups on desktop
// Dummy analytics function
func(category, action, label string) {},
flashlight.WithOnConfig(app.onConfigUpdate),
flashlight.WithOnProxies(app.onProxiesUpdate),
flashlight.WithOnSucceedingProxy(app.onSucceedingProxy),
)
if err != nil {
app.Exit(err)
return
}
app.beforeStart(ctx, listenAddr)

app.flashlight.Run(
listenAddr,
socksAddr,
app.afterStart,
func(err error) { _ = app.Exit(err) },
)
}

// IsFeatureEnabled checks whether or not the given feature is enabled by flashlight
Expand Down
3 changes: 2 additions & 1 deletion desktop/app/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ func startApp(t *testing.T, helper *integrationtest.Helper) (*App, error) {
Timeout: time.Duration(0),
}
ss := settings.EmptySettings()
a := NewAppWithFlags(flags, helper.ConfigDir)
a, err := NewAppWithFlags(flags, helper.ConfigDir)
require.NoError(t, err)
id := ss.GetUserID()
if id == 0 {
ss.SetUserIDAndToken(1, "token")
Expand Down
7 changes: 5 additions & 2 deletions desktop/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ func start() *C.char {
}
golog.SetPrepender(logging.Timestamped)

a = app.NewApp()
a.Run(context.Background())
a, err = app.NewApp()
if err != nil {
log.Fatal(err)
}
go a.Run(context.Background())

return C.CString("")
}
Expand Down
19 changes: 15 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,33 @@ require (
github.com/joho/godotenv v1.5.1
github.com/leekchan/accounting v1.0.0
github.com/moul/http2curl v1.0.0
github.com/pterm/pterm v0.12.80
github.com/shopspring/decimal v1.4.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.31.0
golang.org/x/crypto v0.28.0
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c
golang.org/x/net v0.30.0
golang.org/x/sys v0.28.0
golang.org/x/sys v0.27.0
google.golang.org/protobuf v1.35.2
nhooyr.io/websocket v1.8.17
)

require (
atomicgo.dev/cursor v0.2.0 // indirect
atomicgo.dev/keyboard v0.2.9 // indirect
atomicgo.dev/schedule v0.1.0 // indirect
github.com/alitto/pond/v2 v2.1.5 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/containerd/console v1.0.3 // indirect
github.com/getlantern/fronted v0.0.0-20241212194832-a55b6db2616e // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/term v0.26.0 // indirect
)

require (
Expand Down Expand Up @@ -332,8 +343,8 @@ require (
go.uber.org/zap v1.27.0 // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240808171019-573a1156607a // indirect
Expand Down
Loading
Loading