Add basic desktop UI - systray

This PR adds a basic UI for desktop
applications that support Linux, Max
and Windows.
This commit is contained in:
Givi Khojanashvili 2022-03-20 20:36:35 +04:00 committed by GitHub
parent bd61be24be
commit 4ef3c7a637
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 469 additions and 9 deletions

View File

@ -35,6 +35,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev
- name: Install modules
run: go mod tidy

View File

@ -6,6 +6,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev
- name: golangci-lint
uses: golangci/golangci-lint-action@v2

View File

@ -49,6 +49,8 @@ jobs:
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Install dependencies
run: sudo apt update && sudo apt install -y -q libgtk-3-dev libappindicator3-dev libayatana-appindicator3-dev
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
@ -71,3 +73,39 @@ jobs:
ref: v0.0.2
token: ${{ secrets.SIGN_GITHUB_TOKEN }}
inputs: '{ "tag": "${{ github.ref }}" }'
release_ui:
runs-on: macos-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0 # It is required for GoReleaser to work properly
-
name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17
-
name: Cache Go modules
uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
-
name: Install modules
run: go mod tidy
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: v1.6.3
args: release --config .goreleaser_ui_darwin.yaml --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}
UPLOAD_DEBIAN_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}
UPLOAD_YUM_SECRET: ${{ secrets.PKG_UPLOAD_SECRET }}

View File

@ -4,7 +4,6 @@ builds:
dir: client
binary: wiretrustee
env: [CGO_ENABLED=0]
goos:
- linux
- darwin
@ -55,14 +54,50 @@ builds:
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
mod_timestamp: '{{ .CommitTimestamp }}'
- id: wiretrustee-ui
dir: client/ui
binary: wiretrustee-ui
env: [CGO_ENABLED=1]
goos:
- windows
- linux
goarch:
- amd64
ldflags:
- -s -w -X github.com/wiretrustee/wiretrustee/client/ui/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
mod_timestamp: '{{ .CommitTimestamp }}'
archives:
- builds:
- wiretrustee
nfpms:
- maintainer: Wiretrustee <dev@wiretrustee.com>
description: Wiretrustee client UI.
homepage: https://wiretrustee.com/
id: wiretrustee-ui-deb
package_name: wiretrustee-ui
builds:
- wiretrustee-ui
formats:
- deb
- rpm
contents:
- src: client/ui/wiretrustee.desktop
dst: /usr/share/applications/wiretrustee.desktop
- src: client/ui/wiretrustee.png
dst: /usr/share/icons/hicolor/256x256/wiretrustee.png
dependencies:
- libayatana-appindicator3-1
- libgtk-3-dev
- libappindicator3-dev
- maintainer: Wiretrustee <dev@wiretrustee.com>
description: Wiretrustee client.
homepage: https://wiretrustee.com/
id: deb
id: wiretrustee-deb
bindir: /usr/bin
builds:
- wiretrustee
@ -76,7 +111,7 @@ nfpms:
- maintainer: Wiretrustee <dev@wiretrustee.com>
description: Wiretrustee client.
homepage: https://wiretrustee.com/
id: rpm
id: wiretrustee-rpm
bindir: /usr/bin
builds:
- wiretrustee

View File

@ -0,0 +1,38 @@
project_name: wiretrustee-ui
builds:
- id: wiretrustee-ui-darwin
dir: client/ui
binary: wiretrustee-ui
env: [CGO_ENABLED=1]
goos:
- darwin
goarch:
- amd64
- arm64
gomips:
- hardfloat
- softfloat
ldflags:
- -s -w -X github.com/wiretrustee/wiretrustee/client/ui/system.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} -X main.builtBy=goreleaser
mod_timestamp: '{{ .CommitTimestamp }}'
tags:
- load_wgnt_from_rsrc
archives:
- builds:
- wiretrustee-ui-darwin
brews:
-
tap:
owner: wiretrustee
name: homebrew-client-ui
token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}"
commit_author:
name: Wiretrustee
email: wiretrustee@wiretrustee.com
description: Wiretrustee project.
download_strategy: CurlDownloadStrategy
homepage: https://wiretrustee.com/
license: "BSD3"

42
Makefile Normal file
View File

