diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..06664a6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +dygma/*.json -diff linguist-generated=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..811badc --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +vendor +*.swo +*.swp +bin/mycli +bin/mycli-linux +bin/mycli-darwin + +./secrets + +*.spl diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c0e23a7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Wojciech Kozyra + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e2a0c1d --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +lua_format=~/.cache/nvim/myconfig/lua_lsp/3rd/EmmyLuaCodeStyle/build/CodeFormat/CodeFormat +lua_config=./configs/nvim/lua.editorconfig + +build: + CGO_ENABLED=0 go build -o bin/mycli ./mycli + +unit-tests: + go test ./... -timeout 60m + +e2e-tests: + go test -tags="integration" ./... -timeout 60m + +watch: + modd + +format: + golines --max-len=120 --base-formatter="gofumpt" -w . + find ./configs/nvim -iname '*.lua' | \ + xargs -I {} $(lua_format) format -c $(lua_config) -f {} -ow + +dev-e2e-test-env: + go run ./test/cmd + docker start -i system_setup_dev + +dev-e2e-test-env-rebuild: + -docker stop system_setup_dev + -docker rm system_setup_dev + -docker rmi system_setup_dev_img diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e82782 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# dotfiles + + +### Setup `mycli` inside Docker + +``` +curl -L -o mycli https://github.com/wkozyra95/dotfiles/releases/download/v0.0.0/mycli-linux && chmod +x mycli && ./mycli tool setup:environment:docker +``` diff --git a/action/action.go b/action/action.go new file mode 100644 index 0000000..f0f98c1 --- /dev/null +++ b/action/action.go @@ -0,0 +1,246 @@ +package action + +import ( + "fmt" + "strings" + + "github.com/davecgh/go-spew/spew" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +var log = logger.NamedLogger("action") + +func printAction(depth int, text string) { + split := strings.Split(text, "\n") + for i := range split { + split[i] = fmt.Sprintf("%s%s", strings.Repeat(" ", depth), split[i]) + } + fmt.Println(strings.Join(split, "\n")) +} + +type actionCtx struct { + print bool +} + +type Condition interface { + check(ctx actionCtx) (bool, error) + build() node + string() string +} + +type Object interface { + run(ctx actionCtx, depth int) error + build() node + string() string +} + +type node struct { + children []node +} + +type List []Object + +func (l List) build() node { + children := []node{} + for _, child := range l { + children = append(children, child.build()) + } + return node{ + children: children, + } +} + +func (l List) run(ctx actionCtx, depth int) error { + for _, action := range l { + if ctx.print { + lines := strings.Split(action.string(), "\n") + if action.string() == "condition" { + // empty + } else if len(lines) == 1 { + printAction(depth, fmt.Sprintf(" - %s", lines[0])) + } else { + printAction(depth, fmt.Sprintf(" - %s", lines[0])) + printAction(depth+1, strings.Join(lines[1:], "\n")) + } + } + err := action.run(ctx, depth+1) + if err != nil { + return err + } + } + return nil +} + +func (l List) string() string { + return "" +} + +type Optional struct { + Object Object +} + +func (o Optional) build() node { + return node{ + children: []node{o.Object.build()}, + } +} + +func (o Optional) run(ctx actionCtx, depth int) error { + err := o.Object.run(ctx, depth+1) + if err != nil { + log.Error(err) + if !prompt.ConfirmPrompt("Install failed, do you want to continue?") { + return err + } + } + return nil +} + +func (o Optional) string() string { + return "" +} + +type WithCondition struct { + If Condition + Then Object + Else Object +} + +func (a WithCondition) run(ctx actionCtx, depth int) error { + if ctx.print { + // This is hack, build action tree and print based on that + printAction(depth-1, fmt.Sprintf(" - If: %s", a.If.string())) + } + result, err := a.If.check(ctx) + if err != nil { + return err + } + if result { + if ctx.print { + printAction(depth, fmt.Sprintf("Then: %s", a.Then.string())) + } + return a.Then.run(ctx, depth+1) + } else if a.Else != nil { + if ctx.print { + printAction(depth, fmt.Sprintf("Else: %s", a.Else.string())) + } + return a.Else.run(ctx, depth+1) + } else { + if ctx.print { + printAction(depth, fmt.Sprintf("Else: do nothing")) + } + } + return nil +} + +func (a WithCondition) build() node { + return node{ + children: []node{ + a.If.build(), + a.Then.build(), + a.Else.build(), + }, + } +} + +func (a WithCondition) string() string { + return "condition" +} + +type SimpleActionBuilder[T any] struct { + CreateRun func(T) func() error + String func(T) string +} + +type SimpleAction struct { + runImpl func() error + description string +} + +func (s SimpleAction) run(ctx actionCtx, depth int) error { + return s.runImpl() +} + +func (s SimpleAction) build() node { + return node{} +} + +func (a SimpleAction) string() string { + return a.description +} + +func (s SimpleActionBuilder[T]) Init() func(T) Object { + return func(t T) Object { + description := "" + if s.String != nil { + description = s.String(t) + } else { + description = strings.TrimRight(spew.Sdump(t), "\n ") + } + return SimpleAction{ + runImpl: s.CreateRun(t), + description: description, + } + } +} + +var Func = SimpleActionBuilder[func() error]{ + CreateRun: func(fn func() error) func() error { + return func() error { + return fn() + } + }, +}.Init() + +type scope struct { + fn func() Object +} + +func (s scope) run(ctx actionCtx, depth int) error { + return s.fn().run(ctx, depth) +} + +func (s scope) build() node { + return s.fn().build() +} + +func (a scope) string() string { + return "" +} + +func Scope(fn func() Object) Object { + return scope{fn} +} + +var nop = SimpleActionBuilder[struct{}]{ + CreateRun: func(ignored struct{}) func() error { + return func() error { + return nil + } + }, +}.Init() + +func Nop() Object { + return nop(struct{}{}) +} + +var errAction = SimpleActionBuilder[error]{ + CreateRun: func(err error) func() error { + return func() error { + return err + } + }, +}.Init() + +func Err(err error) Object { + return errAction(err) +} + +func Run(o Object) error { + return o.run(actionCtx{print: true}, 0) +} + +func RunSilent(o Object) error { + return o.run(actionCtx{print: false}, 0) +} diff --git a/action/condition.go b/action/condition.go new file mode 100644 index 0000000..fc2a010 --- /dev/null +++ b/action/condition.go @@ -0,0 +1,158 @@ +package action + +import ( + "fmt" + "strings" + + "github.com/davecgh/go-spew/spew" +) + +type not struct { + Condition +} + +func Not(c Condition) Condition { + return not{Condition: c} +} + +func (a not) check(ctx actionCtx) (bool, error) { + result, resultErr := a.Condition.check(ctx) + return !result, resultErr +} + +func (a not) build() node { + return node{ + children: []node{a.Condition.build()}, + } +} + +func (a not) string() string { + return fmt.Sprintf("!%s", a.Condition.string()) +} + +type Const bool + +func (a Const) check(ctx actionCtx) (bool, error) { + return bool(a), nil +} + +func (a Const) build() node { + return node{} +} + +func (a Const) string() string { + return fmt.Sprint(bool(a)) +} + +type or struct { + cond1 Condition + cond2 Condition +} + +func Or(c1 Condition, c2 Condition) Condition { + return or{cond1: c1, cond2: c2} +} + +func (a or) build() node { + return node{ + children: []node{a.cond1.build(), a.cond2.build()}, + } +} + +func (a or) check(ctx actionCtx) (bool, error) { + r1, err1 := a.cond1.check(ctx) + if err1 != nil { + return false, err1 + } + if r1 { + return true, nil + } + r2, err2 := a.cond2.check(ctx) + if err2 != nil { + return false, err2 + } + return r2, nil +} + +func (a or) string() string { + return fmt.Sprintf("%s || %s", a.cond1.string(), a.cond2.string()) +} + +type and struct { + cond1 Condition + cond2 Condition +} + +func And(c1 Condition, c2 Condition) Condition { + return and{cond1: c1, cond2: c2} +} + +func (a and) build() node { + return node{ + children: []node{a.cond1.build(), a.cond2.build()}, + } +} + +func (a and) check(ctx actionCtx) (bool, error) { + r1, err1 := a.cond1.check(ctx) + if err1 != nil { + return false, err1 + } + if !r1 { + return false, nil + } + r2, err2 := a.cond2.check(ctx) + if err2 != nil { + return false, err2 + } + return r1 && r2, nil +} + +func (a and) string() string { + return fmt.Sprintf("%s && %s", a.cond1.string(), a.cond2.string()) +} + +type SimpleConditionBuilder[T any] struct { + String func(T) string + CreateCondition func(T) func() (bool, error) +} + +type SimpleCondition struct { + checkImpl func() (bool, error) + description string +} + +func (s SimpleCondition) check(ctx actionCtx) (bool, error) { + return s.checkImpl() +} + +func (s SimpleCondition) build() node { + return node{} +} + +func (a SimpleCondition) string() string { + return a.description +} + +func (s SimpleConditionBuilder[T]) Init() func(T) Condition { + return func(t T) Condition { + description := "" + if s.String != nil { + description = s.String(t) + } else { + description = strings.TrimRight(spew.Sdump(t), "\n ") + } + return SimpleCondition{ + checkImpl: s.CreateCondition(t), + description: description, + } + } +} + +var FuncCond = SimpleConditionBuilder[func() (bool, error)]{ + CreateCondition: func(fn func() (bool, error)) func() (bool, error) { + return func() (bool, error) { + return fn() + } + }, +}.Init() diff --git a/action/files.go b/action/files.go new file mode 100644 index 0000000..bdf2b09 --- /dev/null +++ b/action/files.go @@ -0,0 +1,136 @@ +package action + +import ( + "fmt" + "regexp" + "strings" + + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" + "github.com/wkozyra95/dotfiles/utils/http" +) + +type ensureSymlinkArgs struct { + source string + destination string +} + +var ensureSymlink = SimpleActionBuilder[ensureSymlinkArgs]{ + CreateRun: func(args ensureSymlinkArgs) func() error { + return func() error { + return file.EnsureSymlink(args.source, args.destination) + } + }, + String: func(args ensureSymlinkArgs) string { + return fmt.Sprintf("Symlink(%s -> %s)", args.destination, args.source) + }, +}.Init() + +func EnsureSymlink(source string, destination string) Object { + return ensureSymlink(ensureSymlinkArgs{source: source, destination: destination}) +} + +type ensureTextArgs struct { + Path string + Text string + Regexp *regexp.Regexp +} + +var ensureText = SimpleActionBuilder[ensureTextArgs]{ + CreateRun: func(args ensureTextArgs) func() error { + return func() error { + return file.EnsureTextWithRegexp(args.Path, args.Text, args.Regexp) + } + }, + String: func(args ensureTextArgs) string { + text := strings.Split(args.Text, "\n") + if len(text) > 3 { + text = text[0:2] + } + joined := strings.Join(text, "\n# ") + return fmt.Sprintf("ensure text %s\n# %s", args.Path, joined) + }, +}.Init() + +func EnsureText(path string, text string, rg *regexp.Regexp) Object { + return ensureText(ensureTextArgs{ + Path: path, + Text: text, + Regexp: rg, + }) +} + +type commandActionArgs struct { + args []string + cmd *exec.Cmd +} + +var commandAction = SimpleActionBuilder[commandActionArgs]{ + CreateRun: func(c commandActionArgs) func() error { + return func() error { + return c.cmd.Run(c.args[0], c.args[1:]...) + } + }, + String: func(c commandActionArgs) string { + return strings.Join(c.args, " ") + }, +}.Init() + +func ShellCommand(args ...string) Object { + return commandAction(commandActionArgs{ + args: append([]string{}, args...), + cmd: exec.Command().WithStdio(), + }) +} + +func Execute(cmd *exec.Cmd, args ...string) Object { + return commandAction(commandActionArgs{ + args: append([]string{}, args...), + cmd: cmd, + }) +} + +var PathExists = SimpleConditionBuilder[string]{ + CreateCondition: func(arg string) func() (bool, error) { + return func() (bool, error) { + return file.Exists(arg), nil + } + }, + String: func(s string) string { + return fmt.Sprintf("PathExists(%s)", s) + }, +}.Init() + +var CommandExists = SimpleConditionBuilder[string]{ + CreateCondition: func(arg string) func() (bool, error) { + return func() (bool, error) { + return exec.CommandExists(arg), nil + } + }, + String: func(s string) string { + return fmt.Sprintf("CommandExists(%s)", s) + }, +}.Init() + +type downloadFileArgs struct { + path string + url string +} + +var downloadFile = SimpleActionBuilder[downloadFileArgs]{ + CreateRun: func(dfa downloadFileArgs) func() error { + return func() error { + return http.DownloadFile(dfa.url, dfa.path) + } + }, + String: func(dfa downloadFileArgs) string { + return fmt.Sprintf("DownloadFile(%s -> %s)", dfa.url, dfa.path) + }, +}.Init() + +func DownloadFile(url string, path string) Object { + return downloadFile(downloadFileArgs{ + path: path, + url: url, + }) +} diff --git a/action/print.go b/action/print.go new file mode 100644 index 0000000..de2ec22 --- /dev/null +++ b/action/print.go @@ -0,0 +1,61 @@ +package action + +import ( + "fmt" + "strings" +) + +type printer struct { + printFn func(string) + depth int + current int + shouldAddNewlineIfMultiline bool + isMultiline bool +} + +func (p *printer) startLevel() { + p.depth += 2 +} + +func (p *printer) startMultilineLevel() { + p.depth += 2 + p.isMultiline = true +} + +func (p *printer) addNewlineIfMultiline() { + p.shouldAddNewlineIfMultiline = true +} + +func (p *printer) endLevel() { + p.depth -= 2 +} + +func (p *printer) print(textArg string) { + text := textArg + if p.shouldAddNewlineIfMultiline { + if p.isMultiline || strings.Contains(textArg, "\n") { + text = "\n" + text + } + p.shouldAddNewlineIfMultiline = false + } + p.isMultiline = false + split := strings.Split(text, "\n") + if split[0] != "" { + suffixLength := p.depth - p.current + if suffixLength < 0 { + suffixLength = 0 + } + split[0] = fmt.Sprintf("%s%s", strings.Repeat(" ", suffixLength), split[0]) + } + for i := range split[1:] { + split[i+1] = fmt.Sprintf("%s%s", strings.Repeat(" ", p.depth), split[i+1]) + } + result := strings.Join(split, "\n") + + if len(split) == 1 { + p.current += len(split[0]) + } else { + p.current = len(split[len(split)-1]) + } + p.printFn(result) +} diff --git a/action/print_test.go b/action/print_test.go new file mode 100644 index 0000000..03f739f --- /dev/null +++ b/action/print_test.go @@ -0,0 +1,86 @@ +package action + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + oneliner = "example one line output" + multiliner = `first line +second line +third line` +) + +var expectedOutput = ` - example one line output + - first line + second line + third line + - example one line output + - If: true + Then: example one line output + - If: true + Then: + first line + second line + third line + - If: true + Then: + - first line + second line + third line + - first line + second line + third line + - If: true + Then: example one line output + - first line + second line + third line` + +var fakeAction = SimpleActionBuilder[string]{ + CreateRun: func(args string) func() error { + return func() error { + return nil + } + }, + String: func(args string) string { + return args + }, +}.Init() + +func TestNodePrint(t *testing.T) { + err := List{ + fakeAction(oneliner), + fakeAction(multiliner), + fakeAction(oneliner), + WithCondition{ + If: Const(true), + Then: fakeAction(oneliner), + }, + WithCondition{ + If: Const(true), + Then: fakeAction(multiliner), + }, + WithCondition{ + If: Const(true), + Then: List{ + fakeAction(multiliner), + fakeAction(multiliner), + }, + }, + List{ + WithCondition{ + If: Const(true), + Then: fakeAction(oneliner), + }, + List{ + List{ + fakeAction(multiliner), + }, + }, + }, + }.run(actionCtx{}, 0) + assert.Equal(t, nil, err) +} diff --git a/api/alacritty.go b/api/alacritty.go new file mode 100644 index 0000000..c1d59f0 --- /dev/null +++ b/api/alacritty.go @@ -0,0 +1,47 @@ +package api + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type AlacrittyConfig struct { + Command string + Args []string + Cwd string +} + +func AlacrittyCall(params AlacrittyConfig) error { + config := map[string]interface{}{ + "name": "launch", + "command": params.Command, + "args": params.Args, + "cwd": params.Cwd, + } + rawJson, jsonMarshalErr := json.Marshal(config) + if jsonMarshalErr != nil { + return jsonMarshalErr + } + baseEncodedString := base64.StdEncoding.EncodeToString(rawJson) + return exec.Command().Run("alacritty", "-e", "mycli", "api", baseEncodedString) +} + +func AlacrittyRun(params map[string]interface{}) error { + args := []string{} + if params["args"] != nil { + args = make([]string, len(params["args"].([]interface{}))) + for i, arg := range params["args"].([]interface{}) { + args[i] = arg.(string) + } + } + for { + if err := exec.Command().WithStdio().WithCwd(params["cwd"].(string)).Run(params["command"].(string), args...); err != nil { + fmt.Printf(err.Error()) + } + fmt.Println("Press the Enter Key to continue") + fmt.Scanln() + } +} diff --git a/api/backup/backup.go b/api/backup/backup.go new file mode 100644 index 0000000..c396dbe --- /dev/null +++ b/api/backup/backup.go @@ -0,0 +1,272 @@ +package backup + +import ( + "errors" + "fmt" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/tool/drive" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +var log = logger.NamedLogger("backup") + +type KnownEncryptedVolume struct { + Label string + VolumeName string +} + +var secretsBackupVolume = KnownEncryptedVolume{ + Label: "SECRETS_BACKUP", + VolumeName: "secrets_backup", +} + +var dataBackupVolume = KnownEncryptedVolume{ + Label: "DATA_BACKUP", + VolumeName: "data_backup", +} + +var localDataBackupVolume = KnownEncryptedVolume{ + Label: "DATA_LOCAL_BACKUP", + VolumeName: "local_data_backup", +} + +var diskConfigs = []KnownEncryptedVolume{ + secretsBackupVolume, + dataBackupVolume, + localDataBackupVolume, +} + +func filterDrivesByName(name string, devices []drive.StorageDevice) (drive.Partition, error) { + for _, device := range devices { + for _, partition := range device.Partitions { + if partition.Label == name { + return partition, nil + } + } + } + return drive.Partition{}, fmt.Errorf("No partition %s", name) +} + +func withDataBackup( + ctx context.Context, + devices []drive.StorageDevice, + volume KnownEncryptedVolume, + pathPrefix string, + fn func(ctx context.Context, backupDestination string) action.Object, +) error { + partition, partitionsErr := filterDrivesByName(volume.Label, devices) + if partitionsErr != nil { + return partitionsErr + } + + return withEncryptedDrive( + ctx, + partition, + volume.VolumeName, + pathPrefix, + fn, + ) +} + +func withEncryptedDrive( + ctx context.Context, + partition drive.Partition, + mountName string, + pathPrefix string, + fn func(ctx context.Context, backupDestination string) action.Object, +) error { + mountPoint := fmt.Sprintf("/mnt/%s", mountName) + backupDir := fmt.Sprintf("/mnt/%s/%s", mountName, pathPrefix) + mapperPath := fmt.Sprintf("/dev/mapper/%s", mountName) + + actions := action.List{ + action.WithCondition{ + If: action.Not(action.PathExists(mapperPath)), + Then: action.ShellCommand("sudo", "cryptsetup", "luksOpen", partition.PartitionPath, mountName), + }, + action.ShellCommand("sudo", "mkdir", "-p", mountPoint), + action.ShellCommand("sudo", "mount", mapperPath, mountPoint), + action.ShellCommand("sudo", "mkdir", "-p", backupDir), + action.ShellCommand( + "sudo", "chown", fmt.Sprintf("%s:%s", ctx.Username, ctx.Username), backupDir, + ), + fn(ctx, backupDir), + action.ShellCommand("sudo", "umount", mountPoint), + action.ShellCommand("sudo", "cryptsetup", "luksClose", mountName), + } + + if err := action.Run(actions); err != nil { + return err + } + return nil +} + +func RestoreBackup(ctx context.Context) error { + devices, devicesErr := drive.GetStorageDevicesList() + if devicesErr != nil { + return devicesErr + } + + secretBackupErr := withDataBackup( + ctx, + devices, + secretsBackupVolume, + ctx.Environment, + func(ctx context.Context, backupDestination string) action.Object { + return action.List{ + action.WithCondition{ + If: action.Const(ctx.EnvironmentConfig.Backup.GpgKeyring), + Then: restoreGpgKeyringAction(backupDestination), + }, + restoreFilesAction(backupDestination, ctx.EnvironmentConfig.Backup.Secrets), + } + }, + ) + if secretBackupErr != nil { + log.Infof(secretBackupErr.Error()) + } + dataBackupErr := withDataBackup( + ctx, + devices, + dataBackupVolume, + ctx.Environment, + func(ctx context.Context, backupDestination string) action.Object { + return action.List{ + action.WithCondition{ + If: action.Const(ctx.EnvironmentConfig.Backup.GpgKeyring), + Then: restoreGpgKeyringAction(backupDestination), + }, + restoreFilesAction(backupDestination, ctx.EnvironmentConfig.Backup.Data), + } + }, + ) + if dataBackupErr != nil { + log.Infof(dataBackupErr.Error()) + } + + return nil +} + +func UpdateBackup(ctx context.Context) error { + devices, devicesErr := drive.GetStorageDevicesList() + if devicesErr != nil { + return devicesErr + } + + secretBackupErr := withDataBackup( + ctx, + devices, + secretsBackupVolume, + ctx.Environment, + func(ctx context.Context, backupDestination string) action.Object { + return action.List{ + action.WithCondition{ + If: isBitwardenAuthenticated(), + Then: backupBitwardenAction(backupDestination), + }, + action.WithCondition{ + If: action.Const(ctx.EnvironmentConfig.Backup.GpgKeyring), + Then: backupGpgKeyringAction(backupDestination), + }, + backupFilesAction(backupDestination, ctx.EnvironmentConfig.Backup.Secrets), + } + }, + ) + if secretBackupErr != nil { + log.Infof(secretBackupErr.Error()) + } + + dataBackupErr := withDataBackup( + ctx, + devices, + dataBackupVolume, + ctx.Environment, + func(ctx context.Context, backupDestination string) action.Object { + return action.List{ + action.WithCondition{ + If: isBitwardenAuthenticated(), + Then: backupBitwardenAction(backupDestination), + }, + action.WithCondition{ + If: action.Const(ctx.EnvironmentConfig.Backup.GpgKeyring), + Then: backupGpgKeyringAction(backupDestination), + }, + backupFilesAction(backupDestination, ctx.EnvironmentConfig.Backup.Data), + } + }, + ) + if dataBackupErr != nil { + log.Infof(dataBackupErr.Error()) + } + + localDataBackupErr := withDataBackup( + ctx, + devices, + localDataBackupVolume, + ctx.Environment, + func(ctx context.Context, backupDestination string) action.Object { + return action.List{ + action.ShellCommand("mkdir", "-p", backupDestination), + action.WithCondition{ + If: isBitwardenAuthenticated(), + Then: backupBitwardenAction(backupDestination), + }, + action.WithCondition{ + If: action.Const(ctx.EnvironmentConfig.Backup.GpgKeyring), + Then: backupGpgKeyringAction(backupDestination), + }, + backupFilesAction(backupDestination, ctx.EnvironmentConfig.Backup.Data), + } + }, + ) + if localDataBackupErr != nil { + log.Infof(localDataBackupErr.Error()) + } + + return nil +} + +func Connect(ctx context.Context) error { + devices, devicesErr := drive.GetStorageDevicesList() + if devicesErr != nil { + return devicesErr + } + + knownAndConnectedVolumes := []KnownEncryptedVolume{} + for _, volume := range diskConfigs { + _, partitionsErr := filterDrivesByName(volume.Label, devices) + if partitionsErr != nil { + continue + } + knownAndConnectedVolumes = append(knownAndConnectedVolumes, volume) + } + + selected, didSelect := prompt.SelectPrompt( + "Select volume to mount", + knownAndConnectedVolumes, + func(volume KnownEncryptedVolume) string { + return volume.VolumeName + }, + ) + if !didSelect { + return errors.New("No drive selected") + } + + connectionErr := withDataBackup( + ctx, + devices, + selected, + "", + func(ctx context.Context, backupDestination string) action.Object { + return action.Execute(exec.Command().WithCwd(backupDestination).WithStdio(), "bash", "-c", "zsh || true") + }, + ) + if connectionErr != nil { + log.Infof(connectionErr.Error()) + } + return nil +} diff --git a/api/backup/files.go b/api/backup/files.go new file mode 100644 index 0000000..38fddbc --- /dev/null +++ b/api/backup/files.go @@ -0,0 +1,87 @@ +package backup + +import ( + "fmt" + "os" + "path" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type fileSyncActionArgs struct { + src string + dst string +} + +var fileSyncAction = action.SimpleActionBuilder[fileSyncActionArgs]{ + CreateRun: func(args fileSyncActionArgs) func() error { + return func() error { + fileInfo, statErr := os.Stat(args.src) + if statErr != nil { + return statErr + } + src := args.src + dst := args.dst + if fileInfo.IsDir() { + src = fmt.Sprintf("%s/", src) + dst = fmt.Sprintf("%s/", dst) + } + return exec.Command(). + WithStdio(). + Run( + "bash", "-c", + strings.Join([]string{ + "rsync", + "--update", + "--delete", + "--progress", + "--recursive", + "--perms", + "--filter=':- .gitignore'", + src, dst, + }, " "), + ) + } + }, + String: func(args fileSyncActionArgs) string { + return fmt.Sprintf("Syncing files %s -> %s", args.src, args.dst) + }, +}.Init() + +func backupFilesAction(rootDir string, mapPaths map[string]string) action.Object { + dirPath := path.Join(rootDir, "files") + rsyncActions := []action.Object{} + for srcPath, destinationPath := range mapPaths { + rsyncActions = append(rsyncActions, action.WithCondition{ + If: action.PathExists(srcPath), + Then: fileSyncAction(fileSyncActionArgs{ + src: srcPath, + dst: path.Join(dirPath, destinationPath), + }), + }) + } + return append( + action.List{action.ShellCommand("mkdir", "-p", dirPath)}, + rsyncActions..., + ) +} + +func restoreFilesAction(rootDir string, mapPaths map[string]string) action.Object { + dirPath := path.Join(rootDir, "files") + rsyncActions := []action.Object{} + for srcPath, destinationPath := range mapPaths { + rsyncActions = append(rsyncActions, action.WithCondition{ + If: action.PathExists(path.Join(dirPath, destinationPath)), + Then: fileSyncAction(fileSyncActionArgs{ + src: path.Join(dirPath, destinationPath), + dst: srcPath, + }), + }) + } + return append( + action.List{action.ShellCommand("mkdir", "-p", dirPath)}, + rsyncActions..., + ) +} diff --git a/api/backup/secrets.go b/api/backup/secrets.go new file mode 100644 index 0000000..e94dcaf --- /dev/null +++ b/api/backup/secrets.go @@ -0,0 +1,71 @@ +package backup + +import ( + "fmt" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +func isBitwardenAuthenticated() action.Condition { + return action.FuncCond(func() (bool, error) { + if !exec.CommandExists("bw") { + return false, nil + } + err := exec.Command().Run("bw", "login", "--check") + return err == nil, nil + }) +} + +func backupBitwardenAction(rootDir string) action.Object { + backupFile := path.Join(rootDir, "bitwarden.json") + return action.List{ + action.ShellCommand("rm", "-rf", backupFile), + action.ShellCommand("bw", "export", "--format", "json", "--output", backupFile), + } +} + +func backupGpgKeyringAction(rootDir string) action.Object { + gpgPath := path.Join(rootDir, "gpg") + publicKeysPath := path.Join(gpgPath, "gpg_public_keys.asc") + privateKeysPath := path.Join(gpgPath, "gpg_private_keys.asc") + trustDbPath := path.Join(gpgPath, "gpg_trustdb.txt") + return action.List{ + action.ShellCommand("mkdir", "-p", gpgPath), + action.ShellCommand( + "bash", "-c", + fmt.Sprintf("gpg --armor --export > %s", publicKeysPath), + ), + action.ShellCommand( + "bash", "-c", + fmt.Sprintf("gpg --armor --export-secret-keys > %s", privateKeysPath), + ), + action.ShellCommand( + "bash", "-c", + fmt.Sprintf("gpg --export-ownertrust > %s", trustDbPath), + ), + } +} + +func restoreGpgKeyringAction(rootDir string) action.Object { + gpgPath := path.Join(rootDir, "gpg") + publicKeysPath := path.Join(gpgPath, "gpg_public_keys.asc") + privateKeysPath := path.Join(gpgPath, "gpg_private_keys.asc") + trustDbPath := path.Join(gpgPath, "gpg_trustdb.txt") + return action.List{ + action.ShellCommand("mkdir", "-p", gpgPath), + action.WithCondition{ + If: action.PathExists(publicKeysPath), + Then: action.ShellCommand("bash", "-c", fmt.Sprintf("gpg --import %s", publicKeysPath)), + }, + action.WithCondition{ + If: action.PathExists(privateKeysPath), + Then: action.ShellCommand("bash", "-c", fmt.Sprintf("gpg --import %s", privateKeysPath)), + }, + action.WithCondition{ + If: action.PathExists(trustDbPath), + Then: action.ShellCommand("bash", "-c", fmt.Sprintf("gpg --import-ownertrust %s", trustDbPath)), + }, + } +} diff --git a/api/context/context.go b/api/context/context.go new file mode 100644 index 0000000..47fa981 --- /dev/null +++ b/api/context/context.go @@ -0,0 +1,77 @@ +package context + +import ( + "fmt" + "os" + "os/user" + "path" + "regexp" + + "github.com/manifoldco/promptui" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/platform" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/config" + "github.com/wkozyra95/dotfiles/utils/file" +) + +type Context struct { + PkgInstaller api.PackageInstaller + Username string + Homedir string + Environment string + EnvironmentConfig env.EnvironmentConfig +} + +func CreateContext() Context { + userInfo, userErr := user.Current() + if userErr != nil { + panic(userErr) + } + homedir, homedirErr := os.UserHomeDir() + if homedirErr != nil { + panic(homedirErr) + } + pkgInstaller, pkgInstallerErr := platform.GetPackageManager() + if pkgInstallerErr != nil { + panic(pkgInstallerErr) + } + environment, environmentErr := ensureEnvironment(homedir) + if environmentErr != nil { + panic(environmentErr) + } + + return Context{ + Username: userInfo.Username, + Homedir: homedir, + Environment: environment, + EnvironmentConfig: config.GetConfig(), + PkgInstaller: pkgInstaller, + } +} + +func (c Context) FromHome(relative string) string { + return path.Join(c.Homedir, relative) +} + +func (c Context) FromEnvDir(relative string) string { + return path.Join(c.Homedir, ".dotfiles/env", c.Environment, relative) +} + +func ensureEnvironment(homedir string) (string, error) { + environment := os.Getenv("CURRENT_ENV") + if environment != "" { + return environment, nil + } + response, responseErr := (&promptui.Prompt{Label: "Specify name of this environment"}).Run() + if responseErr != nil { + return "", responseErr + } + ensureConfigErr := file.EnsureTextWithRegexp( + path.Join(homedir, ".zshrc.local"), + fmt.Sprintf("export CURRENT_ENV=\"%s\"", response), + regexp.MustCompile("export CURRENT_ENV.*"), + ) + os.Setenv("CURRENT_ENV", response) + return response, ensureConfigErr +} diff --git a/api/helper/directoryPreview.go b/api/helper/directoryPreview.go new file mode 100644 index 0000000..51d1c08 --- /dev/null +++ b/api/helper/directoryPreview.go @@ -0,0 +1,62 @@ +package helper + +import ( + "os" + "path" +) + +type DirectoryPreviewOptions struct { + MaxElements int +} + +type FileInfo struct { + Path string `json:"path"` + IsExecutable bool `json:"is_executable"` + IsDirectory bool `json:"is_directory"` + IsHidden bool `json:"is_hidden"` + Name string `json:"name"` + Symlink *string `json:"symlink"` +} + +func GetDirectoryPreview(directory string, opts DirectoryPreviewOptions) ([]FileInfo, error) { + results := make([]FileInfo, 0, opts.MaxElements) + dirContent, readDirErr := os.ReadDir(directory) + if readDirErr != nil { + return nil, readDirErr + } + for _, dirEntry := range dirContent { + if dirEntry.Name() == ".git" { + continue + } + filePath := path.Join(directory, dirEntry.Name()) + mode := dirEntry.Type() + symlink := (*string)(nil) + if mode&os.ModeSymlink != 0 { + linkDestination, linkDestinationErr := os.Readlink(filePath) + if linkDestinationErr != nil { + return nil, linkDestinationErr + } + symlink = &linkDestination + } + results = append(results, FileInfo{ + Path: filePath, + IsExecutable: mode&0o100 != 0, + IsDirectory: dirEntry.IsDir(), + IsHidden: dirEntry.Name()[0] == '.', + Name: dirEntry.Name(), + Symlink: symlink, + }) + } + orderedResults := make([]FileInfo, 0, len(results)) + for _, maybeDir := range results { + if maybeDir.IsDirectory { + orderedResults = append(orderedResults, maybeDir) + } + } + for _, maybeDir := range results { + if !maybeDir.IsDirectory { + orderedResults = append(orderedResults, maybeDir) + } + } + return orderedResults, nil +} diff --git a/api/language/go.go b/api/language/go.go new file mode 100644 index 0000000..739bae5 --- /dev/null +++ b/api/language/go.go @@ -0,0 +1,36 @@ +package language + +import ( + "fmt" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type goInstallActionArgs struct { + executable string + pkg string + shouldReinstall bool +} + +func GoInstallAction(executable string, pkg string, shouldReinstall bool) action.Object { + return goInstallAction(goInstallActionArgs{ + pkg: pkg, + executable: executable, + shouldReinstall: shouldReinstall, + }) +} + +var goInstallAction = action.SimpleActionBuilder[goInstallActionArgs]{ + CreateRun: func(p goInstallActionArgs) func() error { + return func() error { + if exec.CommandExists(p.executable) && !p.shouldReinstall { + return nil + } + return exec.Command().WithStdio().Run("go", "install", p.pkg) + } + }, + String: func(p goInstallActionArgs) string { + return fmt.Sprintf("go install %s", p.pkg) + }, +}.Init() diff --git a/api/language/node.go b/api/language/node.go new file mode 100644 index 0000000..1e6378b --- /dev/null +++ b/api/language/node.go @@ -0,0 +1,195 @@ +package language + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path" + "regexp" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" +) + +var tsconfig = map[string]any{ + "compilerOptions": map[string]any{ + "target": "es2020", + "lib": []string{"es2020"}, + "module": "commonjs", + "sourceMap": true, + "inlineSources": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + }, + "include": []string{ + "index.ts", + }, +} + +type nodePackageInstallActionArgs struct { + pkg string + shouldReinstall bool +} + +func NodePackageInstallAction(pkg string, shouldReinstall bool) action.Object { + return nodePackageInstallAction(nodePackageInstallActionArgs{ + pkg: pkg, + shouldReinstall: shouldReinstall, + }) +} + +var nodePackageInstallAction = action.SimpleActionBuilder[nodePackageInstallActionArgs]{ + CreateRun: func(p nodePackageInstallActionArgs) func() error { + return func() error { + return installNodePackage(p.pkg, p.shouldReinstall) + } + }, + String: func(p nodePackageInstallActionArgs) string { + return fmt.Sprintf("npm install -g %s", p.pkg) + }, +}.Init() + +func isVoltaPackageInstalled(pkg string) (bool, error) { + var stdout bytes.Buffer + if err := exec.Command().WithBufout(&stdout, &bytes.Buffer{}).Run("volta", "list", "--format", "plain"); err != nil { + return false, err + } + isInstalled, err := regexp.MatchString(regexp.QuoteMeta(fmt.Sprintf("package %s", pkg)), stdout.String()) + if err != nil { + return false, err + } + return isInstalled, nil +} + +func isGlobalNpmPackageInstalled(pkg string) (bool, error) { + var stdout bytes.Buffer + var stderr bytes.Buffer + exec.Command().WithBufout(&stdout, &stderr).Run("npm", "list", "-g", "--json") + var parsedJson struct { + Dependencies map[string]interface{} `json:"dependencies"` + } + if err := json.Unmarshal(stdout.Bytes(), &parsedJson); err != nil { + return false, err + } + for key := range parsedJson.Dependencies { + if key == pkg { + return true, nil + } + } + return false, nil +} + +func installNodePackage(p string, shouldReinstall bool) error { + if exec.CommandExists("volta") { + isInstalled, err := isVoltaPackageInstalled(p) + if err != nil { + return err + } + if isInstalled && !shouldReinstall { + return nil + } + return exec.Command().WithStdio().Run("volta", "install", p) + } else { + isInstalled, err := isGlobalNpmPackageInstalled(p) + if err != nil { + return err + } + if isInstalled && !shouldReinstall { + return nil + } + return exec.Command().WithStdio().Run("npm", "-g", "install", p) + } +} + +func NodePlaygroundCreate(playgroundPath string) error { + if file.Exists(playgroundPath) { + return nil + } + if err := exec.Command().Run("mkdir", "-p", playgroundPath); err != nil { + return err + } + if err := exec.Command().Run("touch", path.Join(playgroundPath, "index.ts")); err != nil { + return err + } + if err := exec.Command().WithCwd(playgroundPath).Run("npm", "init", "-y"); err != nil { + return err + } + rawTsconfigJson, marshalErr := json.Marshal(tsconfig) + if marshalErr != nil { + return marshalErr + } + if err := os.WriteFile(path.Join(playgroundPath, "tsconfig.json"), rawTsconfigJson, 0o644); err != nil { + return err + } + yarnErr := exec.Command(). + WithCwd(playgroundPath). + Run("yarn", "add", "--dev", "@types/node@14", "eslint", "eslint-plugin-import", "prettier", "typescript", "ts-node") + if yarnErr != nil { + return yarnErr + } + return nil +} + +func NodePlaygroundDelete(playgroundPath string) error { + if !file.Exists(playgroundPath) { + return nil + } + if err := os.RemoveAll(playgroundPath); err != nil { + return err + } + return nil +} + +func NodePlaygroundNodeShell(playgroundPath string) error { + return api.AlacrittyCall( + api.AlacrittyConfig{Command: "./node_modules/.bin/ts-node", Cwd: playgroundPath}, + ) +} + +func NodePlaygroundZshShell(playgroundPath string) error { + return api.AlacrittyCall( + api.AlacrittyConfig{Command: "zsh", Cwd: playgroundPath}, + ) +} + +func NodePlaygroundInstall(playgroundPath string, pkg string) error { + if err := exec.Command().WithCwd(playgroundPath).Run("yarn", "add", pkg); err != nil { + return err + } + splitPackage := strings.Split(pkg, "/") + if splitPackage[0] == "@types" { + return nil + } + // without @npmorg prefix + sanitizedName := splitPackage[len(splitPackage)-1] + splitNameSanitizedName := strings.Split(sanitizedName, "-") + if len(splitNameSanitizedName) > 1 { + for i, element := range splitNameSanitizedName { + if i >= 1 { + splitNameSanitizedName[i] = strings.Title(element) + } + } + } + // convert - to camel case + sanitizedName = strings.Join(splitNameSanitizedName, "") + + ensureErr := file.EnsureTextWithRegexp( + path.Join(playgroundPath, "index.ts"), + fmt.Sprintf("import %s from \"%s\";", sanitizedName, pkg), + regexp.MustCompile(fmt.Sprintf(".*%s.*", regexp.QuoteMeta(fmt.Sprintf("from \"%s\"", pkg)))), + ) + if ensureErr != nil { + return ensureErr + } + return nil +} diff --git a/api/pkg.go b/api/pkg.go new file mode 100644 index 0000000..acc989e --- /dev/null +++ b/api/pkg.go @@ -0,0 +1,41 @@ +package api + +import ( + "fmt" + "strings" + + "github.com/wkozyra95/dotfiles/action" +) + +type Package interface { + Install() error + String() string +} + +type PackageInstaller interface { + EnsurePackagerAction(homedir string) action.Object + UpgradePackages() error + DevelopmentTools() Package + ShellTools() Package + Desktop() Package +} + +var PackageInstallAction = action.SimpleActionBuilder[[]Package]{ + CreateRun: func(pkgs []Package) func() error { + return func() error { + for _, pkg := range pkgs { + if err := pkg.Install(); err != nil { + return err + } + } + return nil + } + }, + String: func(pkgs []Package) string { + packages := []string{} + for _, pkg := range pkgs { + packages = append(packages, fmt.Sprintf(" - %s", pkg.String())) + } + return fmt.Sprintf("Install system packages:\n%s", strings.Join(packages, "\n")) + }, +}.Init() diff --git a/api/platform/arch/bluetoothctl.go b/api/platform/arch/bluetoothctl.go new file mode 100644 index 0000000..7c2b526 --- /dev/null +++ b/api/platform/arch/bluetoothctl.go @@ -0,0 +1 @@ +package arch diff --git a/api/platform/arch/btrfs.go b/api/platform/arch/btrfs.go new file mode 100644 index 0000000..b6dcc39 --- /dev/null +++ b/api/platform/arch/btrfs.go @@ -0,0 +1,200 @@ +package arch + +import ( + "bytes" + "fmt" + "path" + "regexp" + "strings" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +var log = logger.NamedLogger("btrfs") + +type Volume struct { + Name string + Path string + ID string + ParrentID string + TopLevelID string +} + +func parseVolumeList(stdout string) ([]Volume, error) { + lines := strings.Split(strings.Trim(stdout, " \n"), "\n") + rg := regexp.MustCompile("ID ([0-9]+) gen ([0-9]+) parent ([0-9]+) top level ([0-9]+) path (.+)") + snapshots := []Volume{} + for _, line := range lines { + match := rg.FindStringSubmatch(line) + if len(match) == 0 { + return nil, fmt.Errorf("No match for line '%s'", line) + } + snapshots = append(snapshots, Volume{ + Name: match[5], + Path: match[5], + ID: match[1], + ParrentID: match[3], + TopLevelID: match[4], + }) + } + return snapshots, nil +} + +func getSubvolumeId(path string) (string, error) { + var stdout bytes.Buffer + cmdErr := exec.Command(). + WithBufout(&stdout, &bytes.Buffer{}). + Run("sudo", "btrfs", "subvolume", "show", path) + if cmdErr != nil { + return "", cmdErr + } + rg := regexp.MustCompile("Subvolume ID:\\s+([0-9]+)") + match := rg.FindStringSubmatch(stdout.String()) + if len(match) < 2 { + return "", fmt.Errorf("no match for subvolume id\n%s", stdout.String()) + } + return match[1], nil +} + +func getSubvolumeChildren(subvolID string) ([]Volume, error) { + var stdout bytes.Buffer + cmdErr := exec.Command(). + WithBufout(&stdout, &bytes.Buffer{}). + Run("sudo", "btrfs", "subvolume", "list", "-p", "/") + if cmdErr != nil { + return nil, cmdErr + } + subvol, subvolErr := parseVolumeList(stdout.String()) + if subvolErr != nil { + return nil, subvolErr + } + filterSubvol := []Volume{} + for _, subvol := range subvol { + if subvolID == subvol.ParrentID { + filterSubvol = append(filterSubvol, subvol) + } + } + return filterSubvol, nil +} + +func GetSnapshots() ([]Volume, error) { + var snaphotListRaw bytes.Buffer + cmdErr := exec.Command(). + WithBufout(&snaphotListRaw, &bytes.Buffer{}). + Run("sudo", "btrfs", "subvolume", "list", "-p", "-o", "/run/btrfs-root/__snapshot/") + if cmdErr != nil { + return nil, cmdErr + } + snapshots, snapshotsErr := parseVolumeList(snaphotListRaw.String()) + if snapshotsErr != nil { + return nil, snapshotsErr + } + filterSnapshots := []Volume{} + for _, snapshot := range snapshots { + if strings.HasPrefix(snapshot.Path, "__snapshot") { + filterSnapshots = append(filterSnapshots, snapshot) + } + } + return filterSnapshots, nil +} + +func SelectSnapshot() (Volume, error) { + snapshots, snapshotErr := GetSnapshots() + if snapshotErr != nil { + return Volume{}, snapshotErr + } + snapshot, isSelected := prompt.SelectPrompt( + "Select snapshot", + snapshots, + func(s Volume) string { return s.Name }, + ) + if !isSelected { + return snapshot, fmt.Errorf("No snapshots were selected") + } + return snapshot, nil +} + +func RestoreRootSnapshot() error { + snapshot, snapshotErr := SelectSnapshot() + if snapshotErr != nil { + return snapshotErr + } + rootPartition := "/run/btrfs-root/__current/root" + rootPartitionBackup := "/run/btrfs-root/__current/root-tmp" + actions := a.List{ + a.WithCondition{ + If: a.PathExists(rootPartitionBackup), + Then: a.ShellCommand("sudo", "btrfs", "subvolume", "delete", rootPartitionBackup), + }, + a.WithCondition{ + If: a.PathExists(rootPartition), + Then: a.ShellCommand("sudo", "mv", rootPartition, rootPartitionBackup), + }, + a.ShellCommand( + "sudo", + "btrfs", + "subvolume", + "snapshot", + path.Join("/run/btrfs-root/", snapshot.Path), + rootPartition, + ), + a.Func(func() error { + tmpRootId, tmpRootIdErr := getSubvolumeId(rootPartitionBackup) + if tmpRootIdErr != nil { + return tmpRootIdErr + } + childVolumes, childVolumesErr := getSubvolumeChildren(tmpRootId) + if childVolumesErr != nil { + return childVolumesErr + } + for _, child := range childVolumes { + pathSuffix := strings.TrimPrefix(child.Path, "__current/root-tmp/") + destinationPath := path.Join(rootPartition, pathSuffix) + srcPath := path.Join("/run/btrfs-root", child.Path) + fmt.Printf("reattach volume %s -> %s\n", srcPath, destinationPath) + if err := exec.Command().WithStdio().Run("sudo", "rmdir", destinationPath); err != nil { + return err + } + err := exec.Command().WithStdio().Run( + "sudo", "mv", + srcPath, + destinationPath, + ) + if err != nil { + return err + } + } + return nil + }), + a.ShellCommand("sudo", "btrfs", "subvolume", "delete", rootPartitionBackup), + } + return a.Run(actions) +} + +func CleanupSnapshots() error { + snapshots, snapshotErr := GetSnapshots() + if snapshotErr != nil { + return snapshotErr + } + selectedSnapshots := prompt.MultiselectPrompt( + "Delete snapshot", + snapshots, + func(s Volume) string { return s.Name }, + ) + log.Info("") + log.Info("Deleting snapshots") + for _, snapshot := range selectedSnapshots { + fullPath := path.Join("/run/btrfs-root", snapshot.Path) + log.Infof(" - %s", fullPath) + err := exec.Command(). + WithSudo(). + Run("btrfs", "subvolume", "delete", fullPath) + if err != nil { + return err + } + } + return nil +} diff --git a/api/platform/arch/upgrade.go b/api/platform/arch/upgrade.go new file mode 100644 index 0000000..a3b3492 --- /dev/null +++ b/api/platform/arch/upgrade.go @@ -0,0 +1,32 @@ +package arch + +import ( + "fmt" + "time" + + a "github.com/wkozyra95/dotfiles/action" +) + +func (y Yay) UpgradePackages() error { + timeString := time.Now().Format("20060102_150405") + yaySnapshot := "/run/btrfs-root/__snapshot/pre-yay-install" + timestampSnaphsot := fmt.Sprintf("/run/btrfs-root/__snapshot/root_%s", timeString) + actions := a.List{ + a.WithCondition{ + If: a.PathExists(yaySnapshot), + Then: a.ShellCommand("sudo", "btrfs", "subvolume", "delete", yaySnapshot), + }, + a.ShellCommand("sudo", "btrfs", "subvolume", "snapshot", "-r", "/", timestampSnaphsot), + a.ShellCommand( + "sudo", + "btrfs", + "subvolume", + "snapshot", + timestampSnaphsot, + yaySnapshot, + ), + a.ShellCommand("sudo", "grub-mkconfig", "-o", "/boot/grub/grub.cfg"), + a.ShellCommand("yay", "-Syu"), + } + return a.Run(actions) +} diff --git a/api/platform/arch/yay.go b/api/platform/arch/yay.go new file mode 100644 index 0000000..3233384 --- /dev/null +++ b/api/platform/arch/yay.go @@ -0,0 +1,123 @@ +package arch + +import ( + "bytes" + "fmt" + "path" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +var _ api.PackageInstaller = Yay{} + +type yayPackage []string + +func (p yayPackage) Install() error { + for _, pkg := range p { + var stdout bytes.Buffer + if err := exec.Command().WithBufout(&stdout, &bytes.Buffer{}).Run("yay", "-Qi", pkg); err == nil { + continue + } + installErr := exec.Command().WithStdio().Run("yay", "-S", pkg) + if installErr != nil && !prompt.ConfirmPrompt("Install failed, do you want to continue?") { + return installErr + } + } + return nil +} + +func (p yayPackage) String() string { + return fmt.Sprintf("yay -S %s", strings.Join(p, " ")) +} + +type Yay struct{} + +func (y Yay) DevelopmentTools() api.Package { + return yayPackage{"clang", "cmake", "go", "ninja"} +} + +func (y Yay) ShellTools() api.Package { + return yayPackage{ + "zsh", + "vim", + "neovim", + "htop", + "ripgrep", + "the_silver_searcher", + "fzf", + "diff-so-fancy", + "git", + "git-crypt", + "fd", + "unzip", + "python-pip", + "rsync", + "ranger", + "clojure-lsp-bin", + "jq", + } +} + +func (y Yay) Desktop() api.Package { + return yayPackage{ + "gnu-free-fonts", + "adobe-source-code-pro-fonts", + "ttf-nerd-fonts-symbols-2048-em-mono", + + "pipewire", + "pipewire-pulse", + "alsa-utils", + "pamixer", + "playerctl", + "bluez", + "bluez-utils", + "alacritty", + "wl-clipboard", + + "vlc", + "rhythmbox", + + "polkit", + "sway", + "swaybg", + "j4-dmenu-desktop", + "bemenu", + "grim", + "wf-recorder", + "slurp", + "swaylock", + "xdg-desktop-portal-wlr", + + "i3status", + "dmenu", + + "openssh", + "btop", + "grub-btrfs", + + "bitwarden-cli", + //"brscan4", + //"brother-dcp1510", + // "leiningen" -- for dactyl development + // "clojure" -- for dactyl development + + } +} + +func (y Yay) CustomPackageList(pkgs []string) api.Package { + return yayPackage(pkgs) +} + +func (y Yay) EnsurePackagerAction(homedir string) action.Object { + return action.WithCondition{ + If: action.Not(action.PathExists(path.Join(homedir, "yay"))), + Then: action.List{ + action.ShellCommand("git", "clone", "https://aur.archlinux.org/yay.git", path.Join(homedir, "/yay")), + action.ShellCommand("bash", "-c", "cd ~/yay && makepkg -si"), + }, + } +} diff --git a/api/platform/macos/brew.go b/api/platform/macos/brew.go new file mode 100644 index 0000000..7d62fc4 --- /dev/null +++ b/api/platform/macos/brew.go @@ -0,0 +1,73 @@ +package macos + +import ( + "bytes" + "fmt" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var log = logger.NamedLogger("brew") + +var _ api.PackageInstaller = Brew{} + +type brewPackage []string + +func (p brewPackage) Install() error { + for _, pkg := range p { + var stdout bytes.Buffer + checkErr := exec. + Command(). + WithBufout(&stdout, &bytes.Buffer{}). + WithEnv("HOMEBREW_NO_AUTO_UPDATE=1"). + Run("brew", "list", pkg) + if err := checkErr; err == nil { + continue + } + installErr := exec. + Command(). + WithStdio(). + WithEnv("HOMEBREW_NO_AUTO_UPDATE=1"). + Run("brew", "install", pkg) + if err := installErr; err != nil { + return err + } + } + return nil +} + +func (p brewPackage) String() string { + return fmt.Sprintf("brew install %s", strings.Join(p, " ")) +} + +type Brew struct{} + +func (y Brew) UpgradePackages() error { + log.Warn("not implemented") + return nil +} + +func (y Brew) DevelopmentTools() api.Package { + return brewPackage{"cmake", "go", "ninja"} +} + +func (y Brew) ShellTools() api.Package { + return brewPackage{"neovim", "htop", "ripgrep", "the_silver_searcher", "fzf", "git", "git-crypt"} +} + +func (y Brew) Desktop() api.Package { + return brewPackage{} +} + +func (y Brew) EnsurePackagerAction(homedir string) action.Object { + return action.WithCondition{ + If: action.Not(action.CommandExists("brew")), + Then: action.List{ + action.ShellCommand("curl", "-fsSL", "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"), + }, + } +} diff --git a/api/platform/pkg.go b/api/platform/pkg.go new file mode 100644 index 0000000..738a948 --- /dev/null +++ b/api/platform/pkg.go @@ -0,0 +1,37 @@ +package platform + +import ( + "bytes" + "fmt" + "strings" + + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/platform/arch" + "github.com/wkozyra95/dotfiles/api/platform/macos" + "github.com/wkozyra95/dotfiles/api/platform/ubuntu" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var log = logger.NamedLogger("platform") + +func GetPackageManager() (api.PackageInstaller, error) { + var stdout bytes.Buffer + var stderr bytes.Buffer + if err := exec.Command().WithBufout(&stdout, &stderr).Run("uname", "-s"); err != nil { + log.Error(stderr.String()) + panic(err) + } + osType := strings.Trim(stdout.String(), " \n\t\r") + if osType == "Linux" { + if exec.CommandExists("pacman") { + return arch.Yay{}, nil + } + if exec.CommandExists("apt-get") { + return ubuntu.Apt{}, nil + } + } else if osType == "Darwin" { + return macos.Brew{}, nil + } + return nil, fmt.Errorf("unknown platform (%s)", osType) +} diff --git a/api/platform/ubuntu/apt.go b/api/platform/ubuntu/apt.go new file mode 100644 index 0000000..cb3bc8e --- /dev/null +++ b/api/platform/ubuntu/apt.go @@ -0,0 +1,97 @@ +package ubuntu + +import ( + "fmt" + "os/user" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +var _ api.PackageInstaller = Apt{} + +type aptPackage []string + +func (p aptPackage) Install() error { + user, userErr := user.Current() + if userErr != nil { + return userErr + } + for _, pkg := range p { + cmd := exec.Command().WithStdio() + if user.Name != "root" { + cmd = cmd.WithSudo() + } + installErr := cmd.Run("apt-get", "install", "-y", pkg) + if installErr != nil && !prompt.ConfirmPrompt("Install failed, do you want to continue?") { + return installErr + } + } + return nil +} + +func (a Apt) UpgradePackages() error { + panic("Upgrading all packages is not supported") +} + +func (p aptPackage) String() string { + return fmt.Sprintf("apt-get install -y %s", strings.Join(p, " ")) +} + +type Apt struct{} + +func (a Apt) DevelopmentTools() api.Package { + return aptPackage{"clang", "cmake", "ninja-build"} +} + +func (a Apt) ShellTools() api.Package { + return aptPackage{ + "build-essential", + + "zsh", + "vim", + "neovim", + "htop", + "ripgrep", + "silversearcher-ag", + "fzf", + //"diff-so-fancy", + "git", + "git-crypt", + //"fd", + "unzip", + "python3-pip", + "rsync", + "ranger", + //"clojure-lsp-bin", + "jq", + + "ssh", + "btop", + "curl", + "wget", + + // for neovim form source + "gettext", "libtool-bin", "g++", "pkg-config", + } +} + +func (a Apt) Desktop() api.Package { + panic("unsupported package group; desktop") +} + +func (a Apt) CustomPackageList(pkgs []string) api.Package { + return aptPackage(pkgs) +} + +func (a Apt) EnsurePackagerAction(homedir string) action.Object { + user, _ := user.Current() + if user != nil && user.Name != "root" { + return action.ShellCommand("sudo", "apt-get", "update", "-y") + } else { + return action.ShellCommand("apt-get", "update", "-y") + } +} diff --git a/api/setup/arch_chroot.go b/api/setup/arch_chroot.go new file mode 100644 index 0000000..3bb95f2 --- /dev/null +++ b/api/setup/arch_chroot.go @@ -0,0 +1,332 @@ +package setup + +import ( + "bytes" + "fmt" + "path" + "regexp" + "strings" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/tool/drive" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +type installTarget struct { + device string + efiPartition string + mainPartition string +} + +func selectInstallTargetDrive(message string) (installTarget, error) { + target := installTarget{} + devices, readDevicesErr := drive.GetStorageDevicesList() + if readDevicesErr != nil { + return target, nil + } + + device, isSelected := prompt.SelectPrompt( + message, + devices, + func(d drive.StorageDevice) string { + return fmt.Sprintf("%s (size: %d MB)", d.DevicePath, d.Size/(1024*1024)) + }, + ) + if !isSelected { + return target, fmt.Errorf("No value was selected") + } + target.device = device.DevicePath + target.efiPartition = drive.GetPartitionPath(target.device, 2) + target.mainPartition = drive.GetPartitionPath(target.device, 3) + + return target, nil +} + +func ProvisionArchChroot() error { + username := "wojtek" + target, targetErr := selectInstallTargetDrive("Which device do you want to provision") + if targetErr != nil { + return targetErr + } + fromHome := func(relative string) string { + return path.Join("/mnt/btrfs-current/home", username, relative) + } + luksEnabled := true + rootPartition := target.mainPartition + if luksEnabled { + rootPartition = "/dev/mapper/root" + } + maybeLuksUuid := "" + actions := a.List{ + a.ShellCommand("timedatectl", "set-ntp", "true"), + a.ShellCommand("sgdisk", "-Z", target.device), + a.ShellCommand("sgdisk", "-a", "2048", "-o", target.device), + a.ShellCommand( + "sgdisk", + "-n", + "1::+1M", + "--typecode=1:ef02", + "--change-name=1:'BIOS boot partition'", + target.device, + ), // partition 1 (BIOS Boot Partition) + a.ShellCommand( + "sgdisk", + "-n", + "2::+300M", + "--typecode=2:ef00", + "--change-name=2:'EFI system partition'", + target.device, + ), // partition 2 (UEFI Boot Partition) + a.ShellCommand( + "sgdisk", + "-n", + "3::-0", + "--typecode=3:8300", + "--change-name=3:'Root'", + target.device, + ), // partition 3 (Root), default start, remaining + a.WithCondition{ + If: a.Not(a.PathExists("/sys/firmware/efi")), // if efi is not supported + Then: a.ShellCommand("sgdisk", "-A", "1:set:2", target.device), + }, + a.ShellCommand("mkfs.fat", "-F", "32", "-n", "EFIBOOT", target.efiPartition), + a.WithCondition{ + If: a.Const(luksEnabled), + Then: a.List{ + a.ShellCommand("cryptsetup", "-y", "-v", "luksFormat", "--type", "luks1", target.mainPartition), + a.ShellCommand("cryptsetup", "open", target.mainPartition, "root"), + a.ShellCommand("mkfs.btrfs", "--label", "BTRFS_ROOT", "--force", rootPartition), + }, + Else: a.List{ + a.ShellCommand("mkfs.btrfs", "--label", "BTRFS_ROOT", "--force", rootPartition), + }, + }, + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-root"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid", + rootPartition, + "/mnt/btrfs-root", + ), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-root/__current"), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-root/__snapshot"), + a.ShellCommand("btrfs", "subvolume", "create", "/mnt/btrfs-root/__current/root"), + a.ShellCommand("btrfs", "subvolume", "create", "/mnt/btrfs-root/__current/home"), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,subvol=__current/root", + rootPartition, + "/mnt/btrfs-current", + ), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current/boot/efi"), + a.ShellCommand("mount", target.efiPartition, "/mnt/btrfs-current/boot/efi"), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current/home"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid,subvol=__current/home", + rootPartition, + "/mnt/btrfs-current/home", + ), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current/run/btrfs-root"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid", + rootPartition, + "/mnt/btrfs-current/run/btrfs-root", + ), + a.ShellCommand( + "pacstrap", + "/mnt/btrfs-current", + "base", + "base-devel", + "btrfs-progs", + "linux", + "linux-firmware", + "networkmanager", + "grub-bios", + "grub", + "efibootmgr", + "os-prober", + ), + a.WithCondition{ + If: a.Const(luksEnabled), + Then: a.Scope(func() a.Object { + var stdout bytes.Buffer + err := exec.Command(). + WithBufout(&stdout, &bytes.Buffer{}). + Run("blkid", "-s", "UUID", "-o", "value", target.mainPartition) + if err != nil { + return a.Err(err) + } + maybeLuksUuid = strings.Trim(stdout.String(), " \n") + return a.List{ + a.ShellCommand( + "dd", + "bs=512", + "count=4", + "if=/dev/random", + "of=/mnt/btrfs-current/root/cryptlvm.keyfile", + "iflag=fullblock", + ), + a.ShellCommand("chmod", "000", "/mnt/btrfs-current/root/cryptlvm.keyfile"), + a.ShellCommand( + "cryptsetup", + "-v", + "luksAddKey", + target.mainPartition, + "/mnt/btrfs-current/root/cryptlvm.keyfile", + ), + a.EnsureText( + "/mnt/btrfs-current/etc/default/grub", + fmt.Sprintf( + "GRUB_CMDLINE_LINUX=\"cryptdevice=UUID=%s:root cryptkey=rootfs:/root/cryptlvm.keyfile\"", + maybeLuksUuid, + ), + regexp.MustCompile(".*GRUB_CMDLINE_LINUX=.*"), + ), + a.EnsureText( + "/mnt/btrfs-current/etc/default/grub", + "GRUB_ENABLE_CRYPTODISK=y", + regexp.MustCompile(".*GRUB_ENABLE_CRYPTODISK.*"), + ), + } + }), + }, + a.EnsureText( + "/mnt/btrfs-current/root/.bash_history", + fmt.Sprintf( + "/home/%s/.dotfiles/bin/mycli tool setup:arch:desktop", + username, + ), + nil, + ), + a.ShellCommand("zsh", "-c", "genfstab -L /mnt/btrfs-current >> /mnt/btrfs-current/etc/fstab"), + a.ShellCommand("sed", "-i", "-E", "s/,subvolid=[0-9]+//g", "/mnt/btrfs-current/etc/fstab"), + a.ShellCommand("mkdir", "-p", fromHome(".")), + a.ShellCommand("cp", "-R", "/root/.dotfiles", fromHome(".dotfiles")), + a.ShellCommand("cp", "-R", "/root/.ssh", fromHome(".ssh")), + a.ShellCommand("cp", "-R", "/root/.secrets", fromHome(".secrets")), + a.ShellCommand("arch-chroot", "/mnt/btrfs-current"), + } + return a.Run(actions) +} + +func ProvisionArchChrootForCompanionSystem() error { + username := "wojtek" + target, targetErr := selectInstallTargetDrive("Which device do you want to provision") + if targetErr != nil { + return targetErr + } + fromHome := func(relative string) string { + return path.Join("/mnt/btrfs-current/home", username, relative) + } + envName := prompt.TextPrompt("Environment name") + if envName == "" { + return fmt.Errorf("Empty value is not allowed") + } + volumePath := fmt.Sprintf("/run/btrfs-root/__%s", envName) + luksEnabled := true + rootPartition := target.mainPartition + if luksEnabled { + rootPartition = "/dev/mapper/root" + } + actions := a.List{ + a.ShellCommand("mkdir", "-p", volumePath), + a.ShellCommand("btrfs", "subvolume", "create", path.Join(volumePath, "/root")), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current"), + a.ShellCommand( + "mount", + "-o", + fmt.Sprintf("defaults,relatime,discard,ssd,nodev,subvol=__%s/root", envName), + rootPartition, + "/mnt/btrfs-current", + ), + a.ShellCommand( + "pacstrap", + "/mnt/btrfs-current", + "base", + "base-devel", + "btrfs-progs", + "linux", + "linux-firmware", + "networkmanager", + ), + a.EnsureText( + "/mnt/btrfs-current/root/.bash_history", + fmt.Sprintf( + "/home/%s/.dotfiles/bin/mycli tool setup:arch:desktop --stage main", + username, + ), + nil, + ), + a.ShellCommand("zsh", "-c", "genfstab -L /mnt/btrfs-current >> /mnt/btrfs-current/etc/fstab"), + a.ShellCommand("sed", "-i", "-E", "s/,subvolid=[0-9]+//g", "/mnt/btrfs-current/etc/fstab"), + a.ShellCommand("mkdir", "-p", fromHome(".")), + a.ShellCommand("cp", "-R", "/home/wojtek/.dotfiles", fromHome(".dotfiles")), + a.ShellCommand("cp", "-R", "/home/wojtek/.ssh", fromHome(".ssh")), + a.ShellCommand("cp", "-R", "/home/wojtek/.secrets", fromHome(".secrets")), + a.ShellCommand("arch-chroot", "/mnt/btrfs-current"), + } + return a.Run(actions) +} + +func ConnectToExistingChrootedEnvironment() error { + target, targetErr := selectInstallTargetDrive( + "Which device do you want to prepare for chroot environment", + ) + if targetErr != nil { + return targetErr + } + luksEnabled := true + rootPartition := target.mainPartition + if luksEnabled { + rootPartition = "/dev/mapper/root" + } + actions := a.List{ + a.WithCondition{ + If: a.Const(luksEnabled), + Then: a.ShellCommand("cryptsetup", "open", target.mainPartition, "root"), + }, + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-root"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid", + rootPartition, + "/mnt/btrfs-root", + ), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,subvol=__current/root", + target.mainPartition, + "/mnt/btrfs-current", + ), + a.ShellCommand("mount", target.efiPartition, "/mnt/btrfs-current/boot/efi"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid,subvol=__current/home", + target.mainPartition, + "/mnt/btrfs-current/home", + ), + a.ShellCommand("mkdir", "-p", "/mnt/btrfs-current/run/btrfs-root"), + a.ShellCommand( + "mount", + "-o", + "defaults,relatime,discard,ssd,nodev,nosuid", + target.mainPartition, + "/mnt/btrfs-current/run/btrfs-root", + ), + a.ShellCommand("arch-chroot", "/mnt/btrfs-current"), + } + + return a.Run(actions) +} diff --git a/api/setup/arch_desktop.go b/api/setup/arch_desktop.go new file mode 100644 index 0000000..4011a80 --- /dev/null +++ b/api/setup/arch_desktop.go @@ -0,0 +1,166 @@ +package setup + +import ( + "fmt" + "path" + "regexp" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +type desktopSetupContext struct { + username string +} + +func (c desktopSetupContext) fromDotfiles(relativePath string) string { + return path.Join("/home", c.username, ".dotfiles", relativePath) +} + +func mainArchStage(ctx desktopSetupContext) a.Object { + actions := a.List{ + a.ShellCommand("bash", "-c", "echo LANG=en_US.UTF-8 > /etc/locale.conf"), + a.EnsureText( + "/etc/locale.gen", + "en_US.UTF-8 UTF-8", + regexp.MustCompile(fmt.Sprintf(".*%s.*", regexp.QuoteMeta("en_US.UTF-8 UTF-8"))), + ), + a.ShellCommand("ln", "-sf", "/usr/share/zoneinfo/Europe/Warsaw", "/etc/localtime"), + a.ShellCommand("hwclock", "--systohc", "--utc"), + a.ShellCommand("locale-gen"), + a.ShellCommand("pacman", "-S", "sudo", "git", "vim", "neovim", "go", "dhcp", "zsh", "openssh"), + a.Scope(func() a.Object { + response := prompt.TextPrompt("/etc/hostname") + if response == "" { + return a.Err(fmt.Errorf("Empty value is not allowed")) + } + return a.ShellCommand("bash", "-c", fmt.Sprintf("echo \"%s\" > /etc/hostname", response)) + }), + a.EnsureText( + "/etc/sudoers", + "\n%sudo\tALL=(ALL) ALL\n", + regexp.MustCompile(fmt.Sprintf("\n(.*)%s\n", regexp.QuoteMeta("%sudo\tALL=(ALL) ALL"))), + ), + a.ShellCommand("groupadd", "sudo"), + a.ShellCommand("useradd", ctx.username), + a.ShellCommand("usermod", "-aG", "sudo", ctx.username), + a.ShellCommand("passwd", ctx.username), + a.EnsureText( + "/etc/mkinitcpio.conf", + "\nHOOKS=(base udev autodetect modconf block encrypt filesystems keyboard btrfs)", + regexp.MustCompile("\nHOOKS=\\(.*\\)"), + ), + a.EnsureText( + "/etc/mkinitcpio.conf", + "\nFILES=(/root/cryptlvm.keyfile)", + regexp.MustCompile("\nFILES=\\(.*\\)"), + ), + a.ShellCommand("mkinitcpio", "-P"), + a.ShellCommand( + "chown", + "-R", + fmt.Sprintf("%s:%s", ctx.username, ctx.username), + fmt.Sprintf("/home/%s", ctx.username), + ), + a.ShellCommand("systemctl", "enable", "NetworkManager"), + a.ShellCommand("systemctl", "enable", "sshd"), + a.Func(func() error { + selected, didSelect := prompt.SelectPrompt( + "Select CPU vendor", + []string{"amd", "intel"}, + func(s string) string { return s }, + ) + if !didSelect { + return nil + } + if selected == "amd" { + return exec.Command().WithStdio().Run("pacman", "-S", "amd-ucode") + } else if selected == "intel" { + return exec.Command().WithStdio().Run("pacman", "-S", "intel-ucode") + } + return nil + }), + a.Func(func() error { + selected, didSelect := prompt.SelectPrompt( + "Select GPU vendor", + []string{"amd", "intel", "nvidia"}, + func(s string) string { return s }, + ) + if !didSelect { + return nil + } + if selected == "amd" { + return exec.Command().WithStdio().Run("pacman", "-S", "vulkan-radeon") + } + return nil + }), + a.EnsureText( + fmt.Sprintf("/home/%s/.bash_history", ctx.username), + "./.dotfiles/bin/mycli tool setup:environment", + nil, + ), + } + return actions +} + +func grubInstallStage(ctx desktopSetupContext) a.Object { + actions := a.List{ + a.WithCondition{ + If: a.PathExists("/sys/firmware/efi"), + Then: a.ShellCommand( + "grub-install", + "--target=x86_64-efi", + "--efi-directory=/boot/efi", + "--bootloader-id=GRUB", + ), + Else: a.ShellCommand("grub-install", "--target=i386-pc", "/dev/sda"), + }, + a.ShellCommand("grub-mkconfig", "-o", "/boot/grub/grub.cfg"), + } + return actions +} + +func grubThemeStage(ctx desktopSetupContext) a.Object { + themePath := "/boot/grub/themes/mytheme" + return a.List{ + a.ShellCommand("rm", "-rf", themePath), + a.ShellCommand("cp", "-R", ctx.fromDotfiles("configs/grub-theme"), themePath), + a.ShellCommand( + "cp", + ctx.fromDotfiles("configs/sway/wallpaper.png"), + path.Join(themePath, "background.png"), + ), + a.EnsureText( + "/etc/default/grub", + fmt.Sprintf("\nGRUB_THEME=\"%s\"\n", path.Join(themePath, "theme.txt")), + regexp.MustCompile(fmt.Sprintf("\n(.*%s.*)\n", regexp.QuoteMeta("GRUB_THEME"))), + ), + a.ShellCommand("grub-mkconfig", "-o", "/boot/grub/grub.cfg"), + } +} + +var archDesktopStages = map[string]func(desktopSetupContext) a.Object{ + "main": mainArchStage, + "grub-install": grubInstallStage, + "grub-theme": grubThemeStage, +} + +func ProvisionArchDesktop(stage string) error { + ctx := desktopSetupContext{ + username: "wojtek", + } + if stage == "" { + return a.Run(a.List{ + archDesktopStages["main"](ctx), + archDesktopStages["grub-install"](ctx), + archDesktopStages["grub-theme"](ctx), + }) + } else { + stageAction, hasStage := archDesktopStages[stage] + if !hasStage { + return fmt.Errorf("Stage %s does not exists", stage) + } + return a.Run(stageAction(ctx)) + } +} diff --git a/api/setup/arch_usb.go b/api/setup/arch_usb.go new file mode 100644 index 0000000..82c61ef --- /dev/null +++ b/api/setup/arch_usb.go @@ -0,0 +1,104 @@ +package setup + +import ( + "fmt" + "io/ioutil" + "os" + "path" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/system/tool" + "github.com/wkozyra95/dotfiles/tool/drive" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +func selectPortableInstallMedia() (string, error) { + devices, readDevicesErr := drive.GetStorageDevicesList() + if readDevicesErr != nil { + return "", nil + } + filteredDevices := []drive.StorageDevice{} + for _, device := range devices { + if device.Label == "dos" { + filteredDevices = append(filteredDevices, device) + } + } + + device, isSelected := prompt.SelectPrompt( + "Which device do you want to provision", + filteredDevices, + func(d drive.StorageDevice) string { + return fmt.Sprintf("%s (size: %d MB)", d.DevicePath, d.Size/(1024*1024)) + }, + ) + if !isSelected { + return "", fmt.Errorf("No value was selected") + } + + return device.DevicePath, nil +} + +func ProvisionUsbArchInstaller(ctx context.Context) error { + target, targetErr := selectPortableInstallMedia() + if targetErr != nil { + return targetErr + } + + workingdir := path.Join(os.TempDir(), "arch-bootable-workingdir") + if !file.Exists("/usr/share/archiso/configs/releng") { + if err := exec.Command().WithStdio().Run("yay", "-S", "archiso"); err != nil { + return err + } + } + + fromRootDir := func(relative string) string { + return path.Join(workingdir, "airootfs", "root", relative) + } + + actions := a.List{ + a.ShellCommand("sudo", "rm", "-rf", workingdir), + a.ShellCommand("cp", "-R", "/usr/share/archiso/configs/releng", workingdir), + a.ShellCommand("mkdir", "-p", fromRootDir(".")), + a.ShellCommand("cp", "-R", ctx.FromHome(".dotfiles"), fromRootDir(".dotfiles")), + a.ShellCommand("cp", "-R", ctx.FromHome(".ssh"), fromRootDir(".ssh")), + a.ShellCommand("cp", "-R", ctx.FromHome(".secrets"), fromRootDir(".secrets")), + a.EnsureText(path.Join(workingdir, "packages.x86_64"), "networkmanager", nil), + a.EnsureText(path.Join(workingdir, "packages.x86_64"), "go", nil), + a.EnsureText(path.Join(workingdir, "packages.x86_64"), "make", nil), + a.EnsureText( + fromRootDir(".zsh_history"), + ": 1601313583:0;chmod +x ./.dotfiles/bin/mycli && ./.dotfiles/bin/mycli tool setup:arch:chroot", + nil, + ), + a.ShellCommand( + "sudo", "mkarchiso", "-v", + "-w", path.Join(workingdir, "tmpdir"), + "-o", path.Join(workingdir, "out"), + workingdir, + ), + a.Func(func() error { + files, fileErr := ioutil.ReadDir(path.Join(workingdir, "out")) + if fileErr != nil { + return fileErr + } + + outputIso := path.Join(workingdir, "out", files[0].Name()) + + if !prompt.ConfirmPrompt(fmt.Sprintf("Do you want to copy files to %s device", target)) { + return fmt.Errorf("Aborting ...") + } + return tool.DD{ + Input: outputIso, + Output: target, + ChunkSizeKB: 4 * 1024, + Status: "progress", + }.Run() + }), + a.ShellCommand("sudo", "rm", "-rf", workingdir), + } + + return a.Run(actions) +} diff --git a/api/setup/environment.go b/api/setup/environment.go new file mode 100644 index 0000000..db7e90f --- /dev/null +++ b/api/setup/environment.go @@ -0,0 +1,31 @@ +package setup + +import ( + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/setup/nvim" +) + +type SetupEnvironmentOptions struct { + Reinstall bool +} + +func SetupEnvironment(ctx context.Context, opts SetupEnvironmentOptions) error { + cmds := a.List{ + a.List{ + ctx.PkgInstaller.EnsurePackagerAction(ctx.Homedir), + api.PackageInstallAction([]api.Package{ + ctx.PkgInstaller.ShellTools(), + ctx.PkgInstaller.DevelopmentTools(), + ctx.PkgInstaller.Desktop(), + }), + }, + SetupLanguageToolchainAction(ctx, SetupLanguageToolchainActionOpts(opts)), + SetupLspAction(ctx, SetupLspActionOpts(opts)), + SetupEnvirionmentCoreAction(ctx), + nvim.NvimEnsureLazyNvimInstalled(ctx), + nvim.NvimInstallAction(ctx, "65046c830e14f8988d9c3b477187f6b871e45af2"), + } + return a.Run(cmds) +} diff --git a/api/setup/environment_actions.go b/api/setup/environment_actions.go new file mode 100644 index 0000000..6bf30d9 --- /dev/null +++ b/api/setup/environment_actions.go @@ -0,0 +1,120 @@ +package setup + +import ( + "os" + "path" + "strings" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/language" + "github.com/wkozyra95/dotfiles/api/setup/nvim" +) + +type SetupLanguageToolchainActionOpts struct { + Reinstall bool +} + +type SetupLspActionOpts struct { + Reinstall bool +} + +func SetupLanguageToolchainAction(ctx context.Context, opts SetupLanguageToolchainActionOpts) a.Object { + return a.List{ + a.WithCondition{ + If: a.CommandExists("go"), + Then: a.List{ + language.GoInstallAction("modd", "github.com/cortesi/modd/cmd/modd@latest", opts.Reinstall), + language.GoInstallAction("golines", "github.com/segmentio/golines@latest", opts.Reinstall), + language.GoInstallAction("gofumpt", "mvdan.cc/gofumpt@latest", opts.Reinstall), + language.GoInstallAction( + "golangci-lint", + "github.com/golangci/golangci-lint/cmd/golangci-lint@latest", + opts.Reinstall, + ), + }, + }, + a.WithCondition{ + // can't check for cmake-format because lsp server also provides executable with that name + If: a.Or(a.Not(a.CommandExists("cmake-lint")), a.Const(opts.Reinstall)), + Then: a.ShellCommand("pip3", "install", "cmakelang"), + }, + } +} + +func SetupLspAction(ctx context.Context, opts SetupLspActionOpts) a.Object { + return a.Optional{ + Object: a.List{ + a.WithCondition{ + If: a.CommandExists("go"), + Then: a.List{ + language.GoInstallAction("gopls", "golang.org/x/tools/gopls@latest", opts.Reinstall), + language.GoInstallAction( + "efm-langserver", + "github.com/mattn/efm-langserver@latest", + opts.Reinstall, + ), + language.GoInstallAction( + "golangci-lint-langserver", + "github.com/nametake/golangci-lint-langserver@latest", + opts.Reinstall, + ), + }, + }, + a.WithCondition{ + If: a.CommandExists("npm"), + Then: a.List{ + language.NodePackageInstallAction("typescript-language-server", opts.Reinstall), + language.NodePackageInstallAction("typescript", opts.Reinstall), + language.NodePackageInstallAction("eslint_d", opts.Reinstall), + language.NodePackageInstallAction("vscode-langservers-extracted", opts.Reinstall), + language.NodePackageInstallAction("yaml-language-server", opts.Reinstall), + }, + }, + a.WithCondition{ + If: a.Or(a.Not(a.CommandExists("cmake-language-server")), a.Const(opts.Reinstall)), + Then: a.ShellCommand("pip3", "install", "cmake-language-server"), + }, + nvim.LuaLspInstallAction(ctx, "6ef1608d857e0179c4db7a14037df84dbef676c8"), + }, + } +} + +func SetupEnvirionmentCoreAction(ctx context.Context) a.Object { + return a.List{ + a.WithCondition{ + If: a.Not(a.FuncCond(func() (bool, error) { + return strings.Contains(os.Getenv("SHELL"), "zsh"), nil + })), + Then: a.ShellCommand("chsh", "-s", "/usr/bin/zsh"), + }, + a.WithCondition{ + If: a.Not( + a.PathExists(path.Join(ctx.Homedir, ".oh-my-zsh")), + ), + Then: a.ShellCommand("bash", + "-c", + "curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash", + ), + }, + a.ShellCommand("mkdir", "-p", ctx.FromHome(".config")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/sway"), ctx.FromHome(".config/sway")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/i3"), ctx.FromHome(".config/i3")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/alacritty.yml"), ctx.FromHome(".alacritty.yml")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/zshrc"), ctx.FromHome(".zshrc")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/vimrc"), ctx.FromHome(".vimrc")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/nvim"), ctx.FromHome(".config/nvim")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/gtk-3.0"), ctx.FromHome(".config/gtk-3.0")), + a.ShellCommand("mkdir", "-p", ctx.FromHome("Dropbox/notes")), + a.EnsureSymlink(ctx.FromHome("Dropbox/notes"), ctx.FromHome("notes")), + a.EnsureSymlink(ctx.FromEnvDir("gitconfig"), ctx.FromHome(".gitconfig")), + a.EnsureSymlink(ctx.FromEnvDir("gitignore"), ctx.FromHome(".gitignore")), + a.EnsureSymlink(ctx.FromHome(".dotfiles/configs/direnv"), ctx.FromHome(".config/direnv")), + a.Scope(func() a.Object { + if ctx.EnvironmentConfig.CustomSetupAction != nil { + return ctx.EnvironmentConfig.CustomSetupAction(ctx) + } + return a.Nop() + }), + } +} diff --git a/api/setup/git/git.go b/api/setup/git/git.go new file mode 100644 index 0000000..a0e5e5b --- /dev/null +++ b/api/setup/git/git.go @@ -0,0 +1,88 @@ +package git + +import ( + "bytes" + "fmt" + "os" + "path" + "strings" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" +) + +type RepoInstallOptions struct { + Path string + RepoUrl string + Name string + CommitHash string +} + +func RepoInstallAction(ctx context.Context, options RepoInstallOptions, installAction action.Object) action.Object { + withCwd := func(path string) *exec.Cmd { + return exec.Command().WithStdio().WithCwd(path) + } + installPrefix := ctx.FromHome(".local") + hashFile := path.Join(installPrefix, fmt.Sprintf(".%s.hash", options.Name)) + return action.List{ + action.WithCondition{ + If: action.Not(action.PathExists(options.Path)), + Then: action.List{ + action.ShellCommand("mkdir", "-p", path.Dir(options.Path)), + action.ShellCommand("git", "clone", options.RepoUrl, options.Path), + }, + }, + action.WithCondition{ + If: action.FuncCond(func() (bool, error) { + if !file.Exists(hashFile) { + return true, nil + } + file, readErr := os.ReadFile(hashFile) + if readErr != nil { + return false, readErr + } + return strings.Trim(string(file), "\n ") != options.CommitHash, nil + }), + Then: action.List{ + action.ShellCommand("mkdir", "-p", installPrefix), + action.Execute(withCwd(options.Path), "git", "fetch", "origin"), + action.Execute(withCwd(options.Path), "git", "checkout", options.CommitHash), + action.Execute(withCwd(options.Path), "git", "clean", "-xfd"), + action.Execute( + withCwd(options.Path), + "git", + "submodule", + "foreach", + "--recursive", + "git", + "clean", + "-xfd", + ), + installAction, + action.Func(func() error { + var stderr, stdout bytes.Buffer + err := exec.Command(). + WithCwd(options.Path). + WithBufout(&stdout, &stderr). + Run("git", "rev-parse", "HEAD") + if err != nil { + return err + } + if file.Exists(hashFile) { + if err := os.Remove(hashFile); err != nil { + return err + } + } + currentHash := strings.Trim(stdout.String(), "\n ") + return os.WriteFile( + hashFile, + []byte(currentHash), + 0o644, + ) + }), + }, + }, + } +} diff --git a/api/setup/installer/fetch.go b/api/setup/installer/fetch.go new file mode 100644 index 0000000..dc29ee5 --- /dev/null +++ b/api/setup/installer/fetch.go @@ -0,0 +1,30 @@ +package installer + +import ( + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" +) + +type DownloadInstallOptions struct { + Path string + ArchivePath string + Url string + Reinstall bool +} + +func DownloadZipInstallAction( + ctx context.Context, + options DownloadInstallOptions, + installAction action.Object, +) action.Object { + return action.List{ + action.WithCondition{ + If: action.Or(action.Not(action.PathExists(options.Path)), action.Const(options.Reinstall)), + Then: action.List{ + action.ShellCommand("rm", "-rf", options.Path, options.ArchivePath), + action.DownloadFile(options.Url, options.ArchivePath), + action.ShellCommand("unzip", "-d", options.Path, options.ArchivePath), + }, + }, + } +} diff --git a/api/setup/nvim/nvim.go b/api/setup/nvim/nvim.go new file mode 100644 index 0000000..acf71b2 --- /dev/null +++ b/api/setup/nvim/nvim.go @@ -0,0 +1,90 @@ +package nvim + +import ( + "fmt" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/setup/git" + "github.com/wkozyra95/dotfiles/api/setup/installer" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +func NvimInstallAction(ctx context.Context, commitHash string) action.Object { + withCwd := func(path string) *exec.Cmd { + return exec.Command().WithStdio().WithCwd(path) + } + cloneDir := ctx.FromHome(".cache/nvim_source") + return git.RepoInstallAction(ctx, git.RepoInstallOptions{ + Path: cloneDir, + Name: "nvim", + CommitHash: commitHash, + RepoUrl: "https://github.com/neovim/neovim.git", + }, action.List{ + action.Execute( + withCwd(cloneDir), + "make", + "-j12", + "CMAKE_BUILD_TYPE=RelWithDebInfo", + fmt.Sprintf("CMAKE_INSTALL_PREFIX=%s", ctx.FromHome(".local")), + ), + action.Execute(withCwd(cloneDir), "make", "install"), + }) +} + +func ElixirLspInstallAction(ctx context.Context, reinstall bool) action.Object { + return installer.DownloadZipInstallAction( + ctx, + installer.DownloadInstallOptions{ + Path: ctx.FromHome(".cache/nvim/myconfig/elixirls"), + ArchivePath: ctx.FromHome(".cache/nvim/myconfig/elixirls.zip"), + Url: "https://github.com/elixir-lsp/elixir-ls/releases/latest/download/elixir-ls.zip", + Reinstall: reinstall, + }, + action.ShellCommand("chmod", "+x", ctx.FromHome(".cache/nvim/myconfig/elixirls/language_server.sh")), + ) +} + +func LuaLspInstallAction(ctx context.Context, commitHash string) action.Object { + withCwd := func(path string) *exec.Cmd { + return exec.Command().WithStdio().WithCwd(path) + } + cloneDir := ctx.FromHome(".cache/nvim/myconfig/lua_lsp") + return git.RepoInstallAction(ctx, git.RepoInstallOptions{ + Path: cloneDir, + Name: "lua_lsp", + CommitHash: commitHash, + RepoUrl: "https://github.com/LuaLS/lua-language-server", + }, action.List{ + action.Execute( + withCwd(cloneDir), + "git", "submodule", "update", "--init", "--recursive", + ), + action.Execute( + withCwd(path.Join(cloneDir, "/3rd/luamake")).WithEnv("ZDOTDIR=/tmp"), + "bash", + "./compile/install.sh", + ), + action.Execute( + withCwd(cloneDir), + "./3rd/luamake/luamake", "rebuild", + ), + action.Execute(withCwd(path.Join(cloneDir, "3rd/EmmyLuaCodeStyle")), "mkdir", "build"), + action.Execute(withCwd(path.Join(cloneDir, "3rd/EmmyLuaCodeStyle/build")), "cmake", ".."), + action.Execute(withCwd(path.Join(cloneDir, "3rd/EmmyLuaCodeStyle/build")), "cmake", "--build", "."), + }) +} + +func NvimEnsureLazyNvimInstalled(ctx context.Context) action.Object { + return action.WithCondition{ + If: action.Not( + action.PathExists(path.Join(ctx.Homedir, ".local/share/nvim/lazy/lazy.nvim")), + ), + Then: action.ShellCommand( + "bash", + "-c", + "git clone --filter=blob:none https://github.com/folke/lazy.nvim.git --branch=stable ~/.local/share/nvim/lazy/lazy.nvim", + ), + } +} diff --git a/api/setup/setup.go b/api/setup/setup.go new file mode 100644 index 0000000..de0d357 --- /dev/null +++ b/api/setup/setup.go @@ -0,0 +1,5 @@ +package setup + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("setup") diff --git a/api/setup/ubuntu_docker.go b/api/setup/ubuntu_docker.go new file mode 100644 index 0000000..1628e74 --- /dev/null +++ b/api/setup/ubuntu_docker.go @@ -0,0 +1,63 @@ +package setup + +import ( + "fmt" + "os" + + a "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/setup/nvim" +) + +func SetupUbuntuInDocker(ctx context.Context, opts SetupEnvironmentOptions) error { + cmds := a.List{ + a.List{ + ctx.PkgInstaller.EnsurePackagerAction(ctx.Homedir), + api.PackageInstallAction([]api.Package{ + ctx.PkgInstaller.ShellTools(), + ctx.PkgInstaller.DevelopmentTools(), + }), + }, + a.WithCondition{ + If: a.Not(a.CommandExists("go")), + Then: a.List{ + a.ShellCommand("wget", "-P", "/tmp", "https://go.dev/dl/go1.20.2.linux-amd64.tar.gz"), + a.WithCondition{ + If: a.Const(ctx.Username == "root"), + Then: a.ShellCommand("tar", "-C", "/usr/local", "-xzf", "/tmp/go1.20.2.linux-amd64.tar.gz"), + Else: a.ShellCommand("sudo", "tar", "-C", "/usr/local", "-xzf", "/tmp/go1.20.2.linux-amd64.tar.gz"), + }, + a.Func(func() error { + os.Setenv("PATH", "/usr/local/go/bin:"+os.Getenv("PATH")) + return nil + }), + }, + }, + SetupLanguageToolchainAction(ctx, SetupLanguageToolchainActionOpts(opts)), + SetupLspAction(ctx, SetupLspActionOpts(opts)), + a.WithCondition{ + If: a.And(a.Const(os.Getenv("GITHUB_TOKEN") != ""), a.Not(a.PathExists(ctx.FromHome(".dotfiles")))), + Then: a.ShellCommand( + "git", + "clone", + fmt.Sprintf("https://wkozyra95:%s@github.com/wkozyra95/dotfiles.git", os.Getenv("GITHUB_TOKEN")), + ctx.FromHome(".dotfiles"), + ), + }, + a.WithCondition{ + If: a.Not(a.PathExists(ctx.FromHome(".fzf"))), + Then: a.ShellCommand( + "git", + "clone", + "--depth", "1", + "https://github.com/junegunn/fzf.git", + ctx.FromHome(".fzf"), + ), + }, + SetupEnvirionmentCoreAction(ctx), + nvim.NvimEnsureLazyNvimInstalled(ctx), + nvim.NvimInstallAction(ctx, "65046c830e14f8988d9c3b477187f6b871e45af2"), + } + return a.Run(cmds) +} diff --git a/api/tool/docker.go b/api/tool/docker.go new file mode 100644 index 0000000..098652b --- /dev/null +++ b/api/tool/docker.go @@ -0,0 +1,34 @@ +package tool + +import ( + "fmt" + "os" + "path" + + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" +) + +func DockerPlaygroundCreate(playgroundPath string, image string) error { + if file.Exists(playgroundPath) { + return nil + } + if err := exec.Command().Run("mkdir", "-p", playgroundPath); err != nil { + return err + } + if err := os.WriteFile(path.Join(playgroundPath, "Dockerfile"), []byte(fmt.Sprintf("FROM %s", image)), 0o644); err != nil { + return err + } + return nil +} + +func DockerPlaygroundShell(playgroundPath string) error { + return api.AlacrittyCall( + api.AlacrittyConfig{ + Command: "zsh", + Args: []string{"-c", "docker build -t test . && docker run -it test"}, + Cwd: playgroundPath, + }, + ) +} diff --git a/bin/.gitkeep b/bin/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cheatsheet.md b/cheatsheet.md new file mode 100644 index 0000000..9f2f79e --- /dev/null +++ b/cheatsheet.md @@ -0,0 +1,44 @@ +### Create hotspot +```bash +nmcli con add type wifi ifname wlan0 con-name Hostspot autoconnect yes ssid Hostspot +nmcli con modify Hostspot 802-11-wireless.mode ap 802-11-wireless.band bg ipv4.method shared +nmcli con modify Hostspot wifi-sec.key-mgmt wpa-psk +nmcli con modify Hostspot wifi-sec.psk "veryveryhardpassword1234" +nmcli con up Hostspot +``` + +### GPG + +#### Setup signing commits + +```bash +gpg --full-generate-key +gpg --list-secret-keys --keyid-format=long +gpg --armor --export 35DF8DFAD0E71E39F047BD01AE51A5682B78648C +``` + +#### Export and restore keyring + +```bash +gpg --armor --export > public_keys.asc +gpg --armor --export-secret-keys > private_keys.asc +gpg --export-ownertrust > trustdb.txt +``` + +```bash +gpg --import public_keys.asc +gpg --import private_keys.asc +gpg --import-ownertrust trustdb.txt +``` + +### Partitioning + +#### Encrypted usb drive +```bash +sudo sgdisk -Z /dev/sdb +sudo sgdisk -n 1::-0 --typecode=1:CA7D7CCB-63ED-4C53-861C-1742536059CC --change-name=1:'DRIVE_NAME' /dev/sdb +sudo cryptsetup luksFormat /dev/sdb1 +sudo cryptsetup luksOpen /dev/sdb1 drive_name +sudo mkfs.ext4 /dev/mapper/drive_name -L drive_name +sudo mount /dev/mapper/drive_name /mnt/sdb1 +``` diff --git a/command/api/cmd.go b/command/api/cmd.go new file mode 100644 index 0000000..8a61121 --- /dev/null +++ b/command/api/cmd.go @@ -0,0 +1,184 @@ +package api + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/helper" + "github.com/wkozyra95/dotfiles/api/language" + "github.com/wkozyra95/dotfiles/api/setup/nvim" + "github.com/wkozyra95/dotfiles/api/tool" + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("api") + +type ( + object = map[string]interface{} + endpoint struct { + name string + handler func(context.Context, object) interface{} + } +) + +var endpoints = map[string]endpoint{ + "workspaces:list": { + name: "workspaces:list", + handler: func(ctx context.Context, input object) interface{} { + return ctx.EnvironmentConfig.Workspaces + }, + }, + "directory:preview": { + name: "directory:preview", + handler: func(ctx context.Context, input object) interface{} { + directoryPreview, err := helper.GetDirectoryPreview( + getStringField(input, "path"), + helper.DirectoryPreviewOptions{MaxElements: 20}, + ) + if err != nil { + panic(err) + } + return directoryPreview + }, + }, + "launch": { + name: "launch", + handler: func(ctx context.Context, input object) interface{} { + if err := api.AlacrittyRun(input); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "node:playground:delete": { + name: "node:playground:delete", + handler: func(ctx context.Context, input object) interface{} { + if err := language.NodePlaygroundDelete(getStringField(input, "path")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "node:playground:create": { + name: "node:playground:create", + handler: func(ctx context.Context, input object) interface{} { + if err := language.NodePlaygroundCreate(getStringField(input, "path")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "node:playground:node-shell": { + name: "node:playground:node-shell", + handler: func(ctx context.Context, input object) interface{} { + if err := language.NodePlaygroundNodeShell(getStringField(input, "path")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "node:playground:zsh-shell": { + name: "node:playground:zsh-shell", + handler: func(ctx context.Context, input object) interface{} { + if err := language.NodePlaygroundZshShell(getStringField(input, "path")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "node:playground:install": { + name: "node:playground:install", + handler: func(ctx context.Context, input object) interface{} { + err := language.NodePlaygroundInstall(getStringField(input, "path"), getStringField(input, "package")) + if err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "docker:playground:create": { + name: "docker:playground:create", + handler: func(ctx context.Context, input object) interface{} { + if err := tool.DockerPlaygroundCreate(getStringField(input, "path"), getStringField(input, "image")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "docker:playground:shell": { + name: "docker:playground:shell", + handler: func(ctx context.Context, input object) interface{} { + if err := tool.DockerPlaygroundShell(getStringField(input, "path")); err != nil { + panic(err) + } + return map[string]string{} + }, + }, + "elixir:lsp:install": { + name: "elixir:lsp:install", + handler: func(ctx context.Context, o object) interface{} { + if err := action.RunSilent(nvim.ElixirLspInstallAction(ctx, true)); err != nil { + panic(err) + } + return map[string]string{} + }, + }, +} + +func getStringField(o object, field string) string { + anyValue, exists := o[field] + if !exists { + panic(fmt.Errorf("field %s does not exists", field)) + } + stringValue, isString := anyValue.(string) + if !isString { + panic(errors.New("field %s has to be a string")) + } + return stringValue +} + +// RegisterCmds ... +func RegisterCmds(rootCmd *cobra.Command) { + apiCmd := &cobra.Command{ + Use: "api", + Short: "mycli api", + Long: "api used by other tools, all commands return json to stdout", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + decodedInput, decodeErr := base64.StdEncoding.DecodeString(args[0]) + if decodeErr != nil { + panic(decodeErr) + } + input := map[string]interface{}{} + if err := json.Unmarshal(decodedInput, &input); err != nil { + panic(err) + } + if input == nil || input["name"] == nil { + panic(fmt.Errorf("missing endpoint name")) + } + inputName, inputNameIsString := (input["name"]).(string) + if !inputNameIsString { + panic(fmt.Errorf("\"name\" has to be a string")) + } + endpoint, endpointExists := endpoints[inputName] + if !endpointExists { + panic(fmt.Errorf("endpoint %s does not exists", inputName)) + } + ctx := context.CreateContext() + result := endpoint.handler(ctx, input) + serialized, serializeErr := json.Marshal(result) + if serializeErr != nil { + panic(serializeErr) + } + fmt.Printf("%s\n", string(serialized)) + }, + } + + rootCmd.AddCommand(apiCmd) +} diff --git a/command/backup.go b/command/backup.go new file mode 100644 index 0000000..ea2d12e --- /dev/null +++ b/command/backup.go @@ -0,0 +1,58 @@ +package command + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/backup" + "github.com/wkozyra95/dotfiles/api/context" +) + +func RegisterBackupCmds(rootCmd *cobra.Command) { + backupCmd := &cobra.Command{ + Use: "backup", + Short: "Custom backup tool", + } + + updateCommand := &cobra.Command{ + Use: "update", + Short: "Update backups", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := backup.UpdateBackup(ctx); err != nil { + fmt.Print(err.Error()) + } + }, + } + + connectCommand := &cobra.Command{ + Use: "connect", + Short: "Mount backup volume locally", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := backup.Connect(ctx); err != nil { + fmt.Print(err.Error()) + } + }, + } + + restoreCommand := &cobra.Command{ + Use: "restore", + Short: "Restore from backups", + Args: cobra.ExactArgs(0), + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := backup.RestoreBackup(ctx); err != nil { + fmt.Print(err.Error()) + } + }, + } + + backupCmd.AddCommand(updateCommand) + backupCmd.AddCommand(restoreCommand) + backupCmd.AddCommand(connectCommand) + + rootCmd.AddCommand(backupCmd) +} diff --git a/command/btrfs.go b/command/btrfs.go new file mode 100644 index 0000000..7e6af25 --- /dev/null +++ b/command/btrfs.go @@ -0,0 +1,43 @@ +package command + +import ( + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/platform/arch" +) + +func RegisterBtrfsCmds(rootCmd *cobra.Command) { + btrfsCmd := &cobra.Command{ + Use: "btrfs", + Short: "btrfs tooling", + Long: "", + } + + btrfsCleanupCmd := &cobra.Command{ + Use: "cleanup", + Short: "cleanup snapshots", + Long: "", + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + if err := arch.CleanupSnapshots(); err != nil { + panic(err) + } + }, + } + + btrfsRootRestoreCmd := &cobra.Command{ + Use: "restore:root", + Short: "restore root partition", + Long: "", + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + if err := arch.RestoreRootSnapshot(); err != nil { + panic(err) + } + }, + } + + btrfsCmd.AddCommand(btrfsRootRestoreCmd) + btrfsCmd.AddCommand(btrfsCleanupCmd) + + rootCmd.AddCommand(btrfsCmd) +} diff --git a/command/command.go b/command/command.go new file mode 100644 index 0000000..cbf2e1f --- /dev/null +++ b/command/command.go @@ -0,0 +1,5 @@ +package command + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("command") diff --git a/command/completion.go b/command/completion.go new file mode 100644 index 0000000..c567387 --- /dev/null +++ b/command/completion.go @@ -0,0 +1,47 @@ +package command + +import ( + "fmt" + "regexp" + "strings" + + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api/context" +) + +func RegisterCompletionCmds(rootCmd *cobra.Command) { + completionCmd := &cobra.Command{ + Use: "completion", + Short: "Generate completion script", + Long: "To load completions", + DisableFlagsInUseLine: true, + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + err := action.Run(action.List{ + action.ShellCommand("mkdir", "-p", ctx.FromHome(".cache/mycli/completion/zsh")), + action.EnsureText( + ctx.FromHome(".cache/mycli/completion/zsh_setup"), + strings.Join( + []string{ + fmt.Sprintf("fpath=(%s \"${fpath[@]}\")", ctx.FromHome(".cache/mycli/completion/zsh")), + "autoload -Uz compinit;", + "compinit;", + "", + }, + "\n", + ), + regexp.MustCompile("(?s).*"), + ), + action.Func(func() error { + return cmd.Root().GenZshCompletionFile(ctx.FromHome(".cache/mycli/completion/zsh/_mycli")) + }), + }) + if err != nil { + panic(err) + } + }, + } + + rootCmd.AddCommand(completionCmd) +} diff --git a/command/git.go b/command/git.go new file mode 100644 index 0000000..f515940 --- /dev/null +++ b/command/git.go @@ -0,0 +1,48 @@ +package command + +import ( + "github.com/spf13/cobra" + + "github.com/wkozyra95/dotfiles/utils/fn" + "github.com/wkozyra95/dotfiles/utils/git" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +func RegisterGitCmds(rootCmd *cobra.Command) { + gitCmds := &cobra.Command{ + Use: "git", + Short: "git helper", + } + + gitPrune := &cobra.Command{ + Use: "prune", + Short: "better git prune", + Run: func(cmd *cobra.Command, args []string) { + if err := git.Prune(); err != nil { + log.Error(err.Error()) + } + branches, branchesErr := git.ListBranches() + if branchesErr != nil { + log.Error(branchesErr) + } + branchesForDeletion := fn.Filter(branches, func(b git.BranchInfo) bool { + return (b.IsRemoteGone || b.RemoteBranch == "") && !b.IsCurrent && b.Name != "main" && + b.Name != "master" + }) + selctedBranches := prompt.MultiselectPrompt( + "Select which branches you want to delete", + branchesForDeletion, + git.BranchInfo.String, + ) + for _, branch := range selctedBranches { + if err := git.DeleteBranch(branch.Name); err != nil { + log.Error(err) + } + } + }, + } + + gitCmds.AddCommand(gitPrune) + + rootCmd.AddCommand(gitCmds) +} diff --git a/command/launcher/cmd.go b/command/launcher/cmd.go new file mode 100644 index 0000000..5d3a55e --- /dev/null +++ b/command/launcher/cmd.go @@ -0,0 +1,101 @@ +package launcher + +import ( + "time" + + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var log = logger.NamedLogger("launcher") + +// RegisterCmds ... +func RegisterCmds(rootCmd *cobra.Command) { + startupCmd := &cobra.Command{ + Use: "launch:startup", + Hidden: true, + Short: "Init commands on sway startup", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + time.Sleep(time.Second * 2) + for _, cmd := range ctx.EnvironmentConfig.Init { + _, initJobErr := exec.Command().WithCwd(cmd.Cwd).Start(cmd.Args[0], cmd.Args[1:]...) + if initJobErr != nil { + log.Error(initJobErr.Error()) + } + } + }, + } + + launcherCmdParams := launchJobParams{} + launcherCmd := &cobra.Command{ + Use: "launch", + Short: "execute cmds based on config file", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + log.Infof("Launching job %s", launcherCmdParams.jobID) + launcher, launcherErr := createLauncher(ctx) + if launcherErr != nil { + log.Errorf("Launcher cration failed") + } + if err := launcher.launchJob(launcherCmdParams); err != nil { + log.Errorf("Launch of the job failed with error [%v]", err) + } + }, + } + launcherCmd.PersistentFlags().StringVar( + &launcherCmdParams.jobID, "job", "default", + "job name", + ) + launcherCmd.PersistentFlags().BoolVar( + &launcherCmdParams.restart, "restart", false, "restart everything", + ) + + internalLauncherCmdParams := launchTaskParams{} + internalLauncherCmd := &cobra.Command{ + Use: "launch:internal", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + launcher, launcherErr := createLauncher(ctx) + if launcherErr != nil { + log.Errorf("Launcher creation failed") + time.Sleep(time.Second * 10) // preserve terminal window before it's closed + } + launcher.launchInternalTask(internalLauncherCmdParams) + }, + } + internalLauncherCmd.PersistentFlags().StringVar( + &internalLauncherCmdParams.taskID, "task", "default", + "task name", + ) + internalLauncherCmd.PersistentFlags().StringVar( + &internalLauncherCmdParams.jobID, "job", "default", + "job name", + ) + internalLauncherCmd.PersistentFlags().BoolVar( + &internalLauncherCmdParams.restart, "restart", false, "restart everything", + ) + + launcherStateCmd := &cobra.Command{ + Use: "launch:internal:state", + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + launcher, launcherErr := createLauncher(ctx) + if launcherErr != nil { + log.Errorf("Launcher creation failed") + } + if err := launcher.printLauncherState(); err != nil { + log.Errorf("Launch of the job failed with error [%v]", err) + } + }, + } + + rootCmd.AddCommand(launcherCmd) + rootCmd.AddCommand(startupCmd) + rootCmd.AddCommand(internalLauncherCmd) + rootCmd.AddCommand(launcherStateCmd) +} diff --git a/command/launcher/config.go b/command/launcher/config.go new file mode 100644 index 0000000..09a766e --- /dev/null +++ b/command/launcher/config.go @@ -0,0 +1,39 @@ +package launcher + +import ( + "fmt" + + "github.com/wkozyra95/dotfiles/env" +) + +type task struct { + Cmd string `json:"cmd"` + Cwd string `json:"cwd"` + RunAsService bool `json:"runAsService"` + IsCritical bool `json:"isCritical"` + Stdio string `json:"stdio"` + X11Tag string `json:"x11Tag"` +} + +type config struct { + Jobs map[string][]string `json:"jobs"` + Tasks map[string]task `json:"tasks"` +} + +func getTask(action env.LauncherAction, taskID string) (env.LauncherTask, error) { + for _, task := range action.Tasks { + if task.Id == taskID { + return task, nil + } + } + return env.LauncherTask{}, fmt.Errorf("No action named %s", taskID) +} + +func getAction(actions []env.LauncherAction, actionID string) (env.LauncherAction, error) { + for _, action := range actions { + if action.Id == actionID { + return action, nil + } + } + return env.LauncherAction{}, fmt.Errorf("No action named %s", actionID) +} diff --git a/command/launcher/project.go b/command/launcher/project.go new file mode 100644 index 0000000..cf5016b --- /dev/null +++ b/command/launcher/project.go @@ -0,0 +1,193 @@ +package launcher + +import ( + "fmt" + "os" + goexec "os/exec" + "path" + "strings" + "time" + + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/command/launcher/state" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/proc" +) + +type launchJobParams struct { + jobID string + restart bool +} + +type launchTaskParams struct { + taskID string + jobID string + restart bool +} + +type launcher struct { + actions []env.LauncherAction + manager state.TaskManager +} + +func createLauncher(ctx context.Context) (*launcher, error) { + manager := state.GetTaskManager() + return &launcher{actions: ctx.EnvironmentConfig.Actions, manager: manager}, nil +} + +func (l *launcher) launchJob(params launchJobParams) error { + action, actionErr := getAction(l.actions, params.jobID) + if actionErr != nil { + return actionErr + } + for _, task := range action.Tasks { + if err := l.launchTask(task, params.jobID, params.restart); err != nil { + log.Errorf("Task %s failed with error %s", task.Id, err.Error()) + return err + } + } + log.Info("Waiting for tasks to start") + time.Sleep(time.Second * 5) + + if err := l.manager.RunGuarded(func(s *state.State) error { + return s.PrintErrors() + }); err != nil { + return err + } + + return nil +} + +func (l *launcher) printLauncherState() error { + if err := l.manager.RunGuarded(func(s *state.State) error { + log.Info("Currently running tasks:") + s.PrintState() + log.Info() + log.Info("Recent errors from running tasks:") + return s.PrintErrors() + }); err != nil { + return err + } + + return nil +} + +func (l *launcher) launchInternalTask(params launchTaskParams) { + action, actionErr := getAction(l.actions, params.jobID) + if actionErr != nil { + log.Errorf("Failed to resolve a value %v", actionErr) + time.Sleep(time.Second * 10) + return + } + task, taskErr := getTask(action, params.taskID) + if taskErr != nil { + log.Errorf("Failed to resolve a task %v", taskErr) + time.Sleep(time.Second * 10) + return + } + listenerCleanup := proc.StartDoubleInteruptExitGuard() + defer listenerCleanup() + + for { + if err := l.doLaunchInternalTask(task, params.restart); err != nil { + managerErr := l.manager.RunGuarded(func(s *state.State) error { + return s.RegisterError( + params.taskID, + err.Error(), + ) + }) + log.Errorf("Task %s failed with error %v", task.Id, err) + if managerErr != nil { + log.Errorf("Failed to register an error %v", managerErr) + } + } + time.Sleep(time.Second * 5) + } +} + +func (l *launcher) launchTaskAsService(task env.LauncherTask, jobID string, restart bool) error { + log.Debugf("Launching service %s", task.Id) + return l.manager.RunGuarded(func(s *state.State) error { + isTaskSupervisorRunning, isTaskRunningErr := s.IsSupervisorRunning(task.Id) + if isTaskRunningErr != nil { + return isTaskRunningErr + } + if !isTaskSupervisorRunning || restart { + if isTaskSupervisorRunning && restart { + log.Debug("Supervisor for this task is already running, killing existing process") + if err := s.KillTask(task.Id); err != nil { + return err + } + } + cmdStr := []string{} + if task.WorkspaceID != 0 { + cmdStr = append(cmdStr, "--class", fmt.Sprintf("workspace%d", task.WorkspaceID)) + } + cmdStr = append(cmdStr, "--command", "mycli", "launch:internal", + "--job", jobID, + "--task", task.Id, + ) + + _, cmdErr := exec.Command().Start("alacritty", cmdStr...) + if cmdErr != nil { + return cmdErr + } + return nil + } + return nil + }) +} + +func (l *launcher) launchTask(task env.LauncherTask, jobID string, restart bool) error { + if task.RunAsService { + return l.launchTaskAsService(task, jobID, restart) + } + log.Debugf("Launching task %s", task.Id) + err := exec.Command().WithStdio().WithCwd(task.Cwd).Run(task.Args[0], task.Args[1:]...) + if err != nil { + log.Errorf("Task %s failed with error %s", task.Id, err.Error()) + return err + } + return nil +} + +func (l *launcher) doLaunchInternalTask(task env.LauncherTask, restart bool) error { + var cmd *goexec.Cmd + log.Infof("Starting task %s", task.Id) + startErr := l.manager.RunGuarded(func(s *state.State) error { + // If there are other supervisors kill + cmdInProgress, cmdErr := exec. + Command().WithStdio().WithCwd(task.Cwd). + Start(task.Args[0], task.Args[1:]...) + if cmdErr != nil { + log.Errorf("Tried to run invalid command %s", strings.Join(task.Args, " ")) + return cmdErr + } + cmd = cmdInProgress + return s.RegisterTask(task.Id, cmd.Process.Pid) + }) + if startErr != nil { + return startErr + } + if cmd == nil { + return nil + } + + log.Info("Waiting for job to finish") + if err := cmd.Wait(); err != nil { + log.Errorf("Task %s failed with error %s", task.Id, err.Error()) + l.manager.RunGuarded(func(s *state.State) error { + return s.RegisterError(task.Id, err.Error()) + }) + } + return nil +} + +func getDefaultConfigPath() string { + homedir, homedirErr := os.UserHomeDir() + if homedirErr != nil { + panic("can't find homedir, HOME env is not defined") + } + return path.Join(homedir, ".dotfiles", "env", os.Getenv("CURRENT_ENV"), "jobs.json") +} diff --git a/command/launcher/state/manager.go b/command/launcher/state/manager.go new file mode 100644 index 0000000..a357ac4 --- /dev/null +++ b/command/launcher/state/manager.go @@ -0,0 +1,182 @@ +package state + +import ( + "fmt" + "os" + "time" + + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/persistentstate" + "github.com/wkozyra95/dotfiles/utils/proc" +) + +var log = logger.NamedLogger("launcher:state-manager") + +// TODO: move that in some proper directory, either in true tmp or in xdg dirs +const statePath = "/tmp/mycli-state.json" + +type processState struct { + TaskID string `json:"taskName"` + ProcessPid int `json:"processPid"` + ProcessSupervisorPid int `json:"ProcessSupervisorPid"` + Time time.Time `json:"time"` +} + +type errorInfo struct { + TaskID string `json:"taskName"` + ProcessState processState `json:"processState"` + ErrorMessage string `json:"errorMessage"` + Time time.Time `json:"time"` +} + +type State struct { + Processes map[string]processState `json:"processes"` + Errors map[string]errorInfo `json:"errors"` +} + +type TaskManager struct { + peristentState persistentstate.StateManager[State] +} + +func ensureDefault(s *State) *State { + if s == nil { + s = &State{} + } + if s.Errors == nil { + s.Errors = map[string]errorInfo{} + } + if s.Processes == nil { + s.Processes = map[string]processState{} + } + return s +} + +func GetTaskManager() TaskManager { + return TaskManager{ + peristentState: persistentstate.GetStateManager(statePath, "mycli", ensureDefault), + } +} + +func (s *TaskManager) RunGuarded(fun func(*State) error) error { + return s.peristentState.RunGuarded(func(s *State) error { + return fun(s) + }) +} + +func (s *TaskManager) GetState() (*State, error) { + state, stateErr := s.peristentState.GetState() + return state, stateErr +} + +// RegisterError ... +func (s *State) RegisterError(taskID string, errMessage string) error { + s.Errors[taskID] = errorInfo{ + TaskID: taskID, + ProcessState: processState{TaskID: taskID, ProcessPid: 0}, + ErrorMessage: errMessage, + Time: time.Now(), + } + return nil +} + +// PrintErrors ,,, +func (s *State) PrintErrors() error { + s.validateManagedProcesses() + for _, errEntry := range s.Errors { + log.Errorf( + "Task failed (pid: %d, name: %s, time %s)\nError message: %s", + errEntry.ProcessState.ProcessPid, + errEntry.ProcessState.TaskID, + errEntry.Time, + errEntry.ErrorMessage, + ) + } + return nil +} + +func (s *State) PrintState() error { + s.validateManagedProcesses() + for _, process := range s.Processes { + log.Infof( + "Task running (pid: %d, ppid %d, name: %s, time %s)", + process.ProcessPid, + process.ProcessSupervisorPid, + process.TaskID, + process.Time, + ) + } + return nil +} + +func (s *State) KillTask(taskID string) error { + s.validateManagedProcesses() + for _, processState := range s.Processes { + if processState.TaskID == taskID { + if os.Getpid() == processState.ProcessSupervisorPid { + proc.Term(processState.ProcessPid) + proc.Term(processState.ProcessPid) + time.Sleep(time.Second) + } else { + log.Debugf("Send term signal %d", processState.ProcessSupervisorPid) + proc.Term(processState.ProcessSupervisorPid) + // need some time to remove handler for sigterm + // TODO: should handle that better + time.Sleep(time.Millisecond * 10) + proc.Term(processState.ProcessSupervisorPid) + time.Sleep(time.Second) + } + s.validateManagedProcesses() + return nil + } + } + return nil +} + +func (s *State) IsTaskRunning(taskID string) (bool, error) { + s.validateManagedProcesses() + for _, processState := range s.Processes { + if processState.TaskID == taskID { + return proc.Exists(processState.ProcessPid), nil + } + } + return false, nil +} + +func (s *State) IsSupervisorRunning(taskID string) (bool, error) { + s.validateManagedProcesses() + for _, processState := range s.Processes { + if processState.TaskID == taskID { + return proc.Exists(processState.ProcessSupervisorPid), nil + } + } + return false, nil +} + +func (s *State) RegisterTask(taskID string, pid int) error { + if !proc.Exists(pid) { + return s.RegisterError(taskID, fmt.Sprintf("Process with pid %d does not exist", pid)) + } + if _, hasError := s.Errors[taskID]; hasError { + delete(s.Errors, taskID) + } + s.Processes[taskID] = processState{ + TaskID: taskID, + ProcessPid: pid, + ProcessSupervisorPid: os.Getpid(), + Time: time.Now(), + } + return nil +} + +func (s *State) validateManagedProcesses() { + for taskID, processState := range s.Processes { + if !proc.Exists(processState.ProcessSupervisorPid) { + log.Debugf("verified process supervisor %d is down", processState.ProcessSupervisorPid) + delete(s.Processes, taskID) + } else if processState.ProcessPid != 0 && !proc.Exists(processState.ProcessPid) { + log.Debugf("verified process %d is down, but it's managed by a supervisor", processState.ProcessSupervisorPid) + processState.ProcessPid = 0 + s.Processes[taskID] = processState + } + } +} diff --git a/command/tool/cmd.go b/command/tool/cmd.go new file mode 100644 index 0000000..471afe0 --- /dev/null +++ b/command/tool/cmd.go @@ -0,0 +1,22 @@ +package tool + +import ( + "github.com/spf13/cobra" +) + +// RegisterCmds ... +func RegisterCmds(rootCmd *cobra.Command) { + toolCmd := &cobra.Command{ + Use: "tool", + Short: "system tools", + } + + registerSetupCommands(toolCmd) + registerUpgradeCommands(toolCmd) + registerReleaseCommands(toolCmd) + toolCmd.AddCommand(registerDriveCommands()) + toolCmd.AddCommand(registerWifiCommands()) + toolCmd.AddCommand(registerPlaygroundCommands()) + toolCmd.AddCommand(registerDebugCommand()) + rootCmd.AddCommand(toolCmd) +} diff --git a/command/tool/debug.go b/command/tool/debug.go new file mode 100644 index 0000000..de9cc18 --- /dev/null +++ b/command/tool/debug.go @@ -0,0 +1,19 @@ +package tool + +import ( + "github.com/davecgh/go-spew/spew" + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/platform/arch" +) + +func registerDebugCommand() *cobra.Command { + rooCmd := &cobra.Command{ + Use: "debug", + Short: "debug", + Run: func(cmd *cobra.Command, args []string) { + spew.Dump(arch.GetSnapshots()) + }, + } + + return rooCmd +} diff --git a/command/tool/drive.go b/command/tool/drive.go new file mode 100644 index 0000000..7943e78 --- /dev/null +++ b/command/tool/drive.go @@ -0,0 +1,79 @@ +package tool + +import ( + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + + "github.com/wkozyra95/dotfiles/system/tool" +) + +var driveMountCmd = &cobra.Command{ + Use: "mount", + Short: "mount drive", + Run: func(cmd *cobra.Command, args []string) { + drives, detectErr := tool.DetectPartitions() + if detectErr != nil { + log.Error(detectErr) + } + + mounted := tool.FilterPartitions(func(d tool.Partition) bool { return !d.IsMounted() }, drives) + + names := []string{} + for _, dst := range mounted { + names = append(names, dst.String()) + } + + index, _, selectErr := (&promptui.Select{ + Label: "Which device you want to mount", + Items: names, + }).Run() + if selectErr != nil { + log.Errorf("Prompt select failed [%v]", selectErr) + return + } + if err := mounted[index].Mount(); err != nil { + log.Error(err) + } + }, +} + +var driveUmountCmd = &cobra.Command{ + Use: "umount", + Short: "umount drive", + Run: func(cmd *cobra.Command, args []string) { + drives, detectErr := tool.DetectPartitions() + if detectErr != nil { + log.Error(detectErr) + } + + mounted := tool.FilterPartitions(func(d tool.Partition) bool { return d.IsMounted() }, drives) + + names := []string{} + for _, dst := range mounted { + names = append(names, dst.String()) + } + + index, _, selectErr := (&promptui.Select{ + Label: "Which device you want to umount", + Items: names, + }).Run() + if selectErr != nil { + log.Errorf("Prompt select failed [%v]", selectErr) + return + } + if err := mounted[index].Umount(); err != nil { + log.Error(err) + } + }, +} + +func registerDriveCommands() *cobra.Command { + driveCmd := &cobra.Command{ + Use: "drive", + Short: "drive managment", + } + + driveCmd.AddCommand(driveMountCmd) + driveCmd.AddCommand(driveUmountCmd) + return driveCmd +} diff --git a/command/tool/playground.go b/command/tool/playground.go new file mode 100644 index 0000000..940ce6f --- /dev/null +++ b/command/tool/playground.go @@ -0,0 +1,23 @@ +package tool + +import ( + "github.com/spf13/cobra" +) + +var dockerCmd = &cobra.Command{ + Use: "docker", + Short: "docker", + Run: func(cmd *cobra.Command, args []string) { + // TODO + }, +} + +func registerPlaygroundCommands() *cobra.Command { + rooCmd := &cobra.Command{ + Use: "playground", + Short: "playground", + } + + rooCmd.AddCommand(dockerCmd) + return rooCmd +} diff --git a/command/tool/release.go b/command/tool/release.go new file mode 100644 index 0000000..7483309 --- /dev/null +++ b/command/tool/release.go @@ -0,0 +1,167 @@ +package tool + +import ( + gocontext "context" + "os" + "path" + + "github.com/google/go-github/v50/github" + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/utils/exec" + "golang.org/x/oauth2" +) + +const ( + releaseName = "release - v0.0.0" + repoOwner = "wkozyra95" + repoName = "dotfiles" +) + +func registerReleaseCommands(rootCmd *cobra.Command) { + upgradeCmd := &cobra.Command{ + Use: "release", + Short: "publish cli as a GitHub release", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + err := release(ctx) + if err != nil { + log.Error(err.Error()) + } + }, + } + + rootCmd.AddCommand(upgradeCmd) +} + +func release(ctx context.Context) error { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: os.Getenv("GITHUB_RELEASE_TOKEN")}, + ) + tc := oauth2.NewClient(gocontext.Background(), ts) + client := github.NewClient(tc) + release, ensureReleaseErr := ensureRelease(client) + if ensureReleaseErr != nil { + return ensureReleaseErr + } + linuxBuildErr := exec.Command().WithStdio(). + WithCwd(path.Join(ctx.Homedir, ".dotfiles")). + WithEnv("CGO_ENABLED=0", "GOOS=linux", "GOARCH=amd64"). + Run("go", "build", "-o", "bin/mycli-linux", "./mycli/main.go") + + if linuxBuildErr != nil { + return linuxBuildErr + } + + darwinBuildErr := exec.Command().WithStdio(). + WithCwd(path.Join(ctx.Homedir, ".dotfiles")). + WithEnv("CGO_ENABLED=0", "GOOS=darwin", "GOARCH=amd64"). + Run("go", "build", "-o", "bin/mycli-darwin", "./mycli/main.go") + if darwinBuildErr != nil { + return darwinBuildErr + } + mycliLinux, openErr := os.Open(ctx.FromHome(".dotfiles/bin/mycli-linux")) + if openErr != nil { + return openErr + } + defer mycliLinux.Close() + mycliDarwin, openErr := os.Open(ctx.FromHome(".dotfiles/bin/mycli-darwin")) + if openErr != nil { + return openErr + } + defer mycliDarwin.Close() + + linuxAsset, darwinAsset, updateErr := updateAssets(client, release.GetID(), mycliLinux, mycliDarwin) + if updateErr != nil { + return updateErr + } + log.Infof("Linux asset %d", linuxAsset.GetID()) + log.Infof("MacOS asset %d", darwinAsset.GetID()) + return nil +} + +func updateAssets( + client *github.Client, + releaseID int64, + mycliLinux, mycliDarwin *os.File, +) (*github.ReleaseAsset, *github.ReleaseAsset, error) { + oldAssetList, _, getErr := client.Repositories.ListReleaseAssets( + gocontext.Background(), + repoOwner, + repoName, + releaseID, + &github.ListOptions{}, + ) + if getErr != nil { + return nil, nil, getErr + } + for _, asset := range oldAssetList { + log.Debugf("Deleting asset %s", *asset.Name) + if _, err := client.Repositories.DeleteReleaseAsset(gocontext.Background(), repoOwner, repoName, asset.GetID()); err != nil { + return nil, nil, err + } + } + linuxAsset, _, uploadLinuxErr := client.Repositories.UploadReleaseAsset( + gocontext.Background(), + repoOwner, + repoName, + releaseID, + &github.UploadOptions{Name: "mycli-linux"}, + mycliLinux, + ) + if uploadLinuxErr != nil { + return nil, nil, uploadLinuxErr + } + + darwinAsset, _, uploadDarwinErr := client.Repositories.UploadReleaseAsset( + gocontext.Background(), + repoOwner, + repoName, + releaseID, + &github.UploadOptions{Name: "mycli-darwin"}, + mycliDarwin, + ) + if uploadDarwinErr != nil { + return nil, nil, uploadDarwinErr + } + return linuxAsset, darwinAsset, nil +} + +func ensureRelease(client *github.Client) (*github.RepositoryRelease, error) { + releaseName := releaseName + tagName := "v0.0.0" + existingRelease, findReleaseErr := findReleaseByName(client, releaseName) + if findReleaseErr != nil { + return nil, findReleaseErr + } + if existingRelease != nil { + return existingRelease, nil + } + release, _, createErr := client.Repositories.CreateRelease( + gocontext.Background(), + repoOwner, repoName, + &github.RepositoryRelease{Name: &releaseName, TagName: &tagName}, + ) + if createErr != nil { + return nil, createErr + } + return release, nil +} + +func findReleaseByName(client *github.Client, name string) (*github.RepositoryRelease, error) { + releases, _, listErr := client.Repositories.ListReleases( + gocontext.Background(), + repoOwner, + repoName, + &github.ListOptions{}, + ) + if listErr != nil { + return nil, listErr + } + for _, release := range releases { + if *release.Name == name { + return release, nil + } + } + return nil, nil +} diff --git a/command/tool/setup.go b/command/tool/setup.go new file mode 100644 index 0000000..0040583 --- /dev/null +++ b/command/tool/setup.go @@ -0,0 +1,142 @@ +package tool + +import ( + "os/user" + + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/context" + "github.com/wkozyra95/dotfiles/api/setup" +) + +func registerSetupCommands(rootCmd *cobra.Command) { + var setupEnvReinstall bool + setupEnvCmd := &cobra.Command{ + Use: "setup:environment", + Short: "Install tools + prepare config files", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := setup.SetupEnvironment(ctx, setup.SetupEnvironmentOptions{ + Reinstall: setupEnvReinstall, + }); err != nil { + log.Error(err) + } + }, + } + + setupArchInstallerCmd := &cobra.Command{ + Use: "setup:arch:usb", + Short: "prepare Arch installer", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := setup.ProvisionUsbArchInstaller(ctx); err != nil { + log.Error(err) + } + }, + } + + setupArchChrootCmd := &cobra.Command{ + Use: "setup:arch:chroot", + Short: "prepare chrooted environment", + Run: func(cmd *cobra.Command, args []string) { + user, userErr := user.Current() + if userErr != nil { + log.Error(userErr) + return + } + if user.Username != "root" { + log.Error("You need to be root to run installer") + return + } + if err := setup.ProvisionArchChroot(); err != nil { + log.Error(err) + } + }, + } + + setupArchCompanionChrootCmd := &cobra.Command{ + Use: "setup:arch:companion_chroot", + Short: "prepare chrooted environment for companion system", + Run: func(cmd *cobra.Command, args []string) { + user, userErr := user.Current() + if userErr != nil { + log.Error(userErr) + return + } + if user.Username != "root" { + log.Error("You need to be root to run installer") + return + } + if err := setup.ProvisionArchChrootForCompanionSystem(); err != nil { + log.Error(err) + } + }, + } + + connectExistingArchChrootCmd := &cobra.Command{ + Use: "connect:arch:chroot", + Short: "connect to existing chrooted environment", + Run: func(cmd *cobra.Command, args []string) { + user, userErr := user.Current() + if userErr != nil { + log.Error(userErr) + return + } + if user.Username != "root" { + log.Error("You need to be root to run installer") + return + } + if err := setup.ConnectToExistingChrootedEnvironment(); err != nil { + log.Error(err) + } + }, + } + + var desktopArchStage string + setupDesktopArchCmd := &cobra.Command{ + Use: "setup:arch:desktop", + Short: "setup desktop arch", + Run: func(cmd *cobra.Command, args []string) { + user, userErr := user.Current() + if userErr != nil { + log.Error(userErr) + return + } + if user.Username != "root" { + log.Error("You need to be root to run installer") + return + } + if err := setup.ProvisionArchDesktop(desktopArchStage); err != nil { + log.Error(err) + } + }, + } + + setupInDockerCmd := &cobra.Command{ + Use: "setup:environment:docker", + Short: "command that should be run inside dockerfile that provisions the system", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := setup.SetupUbuntuInDocker(ctx, setup.SetupEnvironmentOptions{Reinstall: true}); err != nil { + log.Errorln(err.Error()) + } + }, + } + + setupDesktopArchCmd.PersistentFlags().StringVar( + &desktopArchStage, "stage", "", + "setup only specified stage", + ) + + setupEnvCmd.PersistentFlags().BoolVar( + &setupEnvReinstall, "reinstall", false, + "reinstall packages even if they are already installed", + ) + + rootCmd.AddCommand(setupEnvCmd) + rootCmd.AddCommand(setupArchInstallerCmd) + rootCmd.AddCommand(setupArchChrootCmd) + rootCmd.AddCommand(setupArchCompanionChrootCmd) + rootCmd.AddCommand(setupDesktopArchCmd) + rootCmd.AddCommand(connectExistingArchChrootCmd) + rootCmd.AddCommand(setupInDockerCmd) +} diff --git a/command/tool/tool.go b/command/tool/tool.go new file mode 100644 index 0000000..b8cbf1f --- /dev/null +++ b/command/tool/tool.go @@ -0,0 +1,5 @@ +package tool + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("tool") diff --git a/command/tool/upgrade.go b/command/tool/upgrade.go new file mode 100644 index 0000000..3d97c4e --- /dev/null +++ b/command/tool/upgrade.go @@ -0,0 +1,21 @@ +package tool + +import ( + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/api/context" +) + +func registerUpgradeCommands(rootCmd *cobra.Command) { + upgradeCmd := &cobra.Command{ + Use: "upgrade", + Short: "upgrade system packages", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.CreateContext() + if err := ctx.PkgInstaller.UpgradePackages(); err != nil { + log.Error(err) + } + }, + } + + rootCmd.AddCommand(upgradeCmd) +} diff --git a/command/tool/wifi.go b/command/tool/wifi.go new file mode 100644 index 0000000..c60c1d3 --- /dev/null +++ b/command/tool/wifi.go @@ -0,0 +1,59 @@ +package tool + +import ( + "os" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + + "github.com/wkozyra95/dotfiles/secret" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +func wifiManager() { + homedir, homedirErr := os.UserHomeDir() + if homedirErr != nil { + panic(homedirErr) + } + secrets := secret.BestEffortReadSecrets(homedir) + if secrets == nil { + return + } + networks := []struct { + ssid string + password string + }{ + {secrets.Wifi.HomeUpc.Ssid, secrets.Wifi.HomeUpc.Password}, + {secrets.Wifi.HomeOrange.Ssid, secrets.Wifi.HomeOrange.Password}, + } + networkNames := make([]string, len(networks)) + for i, network := range networks { + networkNames[i] = network.ssid + } + index, _, selectErr := (&promptui.Select{ + Label: "Select preconfigured network", + Items: networkNames, + }).Run() + if selectErr != nil { + panic(selectErr) + } + if exec.CommandExists("nmcli") { + exec.Command(). + WithStdio(). + Run("nmcli", "dev", "wifi", "con", networks[index].ssid, "password", networks[index].password) + } else { + log.Infof("Unsupported auto-connect (ssid: %s, password: %s)", networks[index].ssid, networks[index].password) + } +} + +func registerWifiCommands() *cobra.Command { + driveCmd := &cobra.Command{ + Use: "wifi", + Short: "wifi managment", + Run: func(cmd *cobra.Command, args []string) { + wifiManager() + }, + } + + return driveCmd +} diff --git a/configs/alacritty.yml b/configs/alacritty.yml new file mode 100644 index 0000000..ac203c2 --- /dev/null +++ b/configs/alacritty.yml @@ -0,0 +1,695 @@ +# Configuration for Alacritty, the GPU enhanced terminal emulator. + +env: + TERM: xterm-256color + +window: + padding: + x: 4 + y: 4 + + dynamic_padding: false + + # Window title + #title: Alacritty + + # Allow terminal applications to change Alacritty's window title. + #dynamic_title: true + + # Window class (Linux/BSD only): + class: + # Application instance name + instance: Alacritty + # General application class + general: Alacritty + + decorations_theme_variant: Dark + +scrolling: + history: 100000 + multiplier: 5 + +# Font configuration +font: + normal: + family: "Source Code Pro" + style: "Medium" + + bold: + family: "Source Code Pro" + style: "Bold" + + italic: + family: "Source Code Pro" + style: "Italic" + + bold_italic: + family: "Source Code Pro" + style: "Bold Italic" + + size: 10.0 + + offset: + x: 0 + y: 0 + + glyph_offset: + x: 0 + y: 0 + + #use_thin_strokes: false + +# If `true`, bold text is drawn using the bright color variants. +#draw_bold_text_with_bright_colors: false + +colors: + primary: + background: '#1d1f21' + foreground: '#c5c8c6' + + # Bright and dim foreground colors + # + # The dimmed foreground color is calculated automatically if it is not + # present. If the bright foreground color is not set, or + # `draw_bold_text_with_bright_colors` is `false`, the normal foreground + # color will be used. + #dim_foreground: '#828482' + #bright_foreground: '#eaeaea' + + cursor: + text: CellBackground + cursor: CellForeground + + vi_mode_cursor: + text: CellBackground + cursor: CellForeground + + search: + matches: + foreground: '#000000' + background: '#ffffff' + focused_match: + foreground: '#ffffff' + background: '#000000' + + footer_bar: + background: '#c5c8c6' + foreground: '#1d1f21' + + #hints: + # Fist character in the hint label + # + # Allowed values are CellForeground/CellBackground, which reference the + # affected cell, or hexadecimal colors like #ff00ff. + #start: + # foreground: '#1d1f21' + # background: '#e9ff5e' + + # All characters after the first one in the hint label + # + # Allowed values are CellForeground/CellBackground, which reference the + # affected cell, or hexadecimal colors like #ff00ff. + #end: + # foreground: '#e9ff5e' + # background: '#1d1f21' + + # Line indicator + # + # Color used for the indicator displaying the position in history during + # search and vi mode. + # + # By default, these will use the opposing primary color. + #line_indicator: + # foreground: None + # background: None + + # Selection colors + # + # Colors which should be used to draw the selection area. + # + # Allowed values are CellForeground/CellBackground, which reference the + # affected cell, or hexadecimal colors like #ff00ff. + #selection: + # text: CellBackground + # background: CellForeground + + normal: + black: '#1d1f21' + red: '#cc6666' + green: '#b5bd68' + yellow: '#f0c674' + blue: '#81a2be' + magenta: '#b294bb' + cyan: '#8abeb7' + white: '#c5c8c6' + + bright: + black: '#666666' + red: '#d54e53' + green: '#b9ca4a' + yellow: '#e7c547' + blue: '#7aa6da' + magenta: '#c397d8' + cyan: '#70c0b1' + white: '#eaeaea' + + dim: + black: '#131415' + red: '#864343' + green: '#777c44' + yellow: '#9e824c' + blue: '#556a7d' + magenta: '#75617b' + cyan: '#5b7d78' + white: '#828482' + + # Indexed Colors + # + # The indexed colors include all colors from 16 to 256. + # When these are not set, they're filled with sensible defaults. + # + # Example: + # `- { index: 16, color: '#ff00ff' }` + # + #indexed_colors: [] + +# Bell +# +# The bell is rung every time the BEL control character is received. +#bell: + # Visual Bell Animation + # + # Animation effect for flashing the screen when the visual bell is rung. + # + # Values for `animation`: + # - Ease + # - EaseOut + # - EaseOutSine + # - EaseOutQuad + # - EaseOutCubic + # - EaseOutQuart + # - EaseOutQuint + # - EaseOutExpo + # - EaseOutCirc + # - Linear + #animation: EaseOutExpo + + # Duration of the visual bell flash in milliseconds. A `duration` of `0` will + # disable the visual bell animation. + #duration: 0 + + # Visual bell animation color. + #color: '#ffffff' + + # Bell Command + # + # This program is executed whenever the bell is rung. + # + # When set to `command: None`, no command will be executed. + # + # Example: + # command: + # program: notify-send + # args: ["Hello, World!"] + # + #command: None + +# Background opacity +# +# Window opacity as a floating point number from `0.0` to `1.0`. +# The value `0.0` is completely transparent and `1.0` is opaque. +#background_opacity: 1.0 + +#selection: + # This string contains all characters that are used as separators for + # "semantic words" in Alacritty. + #semantic_escape_chars: ",│`|:\"' ()[]{}<>\t" + + # When set to `true`, selected text will be copied to the primary clipboard. + #save_to_clipboard: false + +#cursor: + # Cursor style + #style: + # Cursor shape + # + # Values for `shape`: + # - ▇ Block + # - _ Underline + # - | Beam + #shape: Block + + # Cursor blinking state + # + # Values for `blinking`: + # - Never: Prevent the cursor from ever blinking + # - Off: Disable blinking by default + # - On: Enable blinking by default + # - Always: Force the cursor to always blink + #blinking: Off + + # Vi mode cursor style + # + # If the vi mode cursor style is `None` or not specified, it will fall back to + # the style of the active value of the normal cursor. + # + # See `cursor.style` for available options. + vi_mode_style: None + + # Cursor blinking interval in milliseconds. + blink_interval: 750 + + # If this is `true`, the cursor will be rendered as a hollow box when the + # window is not focused. + unfocused_hollow: true + + # Thickness of the cursor relative to the cell width as floating point number + # from `0.0` to `1.0`. + #thickness: 0.15 + +# Live config reload (changes require restart) +#live_config_reload: true + +# Shell +# +# You can set `shell.program` to the path of your favorite shell, e.g. +# `/bin/fish`. Entries in `shell.args` are passed unmodified as arguments to the +# shell. +# +# Default: +# - (macOS) /bin/bash --login +# - (Linux/BSD) user login shell +# - (Windows) powershell +shell: + program: /bin/zsh + args: + - --login + +# Startup directory +# +# Directory the shell is started in. If this is unset, or `None`, the working +# directory of the parent process will be used. +#working_directory: None + +# Send ESC (\x1b) before characters when alt is pressed. +#alt_send_esc: true + +#mouse: + # Click settings + # + # The `double_click` and `triple_click` settings control the time + # alacritty should wait for accepting multiple clicks as one double + # or triple click. + #double_click: { threshold: 300 } + #triple_click: { threshold: 300 } + + # If this is `true`, the cursor is temporarily hidden when typing. + #hide_when_typing: false + +# Regex hints +# +# Terminal hints can be used to find text in the visible part of the terminal +# and pipe it to other applications. +#hints: + # Keys used for the hint labels. + #alphabet: "jfkdls;ahgurieowpq" + + # List with all available hints + # + # Each hint must have a `regex` and either an `action` or a `command` field. + # The fields `mouse`, `binding` and `post_processing` are optional. + # + # The fields `command`, `binding.key`, `binding.mods` and `mouse.mods` accept + # the same values as they do in the `key_bindings` section. + # + # The `mouse.enabled` field controls if the hint should be underlined while + # the mouse with all `mouse.mods` keys held or the vi mode cursor is above it. + # + # If the `post_processing` field is set to `true`, heuristics will be used to + # shorten the match if there are characters likely not to be part of the hint + # (e.g. a trailing `.`). This is most useful for URIs. + # + # Values for `action`: + # - Copy + # Copy the hint's text to the clipboard. + # - Paste + # Paste the hint's text to the terminal or search. + # - Select + # Select the hint's text. + # - MoveViModeCursor + # Move the vi mode cursor to the beginning of the hint. + #enabled: + # - regex: "(mailto:|gemini:|gopher:|https:|http:|news:|file:|git:|ssh:|ftp:)\ + # [^\u0000-\u001F\u007F-\u009F<>\"\\s{-}\\^⟨⟩`]+" + # command: xdg-open + # post_processing: true + # mouse: + # enabled: true + # mods: None + # binding: + # key: U + # mods: Control|Shift + +# Mouse bindings +# +# Mouse bindings are specified as a list of objects, much like the key +# bindings further below. +# +# To trigger mouse bindings when an application running within Alacritty +# captures the mouse, the `Shift` modifier is automatically added as a +# requirement. +# +# Each mouse binding will specify a: +# +# - `mouse`: +# +# - Middle +# - Left +# - Right +# - Numeric identifier such as `5` +# +# - `action` (see key bindings) +# +# And optionally: +# +# - `mods` (see key bindings) +#mouse_bindings: +# - { mouse: Middle, action: PasteSelection } + +# Key bindings +# +# Key bindings are specified as a list of objects. For example, this is the +# default paste binding: +# +# `- { key: V, mods: Control|Shift, action: Paste }` +# +# Each key binding will specify a: +# +# - `key`: Identifier of the key pressed +# +# - A-Z +# - F1-F24 +# - Key0-Key9 +# +# A full list with available key codes can be found here: +# https://docs.rs/glutin/*/glutin/event/enum.VirtualKeyCode.html#variants +# +# Instead of using the name of the keys, the `key` field also supports using +# the scancode of the desired key. Scancodes have to be specified as a +# decimal number. This command will allow you to display the hex scancodes +# for certain keys: +# +# `showkey --scancodes`. +# +# Then exactly one of: +# +# - `chars`: Send a byte sequence to the running application +# +# The `chars` field writes the specified string to the terminal. This makes +# it possible to pass escape sequences. To find escape codes for bindings +# like `PageUp` (`"\x1b[5~"`), you can run the command `showkey -a` outside +# of tmux. Note that applications use terminfo to map escape sequences back +# to keys. It is therefore required to update the terminfo when changing an +# escape sequence. +# +# - `action`: Execute a predefined action +# +# - ToggleViMode +# - SearchForward +# Start searching toward the right of the search origin. +# - SearchBackward +# Start searching toward the left of the search origin. +# - Copy +# - Paste +# - IncreaseFontSize +# - DecreaseFontSize +# - ResetFontSize +# - ScrollPageUp +# - ScrollPageDown +# - ScrollHalfPageUp +# - ScrollHalfPageDown +# - ScrollLineUp +# - ScrollLineDown +# - ScrollToTop +# - ScrollToBottom +# - ClearHistory +# Remove the terminal's scrollback history. +# - Hide +# Hide the Alacritty window. +# - Minimize +# Minimize the Alacritty window. +# - Quit +# Quit Alacritty. +# - ToggleFullscreen +# - SpawnNewInstance +# Spawn a new instance of Alacritty. +# - ClearLogNotice +# Clear Alacritty's UI warning and error notice. +# - ClearSelection +# Remove the active selection. +# - ReceiveChar +# - None +# +# - Vi mode exclusive actions: +# +# - Open +# Perform the action of the first matching hint under the vi mode cursor +# with `mouse.enabled` set to `true`. +# - ToggleNormalSelection +# - ToggleLineSelection +# - ToggleBlockSelection +# - ToggleSemanticSelection +# Toggle semantic selection based on `selection.semantic_escape_chars`. +# +# - Vi mode exclusive cursor motion actions: +# +# - Up +# One line up. +# - Down +# One line down. +# - Left +# One character left. +# - Right +# One character right. +# - First +# First column, or beginning of the line when already at the first column. +# - Last +# Last column, or beginning of the line when already at the last column. +# - FirstOccupied +# First non-empty cell in this terminal row, or first non-empty cell of +# the line when already at the first cell of the row. +# - High +# Top of the screen. +# - Middle +# Center of the screen. +# - Low +# Bottom of the screen. +# - SemanticLeft +# Start of the previous semantically separated word. +# - SemanticRight +# Start of the next semantically separated word. +# - SemanticLeftEnd +# End of the previous semantically separated word. +# - SemanticRightEnd +# End of the next semantically separated word. +# - WordLeft +# Start of the previous whitespace separated word. +# - WordRight +# Start of the next whitespace separated word. +# - WordLeftEnd +# End of the previous whitespace separated word. +# - WordRightEnd +# End of the next whitespace separated word. +# - Bracket +# Character matching the bracket at the cursor's location. +# - SearchNext +# Beginning of the next match. +# - SearchPrevious +# Beginning of the previous match. +# - SearchStart +# Start of the match to the left of the vi mode cursor. +# - SearchEnd +# End of the match to the right of the vi mode cursor. +# +# - Search mode exclusive actions: +# - SearchFocusNext +# Move the focus to the next search match. +# - SearchFocusPrevious +# Move the focus to the previous search match. +# - SearchConfirm +# - SearchCancel +# - SearchClear +# Reset the search regex. +# - SearchDeleteWord +# Delete the last word in the search regex. +# - SearchHistoryPrevious +# Go to the previous regex in the search history. +# - SearchHistoryNext +# Go to the next regex in the search history. +# +# - Linux/BSD exclusive actions: +# +# - CopySelection +# Copy from the selection buffer. +# - PasteSelection +# Paste from the selection buffer. +# +# - `command`: Fork and execute a specified command plus arguments +# +# The `command` field must be a map containing a `program` string and an +# `args` array of command line parameter strings. For example: +# `{ program: "alacritty", args: ["-e", "vttest"] }` +# +# And optionally: +# +# - `mods`: Key modifiers to filter binding actions +# +# - Command +# - Control +# - Option +# - Super +# - Shift +# - Alt +# +# Multiple `mods` can be combined using `|` like this: +# `mods: Control|Shift`. +# Whitespace and capitalization are relevant and must match the example. +# +# - `mode`: Indicate a binding for only specific terminal reported modes +# +# This is mainly used to send applications the correct escape sequences +# when in different modes. +# +# - AppCursor +# - AppKeypad +# - Search +# - Alt +# - Vi +# +# A `~` operator can be used before a mode to apply the binding whenever +# the mode is *not* active, e.g. `~Alt`. +# +# Bindings are always filled by default, but will be replaced when a new +# binding with the same triggers is defined. To unset a default binding, it can +# be mapped to the `ReceiveChar` action. Alternatively, you can use `None` for +# a no-op if you do not wish to receive input characters for that binding. +# +# If the same trigger is assigned to multiple actions, all of them are executed +# in the order they were defined in. + +key_bindings: + - { key: Paste, action: Paste } + - { key: Copy, action: Copy } + - { key: L, mods: Control, action: ClearLogNotice } + - { key: L, mods: Control, mode: ~Vi|~Search, chars: "\x0c" } + - { key: PageUp, mods: Shift, mode: ~Alt, action: ScrollPageUp, } + - { key: PageDown, mods: Shift, mode: ~Alt, action: ScrollPageDown } + - { key: Home, mods: Shift, mode: ~Alt, action: ScrollToTop, } + - { key: End, mods: Shift, mode: ~Alt, action: ScrollToBottom } + + - { key: Space, mods: Shift|Control, mode: Vi|~Search, action: ScrollToBottom } + - { key: Space, mods: Shift|Control, mode: ~Search, action: ToggleViMode } + - { key: Escape, mode: Vi|~Search, action: ClearSelection } + - { key: I, mode: Vi|~Search, action: ScrollToBottom } + - { key: I, mode: Vi|~Search, action: ToggleViMode } + - { key: C, mods: Control, mode: Vi|~Search, action: ToggleViMode } + - { key: Y, mods: Control, mode: Vi|~Search, action: ScrollLineUp } + - { key: E, mods: Control, mode: Vi|~Search, action: ScrollLineDown } + - { key: G, mode: Vi|~Search, action: ScrollToTop } + - { key: G, mods: Shift, mode: Vi|~Search, action: ScrollToBottom } + - { key: B, mods: Control, mode: Vi|~Search, action: ScrollPageUp } + - { key: F, mods: Control, mode: Vi|~Search, action: ScrollPageDown } + - { key: U, mods: Control, mode: Vi|~Search, action: ScrollHalfPageUp } + - { key: D, mods: Control, mode: Vi|~Search, action: ScrollHalfPageDown } + - { key: Y, mode: Vi|~Search, action: Copy } + - { key: Y, mode: Vi|~Search, action: ClearSelection } + - { key: Copy, mode: Vi|~Search, action: ClearSelection } + - { key: Return, mode: Vi|~Search, action: Open } + - { key: K, mode: Vi|~Search, action: Up } + - { key: J, mode: Vi|~Search, action: Down } + - { key: H, mode: Vi|~Search, action: Left } + - { key: L, mode: Vi|~Search, action: Right } + - { key: Up, mode: Vi|~Search, action: Up } + - { key: Down, mode: Vi|~Search, action: Down } + - { key: Left, mode: Vi|~Search, action: Left } + - { key: Right, mode: Vi|~Search, action: Right } + - { key: Key0, mode: Vi|~Search, action: First } + - { key: Key4, mods: Shift, mode: Vi|~Search, action: Last } + - { key: Key6, mods: Shift, mode: Vi|~Search, action: FirstOccupied } + - { key: H, mods: Shift, mode: Vi|~Search, action: High } + - { key: M, mods: Shift, mode: Vi|~Search, action: Middle } + - { key: L, mods: Shift, mode: Vi|~Search, action: Low } + - { key: B, mode: Vi|~Search, action: SemanticLeft } + - { key: W, mode: Vi|~Search, action: SemanticRight } + - { key: E, mode: Vi|~Search, action: SemanticRightEnd } + - { key: B, mods: Shift, mode: Vi|~Search, action: WordLeft } + - { key: W, mods: Shift, mode: Vi|~Search, action: WordRight } + - { key: E, mods: Shift, mode: Vi|~Search, action: WordRightEnd } + - { key: Key5, mods: Shift, mode: Vi|~Search, action: Bracket } + - { key: Slash, mode: Vi|~Search, action: SearchForward } + - { key: Slash, mods: Shift, mode: Vi|~Search, action: SearchBackward } + - { key: N, mode: Vi|~Search, action: SearchNext } + - { key: N, mods: Shift, mode: Vi|~Search, action: SearchPrevious } + + - { key: Return, mode: Search|Vi, action: SearchConfirm } + - { key: Escape, mode: Search, action: SearchCancel } + - { key: C, mods: Control, mode: Search, action: SearchCancel } + - { key: U, mods: Control, mode: Search, action: SearchClear } + - { key: W, mods: Control, mode: Search, action: SearchDeleteWord } + - { key: P, mods: Control, mode: Search, action: SearchHistoryPrevious } + - { key: N, mods: Control, mode: Search, action: SearchHistoryNext } + - { key: Up, mode: Search, action: SearchHistoryPrevious } + - { key: Down, mode: Search, action: SearchHistoryNext } + - { key: Return, mode: Search|~Vi, action: SearchFocusNext } + - { key: Return, mods: Shift, mode: Search|~Vi, action: SearchFocusPrevious } + + - { key: V, mode: Vi|~Search, action: ToggleNormalSelection } + - { key: V, mods: Shift, mode: Vi|~Search, action: ToggleLineSelection } + - { key: V, mods: Control, mode: Vi|~Search, action: ToggleBlockSelection } + - { key: V, mods: Alt, mode: Vi|~Search, action: ToggleSemanticSelection } + - { key: V, mods: Control|Shift, mode: Vi, chars: "\x69" } + - { key: V, mods: Control|Shift, mode: Vi, action: Paste } + - { key: V, mods: Control|Shift, mode: ~Vi, action: Paste } + + - { key: C, mods: Control|Shift, action: Copy } + - { key: F, mods: Control|Shift, mode: ~Search, action: SearchForward } + - { key: B, mods: Control|Shift, mode: ~Search, action: SearchBackward } + - { key: C, mods: Control|Shift, mode: Vi|~Search, action: ClearSelection } + - { key: Insert, mods: Shift, action: PasteSelection } + - { key: Key0, mods: Control, action: ResetFontSize } + - { key: Equals, mods: Control, action: IncreaseFontSize } + - { key: Plus, mods: Control, action: IncreaseFontSize } + - { key: NumpadAdd, mods: Control, action: IncreaseFontSize } + - { key: Minus, mods: Control, action: DecreaseFontSize } + - { key: NumpadSubtract, mods: Control, action: DecreaseFontSize } + + - { key: Return, mods: Alt, action: None } + + - { key: K, mods: Command, mode: ~Vi|~Search, action: None } + - { key: K, mods: Command, mode: ~Vi|~Search, action: None } + - { key: Key0, mods: Command, action: None } + - { key: Equals, mods: Command, action: None } + - { key: Plus, mods: Command, action: None } + - { key: NumpadAdd, mods: Command, action: None } + - { key: Minus, mods: Command, action: None } + - { key: NumpadSubtract, mods: Command, action: None } + - { key: V, mods: Command, action: None } + - { key: C, mods: Command, action: None } + - { key: C, mods: Command, mode: Vi|~Search, action: None } + - { key: H, mods: Command, action: None } + - { key: H, mods: Command|Alt, action: None } + - { key: M, mods: Command, action: None } + - { key: Q, mods: Command, action: None } + - { key: W, mods: Command, action: None } + - { key: N, mods: Command, action: None } + - { key: F, mods: Command|Control, action: None } + - { key: F, mods: Command, mode: ~Search, action: None } + - { key: B, mods: Command, mode: ~Search, action: None } + + + +debug: + render_timer: false + persistent_logging: false + log_level: Warn + print_events: false diff --git a/configs/direnv/direnv.toml b/configs/direnv/direnv.toml new file mode 100644 index 0000000..7069436 --- /dev/null +++ b/configs/direnv/direnv.toml @@ -0,0 +1,2 @@ +[whitelist] +prefix = [ "/home/wojtek/expo", "/Users/wojciechkozyra" ] diff --git a/configs/grub-theme/boot_menu_c.png b/configs/grub-theme/boot_menu_c.png new file mode 100644 index 0000000..0748b63 Binary files /dev/null and b/configs/grub-theme/boot_menu_c.png differ diff --git a/configs/grub-theme/boot_menu_e.png b/configs/grub-theme/boot_menu_e.png new file mode 100644 index 0000000..4900e0f Binary files /dev/null and b/configs/grub-theme/boot_menu_e.png differ diff --git a/configs/grub-theme/boot_menu_n.png b/configs/grub-theme/boot_menu_n.png new file mode 100644 index 0000000..d062fa9 Binary files /dev/null and b/configs/grub-theme/boot_menu_n.png differ diff --git a/configs/grub-theme/boot_menu_ne.png b/configs/grub-theme/boot_menu_ne.png new file mode 100644 index 0000000..ea2c6b6 Binary files /dev/null and b/configs/grub-theme/boot_menu_ne.png differ diff --git a/configs/grub-theme/boot_menu_nw.png b/configs/grub-theme/boot_menu_nw.png new file mode 100644 index 0000000..fdb7903 Binary files /dev/null and b/configs/grub-theme/boot_menu_nw.png differ diff --git a/configs/grub-theme/boot_menu_s.png b/configs/grub-theme/boot_menu_s.png new file mode 100644 index 0000000..d062fa9 Binary files /dev/null and b/configs/grub-theme/boot_menu_s.png differ diff --git a/configs/grub-theme/boot_menu_se.png b/configs/grub-theme/boot_menu_se.png new file mode 100644 index 0000000..1bfeb06 Binary files /dev/null and b/configs/grub-theme/boot_menu_se.png differ diff --git a/configs/grub-theme/boot_menu_sw.png b/configs/grub-theme/boot_menu_sw.png new file mode 100644 index 0000000..f004794 Binary files /dev/null and b/configs/grub-theme/boot_menu_sw.png differ diff --git a/configs/grub-theme/boot_menu_w.png b/configs/grub-theme/boot_menu_w.png new file mode 100644 index 0000000..4900e0f Binary files /dev/null and b/configs/grub-theme/boot_menu_w.png differ diff --git a/configs/grub-theme/icons/Manjaro.i686.png b/configs/grub-theme/icons/Manjaro.i686.png new file mode 100644 index 0000000..f212f9a Binary files /dev/null and b/configs/grub-theme/icons/Manjaro.i686.png differ diff --git a/configs/grub-theme/icons/Manjaro.x86_64.png b/configs/grub-theme/icons/Manjaro.x86_64.png new file mode 100644 index 0000000..f212f9a Binary files /dev/null and b/configs/grub-theme/icons/Manjaro.x86_64.png differ diff --git a/configs/grub-theme/icons/antergos.png b/configs/grub-theme/icons/antergos.png new file mode 100644 index 0000000..9d1ff9d Binary files /dev/null and b/configs/grub-theme/icons/antergos.png differ diff --git a/configs/grub-theme/icons/arch.png b/configs/grub-theme/icons/arch.png new file mode 100644 index 0000000..e0e90ab Binary files /dev/null and b/configs/grub-theme/icons/arch.png differ diff --git a/configs/grub-theme/icons/archlinux.png b/configs/grub-theme/icons/archlinux.png new file mode 100644 index 0000000..e0e90ab Binary files /dev/null and b/configs/grub-theme/icons/archlinux.png differ diff --git a/configs/grub-theme/icons/cancel.png b/configs/grub-theme/icons/cancel.png new file mode 100644 index 0000000..6374e9d Binary files /dev/null and b/configs/grub-theme/icons/cancel.png differ diff --git a/configs/grub-theme/icons/chakra.png b/configs/grub-theme/icons/chakra.png new file mode 100644 index 0000000..423d3d7 Binary files /dev/null and b/configs/grub-theme/icons/chakra.png differ diff --git a/configs/grub-theme/icons/debian.png b/configs/grub-theme/icons/debian.png new file mode 100644 index 0000000..6f1674d Binary files /dev/null and b/configs/grub-theme/icons/debian.png differ diff --git a/configs/grub-theme/icons/deepin.png b/configs/grub-theme/icons/deepin.png new file mode 100644 index 0000000..7dde50f Binary files /dev/null and b/configs/grub-theme/icons/deepin.png differ diff --git a/configs/grub-theme/icons/driver.png b/configs/grub-theme/icons/driver.png new file mode 100644 index 0000000..8c04e4d Binary files /dev/null and b/configs/grub-theme/icons/driver.png differ diff --git a/configs/grub-theme/icons/edit.png b/configs/grub-theme/icons/edit.png new file mode 100644 index 0000000..024bcad Binary files /dev/null and b/configs/grub-theme/icons/edit.png differ diff --git a/configs/grub-theme/icons/efi.png b/configs/grub-theme/icons/efi.png new file mode 100644 index 0000000..701ab8b Binary files /dev/null and b/configs/grub-theme/icons/efi.png differ diff --git a/configs/grub-theme/icons/elementary.png b/configs/grub-theme/icons/elementary.png new file mode 100644 index 0000000..f0788b4 Binary files /dev/null and b/configs/grub-theme/icons/elementary.png differ diff --git a/configs/grub-theme/icons/endeavouros.png b/configs/grub-theme/icons/endeavouros.png new file mode 100644 index 0000000..bf5d7c3 Binary files /dev/null and b/configs/grub-theme/icons/endeavouros.png differ diff --git a/configs/grub-theme/icons/fedora.png b/configs/grub-theme/icons/fedora.png new file mode 100644 index 0000000..047824f Binary files /dev/null and b/configs/grub-theme/icons/fedora.png differ diff --git a/configs/grub-theme/icons/fedoraos.png b/configs/grub-theme/icons/fedoraos.png new file mode 100644 index 0000000..8bcceb3 Binary files /dev/null and b/configs/grub-theme/icons/fedoraos.png differ diff --git a/configs/grub-theme/icons/find.efi.png b/configs/grub-theme/icons/find.efi.png new file mode 100644 index 0000000..d9d66b8 Binary files /dev/null and b/configs/grub-theme/icons/find.efi.png differ diff --git a/configs/grub-theme/icons/find.none.png b/configs/grub-theme/icons/find.none.png new file mode 100644 index 0000000..6ec0579 Binary files /dev/null and b/configs/grub-theme/icons/find.none.png differ diff --git a/configs/grub-theme/icons/gentoo.png b/configs/grub-theme/icons/gentoo.png new file mode 100644 index 0000000..1dd7138 Binary files /dev/null and b/configs/grub-theme/icons/gentoo.png differ diff --git a/configs/grub-theme/icons/gnu-linux.png b/configs/grub-theme/icons/gnu-linux.png new file mode 100644 index 0000000..9b58b99 Binary files /dev/null and b/configs/grub-theme/icons/gnu-linux.png differ diff --git a/configs/grub-theme/icons/help.png b/configs/grub-theme/icons/help.png new file mode 100644 index 0000000..6fd9d49 Binary files /dev/null and b/configs/grub-theme/icons/help.png differ diff --git a/configs/grub-theme/icons/iso.png b/configs/grub-theme/icons/iso.png new file mode 100644 index 0000000..06f673e Binary files /dev/null and b/configs/grub-theme/icons/iso.png differ diff --git a/configs/grub-theme/icons/kali.png b/configs/grub-theme/icons/kali.png new file mode 100644 index 0000000..950fc77 Binary files /dev/null and b/configs/grub-theme/icons/kali.png differ diff --git a/configs/grub-theme/icons/kaos.png b/configs/grub-theme/icons/kaos.png new file mode 100644 index 0000000..e89373b Binary files /dev/null and b/configs/grub-theme/icons/kaos.png differ diff --git a/configs/grub-theme/icons/kbd.png b/configs/grub-theme/icons/kbd.png new file mode 100644 index 0000000..d11d1f7 Binary files /dev/null and b/configs/grub-theme/icons/kbd.png differ diff --git a/configs/grub-theme/icons/korora.png b/configs/grub-theme/icons/korora.png new file mode 100644 index 0000000..4a5c847 Binary files /dev/null and b/configs/grub-theme/icons/korora.png differ diff --git a/configs/grub-theme/icons/kubuntu.png b/configs/grub-theme/icons/kubuntu.png new file mode 100644 index 0000000..d8bdcc2 Binary files /dev/null and b/configs/grub-theme/icons/kubuntu.png differ diff --git a/configs/grub-theme/icons/lang.png b/configs/grub-theme/icons/lang.png new file mode 100644 index 0000000..54c1407 Binary files /dev/null and b/configs/grub-theme/icons/lang.png differ diff --git a/configs/grub-theme/icons/langitketujuh.png b/configs/grub-theme/icons/langitketujuh.png new file mode 100644 index 0000000..d27de77 Binary files /dev/null and b/configs/grub-theme/icons/langitketujuh.png differ diff --git a/configs/grub-theme/icons/langitketujuhos.png b/configs/grub-theme/icons/langitketujuhos.png new file mode 100644 index 0000000..d27de77 Binary files /dev/null and b/configs/grub-theme/icons/langitketujuhos.png differ diff --git a/configs/grub-theme/icons/lfs.png b/configs/grub-theme/icons/lfs.png new file mode 100644 index 0000000..9b58b99 Binary files /dev/null and b/configs/grub-theme/icons/lfs.png differ diff --git a/configs/grub-theme/icons/linux.png b/configs/grub-theme/icons/linux.png new file mode 100644 index 0000000..9b58b99 Binary files /dev/null and b/configs/grub-theme/icons/linux.png differ diff --git a/configs/grub-theme/icons/linuxmint.png b/configs/grub-theme/icons/linuxmint.png new file mode 100644 index 0000000..2224280 Binary files /dev/null and b/configs/grub-theme/icons/linuxmint.png differ diff --git a/configs/grub-theme/icons/lubuntu.png b/configs/grub-theme/icons/lubuntu.png new file mode 100644 index 0000000..c620240 Binary files /dev/null and b/configs/grub-theme/icons/lubuntu.png differ diff --git a/configs/grub-theme/icons/macosx.png b/configs/grub-theme/icons/macosx.png new file mode 100644 index 0000000..c0d38c6 Binary files /dev/null and b/configs/grub-theme/icons/macosx.png differ diff --git a/configs/grub-theme/icons/mageia.png b/configs/grub-theme/icons/mageia.png new file mode 100644 index 0000000..48a800a Binary files /dev/null and b/configs/grub-theme/icons/mageia.png differ diff --git a/configs/grub-theme/icons/manjaro.png b/configs/grub-theme/icons/manjaro.png new file mode 100644 index 0000000..f212f9a Binary files /dev/null and b/configs/grub-theme/icons/manjaro.png differ diff --git a/configs/grub-theme/icons/memtest.png b/configs/grub-theme/icons/memtest.png new file mode 100644 index 0000000..8c04e4d Binary files /dev/null and b/configs/grub-theme/icons/memtest.png differ diff --git a/configs/grub-theme/icons/opensuse.png b/configs/grub-theme/icons/opensuse.png new file mode 100644 index 0000000..187486c Binary files /dev/null and b/configs/grub-theme/icons/opensuse.png differ diff --git a/configs/grub-theme/icons/parrot.png b/configs/grub-theme/icons/parrot.png new file mode 100644 index 0000000..8a08ce8 Binary files /dev/null and b/configs/grub-theme/icons/parrot.png differ diff --git a/configs/grub-theme/icons/pop-os.png b/configs/grub-theme/icons/pop-os.png new file mode 100644 index 0000000..d149b59 Binary files /dev/null and b/configs/grub-theme/icons/pop-os.png differ diff --git a/configs/grub-theme/icons/poweroff.png b/configs/grub-theme/icons/poweroff.png new file mode 100644 index 0000000..5de536b Binary files /dev/null and b/configs/grub-theme/icons/poweroff.png differ diff --git a/configs/grub-theme/icons/recovery.png b/configs/grub-theme/icons/recovery.png new file mode 100644 index 0000000..d089174 Binary files /dev/null and b/configs/grub-theme/icons/recovery.png differ diff --git a/configs/grub-theme/icons/restart.png b/configs/grub-theme/icons/restart.png new file mode 100644 index 0000000..f3f2687 Binary files /dev/null and b/configs/grub-theme/icons/restart.png differ diff --git a/configs/grub-theme/icons/settings.png b/configs/grub-theme/icons/settings.png new file mode 100644 index 0000000..583f2d5 Binary files /dev/null and b/configs/grub-theme/icons/settings.png differ diff --git a/configs/grub-theme/icons/shutdown.png b/configs/grub-theme/icons/shutdown.png new file mode 100644 index 0000000..53a3e23 Binary files /dev/null and b/configs/grub-theme/icons/shutdown.png differ diff --git a/configs/grub-theme/icons/siduction.png b/configs/grub-theme/icons/siduction.png new file mode 100644 index 0000000..1e6324a Binary files /dev/null and b/configs/grub-theme/icons/siduction.png differ diff --git a/configs/grub-theme/icons/solus.png b/configs/grub-theme/icons/solus.png new file mode 100644 index 0000000..450ec6d Binary files /dev/null and b/configs/grub-theme/icons/solus.png differ diff --git a/configs/grub-theme/icons/steamos.png b/configs/grub-theme/icons/steamos.png new file mode 100644 index 0000000..67752e7 Binary files /dev/null and b/configs/grub-theme/icons/steamos.png differ diff --git a/configs/grub-theme/icons/type.png b/configs/grub-theme/icons/type.png new file mode 100644 index 0000000..c658255 Binary files /dev/null and b/configs/grub-theme/icons/type.png differ diff --git a/configs/grub-theme/icons/tz.png b/configs/grub-theme/icons/tz.png new file mode 100644 index 0000000..26350eb Binary files /dev/null and b/configs/grub-theme/icons/tz.png differ diff --git a/configs/grub-theme/icons/ubuntu.png b/configs/grub-theme/icons/ubuntu.png new file mode 100644 index 0000000..a966099 Binary files /dev/null and b/configs/grub-theme/icons/ubuntu.png differ diff --git a/configs/grub-theme/icons/unknown.png b/configs/grub-theme/icons/unknown.png new file mode 100644 index 0000000..9b58b99 Binary files /dev/null and b/configs/grub-theme/icons/unknown.png differ diff --git a/configs/grub-theme/icons/unset.png b/configs/grub-theme/icons/unset.png new file mode 100644 index 0000000..5c632a4 Binary files /dev/null and b/configs/grub-theme/icons/unset.png differ diff --git a/configs/grub-theme/icons/void.png b/configs/grub-theme/icons/void.png new file mode 100644 index 0000000..3ed48d7 Binary files /dev/null and b/configs/grub-theme/icons/void.png differ diff --git a/configs/grub-theme/icons/windows.png b/configs/grub-theme/icons/windows.png new file mode 100644 index 0000000..5b99083 Binary files /dev/null and b/configs/grub-theme/icons/windows.png differ diff --git a/configs/grub-theme/icons/xubuntu.png b/configs/grub-theme/icons/xubuntu.png new file mode 100644 index 0000000..1590914 Binary files /dev/null and b/configs/grub-theme/icons/xubuntu.png differ diff --git a/configs/grub-theme/icons/xubuntuos.png b/configs/grub-theme/icons/xubuntuos.png new file mode 100644 index 0000000..1590914 Binary files /dev/null and b/configs/grub-theme/icons/xubuntuos.png differ diff --git a/configs/grub-theme/icons/zorin.png b/configs/grub-theme/icons/zorin.png new file mode 100644 index 0000000..ad0575d Binary files /dev/null and b/configs/grub-theme/icons/zorin.png differ diff --git a/configs/grub-theme/icons/zorinos.png b/configs/grub-theme/icons/zorinos.png new file mode 100644 index 0000000..1a180d3 Binary files /dev/null and b/configs/grub-theme/icons/zorinos.png differ diff --git a/configs/grub-theme/menu.png b/configs/grub-theme/menu.png new file mode 100644 index 0000000..bcdf1ed Binary files /dev/null and b/configs/grub-theme/menu.png differ diff --git a/configs/grub-theme/password_field.png b/configs/grub-theme/password_field.png new file mode 100644 index 0000000..0658e86 Binary files /dev/null and b/configs/grub-theme/password_field.png differ diff --git a/configs/grub-theme/progress_bar_s.png b/configs/grub-theme/progress_bar_s.png new file mode 100644 index 0000000..7092784 Binary files /dev/null and b/configs/grub-theme/progress_bar_s.png differ diff --git a/configs/grub-theme/progress_highlight_c.png b/configs/grub-theme/progress_highlight_c.png new file mode 100644 index 0000000..e3b537a Binary files /dev/null and b/configs/grub-theme/progress_highlight_c.png differ diff --git a/configs/grub-theme/select_bkg_c.png b/configs/grub-theme/select_bkg_c.png new file mode 100644 index 0000000..070e58c Binary files /dev/null and b/configs/grub-theme/select_bkg_c.png differ diff --git a/configs/grub-theme/select_bkg_e.png b/configs/grub-theme/select_bkg_e.png new file mode 100644 index 0000000..6d007f1 Binary files /dev/null and b/configs/grub-theme/select_bkg_e.png differ diff --git a/configs/grub-theme/select_bkg_w.png b/configs/grub-theme/select_bkg_w.png new file mode 100644 index 0000000..43e4425 Binary files /dev/null and b/configs/grub-theme/select_bkg_w.png differ diff --git a/configs/grub-theme/slider_c.png b/configs/grub-theme/slider_c.png new file mode 100644 index 0000000..1755502 Binary files /dev/null and b/configs/grub-theme/slider_c.png differ diff --git a/configs/grub-theme/slider_n.png b/configs/grub-theme/slider_n.png new file mode 100644 index 0000000..30fcd31 Binary files /dev/null and b/configs/grub-theme/slider_n.png differ diff --git a/configs/grub-theme/slider_s.png b/configs/grub-theme/slider_s.png new file mode 100644 index 0000000..2221ee6 Binary files /dev/null and b/configs/grub-theme/slider_s.png differ diff --git a/configs/grub-theme/theme.txt b/configs/grub-theme/theme.txt new file mode 100644 index 0000000..5f5b3f6 --- /dev/null +++ b/configs/grub-theme/theme.txt @@ -0,0 +1,50 @@ +desktop-image: "background.png" + +title-text:" " +message-color: "#757575" +terminal-left: "0" +terminal-top: "0" +terminal-width: "100%" +terminal-height: "100%" +terminal-border: "0" + ++ boot_menu { + left = 15% + width = 70% + top = 30% + height = 40% + icon_width = 32 + icon_height = 32 + item_height = 48 + item_padding = 8 + item_icon_space = 12 + item_spacing = 8 + menu_pixmap_style = "boot_menu_*.png" + item_color = "#EDEDED" + selected_item_color= "#DDDDDD" + selected_item_pixmap_style= "select_bkg_*.png" + scrollbar = true + scrollbar_width = 12 + scrollbar_thumb = "slider_*.png" +} + ++ progress_bar { + id = "__timeout__" + left = 0% + width = 100% + top = 100% + height = 23 + show_text = true + text_color = "255, 255, 255" + bar_style = "progress_bar_*.png" + highlight_style = "progress_highlight_*.png" +} + ++ image { + top = 80%+47 + left = 50%-240 + width = 480 + height = 42 + file = "menu.png" +} + diff --git a/configs/gtk-3.0/settings.ini b/configs/gtk-3.0/settings.ini new file mode 100644 index 0000000..2189857 --- /dev/null +++ b/configs/gtk-3.0/settings.ini @@ -0,0 +1,2 @@ +[Settings] +gtk-application-prefer-dark-theme = true diff --git a/configs/i3/config b/configs/i3/config new file mode 100644 index 0000000..5d775c1 --- /dev/null +++ b/configs/i3/config @@ -0,0 +1,123 @@ +set $mod Mod4 +set $left h +set $down j +set $up k +set $right l + +set $term alacritty +set $menu i3-dmenu-desktop + +default_border pixel 4 +default_floating_border pixel 4 + + bindsym $mod+Return exec $term + bindsym $mod+Shift+c kill + bindsym $mod+Ctrl+q exec i3-msg exit + bindsym $mod+p exec $menu + bindsym $mod+Ctrl+r restart + + floating_modifier Mod4 + + bindsym XF86AudioLowerVolume exec amixer -q sset Master 5%- + bindsym XF86AudioRaiseVolume exec amixer -q sset Master 5%+ + bindsym XF86AudioMute exec amixer -q set Master 1+ toggle + + bindsym XF86AudioPlay exec playerctl play-pause + bindsym XF86AudioNext exec playerctl next + bindsym XF86AudioPrev exec playerctl previous + bindsym $mod+$left focus left + bindsym $mod+$down focus down + bindsym $mod+$up focus up + bindsym $mod+$right focus right + + bindsym $mod+Ctrl+$left move left + bindsym $mod+Ctrl+$down move down + bindsym $mod+Ctrl+$up move up + bindsym $mod+Ctrl+$right move right + + assign [instance="myclitag1"] 1 + assign [instance="myclitag2"] 2 + assign [instance="myclitag3"] 3 + assign [instance="myclitag4"] 4 + assign [instance="myclitag5"] 5 + assign [instance="myclitag6"] 6 + assign [instance="myclitag7"] 7 + assign [instance="myclitag8"] 8 + assign [instance="myclitag9"] 9 + assign [instance="myclitag10"] 10 + + workspace 1 output DP-2 + workspace 2 output DP-2 + workspace 3 output DP-2 + workspace 4 output DP-2 + workspace 5 output DP-2 + workspace 6 output DP-3 + workspace 7 output DP-3 + workspace 8 output DP-2 + workspace 9 output DP-2 + workspace 10 output DP-2 + + bindsym $mod+Shift+L workspace next + bindsym $mod+Shift+H workspace prev + + bindsym $mod+1 workspace number 1 + bindsym $mod+2 workspace number 2 + bindsym $mod+3 workspace number 3 + bindsym $mod+4 workspace number 4 + bindsym $mod+5 workspace number 5 + bindsym $mod+6 workspace number 6 + bindsym $mod+7 workspace number 7 + bindsym $mod+8 workspace number 8 + bindsym $mod+9 workspace number 9 + bindsym $mod+0 workspace number 10 + + bindsym $mod+Shift+1 move container to workspace number 1 + bindsym $mod+Shift+2 move container to workspace number 2 + bindsym $mod+Shift+3 move container to workspace number 3 + bindsym $mod+Shift+4 move container to workspace number 4 + bindsym $mod+Shift+5 move container to workspace number 5 + bindsym $mod+Shift+6 move container to workspace number 6 + bindsym $mod+Shift+7 move container to workspace number 7 + bindsym $mod+Shift+8 move container to workspace number 8 + bindsym $mod+Shift+9 move container to workspace number 9 + bindsym $mod+Shift+0 move container to workspace number 10 + + bindsym $mod+b splith + bindsym $mod+v splitv + bindsym $mod+space layout toggle split tabbed + + bindsym $mod+f fullscreen + + bindsym $mod+Ctrl+space floating toggle +mode "resize" { + bindsym $left resize shrink width 10px + bindsym $down resize grow height 10px + bindsym $up resize shrink height 10px + bindsym $right resize grow width 10px + + bindsym Return mode "default" + bindsym Escape mode "default" +} +bindsym $mod+r mode "resize" + +bar { + position top + + colors { + statusline #ffffff + background #323232 + inactive_workspace #285577 #285577 #ffffff + active_workspace #52e2ff #52e2ff #5c5c5c + focused_workspace #52e2ff #52e2ff #5c5c5c + } +} +# class border backgr. text indicator child_border +client.focused #4c7899 #ffaa77 #ffffff #ff0000 #ff9000 +client.focused_inactive #333333 #5f676a #ffffff #ff0000 #5f676a +client.unfocused #333333 #222222 #888888 #ff0000 #1d1f21 +client.urgent #2f343a #900000 #ffffff #ff0000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #ff0000 #0c0c0c + +font pango:SourceCodePro Medium 10 + +client.background #ffffff diff --git a/configs/nvim/.gitignore b/configs/nvim/.gitignore new file mode 100644 index 0000000..ed0e3f9 --- /dev/null +++ b/configs/nvim/.gitignore @@ -0,0 +1 @@ +plugged diff --git a/configs/nvim/init.lua b/configs/nvim/init.lua new file mode 100644 index 0000000..06ad32b --- /dev/null +++ b/configs/nvim/init.lua @@ -0,0 +1,26 @@ +vim.g.mapleader = " " + +require("myconfig.plugins") +require("myconfig.globals") +require("myconfig.base").apply() +require("myconfig.statusline") +require("myconfig.options") +require("myconfig.format").apply() +require("myconfig.spell").apply() +require("myconfig.filetype").apply() +require("myconfig.snippets").apply() +require("myconfig.telescope").apply() +require("myconfig.git").apply() + +require("myconfig.workspaces").apply( + function() + require("myconfig.actions") + require("myconfig.keymap") + require("myconfig.treesitter").apply() + require("myconfig.lsp").apply() + require("myconfig.playground.node").apply() + require("myconfig.playground.docker").apply() + require("myconfig.noice").apply() + require("myconfig.db").apply() + end +) diff --git a/configs/nvim/lazy-lock.json b/configs/nvim/lazy-lock.json new file mode 100644 index 0000000..97f385c --- /dev/null +++ b/configs/nvim/lazy-lock.json @@ -0,0 +1,35 @@ +{ + "Comment.nvim": { "branch": "master", "commit": "8d3aa5c22c2d45e788c7a5fe13ad77368b783c20" }, + "LuaSnip": { "branch": "master", "commit": "a835e3d680c5940b61780c6af07885db95382478" }, + "cmp-buffer": { "branch": "main", "commit": "3022dbc9166796b644a841a02de8dd1cc1d311fa" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "0e6b2ed705ddcff9738ec4ea838141654f12eeef" }, + "cmp-path": { "branch": "main", "commit": "91ff86cd9c29299a64f968ebb45846c485725f23" }, + "cmp_luasnip": { "branch": "master", "commit": "18095520391186d634a0045dacaa346291096566" }, + "diffview.nvim": { "branch": "main", "commit": "58035354fc79c6ec42fa7b218dab90bd3968615f" }, + "gruvbox.nvim": { "branch": "main", "commit": "c6ef9c5a3a2ece0f8635460291eb4a4c06ed3dcc" }, + "lazy.nvim": { "branch": "main", "commit": "8456a867f79e3fb3b5390659c5d11a1e35793372" }, + "lspkind-nvim": { "branch": "master", "commit": "c68b3a003483cf382428a43035079f78474cd11e" }, + "luv-vimdocs": { "branch": "main", "commit": "997b9338fd3ac91a53bbdc19110c127e3bea01c4" }, + "neogit": { "branch": "master", "commit": "dc02950e0364db853b2ed4a169e38d7de8ec95ad" }, + "noice.nvim": { "branch": "main", "commit": "0c493e5d243c39adf3d6ce7683a16e610cc44e0a" }, + "nui.nvim": { "branch": "main", "commit": "0dc148c6ec06577fcf06cbab3b7dac96d48ba6be" }, + "nvim-cmp": { "branch": "main", "commit": "777450fd0ae289463a14481673e26246b5e38bf2" }, + "nvim-lspconfig": { "branch": "master", "commit": "5a871409199d585b52b69952532e3fb435e64566" }, + "nvim-luaref": { "branch": "main", "commit": "9cd3ed50d5752ffd56d88dd9e395ddd3dc2c7127" }, + "nvim-notify": { "branch": "master", "commit": "02047199e2752223c77c624160f720ca227944a9" }, + "nvim-treesitter": { "branch": "master", "commit": "4536156f32b8ab0aab264e3cd7819fc8e3daede4" }, + "nvim-treesitter-context": { "branch": "master", "commit": "c1fa59698850cafa4cae686e652d1512a01dcf3c" }, + "nvim-web-devicons": { "branch": "master", "commit": "585dbc29315ca60be67d18ae6175771c3fb6791e" }, + "playground": { "branch": "master", "commit": "4044b53c4d4fcd7a78eae20b8627f78ce7dc6f56" }, + "plenary.nvim": { "branch": "master", "commit": "253d34830709d690f013daf2853a9d21ad7accab" }, + "popup.nvim": { "branch": "master", "commit": "b7404d35d5d3548a82149238289fa71f7f6de4ac" }, + "telescope-file-browser.nvim": { "branch": "master", "commit": "94fe37a1ea217dd2f90d91222bc1531521146ac3" }, + "telescope-fzf-native.nvim": { "branch": "main", "commit": "580b6c48651cabb63455e97d7e131ed557b8c7e2" }, + "telescope.nvim": { "branch": "master", "commit": "a3f17d3baf70df58b9d3544ea30abe52a7a832c2" }, + "vim-coloresque": { "branch": "master", "commit": "e12a5007068e74c8ffe5b1da91c25989de9c2081" }, + "vim-dadbod": { "branch": "master", "commit": "389a2b0120f82b13d51ff7c07f5c13f9bc9f412f" }, + "vim-dadbod-ui": { "branch": "master", "commit": "986324fa6372170ec47b28a5558ae7f7185e1d71" }, + "vim-endwise": { "branch": "master", "commit": "c3411c95290063f56dfe13b485882111ef403c6e" }, + "vim-fugitive": { "branch": "master", "commit": "5b52a0f395065d6cb7b65a00a5e17eaf9ebd64d5" }, + "vim-gitgutter": { "branch": "master", "commit": "44dbd57dd19e8ec58b2a50c787c8ae7bb231c145" } +} \ No newline at end of file diff --git a/configs/nvim/lua.editorconfig b/configs/nvim/lua.editorconfig new file mode 100644 index 0000000..1f392af --- /dev/null +++ b/configs/nvim/lua.editorconfig @@ -0,0 +1,9 @@ +[*.lua] + +indent_style = space +indent_size = 4 +quote_style = double +continuation_indent = 4 +max_line_length = 100 +space_around_table_field_list = false + diff --git a/configs/nvim/lua/myconfig/actions.lua b/configs/nvim/lua/myconfig/actions.lua new file mode 100644 index 0000000..730054d --- /dev/null +++ b/configs/nvim/lua/myconfig/actions.lua @@ -0,0 +1,143 @@ +local finders = require("telescope.finders") +local pickers = require("telescope.pickers") +local action_state = require "telescope.actions.state" +local tel_actions = require("telescope.actions") +local tel_themes = require("telescope.themes") +local conf = require("telescope.config").values +local tel_builtin = require("telescope.builtin") +local _ = require("myconfig.utils") + +local tel = require("myconfig.telescope") +local workspaces = require("myconfig.workspaces") +local lsp = require("myconfig.lsp") + +local homeDir = vim.env.HOME + +local actions = { + lsp_code_actions = {name = "[lsp] code action", fn = lsp.codeAction}, + lsp_rename = {name = "[lsp] rename", fn = lsp.rename}, + shell_root = {name = "[shell] root dir", fn = workspaces.open_terminal_root}, + shell_cwd = {name = "[shell] current dir", fn = workspaces.open_terminal_current}, + workspace_switch = {name = "workspace switch", fn = workspaces.switch_workspace}, + workspace_switch_current = { + name = "workspace switch to current dir", + fn = workspaces.switch_to_current_dir, + }, + format = {name = "format", fn = lsp.format}, + format_range = {name = "format range", fn = lsp.formatSelected}, + restart = {name = "restart", fn = lsp.restart}, + find_files = { + name = "find files", + fn = function() tel_builtin.find_files({no_ignore = tel.state.show_ignored, hidden = true}) end + }, + grep_files = { + name = "grep all", + fn = function() tel.live_grep({no_ignore = tel.state.show_ignored, hidden = true}) end, + }, + dotfiles_go = { + name = "dotfiles", + fn = function() workspaces.set_workspace(homeDir .. "/.dotfiles") end, + }, + notes_search = { + name = "search notes", + fn = function() + tel_builtin.find_files({cwd = "~/notes", prompt_title = "Notes", hidden = true}) + end, + }, + dotfiles_search = { + name = "search dotfiles", + fn = function() + tel_builtin.find_files({cwd = "~/.dotfiles", prompt_title = "Dot files", hidden = true}) + end, + }, + filebrowser_noingore = {name = "file browser no ignore", fn = tel.file_browser_current_noignore}, + git_commit = { + name = "git add -A && git commit", + fn = function() + vim.cmd.G("add -A") + vim.cmd.G("commit") + end, + }, + git_commit_amend = { + name = "git add -A && git commit --amend", + fn = function() + vim.cmd.G("add -A") + vim.cmd.G("commit --amend") + end, + }, + toggle_ignored = { + name = "toggle ignored files", + fn = function() + if (tel.state.show_ignored) then + vim.notify("hiding ignored files") + tel.state.show_ignored = false + else + vim.notify("showing ignored files") + tel.state.show_ignored = true + end + end, + }, + toggle_diagnostics = { + name = "[lsp] toggle diagnostics", + fn = (function() + local enabled = true; + return function() + if (enabled) then + vim.diagnostic.disable() + else + vim.diagnostic.enable() + end + enabled = not enabled + end + end)(), + }, +} + +local function create_local_action(action) + return { + id = action.id, + name = action.name, + fn = function() + local command = action.args + command.cwd = action.cwd + _.run_async(command) + end, + } +end + +local function select_action() + local function entry_maker(entry) + return {value = entry, display = entry.name, ordinal = entry.id} + end + + local actions_list = {} + for k, v in pairs(workspaces.current.vim.actions or {}) do + table.insert(actions_list, create_local_action(v)) + end + for k, v in pairs(actions) do + table.insert(actions_list, vim.tbl_extend("force", v, {id = k})) + end + return function() + pickers.new( + {}, vim.tbl_extend( + "force", { + prompt_title = "Run action", + finder = finders.new_table({results = actions_list, entry_maker = entry_maker}), + sorter = conf.generic_sorter({}), + attach_mappings = function(prompt_buf) + tel_actions.select_default:replace( + function() + tel_actions.close(prompt_buf) + local entry = action_state.get_selected_entry() + entry.value.fn() + end + ) + return true; + end, + }, tel_themes.get_ivy() + ) + ):find() + end +end + +return {actions = actions, select_action = select_action} diff --git a/configs/nvim/lua/myconfig/base.lua b/configs/nvim/lua/myconfig/base.lua new file mode 100644 index 0000000..fa1a10a --- /dev/null +++ b/configs/nvim/lua/myconfig/base.lua @@ -0,0 +1,27 @@ +local module = {} + +local icons = require("nvim-web-devicons") + +function module.apply() + vim.cmd.filetype("plugin indent on") + vim.cmd.syntax("enable") + vim.g.AutoPairs = {["("] = ")",["["] = "]",["{"] = "}",["`"] = "`",["```"] = "```"} + + vim.g.surround_no_mappings = 1 + local group = vim.api.nvim_create_augroup("main", {clear = true}) + vim.api.nvim_create_autocmd( + "TextYankPost", { + group = group, + pattern = "*", + callback = function() + vim.highlight.on_yank({higroup = "IncSearch", timeout = 1000}) + end, + } + ) + icons.setup { + override = {xml = {icon = "", color = "#e37933", cterm_color = "173", name = "Xml"}}, + default = true, + } +end + +return module diff --git a/configs/nvim/lua/myconfig/base64.lua b/configs/nvim/lua/myconfig/base64.lua new file mode 100644 index 0000000..7394dc8 --- /dev/null +++ b/configs/nvim/lua/myconfig/base64.lua @@ -0,0 +1,113 @@ +local module = {} + +function module.encode(val) + local b64 = { + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "+", + "/", + } + + local function char1(byte1) + -- 252: 0b11111100 + return b64[bit.rshift(bit.band(string.byte(byte1), 252), 2) + 1] + end + + local function char2(byte1, byte2) + -- 3: 0b00000011 + return b64[bit.lshift(bit.band(string.byte(byte1), 3), 4) + + -- 240: 0b11110000 + bit.rshift(bit.band(string.byte(byte2), 240), 4) + 1] + end + + local function char3(byte2, byte3) + -- 15: 0b00001111 + return b64[bit.lshift(bit.band(string.byte(byte2), 15), 2) + + -- 192: 0b11000000 + bit.rshift(bit.band(string.byte(byte3), 192), 6) + 1] + end + + local function char4(byte3) + -- 63: 0b00111111 + return b64[bit.band(string.byte(byte3), 63) + 1] + end + + local result = "" + for byte1, byte2, byte3 in string.gmatch(val, "(.)(.)(.)") do + result = result .. char1(byte1) .. char2(byte1, byte2) .. char3(byte2, byte3) .. + char4(byte3) + end + -- The last bytes might not fit in a triplet so we need to pad them + -- with zeroes + if (string.len(val) % 3) == 1 then + result = result .. char1(string.sub(val, -1)) .. char2(string.sub(val, -1), "\0") .. "==" + elseif (string.len(val) % 3) == 2 then + result = result .. char1(string.sub(val, -2, -2)) .. + char2(string.sub(val, -2, -2), string.sub(val, -1)) .. + char3(string.sub(val, -1), "\0") .. "=" + end + + return result +end + +return module diff --git a/configs/nvim/lua/myconfig/db.lua b/configs/nvim/lua/myconfig/db.lua new file mode 100644 index 0000000..b82f53f --- /dev/null +++ b/configs/nvim/lua/myconfig/db.lua @@ -0,0 +1,13 @@ +local module = {} + +local workspace = require("myconfig.workspaces") + +function module.apply() + vim.g.dbs = workspace.current.vim.databases or {} + vim.g.db_ui_tmp_query_location = "~/.cache/db" + vim.g.db_ui_execute_on_save = 0 -- disabled + vim.g.db_ui_use_nerd_fonts = 1 + vim.g.db_ui_force_echo_notifications = 1 +end + +return module; diff --git a/configs/nvim/lua/myconfig/debug.lua b/configs/nvim/lua/myconfig/debug.lua new file mode 100644 index 0000000..22ce91b --- /dev/null +++ b/configs/nvim/lua/myconfig/debug.lua @@ -0,0 +1,23 @@ +local module = {} + +module.enabled = true + +function module.reload() + for k, _ in pairs(package.loaded) do + if vim.startswith(k, "myconfig") then + print("reload " .. k) + package.loaded[k] = nil + end + end + vim.cmd.luafile({args = {"/home/wojtek/.config/nvim/init.lua"}}) + print("reloaded"); +end + +function module.playground() + vim.cmd.messages("clear") +end + +function module.apply() +end + +return module; diff --git a/configs/nvim/lua/myconfig/filetype.lua b/configs/nvim/lua/myconfig/filetype.lua new file mode 100644 index 0000000..e035316 --- /dev/null +++ b/configs/nvim/lua/myconfig/filetype.lua @@ -0,0 +1,62 @@ +local module = {} + +local format = require("myconfig.format") +local spell = require("myconfig.spell") +local snippets = require("myconfig.snippets") + +local javascript = function() + format.preset(2) + spell.preset("ts") + snippets.load("typescript", require("myconfig.lang.typescript_snippets")) +end + +local handlers = { + default = function() spell.preset("common") end, + css = format.preset_2, + scss = format.preset_2, + sh = format.preset_2, + cpp = format.preset_2, + go = function() + format.preset(4) + spell.preset("go") + vim.opt_local.expandtab = false + snippets.load("go", require("myconfig.lang.go_snippets")) + end, + elixir = require("myconfig.lang.elixir").apply, + json = format.preset_2, + lua = function() + format.preset(4) + spell.preset("lua") + snippets.load("lua", require("myconfig.lang.lua_snippets")) + end, + markdown = function() + format.preset(4) + spell.strict_preset() + end, + gitcommit = spell.strict_preset, + plist = format.preset_4, + yaml = format.preset_2, + ["javascript"] = javascript, + ["javascriptreact"] = javascript, + ["javascript.jsx"] = javascript, + ["typescript"] = javascript, + ["typescript.tsx"] = javascript, + ["typescriptreact"] = javascript, +} + +local on_buf_enter_cb = function() + vim.opt_local.spell = false + local filetype = vim.bo.filetype or "default" + if (handlers[filetype]) then + handlers[vim.bo.filetype]() + end +end + +function module.apply() + local group = vim.api.nvim_create_augroup("OnBufEnter", {clear = true}) + vim.api.nvim_create_autocmd( + "BufEnter", {group = group, pattern = "*", callback = on_buf_enter_cb} + ) +end + +return module diff --git a/configs/nvim/lua/myconfig/format.lua b/configs/nvim/lua/myconfig/format.lua new file mode 100644 index 0000000..904afc6 --- /dev/null +++ b/configs/nvim/lua/myconfig/format.lua @@ -0,0 +1,26 @@ +local module = {} + +local function trim_whitespaces() + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false); + for i, line in ipairs(lines) do + lines[i] = string.gsub(line, "%s+$", "") + end + vim.api.nvim_buf_set_lines(0, 0, -1, true, lines) +end + +function module.apply() + vim.api.nvim_create_user_command("TrimWhitespaces", trim_whitespaces, {nargs = 0}) +end + +function module.preset(number) + vim.opt_local.tabstop = number + vim.opt_local.shiftwidth = number + vim.opt_local.softtabstop = number + vim.opt_local.expandtab = true +end + +function module.preset_2() module.preset(2) end + +function module.preset_4() module.preset(4) end + +return module diff --git a/configs/nvim/lua/myconfig/git.lua b/configs/nvim/lua/myconfig/git.lua new file mode 100644 index 0000000..257ab17 --- /dev/null +++ b/configs/nvim/lua/myconfig/git.lua @@ -0,0 +1,101 @@ +local module = {} + +local diffview = require("diffview") +local neogit = require("neogit") +local cb = require("diffview.config").diffview_callback +local dv_lib = require("diffview.lib") + +function module.toggle_diffview() + local view = dv_lib.get_current_view() + if view then + view:close() + dv_lib.dispose_view(view) + else + diffview.open() + end +end + +function module.toggle_diffhistory() + local view = dv_lib.get_current_view() + if view then + view:close() + dv_lib.dispose_view(view) + else + diffview.file_history() + end +end + +function module.apply() + neogit.setup({integrations = {diffview = true}}) + + diffview.setup { + diff_binaries = false, -- Show diffs for binaries + enhanced_diff_hl = false, -- See ':h diffview-config-enhanced_diff_hl' + use_icons = true, -- Requires nvim-web-devicons + default_args = { + -- Default args prepended to the arg-list for the listed commands + DiffviewOpen = {}, + DiffviewFileHistory = {}, + }, + hooks = {}, -- See ':h diffview-config-hooks' + key_bindings = { + disable_defaults = true, -- Disable the default key bindings + view = { + [""] = cb("select_next_entry"), -- Open the diff for the next file + [""] = cb("select_prev_entry"), -- Open the diff for the previous file + ["gf"] = cb("goto_file"), -- Open the file in a new split in previous tabpage + [""] = cb("goto_file_split"), -- Open the file in a new split + ["gf"] = cb("goto_file_tab"), -- Open the file in a new tabpage + ["e"] = cb("focus_files"), -- Bring focus to the files panel + ["b"] = cb("toggle_files"), -- Toggle the files panel. + }, + file_panel = { + ["j"] = cb("next_entry"), -- Bring the cursor to the next file entry + [""] = cb("next_entry"), + ["k"] = cb("prev_entry"), -- Bring the cursor to the previous file entry. + [""] = cb("prev_entry"), + [""] = cb("select_entry"), -- Open the diff for the selected entry. + ["o"] = cb("select_entry"), + ["<2-LeftMouse>"] = cb("select_entry"), + ["-"] = cb("toggle_stage_entry"), -- Stage / unstage the selected entry. + ["S"] = cb("stage_all"), -- Stage all entries. + ["U"] = cb("unstage_all"), -- Unstage all entries. + ["X"] = cb("restore_entry"), -- Restore entry to the state on the left side. + ["R"] = cb("refresh_files"), -- Update stats and entries in the file list. + [""] = cb("select_next_entry"), + [""] = cb("select_prev_entry"), + ["gf"] = cb("goto_file"), + [""] = cb("goto_file_split"), + ["gf"] = cb("goto_file_tab"), + ["i"] = cb("listing_style"), -- Toggle between 'list' and 'tree' views + ["f"] = cb("toggle_flatten_dirs"), -- Flatten empty subdirectories in tree listing style. + ["e"] = cb("focus_files"), + ["b"] = cb("toggle_files"), + }, + file_history_panel = { + ["g!"] = cb("options"), -- Open the option panel + [""] = cb("open_in_diffview"), -- Open the entry under the cursor in a diffview + ["y"] = cb("copy_hash"), -- Copy the commit hash of the entry under the cursor + ["zR"] = cb("open_all_folds"), + ["zM"] = cb("close_all_folds"), + ["j"] = cb("next_entry"), + [""] = cb("next_entry"), + ["k"] = cb("prev_entry"), + [""] = cb("prev_entry"), + [""] = cb("select_entry"), + ["o"] = cb("select_entry"), + ["<2-LeftMouse>"] = cb("select_entry"), + [""] = cb("select_next_entry"), + [""] = cb("select_prev_entry"), + ["gf"] = cb("goto_file"), + [""] = cb("goto_file_split"), + ["gf"] = cb("goto_file_tab"), + ["e"] = cb("focus_files"), + ["b"] = cb("toggle_files"), + }, + option_panel = {[""] = cb("select"),["q"] = cb("close")}, + }, + } +end + +return module diff --git a/configs/nvim/lua/myconfig/globals.lua b/configs/nvim/lua/myconfig/globals.lua new file mode 100644 index 0000000..521e0de --- /dev/null +++ b/configs/nvim/lua/myconfig/globals.lua @@ -0,0 +1,4 @@ +function P(v) + print(vim.inspect(v)) + return v +end diff --git a/configs/nvim/lua/myconfig/keymap.lua b/configs/nvim/lua/myconfig/keymap.lua new file mode 100644 index 0000000..aa0019e --- /dev/null +++ b/configs/nvim/lua/myconfig/keymap.lua @@ -0,0 +1,152 @@ +local debug = require("myconfig.debug") +local utils = require("myconfig.utils") +local tel = require("myconfig.telescope") +local tel_builtin = require("telescope.builtin") +local tree = require("myconfig.treesitter") +local lsp = require("myconfig.lsp") +local workspaces = require("myconfig.workspaces") +local actions = require("myconfig.actions") +local snippets = require("myconfig.snippets") +local neogit = require("neogit") +local git = require("myconfig.git") +local surround = require("myconfig.surround") + +local ref = utils.ref +local remap = {noremap = false} + +-- # Modes: +-- - normal + select + visual + operator pending +-- n - normal mode +-- v - visual + select +-- s - select ( from visual to enter, behaves like normal editor) +-- x - visual +-- o - operator pending (in the middle of keybinding unless no wait) +-- i - insert (+replace) +-- l - insert + commandline + lang +-- c - commandline (:somecommand) +-- t - terminal (can be launched with :terminal) +-- +-- # Options: +-- noremap - if rhs is binding use original behavior (if noremap is false use remapped behavior) +-- silent - ignore output from commands +-- buffer - current buffer only +-- nowait - execute keybinding even if there is other binding that could match with next keystroke + +local mapping = { + [""] = { + {"u", "i"}, -- history breakpoint u + [""] = {tel.file_browser_current_dir}, + a = { + a = {lsp.codeAction}, + r = {lsp.rename}, + q = {lsp.autoFix}, + f = {{lsp.format, "n"}, {lsp.formatSelected, "v"}}, + }, + f = { + f = {actions.actions.grep_files.fn}, + g = {c = {tel_builtin.git_commits}, b = {tel_builtin.git_branches}}, + v = {actions.actions.dotfiles_search.fn}, + n = {actions.actions.notes_search.fn}, + h = {tel_builtin.help_tags}, + b = {tel_builtin.buffers}, + r = {tel_builtin.resume}, + }, + c = {d = {lsp.showLineDiagnostics}}, + s = {s = {workspaces.switch_workspace}, d = {workspaces.switch_to_current_dir}}, + g = { + s = {function() vim.cmd.vertical("G") end}, + b = {function() vim.cmd.vertical("G blame") end}, + g = {neogit.open}, + d = {git.toggle_diffview}, + h = {git.toggle_diffhistory}, + }, + d = { + l = {lsp.restart}, + p = {debug.playground, "", {silent = false}}, + r = {debug.reload}, + s = {snippets.reload}, + }, + [""] = {":", "", {silent = false}}, + }, + [",,"] = {actions.select_action(), ""}, + [""] = {snippets.expand_or_jump, {"i", "s"}}, + ["j>"] = {snippets.jump_back, {"i", "s"}}, + ["e>"] = {tel.file_browser_root}, + ["n>"] = {"tab split"}, + ["p>"] = {actions.actions.find_files.fn}, + ["s>"] = { + ref(tree, "selection_init"), + ref(tree, "selection_inc"), -- active after selection init + }, + ["a>"] = ref(tree, "selection_inc_scope"), -- active after selection init + ["x>"] = ref(tree, "selection_dec"), -- active after selection init + ["h>"] = {lsp.onHover}, + }, + [""] = {"", "n"}, -- switch alternative buffer + ["?"] = {tel_builtin.current_buffer_fuzzy_find}, + H = {"gT"}, + L = {"gt"}, + Y = {"y$", "n"}, + Q = {"", "", remap}, + g = { + -- g = {} - already used, + b = {{ref(tree, "comment_block")}, b = {ref(tree, "comment_toggle_block")}}, + c = {{ref(tree, "comment_line")}, c = {ref(tree, "comment_toggle_line")}}, + d = {lsp.goToDefinition}, + D = {lsp.goToDeclaration}, + t = {lsp.goToTypeDefinition}, + r = {lsp.references}, + i = {lsp.goToPrev}, + u = {lsp.goToNext}, + }, + d = { + -- d = {} - already used, + s = {surround.remove, "n"}, + }, + c = { + -- c = {} - already used, + s = {surround.replace, "n"}, + }, + t = {t = {"terminal", "n"}}, + ["\""] = {surround.surround_selection({"\""}, {"\""}), "x"}, + ["'"] = {surround.surround_selection({"'"}, {"'"}), "x"}, + ["`"] = { + {surround.surround_selection({"`"}, {"`"}), "x"}, + {surround.auto_pair({"`"}, {"`"}), "i"}, + }, + ["{"] = { + {surround.surround_selection({"{"}, {"}"}), "x"}, + {surround.auto_pair({"{"}, {"}"}), "i"}, + }, + ["("] = { + {surround.surround_selection({"("}, {")"}), "x"}, + {surround.auto_pair({"("}, {")"}), "i"}, + }, + ["["] = { + {surround.surround_selection({"["}, {"]"}), "x"}, + {surround.auto_pair({"["}, {"]"}), "i"}, + }, + ["<"] = {surround.surround_selection({"<"}, {">"}), "x"}, + ["}"] = {surround.surround_selection({"{", ""}, {"", "}"}), "x"}, + [")"] = {surround.surround_selection({"(", ""}, {"", ")"}), "x"}, + ["]"] = {surround.surround_selection({"[", ""}, {"", "]"}), "x"}, + -- + -- biddings keep the same general purpose, just side effects + -- + { + -- history breakpoint u + [","] = {",u", "i"}, + ["."] = {".u", "i"}, + -- do not update copy register, send instead to black hole register + c = {"\"_c", ""}, + C = {"\"_C", ""}, + x = {"\"_x", ""}, + X = {"\"_X", ""}, + -- do not replace copy register when pasting in visual mode + p = {"\"_dP", "v"}, + }, +} + +local default_options = {noremap = true, silent = true} +utils.apply_keymap(mapping, default_options) diff --git a/configs/nvim/lua/myconfig/lang/cmake.lua b/configs/nvim/lua/myconfig/lang/cmake.lua new file mode 100644 index 0000000..33bd3d6 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/cmake.lua @@ -0,0 +1,27 @@ +local module = {} + +local workspaces = require("myconfig.workspaces") + +function module.cmake_config() + if workspaces.current.vim.cmake_efm then + return { + on_attach = function(client) + client.server_capabilities.documentFormattingProvider = false; + end, + } + else + return {} + end +end + +function module.attach_efm(config) + if workspaces.current.vim.cmake_efm then + config.settings.languages = vim.tbl_extend( + "force", config.settings.languages, {cmake = {workspaces.current.vim.cmake_efm}} + ) + config.filetypes = vim.list_extend(config.filetypes, {"cmake"}) + config.root_dir_patterns = vim.list_extend(config.root_dir_patterns, {"CMakeLists.txt"}); + end +end + +return module diff --git a/configs/nvim/lua/myconfig/lang/elixir.lua b/configs/nvim/lua/myconfig/lang/elixir.lua new file mode 100644 index 0000000..c842bf3 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/elixir.lua @@ -0,0 +1,19 @@ +local _ = require("myconfig.utils") + +local module = {} + +local path = { + language_server_script = function() + return vim.fn.expand("~") .. "/.cache/nvim/myconfig/elixirls/language_server.sh" + end, +} + +function module.apply() + vim.api.nvim_create_user_command( + "ElixirLspInstall", function() _.rpc_run({name = "elixir:lsp:install"}) end, {nargs = 0} + ) +end + +function module.elixirls_config() return {cmd = {path.language_server_script()}} end + +return module; diff --git a/configs/nvim/lua/myconfig/lang/go.lua b/configs/nvim/lua/myconfig/lang/go.lua new file mode 100644 index 0000000..e72d705 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/go.lua @@ -0,0 +1,82 @@ +local module = {} + +local workspaces = require("myconfig.workspaces") +local parsers = require("nvim-treesitter.parsers") + +function module.gopls_config() + return { + on_attach = function(client) + client.server_capabilities.documentFormattingProvider = false; + end, + settings = {gopls = {buildFlags = {"-tags=e2e"}}}, + } +end + +function module.golangci_config() + if workspaces.current.vim.go_efm then + return { + on_attach = function(client) + client.server_capabilities.documentFormattingProvider = false; + end, + } + else + return {} + end +end + +function module.attach_efm(config) + if workspaces.current.vim.go_efm then + config.settings.languages = vim.tbl_extend( + "force", config.settings.languages, {go = {workspaces.current.vim.go_efm}} + ) + config.filetypes = vim.list_extend(config.filetypes, {"go"}) + config.root_dir_patterns = vim.list_extend(config.root_dir_patterns, {"go.sum", "go.mod"}); + end +end + +local function get_start_of_next_sibling(node) + local next = node:next_named_sibling() + if next == nil then + local _, _, end_row, _ = node:range() + return end_row, -1 + end + local start_row, start_col, _, _ = next:range() + return start_row, start_col +end + +local function update_node(node) + local _, _, end_row, end_col = node:range() + local lines = vim.api.nvim_buf_get_lines(0, end_row, end_row + 1, false) + local next_start_row, next_start_col = get_start_of_next_sibling(node) + local suffix_end_col = next_start_row == end_row and next_start_col or -1 + local suffix = lines[1]:sub(end_col + 1, suffix_end_col) + local match = suffix:match(" *$") + if match == suffix then + local lines_slice = {lines[1]:sub(0, end_col) .. "," .. lines[1]:sub(end_col + 1, -1)} + vim.api.nvim_buf_set_lines(0, end_row, end_row + 1, false, lines_slice) + end +end + +local function get_last_non_comment(last_node) + local node = last_node + while node and node:type() == "comment" do + node = node:prev_named_sibling() + end + return node +end + +function module.format(original_format) + local tree = parsers.get_parser(0):parse()[1] + local query = vim.treesitter.query.get_query("go", "trailing_commas") + for _, node in query:iter_captures(tree:root(), 0, 0, -1) do + local non_comment = get_last_non_comment(node) + if non_comment then + update_node(non_comment) + end + end + if original_format then + original_format() + end +end + +return module diff --git a/configs/nvim/lua/myconfig/lang/go_snippets.lua b/configs/nvim/lua/myconfig/lang/go_snippets.lua new file mode 100644 index 0000000..940b1fa --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/go_snippets.lua @@ -0,0 +1,12 @@ +local ls = require "luasnip" + +local i = ls.insert_node +local t = ls.text_node +local rep = require("luasnip.extras").rep +local fmt = require("luasnip.extras.fmt").fmt + +return { + main = {t {"func main() {", "\t"}, i(0), t {"", "}"}}, + ef = {i(1, {"val"}), t ", err := ", i(2, {"f"}), t "(", i(3), t ")", i(0)}, + Ef = fmt("{}, {}Err := {}({})", {i(1), rep(1), i(2), i(3)}), +} diff --git a/configs/nvim/lua/myconfig/lang/lua.lua b/configs/nvim/lua/myconfig/lang/lua.lua new file mode 100644 index 0000000..63ff072 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/lua.lua @@ -0,0 +1,88 @@ +local _ = require("myconfig.utils") + +local module = {} + +local path = { + data = function() return vim.fn.stdpath("data") end, -- "~/.local/share/nvim" + language_server_base = function() + return vim.fn.expand("~") .. "/.cache/nvim/myconfig/lua_lsp" + end, + language_server_bin = function(base) return string.format("%s/bin/lua-language-server", base) end, +} + +local sumneko_command = function() + local base_directory = path.language_server_base() + local bin = path.language_server_bin(base_directory) + return {bin} +end + +local function get_runtime() + local result = {}; + -- for _, runtime_paths in pairs(vim.api.nvim_list_runtime_paths()) do + -- local lua_path = runtime_paths .. "/lua"; + -- if vim.fn.isdirectory(lua_path) == 1 then + -- result[lua_path] = true + -- end + -- end + -- dependencies are added to the runtime path after lsp server + -- is initialised so we need to add them explicitly + local plugin_path = path.data() .. "/lazy" + for _, plugin in pairs(vim.fn.readdir(plugin_path)) do + local lua_path = plugin_path .. "/" .. plugin .. "/lua"; + if vim.fn.isdirectory(lua_path) == 1 then + result[lua_path] = true + end + end + result[vim.fn.expand("$VIMRUNTIME/lua")] = true + result[vim.fn.expand("$VIMRUNTIME/lua/vim/lsp")] = true + return result +end + +function module.lua_ls_config() + return { + cmd = sumneko_command(), + settings = { + Lua = { + runtime = {version = "LuaJIT"}, + completion = {keywordSnippet = "Disable"}, + diagnostics = { + enable = true, + disable = {"trailing-space"}, + neededFileStatus = {["codestyle-check"] = "Any"}, + globals = { + -- Neovim + "vim", + -- Busted + "describe", + "it", + "before_each", + "after_each", + "teardown", + "pending", + "clear", + }, + }, + format = { + enable = true, + defaultConfig = { + indent_style = "space", + indent_size = "4", + quote_style = "double", + max_line_length = "101", + space_around_table_field_list = "false", + }, + }, + workspace = { + library = get_runtime(), + maxPreload = 10000, + preloadFileSize = 10000, + checkThirdParty = false, + }, + }, + }, + filetypes = {"lua"}, + handlers = {}, + } +end + +return module; diff --git a/configs/nvim/lua/myconfig/lang/lua_snippets.lua b/configs/nvim/lua/myconfig/lang/lua_snippets.lua new file mode 100644 index 0000000..14da5d4 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/lua_snippets.lua @@ -0,0 +1,10 @@ +local ls = require "luasnip" + +local i = ls.insert_node +local rep = require("luasnip.extras").rep +local fmt = require("luasnip.extras.fmt").fmt + +return { + req = fmt("local {} = require(\"{}\")", {i(1), rep(1)}), + reqq = fmt("local {} = require(\"{}\")", {i(1), i(2)}), +} diff --git a/configs/nvim/lua/myconfig/lang/typescript.lua b/configs/nvim/lua/myconfig/lang/typescript.lua new file mode 100644 index 0000000..8e30a53 --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/typescript.lua @@ -0,0 +1,38 @@ +local workspaces = require("myconfig.workspaces") + +local module = {} + +function module.tsserver_config() + return { + init_options = { + hostInfo = "neovim", + preferences = {importModuleSpecifierPreference = "non-relative"}, + }, + on_attach = function(client) + if workspaces.current.vim.eslint then + client.server_capabilities.documentFormattingProvider = false + end + end, + filetypes = { + "javascript", + "javascriptreact", + "javascript.jsx", + "typescript", + "typescript.tsx", + "typescriptreact", + }, + } +end + +function module.eslint_config() + return { + on_attach = function(client) + if workspaces.current.vim.eslint then + client.server_capabilities.documentFormattingProvider = true + end + end, + settings = {packageManager = "yarn"}, + } +end + +return module; diff --git a/configs/nvim/lua/myconfig/lang/typescript_snippets.lua b/configs/nvim/lua/myconfig/lang/typescript_snippets.lua new file mode 100644 index 0000000..f16ba8b --- /dev/null +++ b/configs/nvim/lua/myconfig/lang/typescript_snippets.lua @@ -0,0 +1,6 @@ +local ls = require "luasnip" + +local i = ls.insert_node +local fmt = require("luasnip.extras.fmt").fmt + +return {req = fmt("import {} from \"{}\"", {i(1), i(2)})} diff --git a/configs/nvim/lua/myconfig/lsp.lua b/configs/nvim/lua/myconfig/lsp.lua new file mode 100644 index 0000000..9600b40 --- /dev/null +++ b/configs/nvim/lua/myconfig/lsp.lua @@ -0,0 +1,222 @@ +local module = {} + +local lspkind = require("lspkind") +local lsp_config = require("lspconfig") +local cmp = require("cmp.init") +local tel = require("telescope.builtin") +local _ = require("myconfig.utils") + +local filetype_mapping = { + lua = _.once(function() return require("myconfig.lang.lua") end), + go = _.once(function() return require("myconfig.lang.go") end), +} + +-- +-- @param action_name refers to method action_name in module assigned to +-- that filetype e.g. require("myconfig.lsp.lua").format() +local function lsp(opts) + return function() + local lsp_fn + -- if client does not support capability + if opts.required_capability then + for _, client in pairs(vim.lsp.buf_get_clients()) do + if client.server_capabilities[opts.required_capability] == true then + lsp_fn = opts.lsp_func + break + end + end + end + if not lsp_fn and opts.fallback then + lsp_fn = opts.fallback + end + + -- if custom implementation exists + local filetype = vim.api.nvim_buf_get_option(0, "filetype") + if filetype and filetype_mapping[filetype] then + local mod = filetype_mapping[filetype]() + if mod and mod[opts.action_name] then + mod[opts.action_name](lsp_fn) + return + end + end + if (lsp_fn) then + lsp_fn() + end + end +end + +module.goToDefinition = tel.lsp_definitions +module.goToDeclaration = vim.lsp.buf.declaration +module.goToTypeDefinition = vim.lsp.buf.type_definition +module.goToNext = vim.diagnostic.goto_next +module.goToPrev = vim.diagnostic.goto_prev +module.references = tel.lsp_references +module.onHover = vim.lsp.buf.hover +module.showLineDiagnostics = vim.diagnostic.open_float + +module.format = lsp { + lsp_func = function() vim.lsp.buf.format({async = true}) end, + action_name = "format", + required_capability = "documentFormattingProvider", + fallback = function() vim.cmd.normal("gg=G") end, +} + +module.formatSelected = lsp { + lsp_func = vim.lsp.buf.range_formatting, + action_name = "format", + required_capability = "documentRangeFormattingProvider", + fallback = module.format, +} + +module.rename = vim.lsp.buf.rename +module.codeAction = vim.lsp.buf.code_action +module.restart = function() + vim.lsp.stop_client(vim.lsp.get_active_clients()) + vim.cmd.edit() +end +module.autoFix = function() print("autoFix not supported") end + +function module.lsp_setup(name, config) + local capabilities = config.capabilities or vim.lsp.protocol.make_client_capabilities() + capabilities = require("cmp_nvim_lsp").default_capabilities(capabilities) + local default_config = { + on_attach = function(client) + if (config.on_attach) then + config.on_attach(client); + end + end, + capabilities = capabilities, + } + local result_config = vim.tbl_extend("force", config, default_config) + lsp_config[name].setup(result_config) +end + +function module.apply() + vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with( + vim.lsp.diagnostic.on_publish_diagnostics, { + -- Enable underline, use default values + underline = true, + -- Enable virtual text, override spacing to 4 + virtual_text = {spacing = 4}, + -- Use a function to dynamically turn signs off + -- and on, using buffer local variables + -- signs = function(bufnr, client_id) return vim.bo[bufnr].show_signs == false end, + -- Disable a feature + update_in_insert = false, + } + ) + + local efm_config = { + on_attach = function(client) + client.server_capabilities.documentFormattingProvider = true; + client.server_capabilities.codeActionProvider = true; + end, + settings = {languages = {}}, + filetypes = {}, + root_dir_patterns = {".git"}, + } + + local go = require("myconfig.lang.go") + module.lsp_setup("gopls", go.gopls_config()) + module.lsp_setup("golangci_lint_ls", go.golangci_config()) + go.attach_efm(efm_config) + + module.lsp_setup("clangd", + {filetypes = {"c", "cpp"}, init_options = {clangdFileStatus = true}}) + + local lua = require("myconfig.lang.lua") + module.lsp_setup("lua_ls", lua.lua_ls_config()) + + local typescript = require("myconfig.lang.typescript") + module.lsp_setup("tsserver", typescript.tsserver_config()) + module.lsp_setup("eslint", typescript.eslint_config()) + module.lsp_setup("clojure_lsp", {}) + module.lsp_setup("rust_analyzer", {}) + + module.lsp_setup( + "yamlls", { + settings = { + yaml = { + schemas = { + ["https://json.schemastore.org/github-action.json"] = { + ".github/actions/*.yml", + }, + ["https://json.schemastore.org/github-workflow.json"] = { + ".github/workflows/*.yml", + }, + ["https://json.schemastore.org/circleciconfig.json"] = { + ".circleci/config.yml", + }, + }, + }, + }, + } + ) + module.lsp_setup( + "jsonls", { + settings = { + json = { + schemas = { + { + fileMatch = {"app.json", "app.config.json"}, + url = + "https://raw.githubusercontent.com/expo/vscode-expo/schemas/schema/expo-xdl.json", + }, + { + fileMatch = {"eas.json"}, + url = + "https://raw.githubusercontent.com/expo/vscode-expo/schemas/schema/eas.json", + }, + { + fileMatch = {"store.config.json"}, + url = + "https://raw.githubusercontent.com/expo/vscode-expo/schemas/schema/eas-metadata.json", + }, + }, + validate = {enable = true}, + }, + }, + } + ) + module.lsp_setup("elixirls", require("myconfig.lang.elixir").elixirls_config()) + + local cmake = require("myconfig.lang.cmake") + module.lsp_setup("cmake", cmake.cmake_config()) + cmake.attach_efm(efm_config) + + efm_config.root_dir = lsp_config.util.root_pattern(unpack(efm_config.root_dir_patterns or {})) + efm_config.root_dir_patterns = nil + module.lsp_setup("efm", efm_config) + + lspkind.init() + + cmp.setup { + snippet = {expand = function(args) require("luasnip").lsp_expand(args.body) end}, + completion = {completeopt = "menu,noselect"}, + preselect = cmp.PreselectMode.None, + mapping = { + [""] = cmp.mapping(cmp.mapping.scroll_docs(-4), {"i", "c"}), + [""] = cmp.mapping(cmp.mapping.scroll_docs(4), {"i", "c"}), + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.close(), + [""] = cmp.mapping.confirm({select = false, behavior = cmp.ConfirmBehavior.Insert}), + [""] = cmp.mapping.confirm({select = true, behavior = cmp.ConfirmBehavior.Insert}), + [""] = cmp.mapping.select_next_item({behavior = cmp.SelectBehavior.Insert}), + [""] = cmp.mapping.select_prev_item({behavior = cmp.SelectBehavior.Insert}), + }, + sources = { + {name = "luasnip"}, + {name = "nvim_lsp"}, + {name = "buffer", keyword_length = 5}, + {name = "path"}, + }, + formatting = { + format = lspkind.cmp_format( + {with_text = false, menu = ({buffer = "[Buffer]", nvim_lsp = "[LSP]"})} + ), + }, + experimental = {ghost_text = true}, + } +end + +return module diff --git a/configs/nvim/lua/myconfig/noice.lua b/configs/nvim/lua/myconfig/noice.lua new file mode 100644 index 0000000..a2c4c7a --- /dev/null +++ b/configs/nvim/lua/myconfig/noice.lua @@ -0,0 +1,27 @@ +local module = {} + +function module.apply() + require("noice").setup( + { + views = { + cmdline_popup = { + position = {row = "50%", col = "50%"}, + size = {width = 120, height = "auto"}, + }, + popupmenu = { + relative = "editor", + position = {row = "60%", col = "50%"}, + size = {width = 60, height = 10}, + border = {style = "rounded", padding = {0, 1}}, + win_options = { + winhighlight = {Normal = "Normal", FloatBorder = "DiagnosticInfo"}, + }, + }, + }, + lsp = {progress = {enabled = false}}, + messages = {enabled = false}, + } + ) +end + +return module diff --git a/configs/nvim/lua/myconfig/options.lua b/configs/nvim/lua/myconfig/options.lua new file mode 100644 index 0000000..33dd67f --- /dev/null +++ b/configs/nvim/lua/myconfig/options.lua @@ -0,0 +1,72 @@ +local o = vim.opt + +o.exrc = true -- read rc files in parent directories +o.secure = true -- exrc applies only if user is an owner + +o.autowriteall = true -- autosave +o.undofile = true -- preserve undo history between sessions +o.undodir = vim.env.HOME .. "/.cache/nvim/undo" +o.history = 5000 +o.clipboard = "unnamedplus" + +o.pumblend = 17 -- transparency for popup (0..100) + +o.number = true -- show numbers +o.relativenumber = true -- show relative numbers (current line is absolute) +o.scrolloff = 8 -- start scrolling n lines before buffer end +o.showmode = false -- do not display mode under statusline + +o.incsearch = true -- highlight partial search results +o.inccommand = "split" -- highlight + split on %s/pater/replace +o.hlsearch = true -- highlight search results after + +o.laststatus = 3 -- (2=always,3=global) statusline behavior +o.mouse = "a" -- (a=all modes) mouse support +o.splitright = true -- new vert split starts on right + +-- what backspace can remove in insert mode +o.backspace = { + "indent", -- indents + "eol", -- eol + "start", -- can only remove stuff added after last switch to insert mode +} + +o.foldenable = false -- do not fold parts of the code (e.g functions) + +o.background = "dark" -- selects dark theme from current scheme +o.termguicolors = true -- use real colors, term support required + +-- default tab behavior +o.tabstop = 4 +o.shiftwidth = 4 +o.softtabstop = 4 +o.expandtab = true +o.smarttab = true -- global + +o.autoindent = true +o.formatoptions = o.formatoptions + - "o" -- O and o, don't continue comments + + "r" -- But do continue when pressing enter. + + "n" -- Indent past the formatlistpat, not underneath it. + + "j" -- Auto-remove comments if possible. + - "2" -- use indent of the second line above + +o.joinspaces = false -- disable 2 spaces after join(J) + +o.completeopt = {"menu", "noselect"} + +o.spelllang = "en" +o.spellfile = vim.env.HOME .. "/.dotfiles/configs/nvim/spell/common.utf-8.add" .. "," .. + vim.env.HOME .. "/.dotfiles/configs/nvim/spell/natural.utf-8.add" .. "," .. + vim.env.HOME .. "/.dotfiles/configs/nvim/spell/blacklistonly.utf-8.add" +o.spellcapcheck = "" +o.spelloptions = {"camel"} + +o.cursorline = true -- highlight current line +local group = vim.api.nvim_create_augroup("OptionsControl", {clear = true}) +vim.api.nvim_create_autocmd( + "WinLeave", {group = group, callback = function() vim.opt_local.cursorline = false end} +) +vim.api.nvim_create_autocmd( + "WinEnter", {group = group, callback = function() vim.opt_local.cursorline = true end} +) diff --git a/configs/nvim/lua/myconfig/playground/docker.lua b/configs/nvim/lua/myconfig/playground/docker.lua new file mode 100644 index 0000000..9e71bb1 --- /dev/null +++ b/configs/nvim/lua/myconfig/playground/docker.lua @@ -0,0 +1,72 @@ +local module = {} + +local _ = require("myconfig.utils") + +local path = { + cache = function() return vim.fn.stdpath("cache") end, -- "~/.cache/nvim" +} + +local playground_buffers = {} + +local function start_playground(user_command_opts) + local fargs = user_command_opts.fargs or {} + local image = fargs[1] + module.ensure_commands() + local playground_path = image and (path.cache() .. "/playground/" .. image) or + ("/tmp/nvim_docker_" .. vim.fn.localtime()) + _.rpc_run({name = "docker:playground:create", path = playground_path, image = image or "ubuntu"}) + local buffers = vim.api.nvim_list_bufs() + for _, b in ipairs(buffers) do + local bufname = vim.api.nvim_buf_get_name(b) + if bufname ~= "" and string.sub(bufname, 0, 8) ~= "noice://" then + vim.api.nvim_buf_delete(b, {}) + end + end + vim.cmd.cd(playground_path) + vim.cmd.edit(playground_path .. "/Dockerfile") + + local buffer = vim.api.nvim_get_current_buf() + playground_buffers[buffer] = {path = playground_path} + vim.api.nvim_buf_attach( + buffer, false, { + on_detach = function() + if not image then + -- _.rpc_run({name = "docker:playground:delete", path = playground_path}) + playground_buffers[buffer] = nil + end + end, + } + ); +end + +local function playground_shell() + local buffer = vim.api.nvim_get_current_buf() + local playground_path = playground_buffers[buffer] and playground_buffers[buffer].path + if playground_path then + _.rpc_start({name = "docker:playground:shell", path = playground_path}, function() + end) + end +end + +local function playground_complete() + local files = _.rpc_run({name = "directory:preview", path = path.cache() .. "/playground"}) + local names = {} + for _, file in ipairs(files) do + table.insert(names, file.name) + end + return names +end + +module.ensure_commands = _.once( + function() + vim.api.nvim_create_user_command("DockerPlaygroundShell", playground_shell, {nargs = 0}) + end +) + +module.apply = function() + vim.api.nvim_create_user_command( + "DockerPlayground", start_playground, {nargs = "?", complete = _.once(playground_complete)} + ) +end + +return module diff --git a/configs/nvim/lua/myconfig/playground/node.lua b/configs/nvim/lua/myconfig/playground/node.lua new file mode 100644 index 0000000..48ec895 --- /dev/null +++ b/configs/nvim/lua/myconfig/playground/node.lua @@ -0,0 +1,99 @@ +local module = {} + +local _ = require("myconfig.utils") + +local path = { + cache = function() return vim.fn.stdpath("cache") end, -- "~/.cache/nvim" +} + +local playground_buffers = {} + +local function start_playground(user_command_opts) + local fargs = user_command_opts.fargs or {} + local name = fargs[1] + module.ensure_commands() + local playground_path = name and (path.cache() .. "/playground/" .. name) or + ("/tmp/nvim_node_" .. vim.fn.localtime()) + _.rpc_run({name = "node:playground:create", path = playground_path}) + local buffers = vim.api.nvim_list_bufs() + for _, b in ipairs(buffers) do + local bufname = vim.api.nvim_buf_get_name(b) + if bufname ~= "" and string.sub(bufname, 0, 8) ~= "noice://" then + vim.api.nvim_buf_delete(b, {}) + end + end + vim.fn.chdir(playground_path) + vim.cmd.edit(playground_path .. "/index.ts") + + local buffer = vim.api.nvim_get_current_buf() + playground_buffers[buffer] = {path = playground_path} + vim.api.nvim_buf_attach( + buffer, false, { + on_detach = function() + if not name then + -- _.rpc_run({name = "node:playground:delete", path = playground_path}) + playground_buffers[buffer] = nil + end + end, + } + ); +end + +local function install_package(user_command_opts) + local fargs = user_command_opts.fargs or {} + local package = fargs[1] + local buffer = vim.api.nvim_get_current_buf() + local playground_path = playground_buffers[buffer] and playground_buffers[buffer].path + if playground_path then + _.rpc_run({name = "node:playground:install", path = playground_path, package = package}) + vim.cmd.edit({bang = true}) + else + vim.notify("Install supported only in playground buffers") + end +end + +local function playground_zsh_shell() + local buffer = vim.api.nvim_get_current_buf() + local playground_path = playground_buffers[buffer] and playground_buffers[buffer].path + if playground_path then + _.rpc_start({name = "node:playground:zsh-shell", path = playground_path}, function() + end) + end +end + +local function playground_node_shell() + local buffer = vim.api.nvim_get_current_buf() + local playground_path = playground_buffers[buffer] and playground_buffers[buffer].path + if playground_path then + _.rpc_start({name = "node:playground:node-shell", path = playground_path}, function() + end) + end +end + +local function playground_complete() + local files = _.rpc_run({name = "directory:preview", path = path.cache() .. "/playground"}) + local names = {} + for _, file in ipairs(files) do + table.insert(names, file.name) + end + return names +end + +module.ensure_commands = _.once( + function() + vim.api + .nvim_create_user_command("NodePlaygroundNodeShell", playground_node_shell, {nargs = 0}) + vim.api + .nvim_create_user_command("NodePlaygroundZshShell", playground_zsh_shell, {nargs = 0}) + vim.api + .nvim_create_user_command("NodePlaygroundInstallPackage", install_package, {nargs = 1}) + end +) + +module.apply = function() + vim.api.nvim_create_user_command( + "NodePlayground", start_playground, {nargs = "?", complete = _.once(playground_complete)} + ) +end + +return module diff --git a/configs/nvim/lua/myconfig/plugins.lua b/configs/nvim/lua/myconfig/plugins.lua new file mode 100644 index 0000000..bcd29c0 --- /dev/null +++ b/configs/nvim/lua/myconfig/plugins.lua @@ -0,0 +1,67 @@ +vim.opt.rtp:prepend(vim.fn.stdpath("data") .. "/lazy/lazy.nvim") +local snapshot_path = "~/.dotfiles/configs/nvim/lazy-lock.json" + +vim.api.nvim_create_user_command( + "PackagesInstall", function() vim.api.nvim_command("Lazy restore") end, {nargs = 0} +) +vim.api.nvim_create_user_command( + "PackagesUpdate", function() vim.api.nvim_command("Lazy update") end, {nargs = 0} +) + +require("lazy").setup( + { + "nvim-lua/popup.nvim", + "kyazdani42/nvim-web-devicons", + + "nvim-lua/plenary.nvim", + "nvim-telescope/telescope.nvim", + { + "nvim-telescope/telescope-fzf-native.nvim", + build = + "cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build", + }, + {"nvim-telescope/telescope-file-browser.nvim"}, + { + "folke/noice.nvim", + dependencies = {"MunifTanjim/nui.nvim", + "rcarriga/nvim-notify"} + }, + {"TimUntersberger/neogit", dependencies = "nvim-lua/plenary.nvim"}, + {"sindrets/diffview.nvim", dependencies = "nvim-lua/plenary.nvim"}, + "airblade/vim-gitgutter", + "tpope/vim-fugitive", + + "tpope/vim-dadbod", + "kristijanhusak/vim-dadbod-ui", + + "neovim/nvim-lspconfig", + "hrsh7th/nvim-cmp", + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + + "L3MON4D3/LuaSnip", + "saadparwaiz1/cmp_luasnip", + + "onsails/lspkind-nvim", + + "nanotee/luv-vimdocs", -- nvim event loop + "milisims/nvim-luaref", -- lua bultin + + "tpope/vim-endwise", + "numToStr/Comment.nvim", + + { + "ellisonleao/gruvbox.nvim", + priority = 1000, + lazy = false, + config = function() vim.cmd.colorscheme("gruvbox") end, + }, + "gorodinskiy/vim-coloresque", -- color preview in css + + {"nvim-treesitter/nvim-treesitter"}, + "nvim-treesitter/playground", + "nvim-treesitter/nvim-treesitter-context", + + }, {lockfile = snapshot_path, install = {missing = true, colorscheme = {"gruvbox"}}} +) diff --git a/configs/nvim/lua/myconfig/snippets.lua b/configs/nvim/lua/myconfig/snippets.lua new file mode 100644 index 0000000..15edfb5 --- /dev/null +++ b/configs/nvim/lua/myconfig/snippets.lua @@ -0,0 +1,46 @@ +local module = {} + +local ls = require("luasnip") + +function module.apply() + ls.config.set_config { + history = true, + updateevents = "TextChanged,TextChangedI", + enable_autosnippets = true, + } +end + +local snippets_loaded = {} + +function module.load(filetype, snippets) + if snippets_loaded[filetype] then + return + end + snippets_loaded[filetype] = true + local result = {} + for k, v in pairs(snippets) do + table.insert(result, (ls.s({trig = k, desc = v.desc}, v))) + end + ls.add_snippets(filetype, result) +end + +function module.expand_or_jump() + if ls.expand_or_jumpable() then + ls.expand_or_jump() + end +end + +function module.jump_back() + if ls.jumpable(-1) then + ls.jump(-1) + end +end + +function module.reload() + snippets_loaded = {} + package.loaded["myconfig.lang.go_snippets"] = nil + module.load("go", require("myconfig.lang.go_snippets")) + vim.cmd.edit() +end + +return module diff --git a/configs/nvim/lua/myconfig/spell.lua b/configs/nvim/lua/myconfig/spell.lua new file mode 100644 index 0000000..e7b735d --- /dev/null +++ b/configs/nvim/lua/myconfig/spell.lua @@ -0,0 +1,33 @@ +local module = {} + +local function spell_file_rebuild() + local spell_dir = vim.env.HOME .. "/.dotfiles/configs/nvim/spell" + for _, spell_file_name in pairs(vim.fn.readdir(spell_dir)) do + if (spell_file_name == string.gsub(spell_file_name, ".spl$", "")) then + local spell_file_path = spell_dir .. "/" .. spell_file_name + vim.cmd.mkspell(spell_file_path) + end + end +end + +function module.preset(name) + local common = vim.env.HOME .. "/.dotfiles/configs/nvim/spell/common.utf-8.add" + local lang = vim.env.HOME .. "/.dotfiles/configs/nvim/spell/" .. name .. ".utf-8.add" + local natural = vim.env.HOME .. "/.dotfiles/configs/nvim/spell/natural.utf-8.add" + + vim.opt_local.spell = true + vim.opt_local.spellfile = common .. "," .. lang .. "," .. natural +end + +function module.strict_preset() + vim.opt_local.spell = true + vim.opt_local.spellcapcheck = "[.?!]\\_[\\])'\" \\t]\\+" + + vim.opt_local.spellfile = vim.env.HOME .. "/.dotfiles/configs/nvim/spell/natural.utf-8.add" +end + +function module.apply() + vim.api.nvim_create_user_command("SpellFileRebuild", spell_file_rebuild, {nargs = 0}) +end + +return module diff --git a/configs/nvim/lua/myconfig/statusline.lua b/configs/nvim/lua/myconfig/statusline.lua new file mode 100644 index 0000000..895a9d2 --- /dev/null +++ b/configs/nvim/lua/myconfig/statusline.lua @@ -0,0 +1,135 @@ +local Job = require("plenary.job") + +local green = "#b8bb26" +local red = "#fb4934" +local blue = "#83a598" +local yellow = "#fabd2f" +local purple = "#d3869b" +local black = "#504945" +local white = "#bdae93" + +local icons = { + indicator_errors = "", + indicator_warnings = "", + indicator_ok = "", + indicator_load = "", +} + +local modeConfigs = { + ["n"] = {name = "N", fg = green}, + ["v"] = {name = "V", fg = purple}, + ["V"] = {name = "V·L", fg = purple}, + ["\22"] = {name = "V·B", fg = purple}, + ["i"] = {name = "I", fg = blue}, + ["R"] = {name = "R", fg = red}, + ["Rv"] = {name = "V·R", fg = red}, + ["c"] = {name = "C", fg = yellow}, +} + +-- local left_separator = "" +local right_separator = "" + +local function set_mode_hl(modeConfig) + vim.api.nvim_set_hl( + 0, "StatusLineModeSeparator", { + foreground = modeConfig.fg, + background = white, + reverse = false, + underline = false, + bold = false, + } + ) + vim.api.nvim_set_hl( + 0, "StatusLineMode", { + foreground = modeConfig.fg, + background = black, + reverse = true, + underline = false, + bold = true, + } + ) +end + +vim.schedule( + function() + vim.api.nvim_set_hl( + 0, "StatusLineGitSeparator", { + foreground = white, + background = black, + reverse = false, + underline = false, + bold = true, + } + ) + vim.api.nvim_set_hl( + 0, "StatusLineGit", { + foreground = white, + background = black, + reverse = true, + underline = false, + bold = true, + } + ) + end +) + +local function git_branch() + local j = Job:new( + {command = "git", args = {"branch", "--show-current"}, cwd = vim.fn.fnamemodify(0, ":h")} + ) + + local ok, result = pcall(function() return vim.trim(j:sync()[1]) end) + + if ok then + return result + end +end + +local function lsp_status() + local status = {} + local errors = #vim.diagnostic.get(0, {severity = vim.diagnostic.severity.ERROR}) + if errors and errors > 0 then + table.insert(status, icons.indicator_errors .. " " .. errors) + end + + local warnings = #vim.diagnostic.get(0, {severity = vim.diagnostic.severity.WARN}) + if warnings and warnings > 0 then + table.insert(status, icons.indicator_warnings .. " " .. warnings) + end + local progress = vim.lsp.util.get_progress_messages() + if progress[1] then + return progress[1].title .. " " .. icons.indicator_load .. " " + end + return #status > 0 and table.concat(status, " ") or icons.indicator_ok .. " " +end + +local function statusline() + -- item is defined as %-0{minwid}.{maxwid}{item} + local rawMode = vim.api.nvim_get_mode().mode + local modeConfig = modeConfigs[rawMode] or {name = rawMode} + set_mode_hl(modeConfig) + local mode = "%#StatusLineMode# " .. modeConfig.name .. " %#StatusLineModeSeparator#" .. + right_separator .. "%#StatusLine#" + local file = "%f" + + local lsp = "" + if (#vim.lsp.buf_get_clients() > 0) then + lsp = lsp_status() .. " " + end + + -- for some reason it breaks noice.nvim + -- local git = "" + -- local git_current_branch = git_branch() + -- if git_current_branch then + -- git = "%#StatusLineGit# " .. git_current_branch .. "%#StatusLineGitSeparator#" .. + -- right_separator .. "%#StatusLine#" + -- end + + local position = "%#StatusLineGit# %2.p%% [%3.l/%L] %c" + return mode .. file .. "%=" .. lsp .. position +end + +_G.statusline = statusline + +vim.opt.statusline = "%!v:lua.statusline()" +vim.opt.winbar = "%#StatusLine#%=%m %f" diff --git a/configs/nvim/lua/myconfig/surround.lua b/configs/nvim/lua/myconfig/surround.lua new file mode 100644 index 0000000..4979e73 --- /dev/null +++ b/configs/nvim/lua/myconfig/surround.lua @@ -0,0 +1,171 @@ +local _ = require("myconfig.utils") +local module = {} + +local function get_visual_pos() + local start = vim.fn.getpos("v") + local eend = vim.fn.getcurpos() + local start_line = start[2] - 1 + local start_col = start[3] - 1 + local end_line = eend[2] - 1 + local end_col = eend[3] - 1 + if (start_line > end_line or (start_line == end_line and start_col > end_col)) then + local end_line_length = #vim.fn.getline(start_line + 1) + return end_line, end_col, start_line, math.min(start_col + 1, end_line_length) + else + local end_line_length = #vim.fn.getline(end_line + 1) + return start_line, start_col, end_line, math.min(end_line_length, end_col + 1) + end +end + +function module.surround_selection(open_sign, close_sign) + return function() + local start_line, start_col, end_line, end_col = get_visual_pos(); + vim.api.nvim_buf_set_text(0, end_line, end_col, end_line, end_col, close_sign) + vim.api.nvim_buf_set_text(0, start_line, start_col, start_line, start_col, open_sign) + vim.api.nvim_feedkeys("=", "v", false) + vim.defer_fn( + function() + -- TODO: very hacky, do it cleaner + vim.api.nvim_win_set_cursor(0, {start_line + 1, start_col}) + end, 100 + ) + end +end + +local valid_auto_pairs = {{"{", "}"}, {"(", ")"}, {"[", "]"}, {"`", "`"}, {"```", "```"}} +local valid_replecements = { + {"{", "}"}, + {"(", ")"}, + {"[", "]"}, + {"\"", "\""}, + {"'", "'"}, + {"`", "`"}, + {"<", ">"}, +} +local MAX_SEARCH_CONTEXT = 20 + +-- 1 based indexing for args and results +-- include symbol under the cursor +local function find_before(line, column, symbol) + local lines = vim.api.nvim_buf_get_lines(0, _.max(line - MAX_SEARCH_CONTEXT, 0), line, false) + local current_line = string.reverse(string.sub(lines[#lines], 0, column)); + local index_in_first_line = string.find(current_line, symbol, 1, true) + if index_in_first_line then + return line, column - index_in_first_line + 1 + end + for i = #lines - 1, 1, -1 do + local current_line_str = string.reverse(lines[i]); + local reversed_index = string.find(current_line_str, symbol, 1, true) + if reversed_index then + return line - #lines + i, #current_line_str - reversed_index + 1 + end + end +end + +-- 1 based indexing for args and results +-- exclude symbol under the cursor +local function find_after(line, column, symbol) + local lines = vim.api.nvim_buf_get_lines(0, line - 1, line + MAX_SEARCH_CONTEXT, false) -- 0 based indexed args + local current_line = string.sub(lines[1], column + 1); + local index_in_first_line = string.find(current_line, symbol, 1, true) + if index_in_first_line then + return line, column + index_in_first_line + end + for i = 2, #lines, 1 do + local current_line_str = lines[i]; + local index = string.find(current_line_str, symbol, 1, true) + if index then + return line + (i - 1), index + end + end +end + +local function find_matchig_r_paren(l_paren) + for _, symbols in ipairs(valid_replecements) do + if symbols[1] == l_paren then + return symbols[2] + end + end +end + +local function find_matchig_r_auto_pair(l_paren) + for _, symbols in ipairs(valid_auto_pairs) do + if symbols[1] == l_paren then + return symbols[2] + end + end +end + +local function is_valid_l_paren(l_paren) + for _, symbols in ipairs(valid_replecements) do + if symbols[1] == l_paren then + return true + end + end + return false +end + +function module.replace() + local source = vim.fn.getchar() + if source == 27 then + return + end + source = string.char(source) + if not is_valid_l_paren(source) then + return + end + local destination = vim.fn.getchar() + if destination == 27 then + return + end + destination = string.char(destination) + if not is_valid_l_paren(destination) then + return + end + local current = vim.fn.getcurpos() -- 1 based indexed + local line = current[2] + local col = current[3] + local open_line, open_col = find_before(line, col, source) + local close_line, close_col = find_after(line, col, find_matchig_r_paren(source)) + + -- 0 based indexed args + vim.api.nvim_buf_set_text( + 0, open_line - 1, open_col - 1, open_line - 1, open_col, {destination} + ) + vim.api.nvim_buf_set_text( + 0, close_line - 1, close_col - 1, close_line - 1, close_col, + {find_matchig_r_paren(destination)} + ) +end + +function module.remove() + local source = vim.fn.getchar() + if source == 27 then + return + end + source = string.char(source) + if not is_valid_l_paren(source) then + return + end + local current = vim.fn.getcurpos() -- 1 based indexed + local line = current[2] + local col = current[3] + local open_line, open_col = find_before(line, col, source) + local close_line, close_col = find_after(line, col, find_matchig_r_paren(source)) + -- 0 based indexed args + vim.api.nvim_buf_set_text(0, close_line - 1, close_col - 1, close_line - 1, close_col, {}) + vim.api.nvim_buf_set_text(0, open_line - 1, open_col - 1, open_line - 1, open_col, {}) +end + +function module.auto_pair(start, eend) + return function() + local current = vim.fn.getcurpos() + local line = current[2] + local col = current[3] + vim.api.nvim_buf_set_text(0, line - 1, col - 1, line - 1, col - 1, eend) + vim.api.nvim_buf_set_text(0, line - 1, col - 1, line - 1, col - 1, start) + vim.api.nvim_win_set_cursor(0, {line, col}) + end +end + +return module diff --git a/configs/nvim/lua/myconfig/telescope.lua b/configs/nvim/lua/myconfig/telescope.lua new file mode 100644 index 0000000..138f251 --- /dev/null +++ b/configs/nvim/lua/myconfig/telescope.lua @@ -0,0 +1,180 @@ +local module = {} +local action = require("telescope.actions") +local action_state = require("telescope.actions.state") +local tel = require("telescope") +local finders = require "telescope.finders" +local pickers = require("telescope.pickers") +local conf = require("telescope.config").values +local make_entry = require("telescope.make_entry") +local workspaces = require("myconfig.workspaces") +local _ = require("myconfig.utils") + +module.state = { + show_ignored = false +} + +function module.file_browser_root() + tel.extensions.file_browser.file_browser( + { + hidden = true, + respect_gitignore = not module.state.show_ignored, + git_status = false + } + ) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false) +end + +function module.file_browser_current_dir() + tel.extensions.file_browser.file_browser( + { + cwd = _.get_current_buf_dir(0), + hidden = true, + respect_gitignore = not module.state.show_ignored, + git_status = false, + } + ) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false) +end + +local function swich_directory_action(bufnr) + local path = action_state.get_selected_entry().cwd + action.close(bufnr) + workspaces.set_workspace(path) +end + +function module.live_grep(opts) + opts = opts or {} + opts.cwd = opts.cwd and vim.fn.expand(opts.cwd) or vim.loop.cwd() + local no_ignore = opts.no_ignore or false + local hidden = opts.hidden or false + + local custom_grep = finders.new_async_job { + command_generator = function(prompt) + if not prompt or prompt == "" then + return nil + end + + local prompt_split = vim.split(prompt, " ") + + local args = {"rg"} + if prompt_split[1] then + table.insert(args, "-e") + table.insert(args, prompt_split[1]) + end + + if prompt_split[2] then + table.insert(args, "-g") + table.insert(args, "**/*" .. prompt_split[2] .. "{*,*/**}") + end + + return vim.tbl_flatten { + args, + { + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + }, + no_ignore and {"--no-ignore"} or {}, + hidden and {"--hidden"} or {}, + } + end, + entry_maker = make_entry.gen_from_vimgrep(opts), + cwd = opts.cwd, + } + + pickers.new( + opts, { + debounce = 100, + prompt_title = "Live Grep", + finder = custom_grep, + previewer = conf.grep_previewer(opts), + sorter = require("telescope.sorters").empty(), + attach_mappings = function(_, map) + local toggle_ignore = function() no_ignore = not no_ignore end + map("n", "t", toggle_ignore) + map("i", "", toggle_ignore) + map("n", "", toggle_ignore) + return true + end, + } + ):find() +end + +function module.apply() + local fb_actions = tel.extensions.file_browser.actions + require("telescope._extensions.file_browser.config").values.mappings = { + ["i"] = {}, + ["n"] = {}, + } + tel.setup { + defaults = { + mappings = { + n = { + [""] = action.move_selection_next, + [""] = action.move_selection_previous, + }, + i = { + [""] = action.move_selection_next, + [""] = action.move_selection_previous, + }, + }, + layout_config = {prompt_position = "top"}, + sorting_strategy = "ascending", + }, + extensions = { + fzf = { + fuzzy = true, + override_generic_sorter = true, + override_file_sorter = true, + case_mode = "smart_case", + }, + file_browser = { + hijack_netrw = true, + mappings = { + ["i"] = { + [""] = fb_actions.goto_cwd, + [""] = require("myconfig.actions").actions.find_files.fn, + + [""] = fb_actions.goto_parent_dir, + [""] = fb_actions.goto_parent_dir, + [""] = action.select_default, + [""] = action.select_default, + [""] = action.close, + }, + ["n"] = { + [""] = fb_actions.goto_cwd, + [""] = require("myconfig.actions").actions.find_files.fn, + ["ff"] = require("myconfig.actions").actions.grep_files.fn, + + ["c"] = fb_actions.create, + ["r"] = fb_actions.rename, + ["m"] = fb_actions.move, + ["y"] = fb_actions.copy, + ["d"] = fb_actions.remove, + ["e"] = fb_actions.goto_cwd, + + [""] = fb_actions.goto_parent_dir, + [""] = fb_actions.goto_parent_dir, + ["h"] = fb_actions.goto_parent_dir, + [""] = action.select_default, + [""] = action.select_default, + ["l"] = action.select_default, + ["sd"] = swich_directory_action, + + [""] = false, + [""] = action.close, + }, + }, + }, + }, + } + if not vim.fn.has("macunix") then + tel.load_extension("fzf") + end + tel.load_extension("file_browser") +end + +return module diff --git a/configs/nvim/lua/myconfig/treesitter.lua b/configs/nvim/lua/myconfig/treesitter.lua new file mode 100644 index 0000000..6399b57 --- /dev/null +++ b/configs/nvim/lua/myconfig/treesitter.lua @@ -0,0 +1,147 @@ +local module = {} + +local ts = require("nvim-treesitter.configs") +local tsi = require("nvim-treesitter.incremental_selection") + +module.incremental_selection_init = tsi.init_selection +module.selection_init = nil +module.selection_inc = nil +module.selection_dec = nil +module.selection_inc_scope = nil + +module.comment_line = nil +module.comment_line = nil +module.comment_toggle_line = nil +module.comment_toggle_block = nil + +local ensure_installed = { + "bash", + "bibtex", + "c", + "c_sharp", + "clojure", + "cmake", + "comment", + "cpp", + "css", + "dart", + "dockerfile", + "eex", + "elixir", + "erlang", + "fish", + "glsl", + "go", + "gomod", + "gowork", + "graphql", + "hcl", + "heex", + "help", + "hjson", + "html", + "java", + "javascript", + "jsdoc", + "json", + "json5", + "jsonc", + "kotlin", + "latex", + "llvm", + "lua", + "make", + "ninja", + "nix", + "norg", + "perl", + "php", + "python", + "query", + "regex", + "ruby", + "rust", + "scala", + "scss", + "swift", + "scheme", + "teal", + "toml", + "tsx", + "typescript", + "vala", + "vim", + "vue", + "yaml", + "zig", +} + +function module.apply() + ts.setup { + ensure_installed = ensure_installed, + sync_install = true, + highlight = {enable = true, additional_vim_regex_highlighting = false}, + incremental_selection = { + enable = true, + keymaps = { + init_selection = module.selection_init, + node_incremental = module.selection_inc, + scope_incremental = module.selection_inc_scope, + node_decremental = module.selection_dec, + }, + }, + indent = {enable = false}, + playground = { + enable = true, + disable = {}, + updatetime = 25, -- Debounced time for highlighting nodes in the playground from source code + persist_queries = false, -- Whether the query persists across vim sessions + keybindings = { + toggle_query_editor = "o", + toggle_hl_groups = "i", + toggle_injected_languages = "t", + toggle_anonymous_nodes = "a", + toggle_language_display = "I", + focus_language = "f", + unfocus_language = "F", + update = "R", + goto_node = "", + show_help = "?", + }, + }, + } + require("Comment").setup( + { + toggler = {line = module.comment_toggle_line, block = module.comment_toggle_block}, + opleader = {line = module.comment_line, block = module.comment_block}, + mappings = {basic = true, extra = false, extended = false}, + } + ) + require("treesitter-context").setup { + enable = true, + throttle = true, + max_lines = 0, + patterns = { + -- For all filetypes + -- Note that setting an entry here replaces all other patterns for this entry. + -- By setting the 'default' entry below, you can control which nodes you want to + -- appear in the context window. + default = {"class", "function", "method", "for", "while", "if", "switch", "case"}, + -- Example for a specific filetype. + -- If a pattern is missing, *open a PR* so everyone can benefit. + -- rust = { + -- 'impl_item', + -- }, + json = {"object", "array"}, + yaml = {"block_mapping_pair"}, + }, + exact_patterns = { + -- Example for a specific filetype with Lua patterns + -- Treat patterns.rust as a Lua pattern (i.e "^impl_item$" will + -- exactly match "impl_item" only) + -- rust = true, + }, + } +end + +return module diff --git a/configs/nvim/lua/myconfig/utils.lua b/configs/nvim/lua/myconfig/utils.lua new file mode 100644 index 0000000..30840e6 --- /dev/null +++ b/configs/nvim/lua/myconfig/utils.lua @@ -0,0 +1,160 @@ +local module = {} + +local Job = require("plenary.job") +local Path = require("plenary.path") +local base64 = require("myconfig.base64") + +local function do_apply_keymap(schema, prefix, default_options) + if schema[1] and (type(schema[1]) == "string" or type(schema[1]) == "function") then + vim.keymap.set( + schema[2] or "", prefix, schema[1], + vim.tbl_extend("force", default_options, schema[3] or {}) + ) + elseif schema.__is_ref then + schema.module[schema.field] = prefix + else + for key, subschema in pairs(schema) do + if type(key) == "number" then + do_apply_keymap(subschema, prefix, default_options) + else + do_apply_keymap(subschema, prefix .. key, default_options) + end + end + end +end + +function module.ref(mod, field) return {module = mod, field = field, __is_ref = true} end + +function module.apply_keymap(schema, default_options) do_apply_keymap(schema, "", default_options) end + +function module.run(input) + local args = {} + for _, v in ipairs(input) do + table.insert(args, v) + end + + local command = table.remove(args, 1) + + Job:new { + command = command, + args = args, + + cwd = input.cwd, + + on_stdout = vim.schedule_wrap(function(_, data) print(command, ":", data) end), + on_stderr = vim.schedule_wrap(function(_, data) print(command, "[err]:", data) end), + skip_validation = input.skip_validation or true, + }:sync(60000) +end + +function module.run_async(input) + local args = {} + for _, v in ipairs(input) do + table.insert(args, v) + end + + local command = table.remove(args, 1) + + Job:new { + command = command, + args = args, + + cwd = input.cwd, + + on_stdout = vim.schedule_wrap(function(_, data) print(command, ":", data) end), + on_stderr = vim.schedule_wrap(function(_, data) print(command, "[err]:", data) end), + skip_validation = input.skip_validation or true, + }:start() +end + +function module.find_recursive(filename, search_start) + local parent_paths = Path:new(search_start):parents() + for _, path in pairs(parent_paths) do + if Path:new(path, filename):exists() then + return Path:new(path, filename) + end + end +end + +function module.once(func) + local already_called = false + local cached_result = nil + return function() + if already_called then + return cached_result + end + cached_result = func() + already_called = true + return cached_result + end +end + +function module.get_dir_from_path(path) + if 1 == vim.fn.isdirectory(path) then + return path + else + return string.gsub(path, "/[^/]+$", "") + end +end + +function module.get_buf_dir() return module.get_current_buf_dir(0) end + +function module.get_current_buf_dir(bufnr) + local current_buffer_path = vim.api.nvim_buf_get_name(bufnr) + if current_buffer_path == "" then + vim.notify("buffer does not have any location") + return vim.fn.getcwd() + end + return module.get_dir_from_path(current_buffer_path) +end + +function module.rpc_run(action) + local json_string = vim.json.encode(action) + local encoded = base64.encode(json_string) + local job = Job:new { + command = "mycli", + args = {"api", encoded}, + + on_stderr = vim.schedule_wrap(function(_, data) print("[err]:", data) end), + skip_validation = true, + } + job:sync(60000) + local stdout = job:result() + if job.code ~= 0 then + vim.notify("action " .. action.name .. " failed with exit code " .. job.code) + return {is_error = true, stderr = job:stderr_result()} + end + return vim.json.decode(stdout[#stdout]) +end + +function module.rpc_start(action, cb) + local json_string = vim.json.encode(action) + local encoded = base64.encode(json_string) + return Job:new { + command = "mycli", + args = {"api", encoded}, + + on_stderr = vim.schedule_wrap(function(_, data) print("[err]:", data) end), + on_exit = vim.schedule_wrap( + function(self, return_val) + local stdout = self:result() + if return_val ~= 0 then + vim.notify("action " .. action.name .. " failed with exit code " .. return_val) + cb({}, {is_error = true, stderr = self:stderr_result()}) + end + cb(vim.json.decode(stdout[1])) + end + ), + skip_validation = true, + }:start() +end + +function module.max(a, b) + if a > b then + return a + else + return b + end +end + +return module diff --git a/configs/nvim/lua/myconfig/workspaces.lua b/configs/nvim/lua/myconfig/workspaces.lua new file mode 100644 index 0000000..81e4ace --- /dev/null +++ b/configs/nvim/lua/myconfig/workspaces.lua @@ -0,0 +1,102 @@ +local module = {} + +local finders = require("telescope.finders") +local pickers = require("telescope.pickers") +local actions = require "telescope.actions" +local action_state = require "telescope.actions.state" +local conf = require("telescope.config").values +local tel = require("telescope") +local _ = require("myconfig.utils") + +local function entry_maker(entry) + return {value = entry, path = entry.path, display = entry.name, ordinal = entry.name} +end + +function module.set_workspace(path) + vim.cmd.cd(path) + vim.cmd.wall() + local buffers = vim.api.nvim_list_bufs() + for _, b in ipairs(buffers) do + local bufname = vim.api.nvim_buf_get_name(b) + if bufname ~= "" and string.sub(bufname, 0, 8) ~= "noice://" then + vim.api.nvim_buf_delete(b, {}) + end + end + tel.extensions.file_browser.file_browser({hidden = true, respect_gitignore = true}) + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false) +end + +module.list = {} +module.current = {} + +local function create_workspaces_list() + return function() + pickers.new( + {}, { + prompt_title = "Switch workspace", + finder = finders.new_table {results = module.list, entry_maker = entry_maker}, + sorter = conf.generic_sorter({}), + previewer = conf.file_previewer({}), + attach_mappings = function(prompt_buf) + actions.select_default:replace( + function() + actions.close(prompt_buf) + local entry = action_state.get_selected_entry() + module.set_workspace(entry.value.path) + end + ) + return true; + end, + } + ):find() + end +end + +module.switch_workspace = create_workspaces_list() + +module.switch_to_current_dir = function() module.set_workspace(_.get_current_buf_dir(0)) end + +module.open_terminal_root = function() + _.run_async({"alacritty", "--working-directory", vim.fn.getcwd()}) +end + +module.open_terminal_current = function() + local current_buf_dir = _.get_current_buf_dir(0) + _.run_async({"alacritty", "--working-directory", current_buf_dir}) +end + +local function get_current_workspace(workspaces) + local cwd = vim.fn.getcwd() + local result + for _, w in ipairs(workspaces) do + if #(w.path) <= #cwd and string.sub(cwd, 0, #(w.path)) == w.path then + if not result or #(result.path) < #(w.path) then + result = w + end + end + end + return result or {vim = {}} +end + +local function show_workspace_info() + if (module.current.name) then + vim.notify(vim.inspect(module.current)) + else + vim.notify("No workspace found at " .. vim.fn.getcwd()) + end +end + +module.apply = function(fn) + vim.api.nvim_create_user_command("WorkspaceInfo", show_workspace_info, {nargs = 0}) + _.rpc_start( + {name = "workspaces:list"}, vim.schedule_wrap( + function(w) + module.list = w + module.current = get_current_workspace(w) + fn() + end + ) + ) +end + +return module diff --git a/configs/nvim/lua_format.yaml b/configs/nvim/lua_format.yaml new file mode 100644 index 0000000..852bd6c --- /dev/null +++ b/configs/nvim/lua_format.yaml @@ -0,0 +1,27 @@ +column_limit: 100 +indent_width: 4 +use_tab: false +continuation_indent_width: 4 +align_args: true +break_after_functioncall_lp: true +break_before_functioncall_rp: true + +align_parameter: false +break_after_functiondef_lp: true +break_before_functiondef_rp: true + +chop_down_parameter: true +chop_down_table: true +chop_down_kv_table: true + +single_quote_to_double_quote: true + +spaces_before_call: 1 + +# Table +table_sep: ',' +extra_sep_at_table_end: true + +keep_simple_control_block_one_line: false +keep_simple_function_one_line: true + diff --git a/configs/nvim/queries/go/highlights.scm b/configs/nvim/queries/go/highlights.scm new file mode 100644 index 0000000..aa1ddca --- /dev/null +++ b/configs/nvim/queries/go/highlights.scm @@ -0,0 +1,4 @@ +;; extends +; highlights + +((_) @spell ) diff --git a/configs/nvim/queries/go/trailing_commas.scm b/configs/nvim/queries/go/trailing_commas.scm new file mode 100644 index 0000000..07985ae --- /dev/null +++ b/configs/nvim/queries/go/trailing_commas.scm @@ -0,0 +1,7 @@ +( + (composite_literal + body: (literal_value (_) @last .)) +) +( + (argument_list (_) @last .) +) diff --git a/configs/nvim/queries/javascript/highlights.scm b/configs/nvim/queries/javascript/highlights.scm new file mode 100644 index 0000000..aa1ddca --- /dev/null +++ b/configs/nvim/queries/javascript/highlights.scm @@ -0,0 +1,4 @@ +;; extends +; highlights + +((_) @spell ) diff --git a/configs/nvim/queries/lua/highlights.scm b/configs/nvim/queries/lua/highlights.scm new file mode 100644 index 0000000..aa1ddca --- /dev/null +++ b/configs/nvim/queries/lua/highlights.scm @@ -0,0 +1,4 @@ +;; extends +; highlights + +((_) @spell ) diff --git a/configs/nvim/queries/typescript/highlights.scm b/configs/nvim/queries/typescript/highlights.scm new file mode 100644 index 0000000..aa1ddca --- /dev/null +++ b/configs/nvim/queries/typescript/highlights.scm @@ -0,0 +1,4 @@ +;; extends +; highlights + +((_) @spell ) diff --git a/configs/nvim/spell/common.utf-8.add b/configs/nvim/spell/common.utf-8.add new file mode 100644 index 0000000..d6c9f5e --- /dev/null +++ b/configs/nvim/spell/common.utf-8.add @@ -0,0 +1,229 @@ +args +configs +utf +retrun/! +keymap +wkozyra95 +mycli +utils +graphql +eas-json +eas-build-job +ctx +github +behaviour/! +vcs +S3 +eas-cli +errors +autocommit +enum +config +json +cwd +sdk +env +io +NOOP +js +APK +cmd +alacritty +zshrc +vimrc +nvim +gitignore +yml +bool +os +lang +homedir +dir +tmp +basename +num +instanceof +auth +api +V2 +otp +TODO +typename +errored +fyi +stdout +ansi +url +uuid +fn +submodule +cmake +init +yaml +filetypes +nargs +clangd +tsserver +eslint +Quickfix +SIGINT +stdin +eol +filetype +param +omnifunc +bo +goto +keymaps +dec +stderr +Const +util +fname +gomod +golang +gopls +expr +dropdown +module +cmp +cpp +struct +xcode +gradle +qwert +SRCROOT +INFOPLIST +testapp +redis +unregister +ip +ws +pid +vm +Ps +Func +ps +typeof +Macstadium +base64 +uuidv4 +bunyan +tmpdir +mkdirp +Fastlane +Asc +fastlane +asc +dirname +ipa +rudderstack +hapi +koa +ok +trx +knex +lodash +joi +redis +gcs +typeof +Params +params +sigfox +javascript +ciphertext +WORKDIR +ENTRYPOINT +src +tsconfig +javascriptreact +typescriptreact +jsx +tsx +efm +langserver +mattn +uuidv4 +www +gitcommit +plist +end +cb +css +scss +envs +gitconfig +homeoffice +jsonls +minimist +feedkeys +argv +lspkind +Gruvbox +conf +rebase +NERDTree +NERDTreeFind +NERDTreeToggle +putils +dotfiles +concat +toplevel +win32 +filesystem +ignorecase +ignorecase +macos +hardlinks +bbb +aaa +gitgnore +Gitignores +cli +Desc +GL +GLSnapshot +GLObject +ExponentGLObjectManager +gl +exgl +worklet +worklets +Unmarshal +workingdir +dst +diffview +tabpage +devicons +arg +eo +neogit +unstage +serializer +presigned +fargs +prebuild +codegen +easignore/= +EasBuild/= +easBuild/= +EAS Build/= +Eas /!= +react native/!= +React native/!= +EasCommand +GraphQL +TypeDefs +EASBuildDeprecationInfo +EASBuildDeprecationInfoType +JSONObject +loadByIDAsync +async +schemastore +circleciconfig +circleci +vscode +xdl +eas +githubusercontent diff --git a/configs/nvim/spell/go.utf-8.add b/configs/nvim/spell/go.utf-8.add new file mode 100644 index 0000000..622f841 --- /dev/null +++ b/configs/nvim/spell/go.utf-8.add @@ -0,0 +1,13 @@ +spf13 +func +cmds +homedir +excludesfile +Getenv +cond +promptui +fmt +Sprintf +wbthomason +manifoldco +omitempty diff --git a/configs/nvim/spell/lua.utf-8.add b/configs/nvim/spell/lua.utf-8.add new file mode 100644 index 0000000..0204a1b --- /dev/null +++ b/configs/nvim/spell/lua.utf-8.add @@ -0,0 +1,61 @@ +exrc +autowriteall +undofile +undodir +pumblend +cursorline +relativenumber +scrolloff +showmode +incsearch +inccommand +unnamedplus +statusline +laststatus +splitright +foldenable +termguicolors +tabstop +shiftwidth +softtabstop +expandtab +smarttab +autoindent +formatoptions +joinspaces +completeopt +spelllang +spellfile +spellcapcheck +spelloptions +nvim +noselect +noinsert +menuone +hlsearch +myconfig +treesitter +lsp +coc +lua +sumneko +isdirectory +luamake +stdpath +Koihik +teardown +VIMRUNTIME +lspconfig +buf +ipairs +gsub +autocmd +whitespaces +func +augroup +pumvisible +elseif +cr +tostring +tbl +noremap diff --git a/configs/nvim/spell/natural.utf-8.add b/configs/nvim/spell/natural.utf-8.add new file mode 100644 index 0000000..f86f7df --- /dev/null +++ b/configs/nvim/spell/natural.utf-8.add @@ -0,0 +1,19 @@ +wkozyra95 +wkozyra +datastore +github +Kubernetes +gradle +Fastlane +unix +use +keystore +onboarding +gitignore +vcs +behaviour/! +worklets +GLView +worklet +commandline +TODO diff --git a/configs/nvim/spell/ts.utf-8.add b/configs/nvim/spell/ts.utf-8.add new file mode 100644 index 0000000..e29adcb --- /dev/null +++ b/configs/nvim/spell/ts.utf-8.add @@ -0,0 +1,38 @@ +getenv +boolish +readonly +Async +utils +TPlatform +TJob +async +const +oclif +ios +skipSDKVersionRequirement +darwin +IOSConfig +CFBundleShortVersionString +nullthrows +semver +plist +CFBundleVersion +extname +retryUsernamePasswordAuthWithOTPAsync +ctrl +param +utf8 +XCBuildConfiguration +XCBuild +getXCBuildConfigurationFromPbxproj +getPbxproj +xc +stringify +ongoody +govc +getBuildCacheGCSKeyPrefix +EASFeatureGate +ExpoWebGLRenderingContext +GLViewProps +EXGLContexts +GraphQLContext diff --git a/configs/sway/config b/configs/sway/config new file mode 100644 index 0000000..08f1855 --- /dev/null +++ b/configs/sway/config @@ -0,0 +1,219 @@ +# Default config for sway +# +# Copy this to ~/.config/sway/config and edit it to your liking. +# +# Read `man 5 sway` for a complete reference. + +### Variables +# +# Logo key. Use Mod1 for Alt. +set $mod Mod4 +set $left h +set $down j +set $up k +set $right l + +set $term alacritty +set $menu j4-dmenu-desktop --term='alacritty' --dmenu='bemenu --fn $uifont -b -p "▶" --tf "$prompt" --hf "$highlight" --sf "$highlight" --scf "$highlight"' | xargs swaymsg exec -- + +set $primary_output DP-2 +set $secondary_output DP-3 + +output * bg ~/.dotfiles/configs/sway/wallpaper.png fill +output eDP-1 scale 0.85 + +# side monitor on the right +#output DP-2 pos 1440 512 +#output DP-3 pos 0 0 transform 270 +# side monitor on the right +output DP-2 pos 0 512 +output DP-3 pos 2560 0 transform 270 + + +# You can get the names of your outputs by running: swaymsg -t get_outputs +default_border pixel 4 +default_floating_border pixel 4 +input * { + xkb_layout "pl" + xkb_options caps:escape +} + +### Key bindings +# +# Basics: +# + bindsym $mod+Return exec $term + bindsym $mod+Shift+c kill + bindsym $mod+Shift+b exec swaylock --config ~/.dotfiles/configs/sway/swaylock + bindsym $mod+Ctrl+q exec swaymsg exit + bindsym $mod+p exec $menu + bindsym $mod+Ctrl+r reload + + floating_modifier $mod normal + + bindsym Print exec grim -g "$(slurp)" + bindsym Shift+Print exec wf-recorder -g "$(slurp)" -f $(date +"recording_%m%d_%H%M.mp4") + bindsym Ctrl+Shift+Print exec killall -s SIGINT wf-recorder + + bindsym XF86AudioLowerVolume exec pamixer -d 5 + bindsym XF86AudioRaiseVolume exec pamixer -i 5 + bindsym XF86AudioMute exec pamixer -t + + bindsym XF86AudioPlay exec playerctl play-pause + bindsym XF86AudioNext exec playerctl next + bindsym XF86AudioPrev exec playerctl previous +# +# Moving around: +# + # Move your focus around + bindsym $mod+$left focus left + bindsym $mod+$down focus down + bindsym $mod+$up focus up + bindsym $mod+$right focus right + + # Move the focused window with the same, but add Shift + bindsym $mod+Ctrl+$left move left + bindsym $mod+Ctrl+$down move down + bindsym $mod+Ctrl+$up move up + bindsym $mod+Ctrl+$right move right + + assign [instance="myclitag1"] 1 + assign [instance="myclitag2"] 2 + assign [instance="myclitag3"] 3 + assign [instance="myclitag4"] 4 + assign [instance="myclitag5"] 5 + assign [instance="myclitag6"] 6 + assign [instance="myclitag7"] 7 + assign [instance="myclitag8"] 8 + assign [instance="myclitag9"] 9 + assign [instance="myclitag10"] 10 + + workspace 1 output DP-2 + workspace 2 output DP-2 + workspace 3 output DP-2 + workspace 4 output DP-2 + workspace 5 output DP-2 + workspace 6 output DP-3 + workspace 7 output DP-3 + workspace 8 output DP-2 + workspace 9 output DP-2 + workspace 10 output DP-2 + + assign [app_id="workspace1"] workspace 1 + assign [app_id="workspace2"] workspace 2 + assign [app_id="workspace3"] workspace 3 + assign [app_id="workspace4"] workspace 4 + assign [app_id="workspace5"] workspace 5 + assign [app_id="workspace6"] workspace 6 + assign [app_id="workspace7"] workspace 7 + assign [app_id="workspace8"] workspace 8 + assign [app_id="workspace9"] workspace 9 + assign [app_id="workspace10"] workspace 10 + + assign [app_id="firefox"] workspace 1 + assign [class="Google-chrome"] workspace 1 + assign [class="Slack"] workspace 10 + + bindsym $mod+Shift+L workspace next + bindsym $mod+Shift+H workspace prev + + bindsym $mod+1 workspace number 1 + bindsym $mod+2 workspace number 2 + bindsym $mod+3 workspace number 3 + bindsym $mod+4 workspace number 4 + bindsym $mod+5 workspace number 5 + bindsym $mod+6 workspace number 6 + bindsym $mod+7 workspace number 7 + bindsym $mod+8 workspace number 8 + bindsym $mod+9 workspace number 9 + bindsym $mod+0 workspace number 10 + + bindsym $mod+Shift+1 move container to workspace number 1 + bindsym $mod+Shift+2 move container to workspace number 2 + bindsym $mod+Shift+3 move container to workspace number 3 + bindsym $mod+Shift+4 move container to workspace number 4 + bindsym $mod+Shift+5 move container to workspace number 5 + bindsym $mod+Shift+6 move container to workspace number 6 + bindsym $mod+Shift+7 move container to workspace number 7 + bindsym $mod+Shift+8 move container to workspace number 8 + bindsym $mod+Shift+9 move container to workspace number 9 + bindsym $mod+Shift+0 move container to workspace number 10 + + bindsym $mod+b splith + bindsym $mod+v splitv + bindsym $mod+space layout toggle split tabbed + + # Make the current focus fullscreen + bindsym $mod+f fullscreen + + # Toggle the current focus between tiling and floating mode + bindsym $mod+Ctrl+space floating toggle + + # Move focus to the parent container + bindsym $mod+a focus parent +# +# Scratchpad: +# + # Sway has a "scratchpad", which is a bag of holding for windows. + # You can send windows there and get them back later. + + # Move the currently focused window to the scratchpad + bindsym $mod+Shift+minus move scratchpad + + # Show the next scratchpad window or hide the focused scratchpad window. + # If there are multiple scratchpad windows, this command cycles through them. + bindsym $mod+minus scratchpad show +# +# Resizing containers: +# +mode "resize" { + bindsym $left resize shrink width 10px + bindsym $down resize grow height 10px + bindsym $up resize shrink height 10px + bindsym $right resize grow width 10px + + bindsym Return mode "default" + bindsym Escape mode "default" +} +bindsym $mod+r mode "resize" + +# +# Status Bar: +# +# Read `man 5 sway-bar` for more information about this section. +bar { + position top + + bindsym button1 nop + # diable vertical scrolling + bindsym button4 nop + bindsym button5 nop + # diable horizontal scrolling + bindsym button6 nop + bindsym button7 nop + + # When the status_command prints a new line to stdout, swaybar updates. + # The default just shows the current date and time. + status_command while date +'%a %d-%m %H:%M:%S %p'; do sleep 1; done + + colors { + statusline #ffffff + background #323232 + inactive_workspace #285577 #285577 #ffffff + active_workspace #52e2ff #52e2ff #5c5c5c + focused_workspace #52e2ff #52e2ff #5c5c5c + } +} +# class border backgr. text indicator child_border +client.focused #4c7899 #ffaa77 #ffffff #ff0000 #ff9000 +client.focused_inactive #333333 #5f676a #ffffff #ff0000 #5f676a +client.unfocused #333333 #222222 #888888 #ff0000 #1d1f21 +client.urgent #2f343a #900000 #ffffff #ff0000 #900000 +client.placeholder #000000 #0c0c0c #ffffff #ff0000 #0c0c0c + +font pango:SourceCodePro Medium 10 + +client.background #ffffff +include /etc/sway/config.d/* + +exec mycli launch:startup diff --git a/configs/sway/swaylock b/configs/sway/swaylock new file mode 100644 index 0000000..5c45c54 --- /dev/null +++ b/configs/sway/swaylock @@ -0,0 +1,8 @@ +ignore-empty-password +show-failed-attempts +scaling=fill +color=#330866 +image=DP-1:~/.dotfiles/configs/sway/wallpaper.png +image=DP-2:~/.dotfiles/configs/sway/wallpaper.png +image=DP-3:~/.dotfiles/configs/sway/wallpaper.png +image=eDP-1:~/.dotfiles/configs/sway/wallpaper.png diff --git a/configs/sway/wallpaper.png b/configs/sway/wallpaper.png new file mode 100644 index 0000000..5674504 Binary files /dev/null and b/configs/sway/wallpaper.png differ diff --git a/configs/vimrc b/configs/vimrc new file mode 100644 index 0000000..4bd4ea5 --- /dev/null +++ b/configs/vimrc @@ -0,0 +1,79 @@ + +call plug#begin() + +Plug 'scrooloose/nerdtree' +Plug 'Xuyuanp/nerdtree-git-plugin' +Plug 'jistr/vim-nerdtree-tabs' +Plug 'tpope/vim-fugitive' +Plug 'airblade/vim-gitgutter' + +Plug 'junegunn/fzf', { 'dir': '~/.fzf', 'do': './install --all' } +Plug 'rking/ag.vim' + +Plug 'tpope/vim-surround' +Plug 'gorodinskiy/vim-coloresque' + +Plug 'morhetz/gruvbox' +Plug 'vim-airline/vim-airline' +Plug 'vim-airline/vim-airline-themes' + +call plug#end() +set exrc +set secure + +filetype plugin indent on + +let mapleader = "" + +noremap :NERDTreeToggle +noremap :NERDTreeFind +noremap +noremap :FZF +noremap :wagT +noremap :wagt +noremap :Ag +noremap s :w +noremap = gg=G +noremap :tab split + +set number relativenumber +set hlsearch +set incsearch +set laststatus=2 +set mouse=a +set backspace=indent,eol,start +let $FZF_DEFAULT_COMMAND = 'ag --ignore-case --ignore .git -l -g ""' +set nofoldenable +set termguicolors + + +syntax enable +set background=dark +try + colorscheme gruvbox +catch +endtry + +set tabstop=4 shiftwidth=4 softtabstop=4 expandtab smarttab +augroup indent + autocmd! + autocmd FileType python setlocal ts=4 sw=4 sts=4 et smarttab + autocmd FileType lua setlocal ts=4 sw=4 sts=4 et smarttab + autocmd FileType sh setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType javascript setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType json setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType typescript setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType typescriptreact setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType css setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType scss setlocal ts=2 sw=2 sts=2 et smarttab + autocmd FileType yaml setlocal ts=2 sts=2 sw=2 expandtab + autocmd FileType plist setlocal ts=4 sts=4 sw=4 expandtab +augroup END + +let g:NERDTreeShowHidden = 1 +let NERDTreeIgnore=['\.py[cd]$', '\~$', '\.swo$', '\.swp$', '^\.git$', '^\.hg$', '^\.svn$', '\.bzr$', 'node_modules'] +let NERDTreeMouseMode=2 + +" VIM_AIRLINE +let g:airline_powerline_fonts=1 +let g:airline_theme = 'gruvbox' diff --git a/configs/zsh/bira.zsh-theme b/configs/zsh/bira.zsh-theme new file mode 100644 index 0000000..77aa346 --- /dev/null +++ b/configs/zsh/bira.zsh-theme @@ -0,0 +1,31 @@ +local return_code="%(?..%{$fg[red]%}%? ↵%{$reset_color%})" +local user_host="%B%(!.%{$fg[red]%}.%{$fg[green]%})%n@%m%{$reset_color%} " +local user_symbol='%(!.#.$)' +local current_dir="%B%{$fg[blue]%}%~ %{$reset_color%}" + +local vcs_branch='$(git_prompt_info)$(hg_prompt_info)' +local node='$(node_prompt_info)' + +function node_prompt_info() { + local prompt + prompt=$(node --version 2>/dev/null) + [[ -z "${prompt}" ]] && return 1 + echo "${ZSH_THEME_NODE_PROMPT_PREFIX}${prompt:gs/%/%%}${ZSH_THEME_NODE_PROMPT_SUFFIX}" +} + +PROMPT="╭─${user_host}${current_dir}${node}${vcs_branch}${venv_prompt} +╰─%B${user_symbol}%b " +RPROMPT="%B${return_code}%b" + +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg[yellow]%}‹" +ZSH_THEME_GIT_PROMPT_SUFFIX="› %{$reset_color%}" +ZSH_THEME_GIT_PROMPT_DIRTY="%{$fg[red]%}●%{$fg[yellow]%}" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg[yellow]%}" + +ZSH_THEME_HG_PROMPT_PREFIX="$ZSH_THEME_GIT_PROMPT_PREFIX" +ZSH_THEME_HG_PROMPT_SUFFIX="$ZSH_THEME_GIT_PROMPT_SUFFIX" +ZSH_THEME_HG_PROMPT_DIRTY="$ZSH_THEME_GIT_PROMPT_DIRTY" +ZSH_THEME_HG_PROMPT_CLEAN="$ZSH_THEME_GIT_PROMPT_CLEAN" + +ZSH_THEME_NODE_PROMPT_PREFIX="%{$fg[green]%}‹" +ZSH_THEME_NODE_PROMPT_SUFFIX="› %{$reset_color%}" diff --git a/configs/zshrc b/configs/zshrc new file mode 100644 index 0000000..06b94a5 --- /dev/null +++ b/configs/zshrc @@ -0,0 +1,72 @@ +export ZSH=$HOME/.oh-my-zsh + +ZSH_CUSTOM="$HOME/.dotfiles/configs/zsh" +ZSH_THEME="bira" +export HISTSIZE=500000 +export SAVEHIST=500000 +setopt appendhistory +setopt INC_APPEND_HISTORY +setopt SHARE_HISTORY + +plugins=( + git common-aliases cp docker golang vi-mode vim-interaction #kubectl +) + +source $ZSH/oh-my-zsh.sh + +export EDITOR='nvim' +export GPG_TTY=$(tty) + +export MOZ_ENABLE_WAYLAND=1 + +export PATH="$HOME/go/bin:$PATH" +export PATH="$HOME/.cargo/bin:$PATH" +export PATH="$HOME/.local/bin:$PATH" +export PATH="$HOME/.dotfiles/bin:$PATH" +export PATH="$HOME/Android/Sdk/platform-tools:$PATH" +export PATH="$HOME/Android/Sdk/tools:$PATH" +export PATH="$HOME/Android/Sdk/cmdline-tools/latest/bin:$PATH" + +export NVM_DIR="$HOME/.nvm" +export ANDROID_SDK_ROOT="$HOME/Android/Sdk" +export ANDROID_HOME="$HOME/Android/Sdk" +export ANDROID_SDK="$HOME/Android/Sdk" + +export DISABLE_UNTRACKED_FILES_DIRTY=true # improve performance on large repos + +alias gti=git +alias g=git +alias ggpush='git push --set-upstream origin $(git_current_branch)' + +[ -s ~/.zshrc.secrets ] && source ~/.zshrc.secrets +[ -s ~/.zshrc.local ] && source ~/.zshrc.local + +if [ ! -z "$CURRENT_ENV" ] && [ -s "$HOME/.dotfiles/env/$CURRENT_ENV/zshrc" ] ; then + source ~/.dotfiles/env/$CURRENT_ENV/zshrc +fi + +[ -x "$(command -v direnv)" ] && eval "$(direnv hook zsh)" + +if [ -s /usr/share/fzf/completion.zsh ]; then + source "/usr/share/fzf/completion.zsh" +elif [ -s $HOME/.fzf/shell/completion.zsh ]; then + source "$HOME/.fzf/shell/completion.zsh" +fi + +if [ -s /usr/share/fzf/key-bindings.zsh ]; then + source "/usr/share/fzf/key-bindings.zsh" +elif [ -s $HOME/.fzf/shell/key-bindings.zsh ]; then + source "$HOME/.fzf/shell/key-bindings.zsh" +fi + +[ -z "$DISABLE_NVM" ] && [ -s "$NVM_DIR/nvm.sh" ] && source "$NVM_DIR/nvm.sh" # This loads nvm +[ -z "$DISABLE_NVM" ] && [ -s "$NVM_DIR/bash_completion" ] && source "$NVM_DIR/bash_completion" # This loads nvm bash_completion + +if [[ -f "$HOME/.rvm/scripts/rvm" ]]; then + export PATH="$HOME/.rvm/bin:$PATH" + source $HOME/.rvm/scripts/rvm +fi + +export VOLTA_HOME="$HOME/.volta" +export PATH="$VOLTA_HOME/bin:$PATH" + diff --git a/dygma/dygma.json b/dygma/dygma.json new file mode 100644 index 0000000..4cfa7d0 --- /dev/null +++ b/dygma/dygma.json @@ -0,0 +1,6627 @@ +{ + "keymap": { + "onlyCustom": true, + "custom": [ + [ + { + "keyCode": 53, + "label": "`" + }, + { + "keyCode": 30, + "label": "1" + }, + { + "keyCode": 31, + "label": "2" + }, + { + "keyCode": 32, + "label": "3" + }, + { + "keyCode": 33, + "label": "4" + }, + { + "keyCode": 34, + "label": "5" + }, + { + "keyCode": 35, + "label": "6" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 36, + "label": "7" + }, + { + "keyCode": 37, + "label": "8" + }, + { + "keyCode": 38, + "label": "9" + }, + { + "keyCode": 39, + "label": "0" + }, + { + "keyCode": 45, + "label": "-" + }, + { + "keyCode": 46, + "label": "=" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 20, + "label": "Q" + }, + { + "keyCode": 26, + "label": "W" + }, + { + "keyCode": 8, + "label": "E" + }, + { + "keyCode": 21, + "label": "R" + }, + { + "keyCode": 23, + "label": "T" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 28, + "label": "Y" + }, + { + "keyCode": 24, + "label": "U" + }, + { + "keyCode": 12, + "label": "I" + }, + { + "keyCode": 18, + "label": "O" + }, + { + "keyCode": 19, + "label": "P" + }, + { + "keyCode": 47, + "label": "[" + }, + { + "keyCode": 48, + "label": "]" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 4, + "label": "A" + }, + { + "keyCode": 22, + "label": "S" + }, + { + "keyCode": 7, + "label": "D" + }, + { + "keyCode": 9, + "label": "F" + }, + { + "keyCode": 10, + "label": "G" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 11, + "label": "H" + }, + { + "keyCode": 13, + "label": "J" + }, + { + "keyCode": 14, + "label": "K" + }, + { + "keyCode": 15, + "label": "L" + }, + { + "keyCode": 51, + "label": ";" + }, + { + "keyCode": 52, + "label": "'" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 225, + "label": "LEFT SHIFT", + "verbose": "Left Shift" + }, + { + "keyCode": 100, + "label": "ISO <>", + "verbose": "ISO <>" + }, + { + "keyCode": 29, + "label": "Z" + }, + { + "keyCode": 27, + "label": "X" + }, + { + "keyCode": 6, + "label": "C" + }, + { + "keyCode": 25, + "label": "V" + }, + { + "keyCode": 5, + "label": "B" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 17, + "label": "N" + }, + { + "keyCode": 16, + "label": "M" + }, + { + "keyCode": 54, + "label": "," + }, + { + "keyCode": 55, + "label": "." + }, + { + "keyCode": 56, + "label": "/" + }, + { + "keyCode": 229, + "label": "RIGHT SHIFT", + "extraLabel": "", + "verbose": "Right Shift" + }, + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 226, + "label": "LEFT ALT", + "verbose": "Left Alt" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 224, + "label": "LEFT CTRL", + "verbose": "Left Control" + }, + { + "keyCode": 51771, + "label": "ESC", + "extraLabel": "L#2/" + }, + { + "keyCode": 51516, + "label": "BACKSPACE", + "extraLabel": "L#1/" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 230, + "label": "RIGHT ALT", + "extraLabel": "", + "verbose": "AltGr" + }, + { + "keyCode": 231, + "label": "RIGHT LINUX", + "verbose": "Right Linux" + }, + { + "keyCode": 101, + "label": "MENU" + }, + { + "keyCode": 228, + "label": "RIGHT CTRL", + "verbose": "Right Control" + } + ], + [ + { + "keyCode": 53, + "label": "`" + }, + { + "keyCode": 30, + "label": "1" + }, + { + "keyCode": 31, + "label": "2" + }, + { + "keyCode": 32, + "label": "3" + }, + { + "keyCode": 33, + "label": "4" + }, + { + "keyCode": 34, + "label": "5" + }, + { + "keyCode": 35, + "label": "6" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 36, + "label": "7" + }, + { + "keyCode": 37, + "label": "8" + }, + { + "keyCode": 22733, + "label": "PLAY", + "extraLabel": "Media", + "verbose": "Play / pause" + }, + { + "keyCode": 19682, + "label": "MUTE", + "extraLabel": "Media" + }, + { + "keyCode": 23786, + "label": "VOL-", + "extraLabel": "Media", + "verbose": "Volume down" + }, + { + "keyCode": 23785, + "label": "VOL+", + "extraLabel": "Media", + "verbose": "Volume up" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 20545, + "label": "LEFT", + "extraLabel": "M.Btn" + }, + { + "keyCode": 20481, + "label": "UP", + "extraLabel": "Mouse" + }, + { + "keyCode": 20546, + "label": "RIGHT", + "extraLabel": "M.Btn" + }, + { + "keyCode": 21, + "label": "R" + }, + { + "keyCode": 23, + "label": "T" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 28, + "label": "Y" + }, + { + "keyCode": 24, + "label": "U" + }, + { + "keyCode": 12, + "label": "I" + }, + { + "keyCode": 18, + "label": "O" + }, + { + "keyCode": 19, + "label": "P" + }, + { + "keyCode": 47, + "label": "[" + }, + { + "keyCode": 48, + "label": "]" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 20484, + "label": "LEFT", + "extraLabel": "Mouse" + }, + { + "keyCode": 20482, + "label": "DOWN", + "extraLabel": "Mouse" + }, + { + "keyCode": 20488, + "label": "RIGHT", + "extraLabel": "Mouse" + }, + { + "keyCode": 9, + "label": "F" + }, + { + "keyCode": 10, + "label": "G" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 80, + "label": "←" + }, + { + "keyCode": 81, + "label": "↓" + }, + { + "keyCode": 82, + "label": "↑" + }, + { + "keyCode": 79, + "label": "→" + }, + { + "keyCode": 51, + "label": ";" + }, + { + "keyCode": 52, + "label": "'" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 225, + "label": "LEFT SHIFT", + "verbose": "Left Shift" + }, + { + "keyCode": 100, + "label": "ISO <>", + "verbose": "ISO <>" + }, + { + "keyCode": 29, + "label": "Z" + }, + { + "keyCode": 27, + "label": "X" + }, + { + "keyCode": 6, + "label": "C" + }, + { + "keyCode": 25, + "label": "V" + }, + { + "keyCode": 5, + "label": "B" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 17, + "label": "N" + }, + { + "keyCode": 16, + "label": "M" + }, + { + "keyCode": 54, + "label": "," + }, + { + "keyCode": 55, + "label": "." + }, + { + "keyCode": 56, + "label": "/" + }, + { + "keyCode": 229, + "label": "RIGHT SHIFT", + "extraLabel": "", + "verbose": "Right Shift" + }, + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 226, + "label": "LEFT ALT", + "verbose": "Left Alt" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 224, + "label": "LEFT CTRL", + "verbose": "Left Control" + }, + { + "keyCode": 51770, + "label": "ENTER", + "extraLabel": "L#2/" + }, + { + "keyCode": 51516, + "label": "BACKSPACE", + "extraLabel": "L#1/" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 230, + "label": "RIGHT ALT", + "extraLabel": "", + "verbose": "AltGr" + }, + { + "keyCode": 231, + "label": "RIGHT LINUX", + "verbose": "Right Linux" + }, + { + "keyCode": 101, + "label": "MENU" + }, + { + "keyCode": 228, + "label": "RIGHT CTRL", + "verbose": "Right Control" + } + ], + [ + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ], + [ + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 53, + "label": "`" + }, + { + "keyCode": 58, + "label": "F1" + }, + { + "keyCode": 59, + "label": "F2" + }, + { + "keyCode": 60, + "label": "F3" + }, + { + "keyCode": 61, + "label": "F4" + }, + { + "keyCode": 62, + "label": "F5" + }, + { + "keyCode": 63, + "label": "F6" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 64, + "label": "F7" + }, + { + "keyCode": 65, + "label": "F8" + }, + { + "keyCode": 66, + "label": "F9" + }, + { + "keyCode": 67, + "label": "F10" + }, + { + "keyCode": 68, + "label": "F11" + }, + { + "keyCode": 69, + "label": "F12" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 74, + "label": "HOME" + }, + { + "keyCode": 82, + "label": "↑" + }, + { + "keyCode": 77, + "label": "END" + }, + { + "keyCode": 75, + "label": "PAGE UP", + "verbose": "Page Up" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 86, + "label": "-", + "extraLabel": "Numpad" + }, + { + "keyCode": 95, + "label": "7", + "extraLabel": "Numpad" + }, + { + "keyCode": 96, + "label": "8", + "extraLabel": "Numpad" + }, + { + "keyCode": 97, + "label": "9", + "extraLabel": "Numpad" + }, + { + "keyCode": 84, + "label": "/", + "extraLabel": "Numpad" + }, + { + "keyCode": 83, + "label": "NUMLOCK", + "verbose": "Num Lock" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 49226, + "label": "CAPSLOCK", + "extraLabel": "CTRL/" + }, + { + "keyCode": 70, + "label": "PRINT SCREEN", + "verbose": "Print Screen" + }, + { + "keyCode": 80, + "label": "←" + }, + { + "keyCode": 81, + "label": "↓" + }, + { + "keyCode": 79, + "label": "→" + }, + { + "keyCode": 78, + "label": "PAGE DOWN", + "verbose": "Page Down" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 87, + "label": "+", + "extraLabel": "Numpad" + }, + { + "keyCode": 92, + "label": "4", + "extraLabel": "Numpad" + }, + { + "keyCode": 93, + "label": "5", + "extraLabel": "Numpad" + }, + { + "keyCode": 94, + "label": "6", + "extraLabel": "Numpad" + }, + { + "keyCode": 85, + "label": "*", + "extraLabel": "Numpad" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 225, + "label": "LEFT SHIFT", + "verbose": "Left Shift" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 22710, + "label": "TRACK-", + "extraLabel": "Media", + "verbose": "Prev. track" + }, + { + "keyCode": 22709, + "label": "TRACK+", + "extraLabel": "Media", + "verbose": "Next track" + }, + { + "keyCode": 23786, + "label": "VOL-", + "extraLabel": "Media", + "verbose": "Volume down" + }, + { + "keyCode": 23785, + "label": "VOL+", + "extraLabel": "Media", + "verbose": "Volume up" + }, + { + "keyCode": 22733, + "label": "PLAY", + "extraLabel": "Media", + "verbose": "Play / pause" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 98, + "label": "0", + "extraLabel": "Numpad" + }, + { + "keyCode": 89, + "label": "1", + "extraLabel": "Numpad" + }, + { + "keyCode": 90, + "label": "2", + "extraLabel": "Numpad" + }, + { + "keyCode": 91, + "label": "3", + "extraLabel": "Numpad" + }, + { + "keyCode": 99, + "label": ".", + "extraLabel": "Numpad" + }, + { + "keyCode": 82, + "label": "↑" + }, + { + "keyCode": 224, + "label": "LEFT CTRL", + "verbose": "Left Control" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 226, + "label": "LEFT ALT", + "verbose": "Left Alt" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 51514, + "label": "ENTER", + "extraLabel": "L#1/" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 17152, + "label": "NEXT", + "extraLabel": "LED" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 230, + "label": "RIGHT ALT", + "extraLabel": "", + "verbose": "AltGr" + }, + { + "keyCode": 80, + "label": "←" + }, + { + "keyCode": 81, + "label": "↓" + } + ], + [ + { + "keyCode": 79, + "label": "→" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + }, + { + "keyCode": 65535, + "label": "", + "extraLabel": "TRANS", + "verbose": "Transparent" + } + ] + ], + "default": [ + [ + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 30, + "label": "1" + }, + { + "keyCode": 31, + "label": "2" + }, + { + "keyCode": 32, + "label": "3" + }, + { + "keyCode": 33, + "label": "4" + }, + { + "keyCode": 34, + "label": "5" + }, + { + "keyCode": 35, + "label": "6" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 36, + "label": "7" + }, + { + "keyCode": 37, + "label": "8" + }, + { + "keyCode": 38, + "label": "9" + }, + { + "keyCode": 39, + "label": "0" + }, + { + "keyCode": 45, + "label": "-" + }, + { + "keyCode": 46, + "label": "=" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 20, + "label": "Q" + }, + { + "keyCode": 26, + "label": "W" + }, + { + "keyCode": 8, + "label": "E" + }, + { + "keyCode": 21, + "label": "R" + }, + { + "keyCode": 23, + "label": "T" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 28, + "label": "Y" + }, + { + "keyCode": 24, + "label": "U" + }, + { + "keyCode": 12, + "label": "I" + }, + { + "keyCode": 18, + "label": "O" + }, + { + "keyCode": 19, + "label": "P" + }, + { + "keyCode": 47, + "label": "[" + }, + { + "keyCode": 48, + "label": "]" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 57, + "label": "CAPSLOCK", + "verbose": "Caps Lock" + }, + { + "keyCode": 4, + "label": "A" + }, + { + "keyCode": 22, + "label": "S" + }, + { + "keyCode": 7, + "label": "D" + }, + { + "keyCode": 9, + "label": "F" + }, + { + "keyCode": 10, + "label": "G" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 11, + "label": "H" + }, + { + "keyCode": 13, + "label": "J" + }, + { + "keyCode": 14, + "label": "K" + }, + { + "keyCode": 15, + "label": "L" + }, + { + "keyCode": 51, + "label": ";" + }, + { + "keyCode": 52, + "label": "'" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 225, + "label": "LEFT SHIFT", + "verbose": "Left Shift" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 29, + "label": "Z" + }, + { + "keyCode": 27, + "label": "X" + }, + { + "keyCode": 6, + "label": "C" + }, + { + "keyCode": 25, + "label": "V" + }, + { + "keyCode": 5, + "label": "B" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 17, + "label": "N" + }, + { + "keyCode": 16, + "label": "M" + }, + { + "keyCode": 54, + "label": "," + }, + { + "keyCode": 55, + "label": "." + }, + { + "keyCode": 56, + "label": "/" + }, + { + "keyCode": 229, + "label": "RIGHT SHIFT", + "extraLabel": "", + "verbose": "Right Shift" + }, + { + "keyCode": 224, + "label": "LEFT CTRL", + "verbose": "Left Control" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 226, + "label": "LEFT ALT", + "verbose": "Left Alt" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 17493, + "label": "1", + "extraLabel": "MOVE" + }, + { + "keyCode": 76, + "label": "DEL" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 230, + "label": "RIGHT ALT", + "extraLabel": "", + "verbose": "AltGr" + }, + { + "keyCode": 231, + "label": "RIGHT LINUX", + "verbose": "Right Linux" + }, + { + "keyCode": 17152, + "label": "NEXT", + "extraLabel": "LED" + }, + { + "keyCode": 228, + "label": "RIGHT CTRL", + "verbose": "Right Control" + } + ], + [ + { + "keyCode": 41, + "label": "ESC" + }, + { + "keyCode": 58, + "label": "F1" + }, + { + "keyCode": 59, + "label": "F2" + }, + { + "keyCode": 60, + "label": "F3" + }, + { + "keyCode": 61, + "label": "F4" + }, + { + "keyCode": 62, + "label": "F5" + }, + { + "keyCode": 63, + "label": "F6" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 64, + "label": "F7" + }, + { + "keyCode": 65, + "label": "F8" + }, + { + "keyCode": 66, + "label": "F9" + }, + { + "keyCode": 67, + "label": "F10" + }, + { + "keyCode": 68, + "label": "F11" + }, + { + "keyCode": 69, + "label": "F12" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 43, + "label": "TAB" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 82, + "label": "↑" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 86, + "label": "-", + "extraLabel": "Numpad" + }, + { + "keyCode": 36, + "label": "7" + }, + { + "keyCode": 37, + "label": "8" + }, + { + "keyCode": 38, + "label": "9" + }, + { + "keyCode": 84, + "label": "/", + "extraLabel": "Numpad" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 57, + "label": "CAPSLOCK", + "verbose": "Caps Lock" + }, + { + "keyCode": 80, + "label": "←" + }, + { + "keyCode": 81, + "label": "↓" + }, + { + "keyCode": 79, + "label": "→" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 87, + "label": "+", + "extraLabel": "Numpad" + }, + { + "keyCode": 33, + "label": "4" + }, + { + "keyCode": 34, + "label": "5" + }, + { + "keyCode": 35, + "label": "6" + }, + { + "keyCode": 85, + "label": "*", + "extraLabel": "Numpad" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 225, + "label": "LEFT SHIFT", + "verbose": "Left Shift" + }, + { + "keyCode": 49, + "label": "\\" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 99, + "label": ".", + "extraLabel": "Numpad" + }, + { + "keyCode": 30, + "label": "1" + }, + { + "keyCode": 31, + "label": "2" + }, + { + "keyCode": 32, + "label": "3" + }, + { + "keyCode": 82, + "label": "↑" + }, + { + "keyCode": 229, + "label": "RIGHT SHIFT", + "extraLabel": "", + "verbose": "Right Shift" + }, + { + "keyCode": 224, + "label": "LEFT CTRL", + "verbose": "Left Control" + }, + { + "keyCode": 227, + "label": "LEFT LINUX", + "verbose": "Left Linux" + }, + { + "keyCode": 226, + "label": "LEFT ALT", + "verbose": "Left Alt" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 0, + "label": "NO KEY", + "verbose": "Disabled" + }, + { + "keyCode": 42, + "label": "BACKSPACE", + "verbose": "Backspace" + }, + { + "keyCode": 40, + "label": "ENTER" + }, + { + "keyCode": 17492, + "label": "0", + "extraLabel": "MOVE" + }, + { + "keyCode": 76, + "label": "DEL" + }, + { + "keyCode": 39, + "label": "0" + }, + { + "keyCode": 44, + "label": "SPACE" + }, + { + "keyCode": 80, + "label": "←" + }, + { + "keyCode": 81, + "label": "↓" + }, + { + "keyCode": 79, + "label": "→" + }, + { + "keyCode": 228, + "label": "RIGHT CTRL", + "verbose": "Right Control" + } + ] + ] + }, + "colormap": [ + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5 + ], + [ + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ], + [ + 4, + 8, + 8, + 8, + 8, + 8, + 8, + 4, + 15, + 9, + 0, + 9, + 10, + 7, + 8, + 0, + 0, + 0, + 10, + 4, + 15, + 7, + 7, + 4, + 4, + 1, + 4, + 4, + 4, + 1, + 1, + 5, + 6, + 5, + 8, + 8, + 8, + 8, + 8, + 8, + 6, + 15, + 11, + 10, + 9, + 9, + 9, + 10, + 15, + 15, + 10, + 9, + 9, + 9, + 10, + 0, + 10, + 9, + 9, + 9, + 9, + 0, + 0, + 0, + 4, + 1, + 1, + 11, + 3, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6 + ], + [ + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15, + 15 + ] + ], + "palette": [ + { + "r": 255, + "g": 196, + "b": 0, + "rgb": "rgb(255, 196, 0)" + }, + { + "r": 0, + "g": 254, + "b": 24, + "rgb": "rgb(0, 254, 24)" + }, + { + "r": 255, + "g": 255, + "b": 255, + "rgb": "rgb(255, 255, 255)" + }, + { + "r": 231, + "g": 255, + "b": 0, + "rgb": "rgb(231, 255, 0)" + }, + { + "r": 0, + "g": 254, + "b": 234, + "rgb": "rgb(0, 254, 234)" + }, + { + "r": 0, + "g": 52, + "b": 255, + "rgb": "rgb(0, 52, 255)" + }, + { + "r": 255, + "g": 0, + "b": 232, + "rgb": "rgb(255, 0, 232)" + }, + { + "r": 87, + "g": 164, + "b": 255, + "rgb": "rgb(87, 164, 255)" + }, + { + "r": 144, + "g": 19, + "b": 254, + "rgb": "rgb(144, 19, 254)" + }, + { + "r": 239, + "g": 219, + "b": 255, + "rgb": "rgb(239, 219, 255)" + }, + { + "r": 214, + "g": 129, + "b": 255, + "rgb": "rgb(214, 129, 255)" + }, + { + "r": 255, + "g": 9, + "b": 0, + "rgb": "rgb(255, 9, 0)" + }, + { + "r": 0, + "g": 0, + "b": 0, + "rgb": "rgb(0, 0, 0)" + }, + { + "r": 0, + "g": 0, + "b": 0, + "rgb": "rgb(0, 0, 0)" + }, + { + "r": 0, + "g": 0, + "b": 0, + "rgb": "rgb(0, 0, 0)" + }, + { + "r": 0, + "g": 0, + "b": 0, + "rgb": "rgb(0, 0, 0)" + } + ] +} \ No newline at end of file diff --git a/env/common/common.go b/env/common/common.go new file mode 100644 index 0000000..83c07f0 --- /dev/null +++ b/env/common/common.go @@ -0,0 +1,34 @@ +package common + +import ( + "os" + "path" + + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/logger" +) + +var homeDir = os.Getenv("HOME") + +var log = logger.NamedLogger("common") + +var DotfilesWorkspace = env.Workspace{ + Name: "dotfiles", + Path: path.Join(homeDir, "/.dotfiles"), + VimConfig: env.VimConfig{ + GoEfm: map[string]interface{}{ + "formatCommand": "golines --max-len=120 --base-formatter=\"gofumpt\"", + "formatStdin": true, + }, + Actions: []env.VimAction{ + { + Id: "dotfiles_go_build", + Name: "[workspace] build", + Args: []string{"make"}, + Cwd: path.Join(homeDir, ".dotfiles"), + }, + }, + }, +} + +var HomeWorkspace = env.Workspace{Name: "home", Path: homeDir} diff --git a/env/common/expo_launcher.go b/env/common/expo_launcher.go new file mode 100644 index 0000000..4a18ba1 --- /dev/null +++ b/env/common/expo_launcher.go @@ -0,0 +1,234 @@ +package common + +import ( + "path" + + "github.com/wkozyra95/dotfiles/env" +) + +type ExpoLauncherConfigType struct { + UniverseWWW env.LauncherAction + UniverseWWWUnit env.LauncherAction + UniverseWebsite env.LauncherAction + UniverseWebsiteInternal env.LauncherAction + EasCli env.LauncherAction + Turtle env.LauncherAction + Submit env.LauncherAction + ExpoCliRebuild env.LauncherAction + ExpoDocs env.LauncherAction + ExpoGL env.LauncherAction +} + +func ExpoLauncherConfig(p string) ExpoLauncherConfigType { + universeWWWDockerUp := env.LauncherTask{ + Id: "www-docker-up", + Cwd: path.Join(p, "universe/server/www"), + Args: []string{"yarn", "docker-up"}, + } + universeWWWYarn := env.LauncherTask{ + Id: "www-docker-yarn", + Cwd: path.Join(p, "universe/server/www"), + Args: []string{"yarn"}, + } + universeWWWStart := env.LauncherTask{ + Id: "www-docker-start", + Cwd: path.Join(p, "universe/server/www"), + Args: []string{"yarn", "start:docker"}, + RunAsService: true, + WorkspaceID: env.Workspace6, + } + easBuildLibsWatch := env.LauncherTask{ + Id: "eas-build-watch", + Cwd: path.Join(p, "eas-build"), + Args: []string{"yarn", "watch"}, + RunAsService: true, + WorkspaceID: env.Workspace6, + } + turtleDockerUp := env.LauncherTask{ + Id: "turtle-docker-up", + Cwd: path.Join(p, "turtle-v2"), + Args: []string{"yarn", "docker:up"}, + } + + return ExpoLauncherConfigType{ + UniverseWWW: env.LauncherAction{ + Id: "www", + Tasks: []env.LauncherTask{ + universeWWWYarn, + universeWWWDockerUp, + universeWWWStart, + }, + }, + UniverseWWWUnit: env.LauncherAction{ + Id: "www-unit", + Tasks: []env.LauncherTask{ + { + Id: "www-unit", + Cwd: path.Join(p, "universe/server/www"), + Args: []string{"yarn", "jest-unit"}, + }, + }, + }, + UniverseWebsite: env.LauncherAction{ + Id: "website", + Tasks: []env.LauncherTask{ + universeWWWYarn, + universeWWWDockerUp, + universeWWWStart, + { + Id: "website-yarn", + Cwd: path.Join(p, "universe/server/website"), + Args: []string{"yarn"}, + }, + { + Id: "website-start", + Cwd: path.Join(p, "universe/server/website"), + Args: []string{"direnv", "exec", ".", "yarn", "start:local"}, + RunAsService: true, + WorkspaceID: env.Workspace6, + }, + }, + }, + UniverseWebsiteInternal: env.LauncherAction{ + Id: "website", + Tasks: []env.LauncherTask{ + universeWWWYarn, + universeWWWDockerUp, + { + Id: "website-yarn", + Cwd: path.Join(p, "universe/server/internal"), + Args: []string{"yarn"}, + }, + universeWWWStart, + { + Id: "website-start", + Cwd: path.Join(p, "universe/server/internal"), + Args: []string{"yarn", "dev"}, + WorkspaceID: env.Workspace6, + }, + }, + }, + EasCli: env.LauncherAction{ + Id: "cli", + Tasks: []env.LauncherTask{ + { + Id: "eas-cli-watch", + Cwd: path.Join(p, "eas-cli"), + Args: []string{"yarn", "watch"}, + RunAsService: true, + WorkspaceID: env.Workspace6, + }, + easBuildLibsWatch, + }, + }, + Turtle: env.LauncherAction{ + Id: "turtle", + Tasks: []env.LauncherTask{ + turtleDockerUp, + easBuildLibsWatch, + { + Id: "turtle-libs-watch", + Cwd: path.Join(p, "turtle-v2"), + Args: []string{"yarn", "watch:libs"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + { + Id: "turtle-start-api", + Cwd: path.Join(p, "turtle-v2/src/services/turtle-api"), + Args: []string{"yarn", "start"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + { + Id: "turtle-start-scheduler", + Cwd: path.Join(p, "turtle-v2/src/services/scheduler"), + Args: []string{"yarn", "start"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + { + Id: "turtle-start-launcher", + Cwd: path.Join(p, "turtle-v2/src/services/launcher"), + Args: []string{"yarn", "start"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + { + Id: "turtle-start-synchronizer", + Cwd: path.Join(p, "turtle-v2/src/services/synchronizer"), + Args: []string{"yarn", "start"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + }, + }, + Submit: env.LauncherAction{ + Id: "submit", + Tasks: []env.LauncherTask{ + easBuildLibsWatch, + turtleDockerUp, + { + Id: "turtle-libs-watch", + Cwd: path.Join(p, "turtle-v2"), + Args: []string{"yarn", "watch:libs"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + { + Id: "turtle-start-submit", + Cwd: path.Join(p, "turtle-v2/src/services/submission-service"), + Args: []string{"yarn", "start"}, + RunAsService: true, + WorkspaceID: env.Workspace7, + }, + }, + }, + ExpoCliRebuild: env.LauncherAction{ + Id: "expo-cli-rebuild", + Tasks: []env.LauncherTask{ + { + Id: "expo-cli-rebuild-config-types", + Cwd: path.Join(p, "expo/packages/@expo/config-types"), + Args: []string{"yarn", "build"}, + }, + { + Id: "expo-cli-rebuild-config-plugins", + Cwd: path.Join(p, "expo/packages/@expo/config-plugins"), + Args: []string{"yarn", "build"}, + }, + { + Id: "expo-cli-rebuild-config", + Cwd: path.Join(p, "expo/packages/@expo/config"), + Args: []string{"yarn", "build"}, + }, + }, + }, + ExpoDocs: env.LauncherAction{ + Id: "docs", + Tasks: []env.LauncherTask{ + { + Id: "expo-docs", + Cwd: path.Join(p, "expo/docs"), + Args: []string{"yarn", "dev"}, + RunAsService: true, + }, + }, + }, + ExpoGL: env.LauncherAction{ + Id: "gl", + Tasks: []env.LauncherTask{ + { + Id: "expo-gl-js-build", + Cwd: path.Join(p, "expo/packages/expo-gl"), + Args: []string{"yarn", "build"}, + }, + { + Id: "expo-gl-cpp", + Cwd: path.Join(p, "expo/android"), + Args: []string{"gradlew", ":expo-gl:buildCMakeDebug"}, + }, + }, + }, + } +} diff --git a/env/common/expo_workspace.go b/env/common/expo_workspace.go new file mode 100644 index 0000000..05f199e --- /dev/null +++ b/env/common/expo_workspace.go @@ -0,0 +1,160 @@ +package common + +import ( + "fmt" + "io/ioutil" + "path" + "regexp" + + "github.com/wkozyra95/dotfiles/env" +) + +var ( + eslintConfigEnabled = true + EslintConfig = env.VimConfig{Eslint: &eslintConfigEnabled} + turtleDatabaseUrls = map[string]string{ + "dev": "LOCAL_DATABASE_URL", + "staging": "STAGING_DATABASE_URL", + "production": "PRODUCTION_DATABASE_URL", + } +) + +func YarnBuild(p string) env.VimAction { + return env.VimAction{ + Id: "yarn build", + Name: "[workspace] yarn build", + Args: []string{"bash", "-c", "yarn && yarn build"}, + Cwd: p, + } +} + +func YarnLint(p string) env.VimAction { + return env.VimAction{ + Id: "yarn lint", + Name: "[workspace] yarn lint", + Args: []string{"bash", "-c", "yarn && yarn lint"}, + Cwd: p, + } +} + +type ExpoWorkspacesConfig struct { + LegacyExpoCli func(p string) env.Workspace + EasCli func(p string) env.Workspace + EasBuild func(p string) env.Workspace + Turtle func(p string) env.Workspace + UniverseWWW func(p string) env.Workspace + UniverseWebsite func(p string) env.Workspace + TurtleClassic func(p string) env.Workspace + ExpoSdk func(p string) env.Workspace + ExpoSdkGl func(p string) env.Workspace + EASBuildCache func(p string) env.Workspace +} + +func tryReadingDatabaseSecrets(file string) map[string]string { + dbMap := map[string]string{} + content, contentErr := ioutil.ReadFile(file) + if contentErr != nil { + log.Error(contentErr.Error()) + } else { + for key, entry := range turtleDatabaseUrls { + rg := regexp.MustCompile(fmt.Sprintf("%s=(.*)", regexp.QuoteMeta(entry))) + matches := rg.FindSubmatchIndex(content) + if matches != nil { + dbMap[key] = string(content[matches[2]:matches[3]]) + } + } + } + return dbMap +} + +var ExpoConfig = ExpoWorkspacesConfig{ + LegacyExpoCli: func(p string) env.Workspace { + return env.Workspace{ + Name: "expo-cli", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + Actions: []env.VimAction{ + YarnBuild(p), + YarnLint(p), + }, + }, + } + }, + EasCli: func(p string) env.Workspace { + return env.Workspace{Name: "eas-cli", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + Actions: []env.VimAction{ + YarnBuild(p), + YarnLint(p), + }, + }} + }, + EasBuild: func(p string) env.Workspace { + return env.Workspace{Name: "eas-build", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + Actions: []env.VimAction{ + YarnBuild(p), + YarnLint(p), + }, + }} + }, + Turtle: func(p string) env.Workspace { + return env.Workspace{ + Name: "turtle", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + Actions: []env.VimAction{ + YarnBuild(p), + YarnLint(p), + }, + Databases: env.LazyValue[map[string]string]( + func() map[string]string { return tryReadingDatabaseSecrets(path.Join(p, "database/secrets.env")) }, + ), + }, + } + }, + UniverseWWW: func(p string) env.Workspace { + return env.Workspace{Name: "www", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + Databases: env.LazyValue[map[string]string]( + func() map[string]string { return tryReadingDatabaseSecrets(path.Join(homeDir, ".secrets/www_db.env")) }, + ), + }} + }, + UniverseWebsite: func(p string) env.Workspace { + return env.Workspace{Name: "website", Path: p, VimConfig: EslintConfig} + }, + TurtleClassic: func(p string) env.Workspace { + return env.Workspace{Name: "classic", Path: p, VimConfig: EslintConfig} + }, + ExpoSdk: func(p string) env.Workspace { + return env.Workspace{Name: "sdk", Path: p, VimConfig: EslintConfig} + }, + ExpoSdkGl: func(p string) env.Workspace { + return env.Workspace{Name: "gl-cpp", Path: p, VimConfig: env.VimConfig{ + Eslint: EslintConfig.Eslint, + CmakeEfm: map[string]interface{}{ + "formatCommand": "cmake-format --tab-size 4 ${INPUT}", + "formatStdin": false, + }, + Actions: []env.VimAction{ + { + Id: "expo-gl-build-cpp", + Name: "[workspace] build cpp", + Args: []string{"./gradlew", ":expo-gl:buildCMakeDebug"}, + Cwd: path.Join(p, "../../android"), + }, + }, + }} + }, + EASBuildCache: func(p string) env.Workspace { + return env.Workspace{ + Name: "cache", + Path: p, + VimConfig: env.VimConfig{ + GoEfm: map[string]interface{}{ + "formatCommand": "gofumpt", + "formatStdin": true, + }, + }, + } + }, +} diff --git a/env/config/config.go b/env/config/config.go new file mode 100644 index 0000000..9f195ad --- /dev/null +++ b/env/config/config.go @@ -0,0 +1,37 @@ +package config + +import ( + "os" + + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/docker" + "github.com/wkozyra95/dotfiles/env/home" + "github.com/wkozyra95/dotfiles/env/homeoffice" + "github.com/wkozyra95/dotfiles/env/macbook" + "github.com/wkozyra95/dotfiles/env/work" + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("api") + +func GetConfig() env.EnvironmentConfig { + switch os.Getenv("CURRENT_ENV") { + case "home": + return home.Config + case "work": + return work.Config + case "homeoffice": + return homeoffice.Config + case "macbook": + return macbook.Config + case "docker": + return docker.Config + default: + log.Warn("Missing or invalid CURRENT_ENV") + return env.EnvironmentConfig{ + Workspaces: []env.Workspace{}, + Actions: []env.LauncherAction{}, + Init: []env.InitAction{}, + } + } +} diff --git a/env/docker/env.go b/env/docker/env.go new file mode 100644 index 0000000..3b8339f --- /dev/null +++ b/env/docker/env.go @@ -0,0 +1,30 @@ +package docker + +import ( + "os" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/platform/ubuntu" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/common" +) + +var homeDir = os.Getenv("HOME") + +var Config = env.EnvironmentConfig{ + Workspaces: []env.Workspace{ + common.DotfilesWorkspace, + }, + Actions: []env.LauncherAction{}, + Init: []env.InitAction{}, + CustomSetupAction: func(ctx env.Context) action.Object { + pkgInstaller := ubuntu.Apt{} + return action.List{ + pkgInstaller.EnsurePackagerAction(homeDir), + api.PackageInstallAction([]api.Package{ + pkgInstaller.CustomPackageList([]string{}), + }), + } + }, +} diff --git a/env/docker/zshrc b/env/docker/zshrc new file mode 100644 index 0000000..e7e1c0e --- /dev/null +++ b/env/docker/zshrc @@ -0,0 +1 @@ +export PATH=/usr/local/go/bin:$PATH diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..3a5ef27 --- /dev/null +++ b/env/env.go @@ -0,0 +1,95 @@ +package env + +import ( + "encoding/json" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("common") + +const ( + Workspace1 int = 1 + Workspace2 = 2 + Workspace3 = 3 + Workspace4 = 4 + Workspace5 = 5 + Workspace6 = 6 + Workspace7 = 7 + Workspace8 = 8 + Workspace9 = 9 + Workspace10 = 10 +) + +type VimConfig struct { + GoEfm map[string]interface{} `json:"go_efm,omitempty"` + CmakeEfm map[string]interface{} `json:"cmake_efm,omitempty"` + Eslint *bool `json:"eslint,omitempty"` + Databases LazyValue[map[string]string] `json:"databases,omitempty"` + Actions []VimAction `json:"actions,omitempty"` +} + +type VimAction struct { + Id string `json:"id"` + Name string `json:"name"` + Args []string `json:"args"` + Cwd string `json:"cwd"` +} + +type LauncherAction struct { + Id string `json:"id"` + Tasks []LauncherTask `json:"tasks"` +} + +type LauncherTask struct { + Id string `json:"string"` + Args []string `json:"args"` + Cwd string `json:"cwd"` + RunAsService bool `json:"run_as_service"` + WorkspaceID int `json:"workspace_id"` +} + +type Workspace struct { + Name string `json:"name"` + Path string `json:"path"` + VimConfig VimConfig `json:"vim"` +} + +type Context interface { + FromHome(string) string + FromEnvDir(string) string +} + +type BackupConfig struct { + GpgKeyring bool + Secrets map[string]string + Data map[string]string +} + +type InitAction struct { + Args []string `json:"args"` + Cwd string `json:"cwd"` +} + +type EnvironmentConfig struct { + Workspaces []Workspace + Actions []LauncherAction + Backup BackupConfig + Init []InitAction + CustomSetupAction func(Context) action.Object +} + +type LazyValue[T any] (func() T) + +func (l *LazyValue[T]) Resolve() T { + if *l == nil { + instance := new(T) + return *instance + } + return (*l)() +} + +func (l *LazyValue[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(l.Resolve()) +} diff --git a/env/home/env.go b/env/home/env.go new file mode 100644 index 0000000..8c934df --- /dev/null +++ b/env/home/env.go @@ -0,0 +1,212 @@ +package home + +import ( + "bytes" + "os" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/api" + "github.com/wkozyra95/dotfiles/api/platform/arch" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/common" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var homeDir = os.Getenv("HOME") + +var expoLauncherConfig = common.ExpoLauncherConfig(path.Join(homeDir, "playground")) + +var Config = env.EnvironmentConfig{ + Workspaces: []env.Workspace{ + common.DotfilesWorkspace, + {Name: "vim plugins", Path: path.Join(homeDir, ".local/share/nvim/lazy")}, + { + Name: "npm-cache", Path: path.Join(homeDir, "drive/MyProjects/npm-cache"), + VimConfig: env.VimConfig{ + Actions: []env.VimAction{ + { + Id: "cargo_build", + Name: "[workspace] cargo build", + Args: []string{"cargo", "build"}, + Cwd: path.Join(homeDir, "drive/MyProjects/npm-cache"), + }, + { + Id: "cargo_watch", + Name: "[workspace] cargo watch (new terminal)", + Args: []string{"mycli", "launch", "--job", "npm-cache"}, + Cwd: path.Join(homeDir, "drive/MyProjects/npm-cache"), + }, + { + Id: "cargo_watch_run", + Name: "[workspace] cargo run (new terminal)", + Args: []string{"mycli", "launch", "--job", "npm-cache-run"}, + Cwd: path.Join(homeDir, "drive/MyProjects/npm-cache"), + }, + }, + }, + }, + { + Name: "dactyl-model", Path: path.Join(homeDir, "/drive/MyProjects/dactyl/dactyl-keyboard"), + VimConfig: env.VimConfig{ + Actions: []env.VimAction{ + { + Id: "dactyl_build", + Name: "[workspace] build", + Args: []string{"lein", "run", "src/dactyl_keyboard/dactyl.clj"}, + Cwd: path.Join(homeDir, "/drive/MyProjects/dactyl/dactyl-keyboard"), + }, + }, + }, + }, + {Name: "telescope", Path: path.Join(homeDir, ".local/share/nvim/lazy/telescope.nvim")}, + {Name: "treesitter", Path: path.Join(homeDir, ".local/share/nvim/lazy/nvim-treesitter")}, + common.HomeWorkspace, + {Name: "test", Path: path.Join(homeDir, "playground/vimtest"), VimConfig: env.VimConfig{ + Eslint: common.EslintConfig.Eslint, + CmakeEfm: map[string]interface{}{ + "formatCommand": "cmake-format --tab-size 4 ${INPUT}", + "formatStdin": false, + }, + }}, + { + Name: "cache", + Path: path.Join(homeDir, "drive/MyProjects/eas-build-cache"), + VimConfig: env.VimConfig{ + GoEfm: map[string]interface{}{ + "formatCommand": "gofumpt", + "formatStdin": true, + }, + }, + }, + { + Name: "expo-rust", + Path: path.Join(homeDir, "drive/MyProjects/expo-rust"), + VimConfig: env.VimConfig{ + Eslint: common.EslintConfig.Eslint, + }, + }, + { + Name: "expo-myapp", + Path: path.Join(homeDir, "drive/MyProjects/myapp"), + VimConfig: env.VimConfig{ + Eslint: common.EslintConfig.Eslint, + }, + }, + }, + Actions: []env.LauncherAction{ + { + Id: "debug", + Tasks: []env.LauncherTask{ + { + Id: "debug", + Cwd: path.Join(homeDir, "playground"), + Args: []string{"zsh", "-c", "sleep 10 && exit 1"}, + RunAsService: true, + WorkspaceID: env.Workspace3, + }, + { + Id: "debug1", + Cwd: path.Join(homeDir, "playground"), + Args: []string{"zsh", "-c", "lskadjfsld;j"}, + RunAsService: true, + WorkspaceID: env.Workspace4, + }, + { + Id: "debug2", + Cwd: path.Join(homeDir, "playground"), + Args: []string{"htop"}, + RunAsService: true, + WorkspaceID: env.Workspace5, + }, + }, + }, + { + Id: "npm-cache-run", + Tasks: []env.LauncherTask{ + { + Id: "npm-watch-run-cargo", + Args: []string{"cargo", "watch", "-x", "run"}, + Cwd: path.Join(homeDir, "drive/MyProjects/npm-cache"), + RunAsService: true, + }, + }, + }, + { + Id: "npm-cache", + Tasks: []env.LauncherTask{ + { + Id: "npm-watch-cargo", + Args: []string{"cargo", "watch"}, + Cwd: path.Join(homeDir, "drive/MyProjects/npm-cache"), + RunAsService: true, + }, + }, + }, + }, + Init: []env.InitAction{ + {Args: []string{"alacritty", "--class", "workspace2"}}, + {Args: []string{"firefox"}}, + }, + Backup: env.BackupConfig{ + GpgKeyring: true, + Secrets: map[string]string{ + path.Join(homeDir, ".secrets"): "secrets", + path.Join(homeDir, ".ssh"): "ssh", + }, + Data: map[string]string{ + path.Join(homeDir, ".secrets"): "secrets", + path.Join(homeDir, ".ssh"): "ssh", + path.Join(homeDir, ".zsh_history"): "zsh_history", + path.Join(homeDir, "drive"): "drive", + path.Join(homeDir, "notes"): "notes", + }, + }, + CustomSetupAction: func(ctx env.Context) action.Object { + pkgInstaller := arch.Yay{} + return action.List{ + pkgInstaller.EnsurePackagerAction(homeDir), + api.PackageInstallAction([]api.Package{ + pkgInstaller.CustomPackageList([]string{ + "firefox", + "google-chrome", + }), + }), + action.WithCondition{ + If: action.FuncCond(func() (bool, error) { return os.Getenv("NVM_DIR") == "", nil }), + Then: action.List{ + action.ShellCommand( + "bash", + "-c", + "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash", + ), + action.ShellCommand("bash", "-c", "source ~/.nvm/nvm.sh && nvm install node"), + }, + }, + action.WithCondition{ + If: action.FuncCond(func() (bool, error) { + err := exec.Command(). + WithBufout(&bytes.Buffer{}, &bytes.Buffer{}). + Run("sudo", "ls", "/etc/sudoers.d/40_no-sudo-timeout") + return err != nil, nil + }), + Then: action.List{ + action.ShellCommand("sudo", "mkdir", "-p", "/etc/sudoers.d"), + action.ShellCommand( + "sudo", + "bash", + "-c", + "echo \"Defaults passwd_timeout=0\" > /etc/sudoers.d/40_no-sudo-timeout", + ), + }, + }, + action.WithCondition{ + If: action.Not(action.PathExists("/etc/systemd/system/bluetooth.target.wants/bluetooth.service")), + Then: action.List{ + action.ShellCommand("sudo", "systemctl", "enable", "bluetooth.service"), + action.ShellCommand("sudo", "systemctl", "start", "bluetooth.service"), + }, + }, + } + }, +} diff --git a/env/home/gitconfig b/env/home/gitconfig new file mode 100644 index 0000000..e91da2f --- /dev/null +++ b/env/home/gitconfig @@ -0,0 +1,21 @@ +[status] + submoduleSummary = true +[pull] + rebase = true +[merge] + conflictstyle = diff3 +[user] + email = wkozyra95@gmail.com + name = Wojciech Kozyra +[push] + default = current +[init] + defaultBranch = main +[core] + excludesfile = /home/wojtek/.gitignore + pager = diff-so-fancy | less --tabs=4 + editor = nvim +[interactive] + diffFilter = diff-so-fancy --patch +[diff-so-fancy] + markEmptyLines = false diff --git a/env/home/gitignore b/env/home/gitignore new file mode 100644 index 0000000..0aa4ebd --- /dev/null +++ b/env/home/gitignore @@ -0,0 +1,2 @@ +compile_commands.json +.git diff --git a/env/home/zshrc b/env/home/zshrc new file mode 100644 index 0000000..6c17498 --- /dev/null +++ b/env/home/zshrc @@ -0,0 +1 @@ +MYCLI_ZSH_SETUP_PATH=/home/wojtek/.cache/mycli/completion/zsh_setup && test -f $MYCLI_ZSH_SETUP_PATH && source $MYCLI_ZSH_SETUP_PATH; diff --git a/env/homeoffice/bin/vscode-css-language-server b/env/homeoffice/bin/vscode-css-language-server new file mode 100755 index 0000000..c1dc37d --- /dev/null +++ b/env/homeoffice/bin/vscode-css-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-css-language-server $@ diff --git a/env/homeoffice/bin/vscode-eslint-language-server b/env/homeoffice/bin/vscode-eslint-language-server new file mode 100755 index 0000000..ca49240 --- /dev/null +++ b/env/homeoffice/bin/vscode-eslint-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-eslint-language-server $@ diff --git a/env/homeoffice/bin/vscode-html-language-server b/env/homeoffice/bin/vscode-html-language-server new file mode 100755 index 0000000..4a9a373 --- /dev/null +++ b/env/homeoffice/bin/vscode-html-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-html-language-server $@ diff --git a/env/homeoffice/bin/vscode-json-language-server b/env/homeoffice/bin/vscode-json-language-server new file mode 100755 index 0000000..deb932d --- /dev/null +++ b/env/homeoffice/bin/vscode-json-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-json-language-server $@ diff --git a/env/homeoffice/eas_local.sh b/env/homeoffice/eas_local.sh new file mode 100755 index 0000000..34ab196 --- /dev/null +++ b/env/homeoffice/eas_local.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +export EAS_LOCAL_BUILD_PLUGIN_PATH=/home/wojtek/expo/eas-build/bin/eas-cli-local-build-plugin +export EAS_LOCAL_BUILD_WORKINGDIR=/home/wojtek/expo/eas-build-workingdir +export EAS_LOCAL_BUILD_SKIP_CLEANUP=1 +export EAS_LOCAL_BUILD_ARTIFACTS_DIR=/home/wojtek/expo/eas-build-workingdir/results + +rm -rf $EAS_LOCAL_BUILD_WORKINGDIR +/home/wojtek/expo/eas-cli/packages/eas-cli/bin/run "$@" diff --git a/env/homeoffice/env.go b/env/homeoffice/env.go new file mode 100644 index 0000000..262aad1 --- /dev/null +++ b/env/homeoffice/env.go @@ -0,0 +1,49 @@ +package homeoffice + +import ( + "os" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/common" +) + +var ( + homeDir = os.Getenv("HOME") + expoConfig = common.ExpoConfig + expoLauncherConfig = common.ExpoLauncherConfig(path.Join(homeDir, "expo")) +) + +var Config = env.EnvironmentConfig{ + Workspaces: []env.Workspace{ + expoConfig.LegacyExpoCli(path.Join(homeDir, "expo/expo-cli")), + expoConfig.EasCli(path.Join(homeDir, "expo/eas-cli")), + expoConfig.EasBuild(path.Join(homeDir, "expo/eas-build")), + expoConfig.Turtle(path.Join(homeDir, "expo/turtle-v2")), + expoConfig.UniverseWWW(path.Join(homeDir, "expo/universe/server/www")), + expoConfig.UniverseWebsite(path.Join(homeDir, "expo/universe/server/website")), + expoConfig.TurtleClassic(path.Join(homeDir, "expo/turtle")), + expoConfig.ExpoSdk(path.Join(homeDir, "expo/expo")), + expoConfig.ExpoSdkGl(path.Join(homeDir, "/expo/expo/packages/expo-gl")), + + common.HomeWorkspace, + common.DotfilesWorkspace, + }, + Actions: []env.LauncherAction{ + expoLauncherConfig.EasCli, + expoLauncherConfig.ExpoCliRebuild, + expoLauncherConfig.ExpoDocs, + expoLauncherConfig.Submit, + expoLauncherConfig.Turtle, + expoLauncherConfig.Submit, + expoLauncherConfig.UniverseWWW, + expoLauncherConfig.UniverseWebsite, + expoLauncherConfig.UniverseWebsiteInternal, + }, + CustomSetupAction: func(ctx env.Context) action.Object { + return action.List{ + action.EnsureSymlink(ctx.FromEnvDir("gitconfig-goody"), ctx.FromHome(".gitconfig-goody")), + } + }, +} diff --git a/env/homeoffice/gitconfig b/env/homeoffice/gitconfig new file mode 100644 index 0000000..f4dd12c --- /dev/null +++ b/env/homeoffice/gitconfig @@ -0,0 +1,23 @@ +[status] + submoduleSummary = true +[pull] + rebase = true +[merge] + conflictstyle = diff3 +[user] + email = wojciech.kozyra@swmansion.com + name = Wojciech Kozyra +[push] + default = current +[init] + defaultBranch = main +[interactive] + diffFilter = diff-so-fancy --patch +[diff-so-fancy] + markEmptyLines = false +[core] + excludesfile = /home/wojtek/.gitignore + pager = diff-so-fancy | less --tabs=4 + editor = nvim +[includeIf "gitdir:~/goody/"] + path = ./.gitconfig-goody diff --git a/env/homeoffice/gitconfig-goody b/env/homeoffice/gitconfig-goody new file mode 100644 index 0000000..a7a506c --- /dev/null +++ b/env/homeoffice/gitconfig-goody @@ -0,0 +1,4 @@ +[user] + email = wojciech.kozyra@ongoody.com +[core] + sshCommand = "ssh -i ~/.ssh/goody" diff --git a/env/homeoffice/gitignore b/env/homeoffice/gitignore new file mode 100644 index 0000000..0aa4ebd --- /dev/null +++ b/env/homeoffice/gitignore @@ -0,0 +1,2 @@ +compile_commands.json +.git diff --git a/env/homeoffice/zshrc b/env/homeoffice/zshrc new file mode 100644 index 0000000..ad35057 --- /dev/null +++ b/env/homeoffice/zshrc @@ -0,0 +1,23 @@ +export LOCAL_BIN="$HOME/.dotfiles/env/homeoffice/bin" +export PATH="$HOME/.nix-profile/bin:$LOCAL_BIN:$PATH" + +# The next line updates PATH for the Google Cloud SDK. +[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc" + +# The next line enables shell command completion for gcloud. +[ -f "$HOME/google-cloud-sdk/completion.zsh.inc" ] && source "$HOME/google-cloud-sdk/completion.zsh.inc" + +alias eas_dev=$HOME/expo/eas-cli/packages/eas-cli/bin/run +alias eas_local=$HOME/.dotfiles/env/homeoffice/eas_local.sh + +[ -e /home/wojtek/.nix-profile/etc/profile.d/nix.sh ] && source $HOME/.nix-profile/etc/profile.d/nix.sh + +export DISABLE_NVM=1 +export VOLTA_HOME="$HOME/.volta" +grep --silent "$VOLTA_HOME/bin" <<< $PATH || export PATH="$VOLTA_HOME/bin:$PATH" + +MYCLI_ZSH_SETUP_PATH=/home/wojtek/.cache/mycli/completion/zsh_setup && test -f $MYCLI_ZSH_SETUP_PATH && source $MYCLI_ZSH_SETUP_PATH; +EAS_AC_ZSH_SETUP_PATH=/home/wojtek/.cache/eas-cli/autocomplete/zsh_setup && test -f $EAS_AC_ZSH_SETUP_PATH && source $EAS_AC_ZSH_SETUP_PATH; +compdef $HOME/expo/eas-cli/bin/run='eas' +compdef $HOME/.dotfiles/env/homeoffice/eas_local.sh='eas' + diff --git a/env/macbook/bin/vscode-css-language-server b/env/macbook/bin/vscode-css-language-server new file mode 100755 index 0000000..c1dc37d --- /dev/null +++ b/env/macbook/bin/vscode-css-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-css-language-server $@ diff --git a/env/macbook/bin/vscode-eslint-language-server b/env/macbook/bin/vscode-eslint-language-server new file mode 100755 index 0000000..ca49240 --- /dev/null +++ b/env/macbook/bin/vscode-eslint-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-eslint-language-server $@ diff --git a/env/macbook/bin/vscode-html-language-server b/env/macbook/bin/vscode-html-language-server new file mode 100755 index 0000000..4a9a373 --- /dev/null +++ b/env/macbook/bin/vscode-html-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-html-language-server $@ diff --git a/env/macbook/bin/vscode-json-language-server b/env/macbook/bin/vscode-json-language-server new file mode 100755 index 0000000..deb932d --- /dev/null +++ b/env/macbook/bin/vscode-json-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-json-language-server $@ diff --git a/env/macbook/eas_local.sh b/env/macbook/eas_local.sh new file mode 100755 index 0000000..4e765fc --- /dev/null +++ b/env/macbook/eas_local.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +set -x + +export EAS_LOCAL_BUILD_PLUGIN_PATH=/Users/wojciechkozyra/expo/eas-build/bin/eas-cli-local-build-plugin +export EAS_LOCAL_BUILD_WORKINGDIR=/Users/wojciechkozyra/expo/eas-build-workingdir +export EAS_LOCAL_BUILD_SKIP_CLEANUP=1 +export EAS_LOCAL_BUILD_ARTIFACTS_DIR=/Users/wojciechkozyra/expo/eas-build-workingdir/results + +rm -rf $EAS_LOCAL_BUILD_WORKINGDIR +/Users/wojciechkozyra/expo/eas-cli/bin/run "$@" diff --git a/env/macbook/env.go b/env/macbook/env.go new file mode 100644 index 0000000..01b4274 --- /dev/null +++ b/env/macbook/env.go @@ -0,0 +1,36 @@ +package macbook + +import ( + "os" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/common" +) + +var ( + homeDir = os.Getenv("HOME") + expoConfig = common.ExpoConfig +) + +var Config = env.EnvironmentConfig{ + Workspaces: []env.Workspace{ + expoConfig.LegacyExpoCli(path.Join(homeDir, "expo/expo-cli")), + expoConfig.EasCli(path.Join(homeDir, "expo/eas-cli")), + expoConfig.EasBuild(path.Join(homeDir, "expo/eas-build")), + expoConfig.Turtle(path.Join(homeDir, "expo/turtle-v2")), + expoConfig.UniverseWWW(path.Join(homeDir, "expo/universe/server/www")), + expoConfig.UniverseWebsite(path.Join(homeDir, "expo/universe/server/website")), + expoConfig.TurtleClassic(path.Join(homeDir, "expo/turtle")), + expoConfig.ExpoSdk(path.Join(homeDir, "expo/expo")), + expoConfig.ExpoSdkGl(path.Join(homeDir, "/expo/expo/packages/expo-gl")), + common.HomeWorkspace, + common.DotfilesWorkspace, + }, + CustomSetupAction: func(ctx env.Context) action.Object { + return action.List{ + action.EnsureSymlink(ctx.FromEnvDir("gitconfig-goody"), ctx.FromHome(".gitconfig-goody")), + } + }, +} diff --git a/env/macbook/gitconfig b/env/macbook/gitconfig new file mode 100644 index 0000000..f8e95f0 --- /dev/null +++ b/env/macbook/gitconfig @@ -0,0 +1,23 @@ +[status] + submoduleSummary = true +[pull] + rebase = true +[merge] + conflictstyle = diff3 +[user] + email = wojciech.kozyra@swmansion.com + name = Wojciech Kozyra +[push] + default = current +[init] + defaultBranch = main +[core] + excludesfile = /home/wojtek/.gitignore + editor = nvim +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true +[includeIf "gitdir:~/goody/"] + path = ./.gitconfig-goody diff --git a/env/macbook/gitconfig-goody b/env/macbook/gitconfig-goody new file mode 100644 index 0000000..a7a506c --- /dev/null +++ b/env/macbook/gitconfig-goody @@ -0,0 +1,4 @@ +[user] + email = wojciech.kozyra@ongoody.com +[core] + sshCommand = "ssh -i ~/.ssh/goody" diff --git a/env/macbook/gitignore b/env/macbook/gitignore new file mode 100644 index 0000000..9bf3746 --- /dev/null +++ b/env/macbook/gitignore @@ -0,0 +1 @@ +compile_commands.json diff --git a/env/macbook/zshrc b/env/macbook/zshrc new file mode 100644 index 0000000..5619591 --- /dev/null +++ b/env/macbook/zshrc @@ -0,0 +1,13 @@ +export LOCAL_BIN="$HOME/.dotfiles/env/work/bin" +export PATH="$LOCAL_BIN:$PATH" +export PATH="/Library/Frameworks/Python.framework/Versions/Current/bin:$PATH" + +alias eas_dev=$HOME/expo/eas-cli/bin/run +alias eas_local=$HOME/.dotfiles/env/$CURRENT_ENV/eas_local.sh + +export DISABLE_NVM=1 +export VOLTA_HOME="$HOME/.volta" +grep --silent "$VOLTA_HOME/bin" <<< $PATH || export PATH="$VOLTA_HOME/bin:$PATH" + +MYCLI_ZSH_SETUP_PATH=/Users/wojciechkozyra/.cache/mycli/completion/zsh_setup && test -f $MYCLI_ZSH_SETUP_PATH && source $MYCLI_ZSH_SETUP_PATH; +EAS_AC_ZSH_SETUP_PATH=/Users/wojciechkozyra/Library/Caches/eas-cli/autocomplete/zsh_setup && test -f $EAS_AC_ZSH_SETUP_PATH && source $EAS_AC_ZSH_SETUP_PATH; diff --git a/env/work/bin/vscode-css-language-server b/env/work/bin/vscode-css-language-server new file mode 100755 index 0000000..c1dc37d --- /dev/null +++ b/env/work/bin/vscode-css-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-css-language-server $@ diff --git a/env/work/bin/vscode-eslint-language-server b/env/work/bin/vscode-eslint-language-server new file mode 100755 index 0000000..ca49240 --- /dev/null +++ b/env/work/bin/vscode-eslint-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-eslint-language-server $@ diff --git a/env/work/bin/vscode-html-language-server b/env/work/bin/vscode-html-language-server new file mode 100755 index 0000000..4a9a373 --- /dev/null +++ b/env/work/bin/vscode-html-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-html-language-server $@ diff --git a/env/work/bin/vscode-json-language-server b/env/work/bin/vscode-json-language-server new file mode 100755 index 0000000..deb932d --- /dev/null +++ b/env/work/bin/vscode-json-language-server @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +$HOME/.volta/tools/image/packages/vscode-langservers-extracted/bin/vscode-json-language-server $@ diff --git a/env/work/eas_local.sh b/env/work/eas_local.sh new file mode 100755 index 0000000..3aa569d --- /dev/null +++ b/env/work/eas_local.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +set -x + +export EAS_LOCAL_BUILD_PLUGIN_PATH=/home/wojtek/expo/eas-build/bin/eas-cli-local-build-plugin +export EAS_LOCAL_BUILD_WORKINGDIR=/home/wojtek/expo/eas-build-workingdir +export EAS_LOCAL_BUILD_SKIP_CLEANUP=1 +export EAS_LOCAL_BUILD_ARTIFACTS_DIR=/home/wojtek/expo/eas-build-workingdir/results + +rm -rf $EAS_LOCAL_BUILD_WORKINGDIR +/home/wojtek/expo/eas-cli/packages/eas-cli/bin/run "$@" diff --git a/env/work/env.go b/env/work/env.go new file mode 100644 index 0000000..79750b9 --- /dev/null +++ b/env/work/env.go @@ -0,0 +1,67 @@ +package work + +import ( + "os" + "path" + + "github.com/wkozyra95/dotfiles/action" + "github.com/wkozyra95/dotfiles/env" + "github.com/wkozyra95/dotfiles/env/common" +) + +var ( + homeDir = os.Getenv("HOME") + expoConfig = common.ExpoConfig + expoLauncherConfig = common.ExpoLauncherConfig(path.Join(homeDir, "expo")) +) + +var Config = env.EnvironmentConfig{ + Workspaces: []env.Workspace{ + expoConfig.LegacyExpoCli(path.Join(homeDir, "expo/expo-cli")), + expoConfig.EasCli(path.Join(homeDir, "expo/eas-cli")), + expoConfig.EasBuild(path.Join(homeDir, "expo/eas-build")), + expoConfig.Turtle(path.Join(homeDir, "expo/turtle-v2")), + expoConfig.UniverseWWW(path.Join(homeDir, "expo/universe/server/www")), + expoConfig.UniverseWebsite(path.Join(homeDir, "expo/universe/server/website")), + expoConfig.TurtleClassic(path.Join(homeDir, "expo/turtle")), + expoConfig.ExpoSdk(path.Join(homeDir, "expo/expo")), + expoConfig.ExpoSdkGl(path.Join(homeDir, "expo/expo/packages/expo-gl")), + expoConfig.EASBuildCache(path.Join(homeDir, "expo/eas-build-cache")), + common.DotfilesWorkspace, + common.HomeWorkspace, + }, + Actions: []env.LauncherAction{ + expoLauncherConfig.EasCli, + expoLauncherConfig.ExpoCliRebuild, + expoLauncherConfig.ExpoDocs, + expoLauncherConfig.Submit, + expoLauncherConfig.Turtle, + expoLauncherConfig.Submit, + expoLauncherConfig.UniverseWWW, + expoLauncherConfig.UniverseWWWUnit, + expoLauncherConfig.UniverseWebsite, + expoLauncherConfig.UniverseWebsiteInternal, + }, + Init: []env.InitAction{ + {Args: []string{"google-chrome-stable", "--proxy-pac-url=http://localhost:2000/proxy.pac"}}, + {Args: []string{"slack"}}, + {Args: []string{"alacritty", "--class", "workspace2"}}, + {Args: []string{"alacritty", "--class", "workspace6"}}, + }, + Backup: env.BackupConfig{ + GpgKeyring: true, + Secrets: map[string]string{ + path.Join(homeDir, ".secrets"): "secrets", + path.Join(homeDir, ".ssh"): "ssh", + }, + Data: map[string]string{ + path.Join(homeDir, ".secrets"): "secrets", + path.Join(homeDir, ".ssh"): "ssh", + }, + }, + CustomSetupAction: func(ctx env.Context) action.Object { + return action.List{ + action.EnsureSymlink(ctx.FromEnvDir("gitconfig-goody"), ctx.FromHome(".gitconfig-goody")), + } + }, +} diff --git a/env/work/gitconfig b/env/work/gitconfig new file mode 100644 index 0000000..45f1153 --- /dev/null +++ b/env/work/gitconfig @@ -0,0 +1,29 @@ +[status] + submoduleSummary = true +[pull] + rebase = true +[merge] + conflictstyle = diff3 +[user] + email = wojciech.kozyra@swmansion.com + name = Wojciech Kozyra + signingkey = CA11EA6343820983 +[push] + default = current +[init] + defaultBranch = main +[core] + excludesfile = /home/wojtek/.gitignore + pager = diff-so-fancy | less --tabs=4 + editor = nvim +[interactive] + diffFilter = diff-so-fancy --patch +[diff-so-fancy] + markEmptyLines = false +[filter "lfs"] + clean = git-lfs clean -- %f + smudge = git-lfs smudge -- %f + process = git-lfs filter-process + required = true +[includeIf "gitdir:~/goody/"] + path = ./.gitconfig-goody diff --git a/env/work/gitconfig-goody b/env/work/gitconfig-goody new file mode 100644 index 0000000..a7a506c --- /dev/null +++ b/env/work/gitconfig-goody @@ -0,0 +1,4 @@ +[user] + email = wojciech.kozyra@ongoody.com +[core] + sshCommand = "ssh -i ~/.ssh/goody" diff --git a/env/work/gitignore b/env/work/gitignore new file mode 100644 index 0000000..8cec38c --- /dev/null +++ b/env/work/gitignore @@ -0,0 +1,3 @@ +compile_commands.json +**/.cache/clangd/** +.git diff --git a/env/work/zshrc b/env/work/zshrc new file mode 100644 index 0000000..554618c --- /dev/null +++ b/env/work/zshrc @@ -0,0 +1,31 @@ +export LOCAL_BIN="$HOME/.dotfiles/env/work/bin" +export PATH="$HOME/.nix-profile/bin:$LOCAL_BIN:$PATH" + +# The next line updates PATH for the Google Cloud SDK. +[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc" + +# The next line enables shell command completion for gcloud. +[ -f "$HOME/google-cloud-sdk/completion.zsh.inc" ] && source "$HOME/google-cloud-sdk/completion.zsh.inc" + +alias eas_dev=$HOME/expo/eas-cli/packages/eas-cli/bin/run +alias eas_local=$HOME/.dotfiles/env/work/eas_local.sh +alias expo_global_dev=$HOME/expo/expo-cli/packages/expo-cli/bin/expo.js +alias expo_dev=$HOME/expo/expo/packages/@expo/cli/build/bin/cli + +export TURTLE_V2_EXAMPLE_PRODUCTION_PROJECT_ID=cb6571a4-8457-45b9-9c4a-d489f63e4f1d +export TURTLE_V2_EXAMPLE_STAGING_PROJECT_ID=8098b803-f4b1-4f82-9960-390a4ab98fde +export TURTLE_V2_EXAMPLE_LOCAL_PROJECT_ID="" +export WWW_DISABLE_LOCAL_VOLTA=1 +export EAS_BUILD_INTERNAL_EXECUTABLE="/home/wojtek/expo/eas-cli/packages/eas-cli/bin/run" + + +[ -e /home/wojtek/.nix-profile/etc/profile.d/nix.sh ] && source $HOME/.nix-profile/etc/profile.d/nix.sh + +export DISABLE_NVM=1 +export VOLTA_HOME="$HOME/.volta" +grep --silent "$VOLTA_HOME/bin" <<< $PATH || export PATH="$VOLTA_HOME/bin:$PATH" + +MYCLI_ZSH_SETUP_PATH=/home/wojtek/.cache/mycli/completion/zsh_setup && test -f $MYCLI_ZSH_SETUP_PATH && source $MYCLI_ZSH_SETUP_PATH; +EAS_AC_ZSH_SETUP_PATH=/home/wojtek/.cache/eas-cli/autocomplete/zsh_setup && test -f $EAS_AC_ZSH_SETUP_PATH && source $EAS_AC_ZSH_SETUP_PATH; +compdef $HOME/expo/eas-cli/packages/eas-cli/bin/run='eas' +compdef $HOME/.dotfiles/env/work/eas_local.sh='eas' diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..20c4a5a --- /dev/null +++ b/go.mod @@ -0,0 +1,56 @@ +module github.com/wkozyra95/dotfiles + +go 1.18 + +require ( + github.com/blang/semver v3.5.1+incompatible + github.com/davecgh/go-spew v1.1.1 + github.com/docker/docker v23.0.0-rc.1+incompatible + github.com/fatih/color v1.13.0 + github.com/google/go-github/v50 v50.2.0 + github.com/juju/mutex v0.0.0-20180619145857-d21b13acf4bf + github.com/manifoldco/promptui v0.9.0 + github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 + github.com/sirupsen/logrus v1.9.0 + github.com/spf13/cobra v1.6.0 + github.com/stretchr/testify v1.8.0 + golang.org/x/oauth2 v0.6.0 +) + +require ( + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/cloudflare/circl v1.1.0 // indirect + github.com/containerd/containerd v1.6.18 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/juju/errors v1.0.0 // indirect + github.com/juju/testing v1.0.2 // indirect + github.com/klauspost/compress v1.15.12 // indirect + github.com/mattn/go-colorable v0.1.9 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/moby/patternmatcher v0.5.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/runc v1.1.4 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/time v0.1.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a1b95f4 --- /dev/null +++ b/go.sum @@ -0,0 +1,232 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/hcsshim v0.9.6 h1:VwnDOgLeoi2du6dAznfmspNqTiwczvjv4K7NxuY9jsY= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= +github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= +github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/containerd v1.6.18 h1:qZbsLvmyu+Vlty0/Ex5xc0z2YtKpIsb5n45mAMI+2Ns= +github.com/containerd/containerd v1.6.18/go.mod h1:1RdCUu95+gc2v9t3IL+zIlpClSmew7/0YS8O5eQZrOw= +github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v23.0.0-rc.1+incompatible h1:Dmn88McWuHc7BSNN1s6RtfhMmt6ZPQAYUEf7FhqpiQI= +github.com/docker/docker v23.0.0-rc.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v50 v50.2.0 h1:j2FyongEHlO9nxXLc+LP3wuBSVU9mVxfpdYUexMpIfk= +github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H4m6xFZlT43q8Q= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/juju/clock v0.0.0-20220203021603-d9deb868a28a h1:Az/6CM/P5guGHNy7r6TkOCctv3lDmN3W1uhku7QMupk= +github.com/juju/errors v1.0.0 h1:yiq7kjCLll1BiaRuNY53MGI0+EQ3rF6GB+wvboZDefM= +github.com/juju/errors v1.0.0/go.mod h1:B5x9thDqx0wIMH3+aLIMP9HjItInYWObRovoCFM5Qe8= +github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4 h1:NO5tuyw++EGLnz56Q8KMyDZRwJwWO8jQnj285J3FOmY= +github.com/juju/mutex v0.0.0-20180619145857-d21b13acf4bf h1:2d3cilQly1OpAfZcn4QRuwDOdVoHsM4cDTkcKbmO760= +github.com/juju/mutex v0.0.0-20180619145857-d21b13acf4bf/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY= +github.com/juju/testing v1.0.2 h1:OR90RqCd9CJONxXamZAjLknpZdtqDyxqW8IwCbgw3i4= +github.com/juju/testing v1.0.2/go.mod h1:h3Vd2rzB57KrdsBEy6R7bmSKPzP76BnNavt7i8PerwQ= +github.com/juju/utils/v3 v3.0.0 h1:Gg3n63mGPbBuoXCo+EPJuMi44hGZfloI8nlCIebHu2Q= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.12 h1:YClS/PImqYbn+UILDnqxQCZ3RehC9N318SU3kElDUEM= +github.com/klauspost/compress v1.15.12/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= +github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1 h1:9iFHD5Kt9hkOfeawBNiEeEaV7bmC4/Z5wJp8E9BptMs= +github.com/opencontainers/image-spec v1.0.3-0.20220303224323-02efb9a75ee1/go.mod h1:K/JAU0m27RFhDRX4PcFdIKntROP6y5Ed6O91aZYDQfs= +github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= +github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= +github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= +golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= +gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= diff --git a/logger/files.go b/logger/files.go new file mode 100644 index 0000000..e48ca70 --- /dev/null +++ b/logger/files.go @@ -0,0 +1,71 @@ +package logger + +import ( + "fmt" + "io" + "os" + "path" + "strings" +) + +var log = NamedLogger("file") + +func LogDirectoryListing(destination string, maxEntries int) { + file, fileErr := os.Open(destination) + if fileErr != nil { + log.Warnf("Directory %s doesn't exist", destination) + return + } + + fileInfo, fileInfoErr := file.Stat() + if fileInfoErr != nil { + log.Warnf("Directory %s doesn't exist", destination) + return + } + + if !fileInfo.IsDir() { + log.Warnf("%s is not a directory", destination) + return + } + + files, readdirErr := file.Readdir(maxEntries) + if readdirErr != nil && readdirErr != io.EOF { + log.Warnf("Unable to read content of %s directory [%v]", destination, readdirErr) + return + } + var fileListing strings.Builder + fileListing.WriteString(fmt.Sprintf("Content of %s directory: \n", destination)) + for _, file := range files { + fileListing.WriteString( + fmt.Sprintf("\t%s %6d KB %s\n", + file.Mode(), file.Size()/1024, file.Name(), + ), + ) + if file.IsDir() { + file, fileErr := os.Open(path.Join(destination, file.Name())) + if fileErr != nil { + fileListing.WriteString("\t\t Unable to access content\n") + continue + } + files, readdirErr := file.Readdir(10) + if readdirErr != nil { + fileListing.WriteString("\t\t Unable to access content\n") + continue + } + for _, file := range files { + fileListing.WriteString( + fmt.Sprintf("\t\t%s %6d KB %s\n", + file.Mode(), file.Size()/1024, file.Name(), + ), + ) + } + if len(files) == maxEntries { + fileListing.WriteString("\t\t...\n") + } + } + } + if len(files) == maxEntries { + fileListing.WriteString("\t...\n") + } + log.Info(fileListing.String()) +} diff --git a/logger/log.go b/logger/log.go new file mode 100644 index 0000000..ed38e3c --- /dev/null +++ b/logger/log.go @@ -0,0 +1,53 @@ +package logger + +import ( + "fmt" + "os" + "path" + "runtime" + + "github.com/fatih/color" + "github.com/sirupsen/logrus" +) + +// NamedLogger creates named package logger. +func NamedLogger(name string) logrus.Logger { + logLevel := logrus.InfoLevel + if os.Getenv("DEBUG") != "" { + logLevel = logrus.DebugLevel + } else if os.Getenv("TRACE") != "" { + logLevel = logrus.TraceLevel + } + return logrus.Logger{ + Out: os.Stderr, + Formatter: &CustomTextFormatter{ + logrus.TextFormatter{ + ForceColors: true, + }, + name, + }, + Hooks: make(logrus.LevelHooks), + Level: logLevel, + } +} + +// CustomTextFormatter ... +type CustomTextFormatter struct { + logrus.TextFormatter + Name string +} + +// Format renders a single log entry +func (f *CustomTextFormatter) Format(entry *logrus.Entry) ([]byte, error) { + if entry.Level <= logrus.ErrorLevel { + return []byte(color.RedString(entry.Message + "\n")), nil + } else if entry.Level <= logrus.WarnLevel { + return []byte(color.YellowString(entry.Message + "\n")), nil + } else if entry.Level <= logrus.InfoLevel { + return []byte(entry.Message + "\n"), nil + } else { + _, file, no, _ := runtime.Caller(5) + entry.Message = fmt.Sprintf("[%-8s][%-15s:%03d]%s", f.Name, path.Base(file), no, entry.Message) + return f.TextFormatter.Format(entry) + } +} diff --git a/modd.conf b/modd.conf new file mode 100644 index 0000000..b62bfaa --- /dev/null +++ b/modd.conf @@ -0,0 +1,4 @@ +**/*.go !vendor/** { + prep: make build + prep: make unit-tests +} diff --git a/mycli/main.go b/mycli/main.go new file mode 100644 index 0000000..d891192 --- /dev/null +++ b/mycli/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/spf13/cobra" + "github.com/wkozyra95/dotfiles/command" + "github.com/wkozyra95/dotfiles/command/api" + "github.com/wkozyra95/dotfiles/command/launcher" + "github.com/wkozyra95/dotfiles/command/tool" + "github.com/wkozyra95/dotfiles/logger" +) + +var rootCmd = &cobra.Command{ + Use: "mycli", + Short: "Set of system tools", +} + +var log = logger.NamedLogger("main") + +func main() { + log.Debug("main()") + + command.RegisterBackupCmds(rootCmd) + launcher.RegisterCmds(rootCmd) + tool.RegisterCmds(rootCmd) + api.RegisterCmds(rootCmd) + command.RegisterBtrfsCmds(rootCmd) + command.RegisterGitCmds(rootCmd) + command.RegisterCompletionCmds(rootCmd) + + rootCmd.Execute() +} diff --git a/secret/.gitattributes b/secret/.gitattributes new file mode 100644 index 0000000..e155600 --- /dev/null +++ b/secret/.gitattributes @@ -0,0 +1 @@ +secrets.json filter=git-crypt diff=git-crypt diff --git a/secret/secret.go b/secret/secret.go new file mode 100644 index 0000000..730b3f4 --- /dev/null +++ b/secret/secret.go @@ -0,0 +1,80 @@ +package secret + +import ( + "encoding/json" + "io/ioutil" + "path" + "strings" + + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/prompt" +) + +const GIT_CRYPT_MAGIC_STRING = "\x00GITCRYPT" + +var log = logger.NamedLogger("exec") + +type WifiConfig struct { + Ssid string `json:"ssid"` + Password string `json:"password"` +} + +type Secrets struct { + Wifi struct { + HomeOrange WifiConfig `json:"home_orange"` + HomeUpc WifiConfig `json:"home_upc"` + } `json:"wifi"` +} + +type fileEncryptedError string + +func (f fileEncryptedError) Error() string { + return string(f) +} + +func ReadSecret(homedir string) (Secrets, error) { + secretsFile := path.Join(homedir, "./.dotfiles/secret/secrets.json") + file, readErr := ioutil.ReadFile(secretsFile) + if readErr != nil { + return Secrets{}, readErr + } + isEncrypted := strings.HasPrefix(string(file), GIT_CRYPT_MAGIC_STRING) + if isEncrypted { + return Secrets{}, fileEncryptedError("File is encrypted") + } + secrets := Secrets{} + if err := json.Unmarshal(file, &secrets); err != nil { + return Secrets{}, err + } + return secrets, nil +} + +func BestEffortReadSecrets(homedir string) *Secrets { + secrets, readSecretsErr := ReadSecret(homedir) + if readSecretsErr == nil { + return &secrets + } + if _, isEncrypted := readSecretsErr.(fileEncryptedError); !isEncrypted { + log.Errorf("Failed to read secrets %v", readSecretsErr) + return nil + } + if !prompt.ConfirmPrompt("You secrets are encrypted, do you want to try unlocking the repo") { + log.Error("Skipping secrets read") + return nil + } + if !exec.CommandExists("git-crypt") { + log.Error("Skipping secrets read, git-crypt is not installed") + return nil + } + if err := exec.Command().WithStdio().Run("git-crypt", "unlock", path.Join(homedir, ".secrets/dotfiles.key")); err != nil { + log.Errorf("Skipping secrets read, unlock failed with %s", err.Error()) + return nil + } + secrets, readSecretsErr = ReadSecret(homedir) + if readSecretsErr != nil { + log.Errorf("Skipping secrets read, failed to read secrets %s", readSecretsErr.Error()) + return nil + } + return &secrets +} diff --git a/secret/secrets.json b/secret/secrets.json new file mode 100644 index 0000000..3fc0024 Binary files /dev/null and b/secret/secrets.json differ diff --git a/system/installer/installer.go b/system/installer/installer.go new file mode 100644 index 0000000..55c8a52 --- /dev/null +++ b/system/installer/installer.go @@ -0,0 +1,39 @@ +package installer + +import ( + "github.com/manifoldco/promptui" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/system/tool" +) + +var log = logger.NamedLogger("installer") + +func PrepareDrive() (tool.Drive, error) { + drives, detectErr := tool.DetectDrives() + if detectErr != nil { + log.Error(detectErr) + } + + names := []string{} + for _, dst := range drives { + names = append(names, dst.String()) + } + + index, _, selectErr := (&promptui.Select{ + Label: "Which device do you want to provision", + Items: names, + }).Run() + if selectErr != nil { + log.Errorf("Prompt select failed [%v]", selectErr) + return tool.Drive{}, selectErr + } + + for _, partition := range drives[index].Partitions() { + if partition.IsMounted() { + if err := partition.Umount(); err != nil { + return tool.Drive{}, err + } + } + } + return drives[index], nil +} diff --git a/system/platform/platform.go b/system/platform/platform.go new file mode 100644 index 0000000..5d34dde --- /dev/null +++ b/system/platform/platform.go @@ -0,0 +1,41 @@ +package platform + +import ( + "bytes" + "errors" + "regexp" + + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type platform string + +var ( + Debian = platform("debian") + Arch = platform("arch") + MacOS = platform("macos") +) + +var ( + archRegexp = regexp.MustCompile("(?i).*arch.*") + debianRegexp = regexp.MustCompile("(?i).*debian.*") + macosRegexp = regexp.MustCompile("(?i).*darwin.*") +) + +func Detect() (platform, error) { + var cmdOut bytes.Buffer + if err := exec.Command().WithBufout(&cmdOut, &bytes.Buffer{}).Run("uname", "-a"); err != nil { + return platform(""), err + } + + if archRegexp.Match(cmdOut.Bytes()) { + return Arch, nil + } + if debianRegexp.Match(cmdOut.Bytes()) { + return Debian, nil + } + if macosRegexp.Match(cmdOut.Bytes()) { + return MacOS, nil + } + return platform(""), errors.New("Unsupported platform") +} diff --git a/system/tool/dd.go b/system/tool/dd.go new file mode 100644 index 0000000..274b0bb --- /dev/null +++ b/system/tool/dd.go @@ -0,0 +1,37 @@ +package tool + +import ( + "errors" + "fmt" + + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type DD struct { + Input string + Output string + ChunkSizeKB int + Status string +} + +func (d DD) Run() error { + args := []string{} + if d.Input == "" { + return errors.New("input can't be empty") + } + args = append(args, fmt.Sprintf("if=%s", d.Input)) + if d.Output == "" { + return errors.New("input can't be empty") + } + args = append(args, fmt.Sprintf("of=%s", d.Output)) + + if d.ChunkSizeKB != 0 { + args = append(args, fmt.Sprintf("bs=%dK", d.ChunkSizeKB)) + } + if d.Status != "" { + args = append(args, fmt.Sprintf("status=%s", d.Status)) + args = append(args, "conv=fsync", "oflag=direct") + } + + return exec.Command().WithStdio().WithSudo().Run("dd", args...) +} diff --git a/system/tool/drive.go b/system/tool/drive.go new file mode 100644 index 0000000..db6aa83 --- /dev/null +++ b/system/tool/drive.go @@ -0,0 +1,262 @@ +package tool + +import ( + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" + "time" + + "github.com/manifoldco/promptui" + + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" +) + +type Drive struct { + name string + device string + partitions []Partition +} + +func (d Drive) String() string { + return fmt.Sprintf( + "dev: %s (%s)", + d.name, d.device, + ) +} + +func (d Drive) Device() string { + return d.device +} + +func (d Drive) Partitions() []Partition { + return d.partitions +} + +type Partition struct { + name string + device string + mountPoint string + wasMounted bool +} + +func (d Partition) MountPoint() string { + return d.mountPoint +} + +func (p Partition) Device() string { + return p.device +} + +func (d Partition) GetPath() string { + if d.mountPoint == "" { + panic("unknown mount point, drive was not mounted") + } + return d.mountPoint +} + +func (d Partition) String() string { + return fmt.Sprintf( + "dev: %s (%s), mount: %s", + d.name, d.device, d.mountPoint, + ) +} + +func (d Partition) IsMounted() bool { + return d.mountPoint != "" +} + +func (d *Partition) Mount() error { + log.Infof("Mounting device %s", d.device) + mount := fmt.Sprintf("/media/%s", d.name) + if !file.Exists(mount) { + if err := exec.Command().WithSudo().WithStdio().Run("mkdir", "-p", mount); err != nil { + log.Errorf("Unable to create mount point [%v]", err) + return err + } + } + okPmount, pmountErr := d.mountWithPmount(mount) + if pmountErr != nil { + log.Warnf("Command \"pmount\" failed with error %v", pmountErr) + } else if !okPmount { + log.Info("Command \"pmount\" not available, falling back to other alternatives") + } else { + return nil + } + log.Info("tests") + return d.mountWithMount(mount) +} + +func (d *Partition) mountWithMount(mount string) error { + cmdErr := exec.Command().WithSudo().WithStdio().Run("mount", d.device, mount) + if cmdErr != nil { + log.Infof("Mount failed with error [%v]", cmdErr) + return cmdErr + } + d.mountPoint = mount + return nil +} + +func (d *Partition) mountWithPmount(mount string) (bool, error) { + if !exec.CommandExists("pmount") { + return false, nil + } + cmdErr := exec.Command().WithStdio().Run("pmount", d.device, mount) + if cmdErr != nil { + log.Infof("Mount failed with error [%v]", cmdErr) + return false, cmdErr + } + d.mountPoint = mount + return true, nil +} + +func (d *Partition) Umount() error { + log.Infof("Unmounting device %s", d.device) + time.Sleep(5 * time.Second) + okPumount, pumountErr := d.umountWithPumount() + if pumountErr != nil { + log.Warnf("Command \"pumount\" failed with error %v", pumountErr) + } else if !okPumount { + log.Info("Command \"pumount\" not available, falling bac to other alternatives") + } else { + return nil + } + if err := d.umountWithUmount(); err != nil { + _, confirmErr := (&promptui.Prompt{Label: "Umount failed, do you want to continue?", IsConfirm: true}).Run() + if confirmErr != nil { + return err + } + return nil + } + return nil +} + +func (d *Partition) umountWithUmount() error { + cmdErr := exec.Command().WithSudo().WithStdio().Run("umount", d.device) + if cmdErr != nil { + log.Warnf("Umount failed with error [%v]", cmdErr) + return cmdErr + } + d.mountPoint = "" + return nil +} + +func (d *Partition) umountWithPumount() (bool, error) { + if !exec.CommandExists("pumount") { + return false, nil + } + cmdErr := exec.Command().WithStdio().Run("pumount", d.device) + if cmdErr != nil { + log.Warnf("Umount failed with error [%v]", cmdErr) + log.Infof("Retrying in 5 seconds ...") + return false, cmdErr + } + d.mountPoint = "" + return true, nil +} + +func FilterPartitions(fn func(Partition) bool, partitions []Partition) []Partition { + result := make([]Partition, 0, len(partitions)) + for _, partition := range partitions { + if fn(partition) { + result = append(result, partition) + } + } + return result +} + +func readDevDirectory() ([]os.FileInfo, error) { + devDir, devDirErr := os.Open("/dev") + if devDirErr != nil { + log.Errorf("Error while reading /dev directory [%v]", devDirErr) + return nil, devDirErr + } + + fileList, fileListErr := devDir.Readdir(0) + if fileListErr != nil { + log.Errorf("Error while reading /dev directory [%v]", fileListErr) + return nil, fileListErr + } + return fileList, nil +} + +func DetectDrives() ([]Drive, error) { + fileList, fileListErr := readDevDirectory() + if fileListErr != nil { + return nil, fileListErr + } + return DetectSdxDrives(fileList) +} + +func DetectPartitions() ([]Partition, error) { + fileList, fileListErr := readDevDirectory() + if fileListErr != nil { + return nil, fileListErr + } + return DetectSdxPartitions(fileList) +} + +func DetectSdxPartitions(fileList []os.FileInfo) ([]Partition, error) { + pattern := regexp.MustCompile("^sd[bcdefg][1-9]$") + var devList []Partition + for _, file := range fileList { + if file.Mode()&os.ModeDevice != 0 && + pattern.MatchString(file.Name()) { + devList = append(devList, Partition{ + name: file.Name(), + device: fmt.Sprintf("/dev/%s", file.Name()), + }) + } + } + // check for mount points + mtabBytes, mtabErr := ioutil.ReadFile("/etc/mtab") + if mtabErr != nil { + log.Errorf("Unable to read /etc/mtab, [%v]", mtabErr) + return nil, mtabErr + } + mtab := strings.Split(string(mtabBytes), "\n") + for i, file := range devList { + mtabPattern, mtabPatternErr := regexp.Compile( + fmt.Sprintf("^%s ([^\\s]*) ", file.device), + ) + if mtabPatternErr != nil { + panic(fmt.Sprintf("Invalid pattern in mtab regex [%v]", mtabPatternErr)) + } + for _, mtabEntry := range mtab { + match := mtabPattern.FindStringSubmatch(mtabEntry) + if len(match) >= 2 { + devList[i].mountPoint = match[1] + } + + } + } + return devList, nil +} + +func DetectSdxDrives(fileList []os.FileInfo) ([]Drive, error) { + partitions, partitionsErr := DetectSdxPartitions(fileList) + if partitionsErr != nil { + return nil, partitionsErr + } + pattern := regexp.MustCompile("^sd[bcdefg]$") + var devList []Drive + for _, file := range fileList { + if file.Mode()&os.ModeDevice != 0 && + pattern.MatchString(file.Name()) { + drive := Drive{ + name: file.Name(), + device: fmt.Sprintf("/dev/%s", file.Name()), + partitions: []Partition{}, + } + for _, partition := range partitions { + if strings.HasPrefix(partition.name, file.Name()) { + drive.partitions = append(drive.partitions, partition) + } + } + devList = append(devList, drive) + } + } + return devList, nil +} diff --git a/system/tool/tool.go b/system/tool/tool.go new file mode 100644 index 0000000..b8cbf1f --- /dev/null +++ b/system/tool/tool.go @@ -0,0 +1,5 @@ +package tool + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("tool") diff --git a/system/x11/x11.go b/system/x11/x11.go new file mode 100644 index 0000000..07a5a2a --- /dev/null +++ b/system/x11/x11.go @@ -0,0 +1,5 @@ +package x11 + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("x11") diff --git a/system/x11/xrandr.go b/system/x11/xrandr.go new file mode 100644 index 0000000..9b2d9fc --- /dev/null +++ b/system/x11/xrandr.go @@ -0,0 +1,87 @@ +package x11 + +import ( + "bytes" + "regexp" + "strconv" + + "github.com/wkozyra95/dotfiles/utils/exec" +) + +type Display struct { + resX int + resY int + name string + isPrimary bool + isActive bool +} + +// match block of output for specific outputs with the exception of first block +// e.g. +// DisplayPort-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm +// +// 2560x1440 59.95*+ +// 1920x1200 59.95 +// 2048x1080 60.00 24.00 +// 1920x1080 60.00 50.00 59.94 +var xrandrDisplayBlockRegexp = regexp.MustCompile("[a-zA-Z].*(?:\n .*)*") + +// match first line of output info and extract +// name, isActive, isPrimary, resolution+offset +// e.g. +// DisplayPort-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm\n +var xrandrDisplayInfoRegexp = regexp.MustCompile( + "^([-a-zA-Z0-9]*) (connected|disconnected)\\ ?(primary|)\\ ?([0-9]+x[0-9]+\\+[0-9]+\\+[0-9]+|)\\ ?.*\n?", +) + +var xrandrDisplayResolutionOffsetRegexp = regexp.MustCompile( + "([0-9]+)x([0-9]+)\\+([0-9]+)\\+([0-9]+)", +) + +type xrandrDetectResult struct { + displays []Display +} + +func xrandrDetect() (xrandrDetectResult, error) { + var stderr bytes.Buffer + var stdout bytes.Buffer + if err := exec.Command().WithBufout(&stdout, &stderr).Run("xrandr"); err != nil { + return xrandrDetectResult{}, err + } + return xrandrParseOutput(stdout.Bytes()) +} + +func xrandrParseOutput(output []byte) (xrandrDetectResult, error) { + var result xrandrDetectResult + // match (name, connectedStatus) for block from regex above + blocks := xrandrDisplayBlockRegexp.FindAllSubmatch(output, -1) + result.displays = make([]Display, len(blocks)-1) + for i, display := range blocks[1:] { + displayInfo := xrandrDisplayInfoRegexp.FindSubmatch(display[0]) + result.displays[i].name = string(displayInfo[1]) + result.displays[i].isActive = string(displayInfo[2]) == "connected" + result.displays[i].isPrimary = string(displayInfo[3]) == "primary" + if string(displayInfo[4]) != "" { + resolutionRaw := xrandrDisplayResolutionOffsetRegexp.FindSubmatch(displayInfo[4]) + if res, err := strconv.Atoi(string(resolutionRaw[1])); err != nil { + return result, err + } else { + result.displays[i].resX = res + } + if res, err := strconv.Atoi(string(resolutionRaw[2])); err != nil { + return result, err + } else { + result.displays[i].resY = res + } + + } + log.Printf("display|||%v", result.displays[i]) + } + return result, nil +} + +type xrandrCmd struct{} + +func XrandrCommand() xrandrCmd { + return xrandrCmd{} +} diff --git a/system/x11/xrandr_test.go b/system/x11/xrandr_test.go new file mode 100644 index 0000000..a235f40 --- /dev/null +++ b/system/x11/xrandr_test.go @@ -0,0 +1,133 @@ +package x11 + +import ( + "fmt" + "regexp" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type regexpTestMatch struct { + input []byte + matched string + matchedSub []string + expression *regexp.Regexp +} + +var xrandrOutputExample1 = `Screen 0: minimum 320 x 200, current 2560 x 1440, maximum 16384 x 16384 +DisplayPort-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm + 2560x1440 59.95*+ + 1920x1200 59.95 + 2048x1080 60.00 24.00 + 1920x1080 60.00 50.00 59.94 + 1600x1200 60.00 + 1680x1050 59.95 + 1280x1024 75.02 60.02 + 1440x900 59.95 + 1280x800 59.95 + 1152x864 75.00 + 1280x720 60.00 50.00 59.94 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 720x576 50.00 + 720x480 60.00 59.94 + 640x480 75.00 60.00 59.94 + 720x400 70.08 +DisplayPort-1 disconnected (normal left inverted right x axis y axis) +DisplayPort-2 disconnected (normal left inverted right x axis y axis) +HDMI-A-0 disconnected (normal left inverted right x axis y axis) +` + +var xrandrOutputDisplayExample1 = `DisplayPort-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm + 2560x1440 59.95*+ + 1920x1200 59.95 + 2048x1080 60.00 24.00 + 1920x1080 60.00 50.00 59.94 + 1600x1200 60.00 + 1680x1050 59.95 + 1280x1024 75.02 60.02 + 1440x900 59.95 + 1280x800 59.95 + 1152x864 75.00 + 1280x720 60.00 50.00 59.94 + 1024x768 75.03 60.00 + 800x600 75.00 60.32 + 720x576 50.00 + 720x480 60.00 59.94 + 640x480 75.00 60.00 59.94 + 720x400 70.08 ` + +var ( + xrandrOutputDisplayExample2 = `HDMI-A-0 disconnected (normal left inverted right x axis y axis)` + xrandrOutputDisplayExample3 = `DisplayPort-0 connected 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm + 2560x1440 59.95*+ + 1920x1200 59.95 + 2048x1080 60.00 24.00 + 1920x1080 60.00 50.00 59.94 + 1600x1200 60.00 ` +) + +func TestXrandrDetectExample1(t *testing.T) { + output, err := xrandrParseOutput([]byte(xrandrOutputExample1)) + require.Nil(t, err) + assert.Equal(t, xrandrDetectResult{ + displays: []Display{ + {resX: 2560, resY: 1440, name: "DisplayPort-0", isActive: true, isPrimary: true}, + {resX: 0, resY: 0, name: "DisplayPort-1", isActive: false, isPrimary: false}, + {resX: 0, resY: 0, name: "DisplayPort-2", isActive: false, isPrimary: false}, + {resX: 0, resY: 0, name: "HDMI-A-0", isActive: false, isPrimary: false}, + }, + }, output) +} + +func TestXrandrRegexpMatch(t *testing.T) { + matchTests := []regexpTestMatch{ + { + []byte(xrandrOutputDisplayExample1), + "DisplayPort-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm\n", + []string{ + "DisplayPort-0", + "connected", + "primary", + "2560x1440+0+0", + }, + xrandrDisplayInfoRegexp, + }, + { + []byte(xrandrOutputDisplayExample2), + xrandrOutputDisplayExample2, + []string{ + "HDMI-A-0", + "disconnected", + "", + "", + }, + xrandrDisplayInfoRegexp, + }, + { + []byte(xrandrOutputDisplayExample3), + "DisplayPort-0 connected 2560x1440+0+0 (normal left inverted right x axis y axis) 597mm x 336mm\n", + []string{ + "DisplayPort-0", + "connected", + "", + "2560x1440+0+0", + }, + xrandrDisplayInfoRegexp, + }, + } + for i, testCase := range matchTests { + t.Run(fmt.Sprintf("testCase %d", i), func(t *testing.T) { + output := testCase.expression.FindSubmatch(testCase.input) + t.Log(spew.Sdump(output)) + assert.Equal(t, testCase.matched, string(output[0])) + for i, outputs := range output[1 : len(output)-1] { + assert.Equal(t, testCase.matchedSub[i], string(outputs)) + } + assert.Equal(t, len(testCase.matchedSub), len(output)-1) + }) + } +} diff --git a/test/cmd/dev.Dockerfile b/test/cmd/dev.Dockerfile new file mode 100644 index 0000000..6a25892 --- /dev/null +++ b/test/cmd/dev.Dockerfile @@ -0,0 +1,15 @@ +FROM debian +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update && \ + apt-get -y install sudo + +RUN useradd -ms /bin/bash test +RUN adduser test sudo +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +USER test +WORKDIR /home/test + +ENTRYPOINT bash + + diff --git a/test/cmd/main.go b/test/cmd/main.go new file mode 100644 index 0000000..4befbc6 --- /dev/null +++ b/test/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import e2e "github.com/wkozyra95/dotfiles/test/e2e_docker" + +func main() { + e2e.RunInDevTestToolsInstall() +} diff --git a/test/cmd/test.install.Dockerfile b/test/cmd/test.install.Dockerfile new file mode 100644 index 0000000..75ff152 --- /dev/null +++ b/test/cmd/test.install.Dockerfile @@ -0,0 +1,16 @@ +FROM debian +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update && \ + apt-get -y install sudo + +RUN useradd -ms /bin/bash test +RUN adduser test sudo +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers + +USER test +WORKDIR /home/test +ADD . .dotfiles + +RUN ~/.dotfiles/bin/mycli install --noninteractive + + diff --git a/test/e2e_docker/docker.go b/test/e2e_docker/docker.go new file mode 100644 index 0000000..d513e36 --- /dev/null +++ b/test/e2e_docker/docker.go @@ -0,0 +1,236 @@ +package e2e + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/docker/docker/pkg/archive" + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +type dockerInstance struct { + dockerfile string + imageName string + containerName string + client *client.Client + cwd string + mounts []mount.Mount +} + +func (d *dockerInstance) init() { + if d.client != nil { + panic("already initialised") + } + if d.dockerfile == "" { + d.dockerfile = "Dockerfile" + } + if d.containerName == "" { + d.containerName = "system_setup_test" + } + if d.imageName == "" { + d.imageName = "system_setup_image" + } + cli, cliErr := client.NewClientWithOpts(client.WithVersion("1.39")) + if cliErr != nil { + panic(cliErr) + } + d.client = cli +} + +func (d *dockerInstance) start() error { + log.Info("Preparing docker ...") + d.init() + image, findImageErr := d.findImage() + if findImageErr != nil { + return findImageErr + } + if image == nil { + if err := d.buildImage(); err != nil { + return err + } + } else { + log.Info("Image already exists") + } + container, findContainerErr := d.findContainer() + if findContainerErr != nil { + newContainer, buildErr := d.buildContainer() + if buildErr != nil { + return buildErr + } + container = newContainer + } else { + log.Info("Container already exists") + } + + if container.Status != "running" { + log.Info("Starting docker container") + if err := d.client.ContainerStart(context.Background(), container.ID, types.ContainerStartOptions{}); err != nil { + return err + } + } else { + log.Info("Container already started") + } + + return nil +} + +func (d *dockerInstance) stop() error { + log.Info("Stopping docker container") + + cont, findErr := d.findContainer() + if findErr != nil { + return findErr + } + + if err := d.client.ContainerStop(context.Background(), cont.ID, container.StopOptions{}); err != nil { + return err + } + return nil +} + +func (d *dockerInstance) remove() error { + container, findErr := d.findContainer() + if findErr != nil { + return findErr + } + log.Info("Removing container") + if err := d.client.ContainerRemove(context.Background(), container.ID, types.ContainerRemoveOptions{}); err != nil { + return err + } + return nil +} + +func (d *dockerInstance) buildContainer() (*types.Container, error) { + log.Info("Creating new docker container") + _, createErr := d.client.ContainerCreate( + context.Background(), + &container.Config{ + Image: d.imageName, + Tty: true, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + OpenStdin: true, + }, + &container.HostConfig{Mounts: d.mounts}, + &network.NetworkingConfig{}, + &specs.Platform{}, + d.containerName, + ) + if createErr != nil { + return nil, createErr + } + container, findErr := d.findContainer() + if findErr != nil { + return nil, findErr + } + return container, nil +} + +func (d *dockerInstance) createDockerContext() (*bytes.Buffer, error) { + buf := new(bytes.Buffer) + reader, tarErr := archive.Tar(d.cwd, archive.Uncompressed) + if tarErr != nil { + return nil, tarErr + } + buf.ReadFrom(reader) + return buf, nil +} + +func (d *dockerInstance) buildImage() error { + log.Info("Creating docker image from dockerfile") + buffer, contextError := d.createDockerContext() + if contextError != nil { + log.Errorf("Preparing docker context failed with error %v", contextError) + return contextError + } + buildContext := bytes.NewReader(buffer.Bytes()) + + buildOptions := types.ImageBuildOptions{ + Dockerfile: d.dockerfile, + Tags: []string{d.imageName}, + Remove: true, + ForceRemove: true, + } + + buildResponse, buildErr := d.client.ImageBuild(context.Background(), buildContext, buildOptions) + if buildErr != nil { + log.Fatal(buildErr) + } + defer buildResponse.Body.Close() + + output, readErr := ioutil.ReadAll(buildResponse.Body) + if readErr != nil { + return readErr + } + + var logBuf bytes.Buffer + logBuf.WriteString("Docker output\n") + rawJsons := strings.Split(string(output), "\n") + for _, rawJson := range rawJsons { + if rawJson == "" { + continue + } + var parsed map[string]interface{} + if err := json.Unmarshal([]byte(rawJson), &parsed); err != nil { + log.Errorf("Parsing of docker output failed %v", rawJson) + return err + } + if stream, ok := parsed["stream"].(string); ok { + logBuf.WriteString(stream) + } else if errorMsg, ok := parsed["error"].(string); ok { + logBuf.WriteString(errorMsg) + } + } + log.Info(logBuf.String()) + + return buildErr +} + +func (d *dockerInstance) findImage() (*types.ImageSummary, error) { + log.Info("Looking for existing images locally") + args := filters.NewArgs() + args.Add("reference", d.imageName) + list, listErr := d.client.ImageList(context.Background(), types.ImageListOptions{Filters: args}) + if listErr != nil { + return nil, listErr + } + if len(list) == 0 { + return nil, nil + } + return &list[0], nil +} + +func (d *dockerInstance) findContainer() (*types.Container, error) { + containers, listErr := d.client.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + if listErr != nil { + return nil, listErr + } + for _, container := range containers { + for _, name := range container.Names { + if name == fmt.Sprintf("/%s", d.containerName) { + return &container, nil + } + } + } + return nil, fmt.Errorf("not found") +} + +func (d *dockerInstance) exec(cmd string) error { + // container, findErr := d.findContainer() + // if findErr != nil { + // return findErr + // } + + return nil +} diff --git a/test/e2e_docker/e2e.go b/test/e2e_docker/e2e.go new file mode 100644 index 0000000..8701476 --- /dev/null +++ b/test/e2e_docker/e2e.go @@ -0,0 +1,5 @@ +package e2e + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("e2e") diff --git a/test/e2e_docker/install_dev.go b/test/e2e_docker/install_dev.go new file mode 100644 index 0000000..38058fd --- /dev/null +++ b/test/e2e_docker/install_dev.go @@ -0,0 +1,37 @@ +package e2e + +import ( + "os" + "os/exec" + + "github.com/docker/docker/api/types/mount" +) + +func RunInDevTestToolsInstall() { + log.Info("Executing go build") + cmd := exec.Command("go", "build", "-o", "mycli") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + log.Error("Compilation failed with error") + panic(err) + } + container := dockerInstance{ + dockerfile: "./dev.Dockerfile", + cwd: "./test/cmd", + containerName: "system_setup_dev", + imageName: "system_setup_dev_img", + mounts: []mount.Mount{ + { + Type: mount.TypeBind, + Source: "/home/wojtek/.dotfiles", + Target: "/home/test/mounted_tmp_system_setup", + ReadOnly: true, + }, + }, + } + if err := container.start(); err != nil { + log.Error(err.Error()) + panic(err) + } +} diff --git a/test/e2e_docker/install_test.go b/test/e2e_docker/install_test.go new file mode 100644 index 0000000..896567a --- /dev/null +++ b/test/e2e_docker/install_test.go @@ -0,0 +1,30 @@ +//go:build integration +// +build integration + +package e2e + +import ( + "os" + "os/exec" + "testing" +) + +func TestToolsInstall(t *testing.T) { + t.Log("Executing go build") + cmd := exec.Command("go", "build", "-o", "mycli") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Dir = "../../" + if err := cmd.Run(); err != nil { + t.Error("Compilation failed with error") + return + } + container := dockerInstance{ + dockerfile: "./test/cmd/test.install.Dockerfile", + cwd: "../..", + } + container.init() + if err := container.buildImage(); err != nil { + t.Errorf("Unable to start docker with error %v", err) + } +} diff --git a/tool/bitwarden/bitwarden.go b/tool/bitwarden/bitwarden.go new file mode 100644 index 0000000..912cb76 --- /dev/null +++ b/tool/bitwarden/bitwarden.go @@ -0,0 +1,44 @@ +package bitwarden + +import ( + "bytes" + "encoding/json" + + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var log = logger.NamedLogger("bitwarden") + +func IsLoggedIn() (bool, error) { + var stdout, stderr bytes.Buffer + if err := exec.Command().WithBufout(&stdout, &stderr).Run("bw", "status"); err != nil { + return false, err + } + var output map[string]interface{} + if err := json.Unmarshal(stdout.Bytes(), &output); err != nil { + return false, err + } + if output["status"] == "authenticated" { + return true, nil + } + return false, nil +} + +func InteractiveLogin() error { + return exec.Command().WithStdio().Run("bw", "login") +} + +func EnsureLoggedIn() error { + isLoggedIn, err := IsLoggedIn() + if err != nil { + return err + } + if isLoggedIn { + return nil + } + if err := InteractiveLogin(); err != nil { + return err + } + return nil +} diff --git a/tool/drive/device.go b/tool/drive/device.go new file mode 100644 index 0000000..3203c54 --- /dev/null +++ b/tool/drive/device.go @@ -0,0 +1,114 @@ +package drive + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/wkozyra95/dotfiles/utils/exec" + "github.com/wkozyra95/dotfiles/utils/file" +) + +var potentialNvmeStorageDevicePaths = []string{ + "/dev/nvme0n1", + "/dev/nvme1n1", + "/dev/nvme2n1", + "/dev/nvme3n1", +} + +var potentialHddStorageDevicePaths = []string{ + "/dev/sda", + "/dev/sdb", + "/dev/sdc", + "/dev/sdd", + "/dev/sde", +} + +var potentialStorageDevicePaths = append( + potentialNvmeStorageDevicePaths, + potentialHddStorageDevicePaths..., +) + +type Partition struct { + Label string + PartitionPath string + Size int +} + +type StorageDevice struct { + Label string + DevicePath string + Size int + Partitions []Partition +} + +type SfdiskPartition struct { + Label string `json:"name"` + PartitionPath string `json:"node"` + Sectors int `json:"size"` + Type string `json:"type"` + Uuid string `json:"uuid"` +} + +type SfdiskDeviceInfo struct { + Paritiontable struct { + Label string `json:"label"` + Unit string `json:"unit"` + SectorSize int `json:"sectorsize"` + DevicePath string `json:"device"` + LbaStart int `json:"firstlba"` + LbaEnd int `json:"lastlba"` + Partitions []SfdiskPartition `json:"partitions"` + } `json:"partitiontable"` +} + +func GetPartitionPath(devicePath string, partitionNumber int) string { + for _, dev := range potentialNvmeStorageDevicePaths { + if dev == devicePath { + return fmt.Sprintf("%sp%d", devicePath, partitionNumber) + } + } + for _, dev := range potentialHddStorageDevicePaths { + if dev == devicePath { + return fmt.Sprintf("%s%d", devicePath, partitionNumber) + } + } + panic(fmt.Sprintf("Device %s is not supported", devicePath)) +} + +func GetStorageDevicesList() ([]StorageDevice, error) { + installedStorageDevicesPaths := make([]StorageDevice, 0, len(potentialStorageDevicePaths)) + for _, devicePath := range potentialStorageDevicePaths { + if file.Exists(devicePath) { + device := StorageDevice{DevicePath: devicePath} + deviceInfo, deviceInfoErr := GetDeviceInfo(devicePath) + if deviceInfoErr != nil { + return nil, deviceInfoErr + } + device.Size = deviceInfo.Paritiontable.SectorSize * (deviceInfo.Paritiontable.LbaEnd - deviceInfo.Paritiontable.LbaStart) + device.Label = deviceInfo.Paritiontable.Label + for _, partition := range deviceInfo.Paritiontable.Partitions { + device.Partitions = append(device.Partitions, Partition{ + Label: partition.Label, + PartitionPath: partition.PartitionPath, + Size: deviceInfo.Paritiontable.SectorSize * partition.Sectors, + }) + } + installedStorageDevicesPaths = append(installedStorageDevicesPaths, device) + } + } + return installedStorageDevicesPaths, nil +} + +func GetDeviceInfo(devicePath string) (SfdiskDeviceInfo, error) { + var stdout bytes.Buffer + sfdiskCmdErr := exec.Command().WithBufout(&stdout, &bytes.Buffer{}).Run("sudo", "sfdisk", devicePath, "--json") + if sfdiskCmdErr != nil { + return SfdiskDeviceInfo{}, nil + } + var deviceInfo SfdiskDeviceInfo + if err := json.Unmarshal(stdout.Bytes(), &deviceInfo); err != nil { + return deviceInfo, err + } + return deviceInfo, nil +} diff --git a/tool/drive/drive.go b/tool/drive/drive.go new file mode 100644 index 0000000..70f31cc --- /dev/null +++ b/tool/drive/drive.go @@ -0,0 +1,5 @@ +package drive + +import "github.com/wkozyra95/dotfiles/logger" + +var log = logger.NamedLogger("drive") diff --git a/utils/exec/exec.go b/utils/exec/exec.go new file mode 100644 index 0000000..1032a9d --- /dev/null +++ b/utils/exec/exec.go @@ -0,0 +1,150 @@ +package exec + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("exec") + +// LookPath ... +func LookPath(cmd string) string { + str, err := exec.LookPath(cmd) + if err != nil { + return "" + } + return str +} + +// CommandExists ... +func CommandExists(cmd string) bool { + _, err := exec.LookPath(cmd) + if err != nil { + return false + } + return true +} + +// Cmd ... +type Cmd struct { + *exec.Cmd + stdio bool + bufout io.Writer + buferr io.Writer + sudo bool + cwd string +} + +// Command ... +func Command() *Cmd { + cmd := &exec.Cmd{} + return &Cmd{cmd, false, nil, nil, false, ""} +} + +// WithEnv ... +func (c *Cmd) WithEnv(envs ...string) *Cmd { + if c.Env == nil { + c.Env = os.Environ() + } + c.Env = append(c.Env, envs...) + return c +} + +// WithStdio ... +func (c *Cmd) WithStdio() *Cmd { + c.stdio = true + return c +} + +// WithBufout ... +func (c *Cmd) WithBufout(stdout io.Writer, stderr io.Writer) *Cmd { + c.bufout = stdout + c.buferr = stderr + return c +} + +// WithCwd ... +func (c *Cmd) WithCwd(cwd string) *Cmd { + c.cwd = cwd + return c +} + +// WithSudo ... +func (c *Cmd) WithSudo() *Cmd { + c.sudo = true + return c +} + +// Start ... +func (c *Cmd) Start(cmdName string, args ...string) (*exec.Cmd, error) { + c.prepare(cmdName, args...) + if err := c.Cmd.Start(); err != nil { + return c.Cmd, fmt.Errorf("Command %v failed with error [%s]", c.Cmd.Args, err.Error()) + } + return c.Cmd, nil +} + +// Run ... +func (c *Cmd) Run(cmdName string, args ...string) error { + c.prepare(cmdName, args...) + if c.Stdout != nil { + if err := c.Cmd.Run(); err != nil { + return fmt.Errorf("Command %v failed with error [%s]", c.Cmd.Args, err.Error()) + } + } else { + return c.runWithoutStdio() + } + return nil +} + +func (c *Cmd) prepare(cmdName string, args ...string) { + if c.sudo { + c.Path = "sudo" + c.Args = append([]string{"sudo", cmdName}, args...) + } else { + c.Path = cmdName + c.Args = append([]string{cmdName}, args...) + } + if filepath.Base(c.Path) == c.Path { + if lp, err := exec.LookPath(c.Path); err != nil { + // return err + } else { + c.Path = lp + } + } + c.Cmd.Dir = c.cwd + log.Debugf("Call [%s]", strings.Join(c.Args, " ")) + if c.stdio { + c.Stdout = os.Stdout + c.Stderr = os.Stderr + c.Stdin = os.Stdin + } else if c.bufout != nil && c.buferr != nil { + c.Stdout = c.bufout + c.Stderr = c.buferr + } +} + +func (c *Cmd) runWithoutStdio() error { + cmd := c.Cmd + output, cmdErr := cmd.CombinedOutput() + if cmdErr != nil { + log.Errorf("Command [%s %v] failed", cmd.Path, cmd.Args) + lines := strings.Split(string(output), "\n") + var buffer bytes.Buffer + buffer.WriteString("Failed command error") + for _, line := range lines { + buffer.WriteString("\n\t\t") + buffer.WriteString(line) + } + log.Errorf(buffer.String()) + return cmdErr + } + return nil +} diff --git a/utils/file/file.go b/utils/file/file.go new file mode 100644 index 0000000..b8b3ef8 --- /dev/null +++ b/utils/file/file.go @@ -0,0 +1,125 @@ +package file + +import ( + "fmt" + "io/fs" + "io/ioutil" + "os" + "regexp" + "strings" + + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var log = logger.NamedLogger("file") + +func Exists(name string) bool { + if _, err := os.Stat(name); err != nil { + if os.IsNotExist(err) { + return false + } + log.Errorf("Accessing file failed with [%s]", err.Error()) + } + return true +} + +func Copy(source string, destination string) error { + data, readErr := ioutil.ReadFile(source) + if readErr != nil { + return readErr + } + if err := ioutil.WriteFile(destination, data, 0o644); err != nil { + return err + } + return nil +} + +func CopyAsRoot(source string, destination string) error { + return exec.Command().WithSudo().Run("cp", "-R", source, destination) +} + +func EnsureSymlink(source string, destination string) error { + log.Debugf("EnsureSymlink(%s, %s)", source, destination) + linkStat, linkStatErr := os.Lstat(destination) + if linkStatErr != nil { + if os.IsNotExist(linkStatErr) { + return os.Symlink(source, destination) + } else { + return linkStatErr + } + } + if linkStat.Mode()&fs.ModeSymlink == 0 { + log.Debug("Replacing files with symlink") + if err := os.RemoveAll(destination); err != nil { + return err + } + return os.Symlink(source, destination) + } + linkDestination, linkDestinationErr := os.Readlink(destination) + if linkDestinationErr != nil || linkDestination != source { + log.Debug("Recreating link") + if err := os.RemoveAll(destination); err != nil { + return err + } + return os.Symlink(source, destination) + } + return nil +} + +func ensureTextInString(content string, text string, rg *regexp.Regexp) (bool, string) { + var match []int + if rg != nil { + match = rg.FindStringIndex(content) + } + if len(match) == 0 { + if len(content) == 0 { + return true, fmt.Sprintf("%s\n", text) + } + lastCharacter := content[len(content)-1] + if lastCharacter == '\n' { + return true, fmt.Sprintf("%s%s\n", content, text) + } else { + return true, fmt.Sprintf("%s\n%s\n", content, text) + } + } else { + updatedContent := strings.Join( + []string{ + string(content[0:match[0]]), + text, + string(content[match[1]:]), + }, + "", + ) + return updatedContent != content, updatedContent + } +} + +func EnsureText(path string, text string) error { + return EnsureTextWithRegexp(path, text, regexp.MustCompile(regexp.QuoteMeta(text))) +} + +func EnsureTextWithRegexp(path string, text string, rg *regexp.Regexp) error { + content := "" + if Exists(path) { + byteContent, readErr := ioutil.ReadFile(path) + if readErr != nil { + return readErr + } + content = string(byteContent) + } + shouldUpdate, updatedContent := ensureTextInString(content, text, rg) + if !shouldUpdate { + return nil + } + + file, openErr := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) + if openErr != nil { + return openErr + } + defer file.Close() + if _, err := file.WriteString(updatedContent); err != nil { + return err + } + return nil +} diff --git a/utils/file/file_test.go b/utils/file/file_test.go new file mode 100644 index 0000000..5880db3 --- /dev/null +++ b/utils/file/file_test.go @@ -0,0 +1,100 @@ +package file + +import ( + "regexp" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +type testCase struct { + content string + text string + rg *regexp.Regexp + resultShouldUpdate bool + resultUpdatedContent string +} + +func TestEnsureLineText(t *testing.T) { + cases := []testCase{ + { + content: "export CURRENT_ENV=\"home\"\n", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: "export CURRENT_ENV=\"work\"\n", + }, + { + content: " ", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: " \nexport CURRENT_ENV=\"work\"\n", + }, + { + content: "test", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: "test\nexport CURRENT_ENV=\"work\"\n", + }, + { + content: "test\n", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: "test\nexport CURRENT_ENV=\"work\"\n", + }, + { + content: "test\nexport CURRENT_ENV=\"home\"\ntest2\n", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: "test\nexport CURRENT_ENV=\"work\"\ntest2\n", + }, + { + content: "test\n export CURRENT_ENV=\"home\"\ntest2\n", + text: "export CURRENT_ENV=\"work\"", + rg: regexp.MustCompile("[ \t]*export CURRENT_ENV.*"), + resultShouldUpdate: true, + resultUpdatedContent: "test\nexport CURRENT_ENV=\"work\"\ntest2\n", + }, + { + content: "test\nexport CURRENT_ENV=\"home\"\ntest2\n", + text: "export CURRENT_ENV=\"home\"", + rg: regexp.MustCompile("export CURRENT_ENV.*"), + resultShouldUpdate: false, + resultUpdatedContent: "test\nexport CURRENT_ENV=\"home\"\ntest2\n", + }, + { + content: "aaabbbccc", + text: "d", + rg: regexp.MustCompile("bbb"), + resultShouldUpdate: true, + resultUpdatedContent: "aaadccc", + }, + { + content: "aaaccc", + text: "ddd", + rg: regexp.MustCompile(regexp.QuoteMeta("ddd")), + resultShouldUpdate: true, + resultUpdatedContent: "aaaccc\nddd\n", + }, + { + content: "aaadddccc", + text: "ddd", + rg: regexp.MustCompile(regexp.QuoteMeta("ddd")), + resultShouldUpdate: false, + resultUpdatedContent: "aaadddccc", + }, + } + + for i, testCase := range cases { + t.Run(strconv.Itoa(i), func(t *testing.T) { + shouldUpdate, content := ensureTextInString(testCase.content, testCase.text, testCase.rg) + assert.Equal(t, testCase.resultShouldUpdate, shouldUpdate) + assert.Equal(t, testCase.resultUpdatedContent, content) + }) + } +} diff --git a/utils/fn/fn.go b/utils/fn/fn.go new file mode 100644 index 0000000..44f4b08 --- /dev/null +++ b/utils/fn/fn.go @@ -0,0 +1,23 @@ +package fn + +func Filter[T any](list []T, cond func(T) bool) []T { + results := []T{} + for _, element := range list { + if cond(element) { + results = append(results, element) + } + } + return results +} + +func Map[T any, P any](list []T, mapFn func(T) P) []P { + results := make([]P, len(list)) + for index, element := range list { + results[index] = mapFn(element) + } + return results +} + +func Identity[T any](t T) T { + return t +} diff --git a/utils/git/git.go b/utils/git/git.go new file mode 100644 index 0000000..a37f509 --- /dev/null +++ b/utils/git/git.go @@ -0,0 +1,72 @@ +package git + +import ( + "bytes" + "fmt" + "regexp" + + "github.com/wkozyra95/dotfiles/utils/exec" +) + +var branchInfoRg = regexp.MustCompile( + `(?m)^(\*?)\s+(\S+)\s+(\S+)\s+(\[(\S+)(: gone|)\]\s+|)(.+)$`, +) + +type BranchInfo struct { + IsCurrent bool + Name string + CommitHash string + RemoteBranch string + IsRemoteGone bool + Message string +} + +func (b BranchInfo) String() string { + maybeRemote := "" + if b.RemoteBranch != "" { + maybeRemote = fmt.Sprintf(" [%s]", b.RemoteBranch) + } + maybeGone := "" + if b.IsRemoteGone { + maybeGone = "[gone] " + } + return fmt.Sprintf("%s%s%s - %s %s", maybeGone, b.Name, maybeRemote, b.CommitHash, b.Message) +} + +func Prune() error { + return exec.Command().WithStdio().Run("git", "fetch", "origin", "--prune") +} + +func DeleteBranch(name string) error { + return exec.Command().WithStdio().Run("git", "branch", "-D", name) +} + +func ListBranches() ([]BranchInfo, error) { + var stdout bytes.Buffer + spawnErr := exec.Command().WithBufout(&stdout, &bytes.Buffer{}).Run("git", "branch", "-vv") + if spawnErr != nil { + return nil, spawnErr + } + return parseListBranches(stdout.String()), nil +} + +func parseListBranches(gitOutput string) []BranchInfo { + matches := branchInfoRg.FindAllStringSubmatch(gitOutput, -1) + results := make([]BranchInfo, 0, len(matches)) + for _, match := range matches { + branch := parseBranchInfo(match) + results = append(results, branch) + } + return results +} + +func parseBranchInfo(match []string) BranchInfo { + return BranchInfo{ + IsCurrent: match[1] == "*", + Name: match[2], + CommitHash: match[3], + RemoteBranch: match[5], + IsRemoteGone: match[6] == ": gone", + Message: match[7], + } +} diff --git a/utils/git/git_test.go b/utils/git/git_test.go new file mode 100644 index 0000000..5ca3ecb --- /dev/null +++ b/utils/git/git_test.go @@ -0,0 +1,60 @@ +package git + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var example = ` feature-1 4ea9770 [origin/feature-1] test commit + feature-2 4ea9770 test commit + feature-6 4ea9770 [origin/feature-6] test commit + feature-7 4ea9770 [origin/feature-7: gone] test commit +* main 4ea9770 [origin/main] test commit +` + +func TestGetGitBranchInfo(t *testing.T) { + result := parseListBranches(example) + assert.Equal(t, []BranchInfo{ + { + IsCurrent: false, + Name: "feature-1", + CommitHash: "4ea9770", + RemoteBranch: "origin/feature-1", + IsRemoteGone: false, + Message: "test commit", + }, + { + IsCurrent: false, + Name: "feature-2", + CommitHash: "4ea9770", + RemoteBranch: "", + IsRemoteGone: false, + Message: "test commit", + }, + { + IsCurrent: false, + Name: "feature-6", + CommitHash: "4ea9770", + RemoteBranch: "origin/feature-6", + IsRemoteGone: false, + Message: "test commit", + }, + { + IsCurrent: false, + Name: "feature-7", + CommitHash: "4ea9770", + RemoteBranch: "origin/feature-7", + IsRemoteGone: true, + Message: "test commit", + }, + { + IsCurrent: true, + Name: "main", + CommitHash: "4ea9770", + RemoteBranch: "origin/main", + IsRemoteGone: false, + Message: "test commit", + }, + }, result) +} diff --git a/utils/http/http.go b/utils/http/http.go new file mode 100644 index 0000000..4093b1b --- /dev/null +++ b/utils/http/http.go @@ -0,0 +1,40 @@ +package http + +import ( + "io" + "io/ioutil" + "net/http" + "os" +) + +func GetPage(url string) (string, error) { + resp, err := http.Get(url) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + return string(body), nil +} + +func DownloadFile(url string, filepath string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + file, createFileErr := os.Create(filepath) + if createFileErr != nil { + return createFileErr + } + defer file.Close() + _, copyErr := io.Copy(file, resp.Body) + if copyErr != nil { + return copyErr + } + return nil +} diff --git a/utils/persistentstate/state.go b/utils/persistentstate/state.go new file mode 100644 index 0000000..59d6933 --- /dev/null +++ b/utils/persistentstate/state.go @@ -0,0 +1,113 @@ +package persistentstate + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + "time" + + "github.com/juju/mutex" + + "github.com/davecgh/go-spew/spew" + "github.com/wkozyra95/dotfiles/logger" + "github.com/wkozyra95/dotfiles/utils/file" +) + +var log = logger.NamedLogger("util:persistentstate") + +type stateManager[State any] struct { + statePath string + mutextSpec mutex.Spec + initFunc func(*State) *State +} + +// StateManager ... +type StateManager[State any] interface { + RunGuarded(func(*State) error) error + GetState() (*State, error) +} + +type clock struct { + delay time.Duration +} + +func (f *clock) After(t time.Duration) <-chan time.Time { + return time.After(t) +} + +func (f *clock) Now() time.Time { + return time.Now() +} + +func GetStateManager[State any](statePath string, name string, initFn func(*State) *State) StateManager[State] { + return &stateManager[State]{ + statePath: statePath, + mutextSpec: mutex.Spec{ + Name: name, + Clock: &clock{}, + Delay: time.Second, + Timeout: 0, + Cancel: make(chan struct{}), + }, + initFunc: initFn, + } +} + +func (s *stateManager[State]) RunGuarded(fun func(*State) error) error { + mutexLock, mutexErr := mutex.Acquire(s.mutextSpec) + if mutexErr != nil { + return mutexErr + } + defer mutexLock.Release() + state, readErr := s.read() + if readErr != nil { + return readErr + } + state = s.initFunc(state) + log.Tracef("Read state %s", spew.Sdump(state)) + if err := fun(state); err != nil { + return err + } + log.Tracef("Write state %s", spew.Sdump(state)) + return s.write(state) +} + +func (s *stateManager[State]) GetState() (*State, error) { + state, readErr := s.read() + if readErr != nil { + emptyState := new(State) + return emptyState, readErr + } + return state, nil +} + +func (s *stateManager[State]) read() (*State, error) { + if file.Exists(s.statePath) { + rawFile, readErr := ioutil.ReadFile(s.statePath) + if readErr != nil { + return nil, readErr + } + state := new(State) + if err := json.Unmarshal(rawFile, state); err != nil { + return nil, err + } + return state, nil + } + return new(State), nil +} + +func (s *stateManager[State]) write(state *State) error { + mkdirErr := os.MkdirAll(path.Dir(s.statePath), os.ModePerm) + if mkdirErr != nil { + return mkdirErr + } + rawState, marshalErr := json.Marshal(&state) + if marshalErr != nil { + return marshalErr + } + if err := os.WriteFile(s.statePath, rawState, os.ModePerm); err != nil { + return err + } + return nil +} diff --git a/utils/proc/proc.go b/utils/proc/proc.go new file mode 100644 index 0000000..6bf7f88 --- /dev/null +++ b/utils/proc/proc.go @@ -0,0 +1,60 @@ +package proc + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("utils:proc") + +// Exists checks if process specified by pid exists +func Exists(pid int) bool { + process, processErr := os.FindProcess(pid) + if processErr != nil { + panic(fmt.Sprintf("should happen only on windows %s", processErr.Error())) + } + singalErr := process.Signal(syscall.Signal(0)) + if singalErr != nil { + log.Debug(singalErr.Error()) + } + return singalErr == nil +} + +// Term ... +func Term(pid int) { + process, processErr := os.FindProcess(pid) + if processErr != nil { + panic(fmt.Sprintf("should happen only on windows %s", processErr.Error())) + } + process.Signal(syscall.SIGTERM) +} + +type DoubleInteruptExitGuard interface { + Cleanup() +} + +func StartDoubleInteruptExitGuard() func() { + sig := make(chan os.Signal, 1) + closed := false + go func() { + for { + signal.Notify(sig, os.Interrupt, syscall.SIGTERM) + <-sig + if closed { + return + } + signal.Stop(sig) + log.Info("Press Ctrl+C again to kill this process") + time.Sleep(time.Second * 2) + } + }() + return func() { + close(sig) + closed = true + } +} diff --git a/utils/prompt/prompt.go b/utils/prompt/prompt.go new file mode 100644 index 0000000..48287ff --- /dev/null +++ b/utils/prompt/prompt.go @@ -0,0 +1,58 @@ +package prompt + +import "github.com/manifoldco/promptui" + +func TextPrompt(msg string) string { + response, responseErr := (&promptui.Prompt{Label: msg}).Run() + if responseErr != nil { + return "" + } + return response +} + +func ConfirmPrompt(msg string) bool { + _, responseErr := (&promptui.Prompt{IsConfirm: true, Label: msg}).Run() + if responseErr != nil { + return false + } + return true +} + +func SelectPrompt[T any](msg string, choices []T, printFn func(T) string) (T, bool) { + names := []string{} + for _, dst := range choices { + names = append(names, printFn(dst)) + } + index, _, selectErr := (&promptui.Select{ + Label: msg, + Items: names, + }).Run() + if selectErr != nil { + return *new(T), false + } + return choices[index], true +} + +func MultiselectPrompt[T any](msg string, choices []T, printFn func(T) string) []T { + names := []string{} + availableChoices := choices + for _, dst := range choices { + names = append(names, printFn(dst)) + } + results := []T{} + cursor := 0 + for { + index, _, selectErr := (&promptui.Select{ + Label: msg, + Items: names, + CursorPos: cursor, + }).Run() + if selectErr != nil { + return results + } + cursor = index + results = append(results, availableChoices[index]) + names = append(names[:index], names[index+1:]...) + availableChoices = append(availableChoices[:index], availableChoices[index+1:]...) + } +} diff --git a/utils/semver/semver.go b/utils/semver/semver.go new file mode 100644 index 0000000..d7ba45e --- /dev/null +++ b/utils/semver/semver.go @@ -0,0 +1,22 @@ +package semver + +import ( + "github.com/blang/semver" + "github.com/wkozyra95/dotfiles/logger" +) + +var log = logger.NamedLogger("install") + +func ShouldUpdate(current string, latest string) (bool, error) { + currentSemver, currentSemverErr := semver.Make(current) + if currentSemverErr != nil { + return false, currentSemverErr + } + + latestSemver, latestSemverErr := semver.Make(latest) + if latestSemverErr != nil { + return false, latestSemverErr + } + + return latestSemver.GT(currentSemver), nil +}