diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad9d203..84e4907 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,11 @@ jobs: run: go get . - name: Install golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v6 with: version: latest args: --exclude-use-default skip-cache: true - skip-pkg-cache: true - name: Run Gosec Security Scanner uses: securego/gosec@master @@ -51,7 +50,7 @@ jobs: args: -v build:ci - name: Upload Code Climate Report - uses: paambaati/codeclimate-action@v5.0.0 + uses: paambaati/codeclimate-action@v9.0.0 env: CC_TEST_REPORTER_ID: ${{secrets.CC_TEST_REPORTER_ID}} with: @@ -99,5 +98,4 @@ jobs: generate_release_notes: true files: | ./grafana-kiosk-v*/** - body: | - ** Draft release ** + body_path: CHANGELOG.md diff --git a/.gitignore b/.gitignore index 40c3ff3..e470c63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ bin/ coverage/ +coverage.html +coverage.out diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f074bd6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,35 @@ +{ + "cSpell.words": [ + "apikey", + "armv", + "autofit", + "bkgann", + "changeme", + "chromedp", + "dpms", + "Fieldname", + "fullscreen", + "gcom", + "goauth", + "googleusercontent", + "Heylin", + "idtoken", + "journalctl", + "Keycloak", + "KEYFILE", + "LXDE", + "lxpanel", + "lxsession", + "Magefile", + "myoauthid", + "noblank", + "Pasqualone", + "pcmanfs", + "Ramberg", + "Ronan", + "Stäheli", + "TOPNAV", + "XAUTHORITY", + "xset" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c0c91..cd4d0f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Change Log +## 1.1.0 + +NOTICE: BREAKING CHANGES! + +The configuration parameters and YAML configurations have significantly changed. + +- NEW configuration options: + - toggle for GPU (default: true) + +```YAML + --- +general: + gpu-enabled: false +``` + +- debug output to assist with setting up kiosk for first time (default: false) + +```YAML +--- +general: + debug: true +``` + +- start-maximized (default: true) +- start-fullscreen (default: true) + + chromedp.Flag("start-fullscreen", true), + chromedp.Flag("start-maximized", true), + +- PageLoadDelayMS, delay can be set to extend timeout to websocket (default: 2000)(verified working) +- Fixes: + - `--kiosk` toggle to fix for latest chrome/bookworm (default: true) + + chromedp.Flag("kiosk", true), + +incognito toggle + +geolocation toggle + + autofit now working from config + +- Update workflow +- Fix for GCOM login Issue [#132](https://github.com/grafana/grafana-kiosk/issues/132) + ## 1.0.8 - Fix for issue [#137](https://github.com/grafana/grafana-kiosk/issues/137) How to get rid of "Choose your search engine" window diff --git a/README.md b/README.md index 17ab2b4..7045038 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ The utility provides these options: - to a Grafana server (local account or bypass OAuth) - to a Grafana server with anonymous-mode enabled (same method used on [play.grafana.org](https://play.grafana.org)) - to a Grafana Cloud instance + - to a Grafana Cloud instance with Legacy API Token + - to a Grafana Cloud instance with Service Account Token - to a Grafana server with OAuth enabled - to an AWS Managed Grafana instance (both with and without MFA) - Switch to kiosk or kiosk-tv mode @@ -25,6 +27,10 @@ The utility provides these options: Additionally, an initialize option is provided to configure LXDE for Raspberry Pi Desktop. +## BREAKING CHANGES from 1.0 to 1.1 + +NOTICE: Release 1.1.0 contains breaking changes. Parameters and YAML configurations have significantly changed. + ## Installing on Linux Download the zip or tar file from [releases](https://github.com/grafana/grafana-kiosk/releases) @@ -40,6 +46,7 @@ The release file includes pre-built binaries. See table below for the types avai | linux | arm | ARM v6 | grafana-kiosk.linux.armv6 | | linux | arm | ARM v7 | grafana-kiosk.linux.armv7 | | darwin | amd64 | 64bit | grafana-kiosk.darwin.amd64 | +| darwin | arm64 | 64bit | grafana-kiosk.darwin.arm64 | | windows | amd64 | 64bit | grafana-kiosk.windows.amd64.exe | Extract the zip or tar file, and copy the appropriate binary to /usr/bin/grafana-kiosk: @@ -54,7 +61,7 @@ Extract the zip or tar file, and copy the appropriate binary to /usr/bin/grafana This application can run on most operating systems, but for linux some additional binaries are suggested for full support. -Suggesting Packages: +Suggested Packages: `unclutter` (for hiding mouse/cursor) `rng-tools` (for entropy issues) @@ -74,7 +81,7 @@ NOTE: Flags with parameters should use an "equals" idtoken audience -auto-login oauth_auto_login is enabled in grafana config - (set this flag along with the "local" login-method to bypass OAuth via the /login/local url and use a local grafana user/pass before continuing to the target URL) + (set this flag along with the "local" login-method to bypass OAuth via the /login/local url and use a local grafana user/pass before continuing to the target URL) -autofit Fit panels to screen (default true) -c string @@ -180,7 +187,7 @@ They can also be used instead of a configuration file. KIOSK_IDTOKEN_KEYFILE string JSON Credentials for idtoken (default "key.json") KIOSK_IDTOKEN_AUDIENCE string - Audience for idtoken, tpyically your oauth client id + Audience for idtoken, typically your oauth client id KIOSK_APIKEY_APIKEY string APIKEY Generated in Grafana Server ``` @@ -219,7 +226,7 @@ If you are using a self-signed certificate, you can remove the certificate error ./bin/grafana-kiosk -URL=https://localhost:3000 -login-method=local -username=admin -password=admin -kiosk-mode=tv -ignore-certificate-errors ``` -This will login to a grafana server, configured for AzureAD OAuth and has Oauth_auto_login is enabled, bypassing OAuth and using a manually setup local username and password. +This will login to a grafana server, configured for AzureAD OAuth and has OAuth_auto_login is enabled, bypassing OAuth and using a manually setup local username and password. ```bash ./bin/grafana-kiosk -URL=https://localhost:3000 -login-method=local -username=admin -password=admin -auto-login=true -kiosk-mode=tv @@ -247,21 +254,27 @@ This will take the browser to the default dashboard on play.grafana.org in fulls ./bin/grafana-kiosk -URL=https://play.grafana.org -login-method apikey --apikey "xxxxxxxxxxxxxxx" -kiosk-mode=tv ``` -### Grafana Server with Generic Oauth +Note: You can use a legacy API key, or the new Service Account method that has a token issued to it: + +See: [Create A Service Account] and [Service Account Tokens] + +For Legacy Keys see: [API Keys] + +### Grafana Server with Generic OAuth -This will login to a Generic Oauth service, configured on Grafana. Oauth_auto_login is disabeld. As Oauth provider is Keycloak used. +This will login to a Generic OAuth service, configured on Grafana. `oauth_auto_login` is disabled. An OAuth provider called `Keycloak` is used. ```bash go run pkg/cmd/grafana-kiosk/main.go -URL=https://my.grafana.oauth/playlists/play/1 -login-method=goauth -username=test -password=test ``` -This will login to a Generic Oauth service, configured on Grafana. Oauth_auto_login is disabeld. As Oauth provider is Keycloak used and also the login and password html input name is set. +This will login to a Generic OAuth service, configured on Grafana. `oauth_auto_login` is disabled. An OAuth provider called `Keycloak` is used and the login and password html input name is set. ```bash go run pkg/cmd/grafana-kiosk/main.go -URL=https://my.grafana.oauth/playlists/play/1 -login-method=goauth -username=test -password=test -field-username=username -field-password=password ``` -This will login to a Generic Oauth service, configured on Grafana. Oauth_auto_login is enabled. As Oauth provider is Keycloak used and also the login and password html input name is set. +This will login to a Generic OAuth service, configured on Grafana. `oauth_auto_login` is enabled. An OAuth provider called `Keycloak` is used and the login and password html input name is set. ```bash go run pkg/cmd/grafana-kiosk/main.go -URL=https://my.grafana.oauth/playlists/play/1 -login-method=goauth -username=test -password=test -field-username=username -field-password=password -auto-login=true diff --git a/config-example.yaml b/config-example.yaml index ccb27eb..3449bab 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -1,8 +1,9 @@ general: kiosk-mode: full autofit: true - lxde: true + lxde: false lxde-home: /home/pi + window-position: 1920,1000 target: login-method: anon @@ -16,4 +17,4 @@ target: goauth: auto-login: false fieldname-username: username - fieldname-password: password \ No newline at end of file + fieldname-password: password diff --git a/pkg/cmd/grafana-kiosk/main.go b/pkg/cmd/grafana-kiosk/main.go index 7a81ce2..2177b23 100644 --- a/pkg/cmd/grafana-kiosk/main.go +++ b/pkg/cmd/grafana-kiosk/main.go @@ -38,6 +38,7 @@ type Args struct { Password string UsernameField string PasswordField string + PageLoadDelayMS int64 WindowPosition string WindowSize string ScaleFactor string @@ -62,6 +63,7 @@ func ProcessArgs(cfg interface{}) Args { flagSettings.BoolVar(&processedArgs.AutoFit, "autofit", true, "Fit panels to screen") flagSettings.BoolVar(&processedArgs.LXDEEnabled, "lxde", false, "Initialize LXDE for kiosk mode") flagSettings.StringVar(&processedArgs.LXDEHome, "lxde-home", "/home/pi", "Path to home directory of LXDE user running X Server") + flagSettings.Int64Var(&processedArgs.PageLoadDelayMS, "pageload-delay-ms", 2000, "Milliseconds to wait for page to load") flagSettings.BoolVar(&processedArgs.IgnoreCertificateErrors, "ignore-certificate-errors", false, "Ignore SSL/TLS certificate error") flagSettings.BoolVar(&processedArgs.OauthAutoLogin, "auto-login", false, "oauth_auto_login is enabled in grafana config") flagSettings.StringVar(&processedArgs.UsernameField, "field-username", "username", "Fieldname for the username") @@ -118,13 +120,14 @@ func setEnvironment() { func summary(cfg *kiosk.Config) { // general - log.Println("AutoFit:", cfg.General.AutoFit) + log.Println("AutoFit:", cfg.GrafanaOptions.AutoFit) log.Println("LXDEEnabled:", cfg.General.LXDEEnabled) log.Println("LXDEHome:", cfg.General.LXDEHome) - log.Println("Mode:", cfg.General.Mode) - log.Println("WindowPosition:", cfg.General.WindowPosition) - log.Println("WindowSize:", cfg.General.WindowSize) - log.Println("ScaleFactor:", cfg.General.ScaleFactor) + log.Println("PageLoadDelayMS:", cfg.General.PageLoadDelayMS) + log.Println("GrafanaOptions - Kiosk Mode:", cfg.GrafanaOptions.KioskMode) + log.Println("WindowPosition:", cfg.ChromeDPFlags.WindowPosition) + log.Println("WindowSize:", cfg.ChromeDPFlags.WindowSize) + log.Println("ScaleFactor:", cfg.ChromeDPFlags.ScaleFactor) // target log.Println("URL:", cfg.Target.URL) log.Println("LoginMethod:", cfg.Target.LoginMethod) @@ -171,6 +174,9 @@ func main() { if err := cleanenv.ReadEnv(&cfg); err != nil { log.Println("Error reading config from environment", err) } + // + cfg.Bearer.APIKey = args.APIKey + // cfg.Target.URL = args.URL cfg.Target.LoginMethod = args.LoginMethod cfg.Target.Username = args.Username @@ -179,22 +185,32 @@ func main() { cfg.Target.IsPlayList = args.IsPlayList cfg.Target.UseMFA = args.UseMFA // - cfg.General.AutoFit = args.AutoFit + cfg.ChromeDPFlags.WindowPosition = args.WindowPosition + cfg.ChromeDPFlags.WindowSize = args.WindowSize + cfg.ChromeDPFlags.ScaleFactor = args.ScaleFactor + // cfg.General.LXDEEnabled = args.LXDEEnabled cfg.General.LXDEHome = args.LXDEHome - cfg.General.Mode = args.Mode - cfg.General.WindowPosition = args.WindowPosition - cfg.General.WindowSize = args.WindowSize - cfg.General.ScaleFactor = args.ScaleFactor + cfg.General.PageLoadDelayMS = args.PageLoadDelayMS // cfg.GoAuth.AutoLogin = args.OauthAutoLogin cfg.GoAuth.UsernameField = args.UsernameField cfg.GoAuth.PasswordField = args.PasswordField - + // + cfg.GrafanaOptions.AutoFit = args.AutoFit + cfg.GrafanaOptions.KioskMode = args.Mode + // cfg.IDToken.Audience = args.Audience cfg.IDToken.KeyFile = args.KeyFile + // + cfg.Target.URL = args.URL + cfg.Target.LoginMethod = args.LoginMethod + cfg.Target.Username = args.Username + cfg.Target.Password = args.Password + cfg.Target.IgnoreCertificateErrors = args.IgnoreCertificateErrors + cfg.Target.IsPlayList = args.IsPlayList + cfg.Target.UseMFA = args.UseMFA - cfg.APIKey.APIKey = args.APIKey } // make sure the url has content diff --git a/pkg/cmd/grafana-kiosk/main_test.go b/pkg/cmd/grafana-kiosk/main_test.go index f1f8389..82387fa 100644 --- a/pkg/cmd/grafana-kiosk/main_test.go +++ b/pkg/cmd/grafana-kiosk/main_test.go @@ -18,10 +18,14 @@ func TestMain(t *testing.T) { Version: "1.0.0", }, General: kiosk.General{ - AutoFit: true, - LXDEEnabled: true, - LXDEHome: "/home/pi", - Mode: "full", + LXDEEnabled: true, + LXDEHome: "/home/pi", + }, + GrafanaOptions: kiosk.GrafanaOptions{ + AutoFit: true, + KioskMode: "full", + }, + ChromeDPFlags: kiosk.ChromeDPFlags{ WindowPosition: "0,0", WindowSize: "1920,1080", ScaleFactor: "1.0", @@ -44,7 +48,7 @@ func TestMain(t *testing.T) { KeyFile: "/tmp/key.json", Audience: "clientid", }, - APIKey: kiosk.APIKey{ + Bearer: kiosk.Bearer{ APIKey: "abc", }, } @@ -69,12 +73,12 @@ func TestMain(t *testing.T) { oldArgs := os.Args defer func() { os.Args = oldArgs }() os.Args = []string{"grafana-kiosk", ""} - os.Setenv("KIOSK_AUTOFIT", "false") + os.Setenv("KIOSK_GRAFANA_AUTOFIT", "false") cfg := kiosk.Config{} if err := cleanenv.ReadEnv(&cfg); err != nil { log.Println("Error reading config from environment", err) } - So(cfg.General.AutoFit, ShouldBeFalse) + So(cfg.GrafanaOptions.AutoFit, ShouldBeFalse) }) }) // end of general options diff --git a/pkg/cmd/migrate-config/main.go b/pkg/cmd/migrate-config/main.go new file mode 100644 index 0000000..0c53dee --- /dev/null +++ b/pkg/cmd/migrate-config/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/ilyakaznacheev/cleanenv" + + "github.com/grafana/grafana-kiosk/pkg/kiosk" +) + +var ( + // Version this is set during build time using git tags + Version string +) + +// Args command-line parameters. +type Args struct { + ConfigPath string +} + +// ProcessArgs processes and handles CLI arguments. +func ProcessArgs(cfg interface{}) Args { + var processedArgs Args + + flagSettings := flag.NewFlagSet("migrate-config", flag.ContinueOnError) + flagSettings.StringVar(&processedArgs.ConfigPath, "c", "", "Path to configuration file (config.yaml)") + + fu := flagSettings.Usage + flagSettings.Usage = func() { + fu() + + envHelp, _ := cleanenv.GetDescription(cfg, nil) + + fmt.Fprintln(flagSettings.Output()) + fmt.Fprintln(flagSettings.Output(), envHelp) + } + + err := flagSettings.Parse(os.Args[1:]) + if err != nil { + os.Exit(-1) + } + + return processedArgs +} + +func summary(cfg *kiosk.Config) { + // general + log.Println("AutoFit:", cfg.GrafanaOptions.AutoFit) + log.Println("LXDEEnabled:", cfg.General.LXDEEnabled) + log.Println("LXDEHome:", cfg.General.LXDEHome) + log.Println("GrafanaOptions - Kiosk Mode:", cfg.GrafanaOptions.KioskMode) + log.Println("WindowPosition:", cfg.ChromeDPFlags.WindowPosition) + log.Println("WindowSize:", cfg.ChromeDPFlags.WindowSize) + log.Println("ScaleFactor:", cfg.ChromeDPFlags.ScaleFactor) + // target + log.Println("URL:", cfg.Target.URL) + log.Println("LoginMethod:", cfg.Target.LoginMethod) + log.Println("Username:", cfg.Target.Username) + log.Println("Password:", "*redacted*") + log.Println("IgnoreCertificateErrors:", cfg.Target.IgnoreCertificateErrors) + log.Println("IsPlayList:", cfg.Target.IsPlayList) + log.Println("UseMFA:", cfg.Target.UseMFA) + // goauth + log.Println("Fieldname AutoLogin:", cfg.GoAuth.AutoLogin) + log.Println("Fieldname Username:", cfg.GoAuth.UsernameField) + log.Println("Fieldname Password:", cfg.GoAuth.PasswordField) +} + +func main() { + var cfg kiosk.Config + fmt.Println("Migrate Config Version:", Version) + // set the version + cfg.BuildInfo.Version = Version + + // override + args := ProcessArgs(&cfg) + + // check if config specified + if args.ConfigPath != "" { + // read configuration from the file and then override with environment variables + if err := cleanenv.ReadConfig(args.ConfigPath, &cfg); err != nil { + log.Println("Error reading config file", err) + os.Exit(-1) + } else { + log.Println("Using config from", args.ConfigPath) + } + } + summary(&cfg) + +} diff --git a/pkg/cmd/migrate-config/main_test.go b/pkg/cmd/migrate-config/main_test.go new file mode 100644 index 0000000..2556937 --- /dev/null +++ b/pkg/cmd/migrate-config/main_test.go @@ -0,0 +1,43 @@ +package main + +import ( + "log" + "os" + "testing" + + "github.com/grafana/grafana-kiosk/pkg/kiosk" + "github.com/ilyakaznacheev/cleanenv" + . "github.com/smartystreets/goconvey/convey" +) + +// TestMigration checks kiosk command. +func TestMigration(t *testing.T) { + Convey("Given Previous YAML Configuration", t, func() { + Convey("Migrate YAML Configuration", func() { + Convey("General", func() { + cfg := kiosk.ConfigLegacy{} + pathToYAML := "../../../testdata/legacy-config-local.yaml" + if err := cleanenv.ReadConfig(pathToYAML, &cfg); err != nil { + log.Println("Error reading config file", err) + os.Exit(-1) + } else { + log.Println("Using config from", pathToYAML) + } + So(cfg.General.AutoFit, ShouldBeTrue) + So(cfg.General.Mode, ShouldEqual, "full") + So(cfg.General.LXDEEnabled, ShouldBeTrue) + So(cfg.General.LXDEEnabled, ShouldBeTrue) + So(cfg.General.WindowSize, ShouldEqual, "1920,1080") + // + So(cfg.Target.LoginMethod, ShouldEqual, "local") + So(cfg.Target.Username, ShouldEqual, "user1") + So(cfg.Target.Password, ShouldEqual, "changeme") + So(cfg.Target.IsPlayList, ShouldBeFalse) + So(cfg.Target.URL, ShouldEqual, "https://notplay.grafana.com") + So(cfg.Target.IgnoreCertificateErrors, ShouldBeFalse) + + // now migrate to new config + }) + }) + }) +} diff --git a/pkg/kiosk/anonymous_login.go b/pkg/kiosk/anonymous_login.go index 416b298..2a86498 100644 --- a/pkg/kiosk/anonymous_login.go +++ b/pkg/kiosk/anonymous_login.go @@ -39,7 +39,11 @@ func GrafanaKioskAnonymous(cfg *Config, messages chan string) { log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS) time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond) - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) /* diff --git a/pkg/kiosk/apikey_login.go b/pkg/kiosk/apikey_login.go index d5b8c73..7c1dcd3 100644 --- a/pkg/kiosk/apikey_login.go +++ b/pkg/kiosk/apikey_login.go @@ -40,18 +40,28 @@ func GrafanaKioskAPIKey(cfg *Config, messages chan string) { log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS) time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond) - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL(cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) - log.Println("Navigating to ", generatedURL) /* Launch chrome and look for main-view element */ + useKey := cfg.Bearer.APIKey + if useKey == "" { + useKey = cfg.Bearer.ServiceAccountToken + log.Println("Using Service Account Token") + } else { + log.Println("Using legacy API Key") + } headers := map[string]interface{}{ - "Authorization": "Bearer " + cfg.APIKey.APIKey, + "Access-Control-Allow-Origin": "*.grafana.net *.grafana.com *.grafana-ops.net", + "Authorization": "Bearer " + useKey, } + log.Println("Navigating to ", generatedURL) + if err := chromedp.Run(taskCtx, - network.Enable(), - network.SetExtraHTTPHeaders(network.Headers(headers)), network.Enable(), network.SetExtraHTTPHeaders(network.Headers(headers)), chromedp.Navigate(generatedURL), diff --git a/pkg/kiosk/aws_login.go b/pkg/kiosk/aws_login.go index 0fecf33..b2c4b77 100644 --- a/pkg/kiosk/aws_login.go +++ b/pkg/kiosk/aws_login.go @@ -39,7 +39,11 @@ func GrafanaKioskAWSLogin(cfg *Config, messages chan string) { panic(err) } - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) diff --git a/pkg/kiosk/config.go b/pkg/kiosk/config.go index 87a8f06..5eb4098 100644 --- a/pkg/kiosk/config.go +++ b/pkg/kiosk/config.go @@ -5,33 +5,45 @@ type BuildInfo struct { Version string `yaml:"version,omitempty"` } -// General non-site specific configuations +// General non-site specific configurations type General struct { - AutoFit bool `yaml:"autofit" env:"KIOSK_AUTOFIT" env-default:"true" env-description:"fit panels to screen"` - DebugEnabled bool `yaml:"debug" env:"KIOSK_DEBUG" env-default:"false" env-description:"enables debug output"` - GPUEnabled bool `yaml:"gpu-enabled" env:"KIOSK_GPU_ENABLED" env-default:"false" env-description:"disable GPU support"` - LXDEEnabled bool `yaml:"lxde" env:"KIOSK_LXDE_ENABLED" env-default:"false" env-description:"initialize LXDE for kiosk mode"` - LXDEHome string `yaml:"lxde-home" env:"KIOSK_LXDE_HOME" env-default:"/home/pi" env-description:"path to home directory of LXDE user running X Server"` - Mode string `yaml:"kiosk-mode" env:"KIOSK_MODE" env-default:"full" env-description:"[full|tv|disabled]"` - OzonePlatform string `yaml:"ozone-platform" env:"KIOSK_OZONE_PLATFORM" env-default:"" env-description:"Set ozone-platform option (wayland|cast|drm|wayland|x11)"` - PageLoadDelayMS int64 `yaml:"page-load-delay-ms" env:"KIOSK_PAGE_LOAD_DELAY_MS" env-default:"2000" env-description:"milliseconds to wait before expecting page load"` - ScaleFactor string `yaml:"scale-factor" env:"KIOSK_SCALE_FACTOR" env-default:"1.0" env-description:"Scale factor, like zoom"` - WindowPosition string `yaml:"window-position" env:"KIOSK_WINDOW_POSITION" env-default:"0,0" env-description:"Top Left Position of Kiosk"` - WindowSize string `yaml:"window-size" env:"KIOSK_WINDOW_SIZE" env-default:"" env-description:"Size of Kiosk in pixels (width,height)"` + LXDEEnabled bool `yaml:"lxde" env:"KIOSK_GENERAL_LXDE_ENABLED" env-default:"false" env-description:"initialize LXDE for kiosk mode"` + LXDEHome string `yaml:"lxde-home" env:"KIOSK_GENERAL_LXDE_HOME" env-default:"/home/pi" env-description:"path to home directory of LXDE user running X Server"` + PageLoadDelayMS int64 `yaml:"page-load-delay-ms" env:"KIOSK_GENERAL_PAGE_LOAD_DELAY_MS" env-default:"2000" env-description:"milliseconds to wait before expecting page load"` +} + +// GrafanaOptions grafana specific flags +type GrafanaOptions struct { + AutoFit bool `yaml:"autofit" env:"KIOSK_GRAFANA_AUTOFIT" env-default:"true" env-description:"fit panels to screen"` + KioskMode string `yaml:"kiosk-mode" env:"KIOSK_GRAFANA_MODE" env-default:"full" env-description:"[full|tv|disabled]"` +} + +// ChromeDPFlags flags specific to chrome +type ChromeDPFlags struct { + DebugEnabled bool `yaml:"debug" env:"KIOSK_CHROMEDP_DEBUG" env-default:"false" env-description:"enables debug output"` + GPUEnabled bool `yaml:"gpu-enabled" env:"KIOSK_CHROMEDP_GPU_ENABLED" env-default:"true" env-description:"Enable/Disable GPU support"` + IncognitoEnabled bool `yaml:"incognito-enabled" env:"KIOSK_CHROMEDP_INCOGNITO_ENABLED" env-default:"true" env-description:"Enable/Disable Incognito Mode"` + Kiosk bool `yaml:"kiosk,omitempty" env:"KIOSK_CHROMEDP_KIOSK" env-default:"true" env-description:"pass kiosk flag to chromedp"` + OzonePlatform string `yaml:"ozone-platform" env:"KIOSK_CHROMEDP_OZONE_PLATFORM" env-default:"" env-description:"Set ozone-platform option (wayland|cast|drm|wayland|x11)"` + ScaleFactor string `yaml:"scale-factor" env:"KIOSK_CHROMEDP_SCALE_FACTOR" env-default:"1.0" env-description:"Scale factor, like zoom"` + StartFullscreen bool `yaml:"start-fullscreen,omitempty" env:"KIOSK_CHROMEDP_START_FULLSCREEN" env-default:"true" env-description:"Scale factor, like zoom"` + StartMaximized bool `yaml:"start-maximized,omitempty" env:"KIOSK_CHROMEDP_START_MAXIMIZED" env-default:"true" env-description:"Scale factor, like zoom"` + WindowPosition string `yaml:"window-position" env:"KIOSK_CHROMEDP_WINDOW_POSITION" env-default:"0,0" env-description:"Top Left Position of Kiosk"` + WindowSize string `yaml:"window-size" env:"KIOSK_CHROMEDP_WINDOW_SIZE" env-default:"" env-description:"Size of Kiosk in pixels (width,height)"` } // Target the dashboard/playlist details type Target struct { - IgnoreCertificateErrors bool `yaml:"ignore-certificate-errors" env:"KIOSK_IGNORE_CERTIFICATE_ERRORS" env-description:"ignore SSL/TLS certificate errors" env-default:"false"` - IsPlayList bool `yaml:"playlist" env:"KIOSK_IS_PLAYLIST" env-default:"false" env-description:"URL is a playlist"` - LoginMethod string `yaml:"login-method" env:"KIOSK_LOGIN_METHOD" env-default:"anon" env-description:"[anon|local|gcom|goauth|idtoken|apikey]"` - Password string `yaml:"password" env:"KIOSK_LOGIN_PASSWORD" env-default:"guest" env-description:"password"` - URL string `yaml:"URL" env:"KIOSK_URL" env-default:"https://play.grafana.org" env-description:"URL to Grafana server"` - Username string `yaml:"username" env:"KIOSK_LOGIN_USER" env-default:"guest" env-description:"username"` - UseMFA bool `yaml:"use-mfa" env:"KIOSK_USE_MFA" env-default:"false" env-description:"MFA is enabled for given account"` + IgnoreCertificateErrors bool `yaml:"ignore-certificate-errors" env:"KIOSK_TARGET_IGNORE_CERTIFICATE_ERRORS" env-description:"ignore SSL/TLS certificate errors" env-default:"false"` + IsPlayList bool `yaml:"playlist" env:"KIOSK_TARGET_IS_PLAYLIST" env-default:"false" env-description:"URL is a playlist"` + LoginMethod string `yaml:"login-method" env:"KIOSK_TARGET_LOGIN_METHOD" env-default:"anon" env-description:"[anon|local|gcom|goauth|idtoken|apikey]"` + Password string `yaml:"password" env:"KIOSK_TARGET_LOGIN_PASSWORD" env-default:"guest" env-description:"password"` + URL string `yaml:"URL" env:"KIOSK_TARGET_URL" env-default:"https://play.grafana.org" env-description:"URL to Grafana server"` + Username string `yaml:"username" env:"KIOSK_TARGET_LOGIN_USER" env-default:"guest" env-description:"username"` + UseMFA bool `yaml:"use-mfa" env:"KIOSK_TARGET_USE_MFA" env-default:"false" env-description:"MFA is enabled for given account"` } -// GoAuth OAuth +// GoAuth Generic OAuth type GoAuth struct { AutoLogin bool `yaml:"auto-login" env:"KIOSK_GOAUTH_AUTO_LOGIN" env-description:"[false|true]"` UsernameField string `yaml:"fieldname-username" env:"KIOSK_GOAUTH_FIELD_USER" env-description:"Username html input name value"` @@ -44,17 +56,20 @@ type IDToken struct { Audience string `yaml:"idtoken-audience" env:"KIOSK_IDTOKEN_AUDIENCE" env-description:"Audience for idtoken, tpyically your oauth client id"` } -// APIKey APIKey for login -type APIKey struct { - APIKey string `yaml:"apikey" env:"KIOSK_APIKEY_APIKEY" env-description:"APIKEY"` +// Bearer Bearer parameter for login +type Bearer struct { + APIKey string `yaml:"api-key,omitempty" env:"KIOSK_BEARER_APIKEY" env-description:"Legacy API Key"` + ServiceAccountToken string `yaml:"service-account-token,omitempty" env:"KIOSK_BEARER_SERVICE_ACCOUNT_TOKEN" env-description:"Service Account Token"` } // Config configuration for backend. type Config struct { - BuildInfo BuildInfo `yaml:"buildinfo"` - General General `yaml:"general"` - Target Target `yaml:"target"` - GoAuth GoAuth `yaml:"goauth"` - IDToken IDToken `yaml:"idtoken"` - APIKey APIKey `yaml:"apikey"` + BuildInfo BuildInfo `yaml:"buildinfo"` + General General `yaml:"general"` + ChromeDPFlags ChromeDPFlags `yaml:"chromedp-options,omitempty"` + GrafanaOptions GrafanaOptions `yaml:"grafana-options,omitempty"` + Target Target `yaml:"target,omitempty"` + GoAuth GoAuth `yaml:"goauth,omitempty"` + IDToken IDToken `yaml:"idtoken,omitempty"` + Bearer Bearer `yaml:"bearer,omitempty"` } diff --git a/pkg/kiosk/config_legacy.go b/pkg/kiosk/config_legacy.go new file mode 100644 index 0000000..cbb9aa1 --- /dev/null +++ b/pkg/kiosk/config_legacy.go @@ -0,0 +1,55 @@ +package kiosk + +// ConfigLegacy configuration for backend. +type ConfigLegacy struct { + BuildInfo BuildInfo `yaml:"buildinfo"` + General LegacyGeneral `yaml:"general"` + Target LegacyTarget `yaml:"target"` + GoAuth LegacyGoAuth `yaml:"goauth"` + IDToken LegacyIDToken `yaml:"idtoken"` + APIKey LegacyAPIKey `yaml:"apikey"` +} + +// LegacyGeneral non-site specific configurations +type LegacyGeneral struct { + AutoFit bool `yaml:"autofit" env:"KIOSK_AUTOFIT" env-default:"true" env-description:"fit panels to screen"` + DebugEnabled bool `yaml:"debug" env:"KIOSK_DEBUG" env-default:"false" env-description:"enables debug output"` + GPUEnabled bool `yaml:"gpu-enabled" env:"KIOSK_GPU_ENABLED" env-default:"false" env-description:"disable GPU support"` + LXDEEnabled bool `yaml:"lxde" env:"KIOSK_LXDE_ENABLED" env-default:"false" env-description:"initialize LXDE for kiosk mode"` + LXDEHome string `yaml:"lxde-home" env:"KIOSK_LXDE_HOME" env-default:"/home/pi" env-description:"path to home directory of LXDE user running X Server"` + Mode string `yaml:"kiosk-mode" env:"KIOSK_MODE" env-default:"full" env-description:"[full|tv|disabled]"` + OzonePlatform string `yaml:"ozone-platform" env:"KIOSK_OZONE_PLATFORM" env-default:"" env-description:"Set ozone-platform option (wayland|cast|drm|wayland|x11)"` + PageLoadDelayMS int64 `yaml:"page-load-delay-ms" env:"KIOSK_PAGE_LOAD_DELAY_MS" env-default:"2000" env-description:"milliseconds to wait before expecting page load"` + ScaleFactor string `yaml:"scale-factor" env:"KIOSK_SCALE_FACTOR" env-default:"1.0" env-description:"Scale factor, like zoom"` + WindowPosition string `yaml:"window-position" env:"KIOSK_WINDOW_POSITION" env-default:"0,0" env-description:"Top Left Position of Kiosk"` + WindowSize string `yaml:"window-size" env:"KIOSK_WINDOW_SIZE" env-default:"" env-description:"Size of Kiosk in pixels (width,height)"` +} + +// LegacyTarget the dashboard/playlist details +type LegacyTarget struct { + IgnoreCertificateErrors bool `yaml:"ignore-certificate-errors" env:"KIOSK_IGNORE_CERTIFICATE_ERRORS" env-description:"ignore SSL/TLS certificate errors" env-default:"false"` + IsPlayList bool `yaml:"playlist" env:"KIOSK_IS_PLAYLIST" env-default:"false" env-description:"URL is a playlist"` + LoginMethod string `yaml:"login-method" env:"KIOSK_LOGIN_METHOD" env-default:"anon" env-description:"[anon|local|gcom|goauth|idtoken|apikey]"` + Password string `yaml:"password" env:"KIOSK_LOGIN_PASSWORD" env-default:"guest" env-description:"password"` + URL string `yaml:"URL" env:"KIOSK_URL" env-default:"https://play.grafana.org" env-description:"URL to Grafana server"` + Username string `yaml:"username" env:"KIOSK_LOGIN_USER" env-default:"guest" env-description:"username"` + UseMFA bool `yaml:"use-mfa" env:"KIOSK_USE_MFA" env-default:"false" env-description:"MFA is enabled for given account"` +} + +// LegacyGoAuth OAuth +type LegacyGoAuth struct { + AutoLogin bool `yaml:"auto-login" env:"KIOSK_GOAUTH_AUTO_LOGIN" env-description:"[false|true]"` + UsernameField string `yaml:"fieldname-username" env:"KIOSK_GOAUTH_FIELD_USER" env-description:"Username html input name value"` + PasswordField string `yaml:"fieldname-password" env:"KIOSK_GOAUTH_FIELD_PASSWORD" env-description:"Password html input name value"` +} + +// LegacyIDToken token based login +type LegacyIDToken struct { + KeyFile string `yaml:"idtoken-keyfile" env:"KIOSK_IDTOKEN_KEYFILE" env-default:"key.json" env-description:"JSON Credentials for idtoken"` + Audience string `yaml:"idtoken-audience" env:"KIOSK_IDTOKEN_AUDIENCE" env-description:"Audience for idtoken, typically your oauth client id"` +} + +// LegacyAPIKey APIKey for login +type LegacyAPIKey struct { + APIKey string `yaml:"apikey" env:"KIOSK_APIKEY_APIKEY" env-description:"APIKEY"` +} diff --git a/pkg/kiosk/grafana_com_login.go b/pkg/kiosk/grafana_com_login.go index e44e0c8..26110f6 100644 --- a/pkg/kiosk/grafana_com_login.go +++ b/pkg/kiosk/grafana_com_login.go @@ -39,7 +39,11 @@ func GrafanaKioskGCOM(cfg *Config, messages chan string) { log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS) time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond) - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) /* diff --git a/pkg/kiosk/grafana_genericoauth_login.go b/pkg/kiosk/grafana_genericoauth_login.go index 02e9ac6..553ef38 100644 --- a/pkg/kiosk/grafana_genericoauth_login.go +++ b/pkg/kiosk/grafana_genericoauth_login.go @@ -36,7 +36,11 @@ func GrafanaKioskGenericOauth(cfg *Config, messages chan string) { panic(err) } - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) diff --git a/pkg/kiosk/grafana_idtoken_login.go b/pkg/kiosk/grafana_idtoken_login.go index b0d8031..51d77fb 100644 --- a/pkg/kiosk/grafana_idtoken_login.go +++ b/pkg/kiosk/grafana_idtoken_login.go @@ -45,7 +45,11 @@ func GrafanaKioskIDToken(cfg *Config, messages chan string) { log.Printf("Sleeping %d MS before navigating to url", cfg.General.PageLoadDelayMS) time.Sleep(time.Duration(cfg.General.PageLoadDelayMS) * time.Millisecond) - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) diff --git a/pkg/kiosk/listen_chrome_events.go b/pkg/kiosk/listen_chrome_events.go index 2247666..2f5ffeb 100644 --- a/pkg/kiosk/listen_chrome_events.go +++ b/pkg/kiosk/listen_chrome_events.go @@ -33,8 +33,13 @@ func listenChromeEvents(taskCtx context.Context, cfg *Config, events chromeEvent _ = chromedp.Run(taskCtx, chromedp.Reload()) }() } + //case *network.CorsError: + // if cfg.ChromeDPFlags.DebugEnabled { + // log.Printf("Cors Error: %+v", ev) + // //log.Printf("CORS Event: %+v", string(ev)) + // } default: - if cfg.General.DebugEnabled { + if cfg.ChromeDPFlags.DebugEnabled { log.Printf("Unknown Event: %+v", ev) } } diff --git a/pkg/kiosk/local_login.go b/pkg/kiosk/local_login.go index 2951c66..19f7925 100644 --- a/pkg/kiosk/local_login.go +++ b/pkg/kiosk/local_login.go @@ -37,7 +37,11 @@ func GrafanaKioskLocal(cfg *Config, messages chan string) { panic(err) } - var generatedURL = GenerateURL(cfg.Target.URL, cfg.General.Mode, cfg.General.AutoFit, cfg.Target.IsPlayList) + var generatedURL = GenerateURL( + cfg.Target.URL, + cfg.GrafanaOptions.KioskMode, + cfg.GrafanaOptions.AutoFit, + cfg.Target.IsPlayList) log.Println("Navigating to ", generatedURL) /* diff --git a/pkg/kiosk/utils.go b/pkg/kiosk/utils.go index e5d9160..1db52e0 100644 --- a/pkg/kiosk/utils.go +++ b/pkg/kiosk/utils.go @@ -68,39 +68,51 @@ func generateExecutorOptions(dir string, cfg *Config) []chromedp.ExecAllocatorOp chromedp.Flag("check-for-update-interval", "31536000"), chromedp.Flag("disable-features", "Translate"), chromedp.Flag("disable-notifications", true), + //chromedp.Flag("disable-web-security", true), // polystat did load... chromedp.Flag("disable-overlay-scrollbar", true), chromedp.Flag("disable-search-engine-choice-screen", true), chromedp.Flag("disable-sync", true), chromedp.Flag("ignore-certificate-errors", cfg.Target.IgnoreCertificateErrors), - chromedp.Flag("incognito", true), - chromedp.Flag("kiosk", true), + chromedp.Flag("kiosk", cfg.ChromeDPFlags.Kiosk), chromedp.Flag("noerrdialogs", true), - chromedp.Flag("start-fullscreen", true), - chromedp.Flag("start-maximized", true), + chromedp.Flag("start-fullscreen", cfg.ChromeDPFlags.StartFullscreen), + chromedp.Flag("start-maximized", cfg.ChromeDPFlags.StartMaximized), chromedp.Flag("user-agent", userAgent), - chromedp.Flag("window-position", cfg.General.WindowPosition), + chromedp.Flag("window-position", cfg.ChromeDPFlags.WindowPosition), chromedp.UserDataDir(dir), } - if !cfg.General.GPUEnabled { + if cfg.ChromeDPFlags.StartFullscreen { execAllocatorOption = append( execAllocatorOption, - chromedp.Flag("disable-gpu", cfg.General.GPUEnabled)) + chromedp.Flag("incognito", true)) } - if cfg.General.OzonePlatform != "" { + + if cfg.ChromeDPFlags.IncognitoEnabled { + execAllocatorOption = append( + execAllocatorOption, + chromedp.Flag("incognito", true)) + } + + if !cfg.ChromeDPFlags.GPUEnabled { + execAllocatorOption = append( + execAllocatorOption, + chromedp.Flag("disable-gpu", true)) + } + if cfg.ChromeDPFlags.OzonePlatform != "" { execAllocatorOption = append( execAllocatorOption, - chromedp.Flag("ozone-platform", cfg.General.OzonePlatform)) + chromedp.Flag("ozone-platform", cfg.ChromeDPFlags.OzonePlatform)) } - if cfg.General.WindowSize != "" { + if cfg.ChromeDPFlags.WindowSize != "" { execAllocatorOption = append( execAllocatorOption, - chromedp.Flag("window-size", cfg.General.WindowSize)) + chromedp.Flag("window-size", cfg.ChromeDPFlags.WindowSize)) } - if cfg.General.ScaleFactor != "" { + if cfg.ChromeDPFlags.ScaleFactor != "" { execAllocatorOption = append( execAllocatorOption, - chromedp.Flag("force-device-scale-factor", cfg.General.ScaleFactor)) + chromedp.Flag("force-device-scale-factor", cfg.ChromeDPFlags.ScaleFactor)) } return execAllocatorOption diff --git a/testdata/config-local.yaml b/testdata/config-local.yaml index 3de9708..59e2a20 100644 --- a/testdata/config-local.yaml +++ b/testdata/config-local.yaml @@ -1,10 +1,14 @@ --- general: + lxde: true + lxde-home: /home/pi + +grafana-options: kiosk-mode: full autofit: true + +chromedp-options: chromium-disable-update: true - lxde: true - lxde-home: /home/pi target: login-method: local diff --git a/testdata/legacy-config-local.yaml b/testdata/legacy-config-local.yaml new file mode 100644 index 0000000..b9a2ff8 --- /dev/null +++ b/testdata/legacy-config-local.yaml @@ -0,0 +1,15 @@ +--- +general: + kiosk-mode: full + autofit: true + lxde: true + lxde-home: /home/pi + window-size: 1920,1080 + +target: + login-method: local + username: user1 + password: changeme + playlist: false + URL: https://notplay.grafana.com + ignore-certificate-errors: false