@ -0,0 +1,42 @@
PACKAGE_NAME := github.com/wiretrustee/wiretrustee/
GOLANG_CROSS_VERSION ?= v1.17.6
SYSROOT_DIR ?= sysroots
SYSROOT_ARCHIVE ?= sysroots.tar.bz2
.PHONY: sysroot-pack
sysroot-pack:
@tar cf - $(SYSROOT_DIR) -P | pv -s $[$(du -sk $(SYSROOT_DIR) | awk '{print $1}') * 1024] | pbzip2 > $(SYSROOT_ARCHIVE)
.PHONY: sysroot-unpack
sysroot-unpack:
@pv $(SYSROOT_ARCHIVE) | pbzip2 -cd | tar -xf -
.PHONY: release-dry-run
release-dry-run:
@docker run \
--rm \
--privileged \
-e CGO_ENABLED=1 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-w /go/src/$(PACKAGE_NAME) \
goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \
--rm-dist --skip-validate --skip-publish
.PHONY: release
release:
@if [ ! -f ".release-env" ]; then \
echo "\033[91m.release-env is required for release\033[0m";\
exit 1;\
fi
docker run \
--rm \
--privileged \
-e CGO_ENABLED=1 \
--env-file .release-env \
-v /var/run/docker.sock:/var/run/docker.sock \
-v `pwd`:/go/src/$(PACKAGE_NAME) \
-w /go/src/$(PACKAGE_NAME) \
goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \
release --rm-dist

View File

@ -138,15 +138,15 @@ Hosted version:
sudo mv wiretrusee /usr/bin/wiretrustee
chmod +x /usr/bin/wiretrustee
```
After that you may need to add /usr/bin in your PATH environment variable:
After that you may need to add /usr/bin in your PATH environment variable:
````shell
export PATH=$PATH:/usr/bin
````
4. Install and run the service
```shell
```shell
sudo wiretrustee service install
sudo wiretrustee service start
```
```
#### Windows
1. Checkout Wiretrustee [releases](https://github.com/wiretrustee/wiretrustee/releases/latest)

216
client/ui/client_ui.go Normal file
View File

@ -0,0 +1,216 @@
package main
import (
"context"
"flag"
"fmt"
"runtime"
"strings"
"time"
_ "embed"
"github.com/getlantern/systray"
log "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open"
"github.com/wiretrustee/wiretrustee/client/internal"
"github.com/wiretrustee/wiretrustee/client/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func main() {
var daemonAddr string
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731"
}
flag.StringVar(
&daemonAddr, "daemon-addr",
defaultDaemonAddr,
"Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]")
flag.Parse()
client := newServiceClient(daemonAddr)
systray.Run(client.onTrayReady, client.onTrayExit)
}
//go:embed connected.ico
var iconConnected []byte
//go:embed disconnected.ico
var iconDisconnected []byte
type serviceClient struct {
ctx context.Context
addr string
conn proto.DaemonServiceClient
mStatus *systray.MenuItem
mUp *systray.MenuItem
mDown *systray.MenuItem
}
func newServiceClient(addr string) *serviceClient {
s := &serviceClient{
ctx: context.Background(),
addr: addr,
}
return s
}
func (s *serviceClient) up() error {
conn, err := s.client()
if err != nil {
log.Errorf("get client: %v", err)
return err
}
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
if err != nil {
log.Errorf("get service status: %v", err)
return err
}
if status.Status != string(internal.StatusIdle) {
log.Warnf("already connected")
return nil
}
if _, err := s.conn.Up(s.ctx, &proto.UpRequest{}); err != nil {
log.Errorf("up service: %v", err)
return err
}
return nil
}
func (s *serviceClient) down() error {
conn, err := s.client()
if err != nil {
log.Errorf("get client: %v", err)
return err
}
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
if err != nil {
log.Errorf("get service status: %v", err)
return err
}
if status.Status != string(internal.StatusConnected) {
log.Warnf("already down")
return nil
}
if _, err := s.conn.Down(s.ctx, &proto.DownRequest{}); err != nil {
log.Errorf("down service: %v", err)
return err
}
return nil
}
func (s *serviceClient) updateStatus() {
conn, err := s.client()
if err != nil {
log.Errorf("get client: %v", err)
return
}
status, err := conn.Status(s.ctx, &proto.StatusRequest{})
if err != nil {
log.Errorf("get service status: %v", err)
return
}
if status.Status == string(internal.StatusConnected) {
systray.SetTemplateIcon(iconConnected, iconConnected)
s.mStatus.SetTitle("Connected")
s.mUp.Disable()
s.mDown.Enable()
} else {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
s.mStatus.SetTitle("Disconnected")
s.mDown.Disable()
s.mUp.Enable()
}
}
func (s *serviceClient) onTrayReady() {
systray.SetTemplateIcon(iconDisconnected, iconDisconnected)
go func() {
s.mStatus = systray.AddMenuItem("Disconnected", "Disconnected")
s.mStatus.Disable()
systray.AddSeparator()
s.mUp = systray.AddMenuItem("Up", "Up")
s.mDown = systray.AddMenuItem("Down", "Down")
s.mDown.Disable()
mURL := systray.AddMenuItem("Open UI", "wiretrustee website")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Quit the whole app")
s.updateStatus()
ticker := time.NewTicker(time.Second * 3)
defer ticker.Stop()
var err error
for {
select {
case <-mURL.ClickedCh:
err = open.Run("https://app.wiretrustee.com")
case <-s.mUp.ClickedCh:
s.mUp.Disable()
if err = s.up(); err != nil {
s.mUp.Enable()
}
case <-s.mDown.ClickedCh:
s.mDown.Disable()
if err = s.down(); err != nil {
s.mDown.Enable()
}
case <-mQuit.ClickedCh:
systray.Quit()
return
case <-ticker.C:
s.updateStatus()
}
if err != nil {
log.Errorf("process connection: %v", err)
}
}
}()
}
func (s *serviceClient) onTrayExit() {}
func (s *serviceClient) client() (proto.DaemonServiceClient, error) {
if s.conn != nil {
return s.conn, nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
conn, err := grpc.DialContext(
ctx,
strings.TrimPrefix(s.addr, "tcp://"),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithBlock(),
)
if err != nil {
return nil, fmt.Errorf("dial service: %w", err)
}
s.conn = proto.NewDaemonServiceClient(conn)
return s.conn, nil
}

View File

@ -0,0 +1,46 @@
package config
import (
"os"
"runtime"
)
// ClientConfig basic settings for the UI application.
type ClientConfig struct {
configPath string
logFile string
daemonAddr string
}
// Config object with default settings.
//
// We are creating this package to extract utility functions from the cmd package
// reading and parsing the configurations for the client should be done here
func Config() *ClientConfig {
defaultConfigPath := "/etc/wiretrustee/config.json"
defaultLogFile := "/var/log/wiretrustee/client.log"
if runtime.GOOS == "windows" {
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "client.log"
}
defaultDaemonAddr := "unix:///var/run/wiretrustee.sock"
if runtime.GOOS == "windows" {
defaultDaemonAddr = "tcp://127.0.0.1:41731"
}
return &ClientConfig{
configPath: defaultConfigPath,
logFile: defaultLogFile,
daemonAddr: defaultDaemonAddr,
}
}
// DaemonAddr of the gRPC API.
func (c *ClientConfig) DaemonAddr() string {
return c.daemonAddr
}
// LogFile path.
func (c *ClientConfig) LogFile() string {
return c.logFile
}

BIN
client/ui/connected.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
client/ui/disconnected.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -0,0 +1,9 @@
#!/usr/bin/env xdg-open
[Desktop Entry]
Version=1.0
Terminal=false
Type=Application
Name=Wiretrustee UI
Exec=/usr/bin/wiretrustee-ui
Icon=/usr/share/icons/hicolor/256x256/wiretrustee.png
Comment=Wiretrustee client UI.

BIN
client/ui/wiretrustee.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

10
go.mod
View File

@ -28,8 +28,10 @@ require (
)
require (
github.com/getlantern/systray v1.2.1
github.com/magiconair/properties v1.8.5
github.com/rs/xid v1.3.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/stretchr/testify v1.7.0
)
@ -37,6 +39,13 @@ require (
github.com/BurntSushi/toml v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
@ -44,6 +53,7 @@ require (
github.com/mdlayher/netlink v1.4.2 // indirect
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pion/dtls/v2 v2.1.2 // indirect
github.com/pion/logging v0.2.2 // indirect
github.com/pion/mdns v0.0.5 // indirect

19
go.sum
View File

@ -116,6 +116,20 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
@ -128,6 +142,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
@ -353,6 +368,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
@ -406,6 +423,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=