mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-08-25 02:16:38 +00:00
feat: autoinstall required restic version
This commit is contained in:
committed by
garethgeorge
parent
48837b0aa2
commit
b385c01121
@@ -3,7 +3,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
@@ -11,19 +11,18 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
"github.com/garethgeorge/resticui/internal/api"
|
||||
"github.com/garethgeorge/resticui/internal/config"
|
||||
"github.com/garethgeorge/resticui/internal/envopts"
|
||||
"github.com/garethgeorge/resticui/internal/oplog"
|
||||
"github.com/garethgeorge/resticui/internal/orchestrator"
|
||||
static "github.com/garethgeorge/resticui/webui"
|
||||
"github.com/mattn/go-colorable"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
var flagAutoInstallRestic = flag.Bool("auto-install-restic", true, "Automatically install restic if $RESTICUI_")
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -40,10 +39,15 @@ func main() {
|
||||
|
||||
// Configure the HTTP mux
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/", http.FileServer(http.FS(&SubdirFilesystem{FS: static.FS, subdir: "dist"})))
|
||||
|
||||
if box, err := rice.FindBox("webui/dist"); err == nil {
|
||||
mux.Handle("/", http.FileServer(box.HTTPBox()))
|
||||
} else {
|
||||
zap.S().Warnf("Error loading static assets, not serving UI: %v", err)
|
||||
}
|
||||
|
||||
// Create and serve API server
|
||||
oplogFile := path.Join(dataPath(), "oplog.boltdb")
|
||||
oplogFile := path.Join(config.DataDir(), "oplog.boltdb")
|
||||
oplog, err := oplog.NewOpLog(oplogFile)
|
||||
if err != nil {
|
||||
zap.S().Warnf("Operation log may be corrupted, if errors recur delete the file %q and restart. Your backups stored in your repos are safe.", oplogFile)
|
||||
@@ -84,7 +88,7 @@ func main() {
|
||||
|
||||
// Serve the HTTP gateway
|
||||
server := &http.Server{
|
||||
Addr: envopts.BindAddress(),
|
||||
Addr: config.BindAddress(),
|
||||
Handler: mux,
|
||||
}
|
||||
|
||||
@@ -123,7 +127,7 @@ func init() {
|
||||
|
||||
func createConfigProvider() config.ConfigStore {
|
||||
return &config.CachingValidatingStore{
|
||||
ConfigStore: &config.JsonFileStore{Path: envopts.ConfigFilePath()},
|
||||
ConfigStore: &config.JsonFileStore{Path: config.ConfigFilePath()},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,30 +138,9 @@ func onterm(callback func()) {
|
||||
callback()
|
||||
}
|
||||
|
||||
type SubdirFilesystem struct {
|
||||
fs.FS
|
||||
subdir string
|
||||
}
|
||||
func findResticBin() {
|
||||
resticBin := config.ResticBinPath()
|
||||
if resticBin != "" {
|
||||
|
||||
var _ fs.FS = &SubdirFilesystem{}
|
||||
var _ fs.ReadDirFS = &SubdirFilesystem{}
|
||||
|
||||
func (s *SubdirFilesystem) Open(name string) (fs.File, error) {
|
||||
return s.FS.Open(path.Join(s.subdir, name))
|
||||
}
|
||||
|
||||
func (s *SubdirFilesystem) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
readDirFS := s.FS.(fs.ReadDirFS)
|
||||
if readDirFS == nil {
|
||||
return nil, &fs.PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")}
|
||||
}
|
||||
return readDirFS.ReadDir(path.Join(s.subdir, name))
|
||||
}
|
||||
|
||||
func dataPath() string {
|
||||
datahome := os.Getenv("XDG_DATA_HOME")
|
||||
if datahome == "" {
|
||||
datahome = path.Join(os.Getenv("HOME") + "/.local/share")
|
||||
}
|
||||
return path.Join(datahome, "resticui")
|
||||
}
|
||||
@@ -3,6 +3,7 @@ module github.com/garethgeorge/resticui
|
||||
go 1.21.3
|
||||
|
||||
require (
|
||||
github.com/GeertJohan/go.rice v1.0.3
|
||||
github.com/gitploy-io/cronexpr v0.2.2
|
||||
github.com/google/renameio v1.0.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.0.1
|
||||
@@ -11,19 +12,20 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
go.etcd.io/bbolt v1.3.8
|
||||
go.uber.org/zap v1.26.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/daaku/go.zipexe v1.0.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
|
||||
)
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
|
||||
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk=
|
||||
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
|
||||
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/gitploy-io/cronexpr v0.2.2 h1:Au+wK6FqmOLAF7AkW6q4gnrNXTe3rEW97XFZ4chy0xs=
|
||||
@@ -19,15 +25,19 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
||||
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/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA=
|
||||
go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
@@ -38,19 +48,29 @@ go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
|
||||
google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
|
||||
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4 h1:W12Pwm4urIbRdGhMEg2NM9O3TWKjNcxQhs46V0ypf/k=
|
||||
google.golang.org/genproto v0.0.0-20231127180814-3a041ad873d4/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4 h1:ZcOkrmX74HbKFYnpPY8Qsw93fC29TbJXspYKaBkSXDQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231127180814-3a041ad873d4/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 h1:DC7wcm+i+P1rN3Ff07vL+OndGg5OhNddHyTA+ocPqYE=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
|
||||
Regular → Executable
+6
-3
@@ -1,5 +1,8 @@
|
||||
#! /bin/sh
|
||||
set -x
|
||||
|
||||
(cd proto && ./build.sh)
|
||||
|
||||
go build ./..
|
||||
(cd proto && ./update.sh)
|
||||
(cd webui && npm run build)
|
||||
rm -f resticui
|
||||
go build ./cmd/resticui
|
||||
rice append --exec resticui
|
||||
|
||||
Regular → Executable
+2
-1
@@ -1,5 +1,6 @@
|
||||
#! /bin/sh
|
||||
set -x
|
||||
|
||||
go install github.com/GeertJohan/go.rice/rice@latest
|
||||
go install github.com/GeertJohan/go.rice@latest
|
||||
pip install lastversion
|
||||
python -m pip install lastversion
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#! /bin/sh
|
||||
set -x
|
||||
|
||||
DEBUG=1 go run ./cmd/resticui
|
||||
@@ -31,6 +31,7 @@ var _ v1.ResticUIServer = &Server{}
|
||||
|
||||
func NewServer(config config.ConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server {
|
||||
s := &Server{
|
||||
config: config,
|
||||
orchestrator: orchestrator,
|
||||
oplog: oplog,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package envopts
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,8 +7,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
EnvVarConfigPath = "RESTICUI_CONFIG_PATH"
|
||||
EnvVarDataDir = "RESTICUI_DATA_DIR"
|
||||
EnvVarBindAddress = "RESTICUI_PORT"
|
||||
EnvVarBinPath = "RESTICUI_RESTIC_BIN_PATH"
|
||||
)
|
||||
|
||||
func ConfigFilePath() string {
|
||||
if val := os.Getenv("RESTICUI_CONFIG_PATH"); val != "" {
|
||||
if val := os.Getenv(EnvVarConfigPath); val != "" {
|
||||
return val
|
||||
}
|
||||
if val := os.Getenv("XDG_CONFIG_HOME"); val != "" {
|
||||
@@ -18,7 +25,7 @@ func ConfigFilePath() string {
|
||||
}
|
||||
|
||||
func DataDir() string {
|
||||
if val := os.Getenv("RESTICUI_DATA_DIR"); val != "" {
|
||||
if val := os.Getenv(EnvVarDataDir); val != "" {
|
||||
return val
|
||||
}
|
||||
if val := os.Getenv("XDG_DATA_HOME"); val != "" {
|
||||
@@ -28,7 +35,7 @@ func DataDir() string {
|
||||
}
|
||||
|
||||
func BindAddress() string {
|
||||
if val := os.Getenv("RESTICUI_PORT"); val != "" {
|
||||
if val := os.Getenv(EnvVarBindAddress); val != "" {
|
||||
if !strings.Contains(val, ":") {
|
||||
return ":" + val
|
||||
}
|
||||
@@ -37,6 +44,13 @@ func BindAddress() string {
|
||||
return ":9898"
|
||||
}
|
||||
|
||||
func ResticBinPath() string {
|
||||
if val := os.Getenv("RESTICUI_RESTIC_BIN_PATH"); val != "" {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getHomeDir() string {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
|
||||
v1 "github.com/garethgeorge/resticui/gen/go/v1"
|
||||
test "github.com/garethgeorge/resticui/internal/test/helpers"
|
||||
"github.com/garethgeorge/resticui/pkg/restic"
|
||||
test "github.com/garethgeorge/resticui/test/helpers"
|
||||
)
|
||||
|
||||
func TestBackup(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package resticinstaller
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/garethgeorge/resticui/internal/config"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrResticNotFound = errors.New("no restic binary")
|
||||
)
|
||||
|
||||
var (
|
||||
RequiredResticVersion = "0.16.2"
|
||||
|
||||
findResticMu sync.Mutex
|
||||
didTryInstall bool
|
||||
)
|
||||
|
||||
func resticDownloadURL(version string) string {
|
||||
return fmt.Sprintf("https://github.com/restic/restic/releases/download/v%v/restic_%v_%v_%v.bz2", version, version, runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func downloadFile(url string, path string) error {
|
||||
// Download ur as a file and save it to path
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http GET %v: %w", url, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var body io.Reader = resp.Body
|
||||
if strings.HasSuffix(url, ".bz2") {
|
||||
body = bzip2.NewReader(resp.Body)
|
||||
}
|
||||
|
||||
out, err := os.Create(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create file %v: %w", path, err)
|
||||
}
|
||||
defer out.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create file %v: %w", path, err)
|
||||
}
|
||||
_, err = io.Copy(out, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy response body to file %v: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadExecutable(url string, path string) error {
|
||||
if err := downloadFile(url, path + ".tmp"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(path + ".tmp", 0755); err != nil {
|
||||
return fmt.Errorf("chmod executable %v: %w", path, err)
|
||||
}
|
||||
|
||||
if err := os.Rename(path + ".tmp", path); err != nil {
|
||||
return fmt.Errorf("rename %v.tmp to %v: %w", path, path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindOrInstallResticBinary first tries to find the restic binary if provided as an environment variable. Otherwise it downloads restic if not already installed.
|
||||
func FindOrInstallResticBinary() (string, error) {
|
||||
findResticMu.Lock()
|
||||
defer findResticMu.Unlock()
|
||||
|
||||
// Check if restic is provided.
|
||||
resticBin := config.ResticBinPath()
|
||||
if resticBin != "" {
|
||||
if _, err := os.Stat(resticBin); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return "", fmt.Errorf("stat(%v): %w", resticBin, err)
|
||||
}
|
||||
return "", fmt.Errorf("no binary found at path %v: %w", resticBin, ErrResticNotFound)
|
||||
}
|
||||
return resticBin, nil
|
||||
}
|
||||
|
||||
// Check for restic installation in data directory.
|
||||
resticInstallPath := path.Join(config.DataDir(), fmt.Sprintf("restic-%v", RequiredResticVersion))
|
||||
if _, err := os.Stat(resticInstallPath); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return "", fmt.Errorf("could not stat restic binary at %v: %w", resticBin, err)
|
||||
}
|
||||
|
||||
if didTryInstall {
|
||||
return "", fmt.Errorf("already tried to install: %w", ErrResticNotFound)
|
||||
}
|
||||
didTryInstall = true
|
||||
|
||||
if err := os.MkdirAll(path.Dir(resticInstallPath), 0755); err != nil {
|
||||
return "", fmt.Errorf("create restic install directory %v: %w", path.Dir(resticInstallPath), err)
|
||||
}
|
||||
|
||||
if err := downloadExecutable(resticDownloadURL(RequiredResticVersion), resticInstallPath); err != nil {
|
||||
return "", fmt.Errorf("download restic version %v: %w", RequiredResticVersion, err)
|
||||
}
|
||||
}
|
||||
|
||||
return resticInstallPath, nil
|
||||
}
|
||||
@@ -27,14 +27,14 @@ type Repo struct {
|
||||
}
|
||||
|
||||
// NewRepo instantiates a new repository. TODO: should not accept a v1.Repo, should instead be configured by parameters.
|
||||
func NewRepo(repo *v1.Repo, opts ...GenericOption) *Repo {
|
||||
func NewRepo(resticBin string, repo *v1.Repo, opts ...GenericOption) *Repo {
|
||||
opt := &GenericOpts{}
|
||||
for _, o := range opts {
|
||||
o(opt)
|
||||
}
|
||||
|
||||
return &Repo{
|
||||
cmd: "restic", // TODO: configurable binary path
|
||||
cmd: resticBin, // TODO: configurable binary path
|
||||
repo: repo,
|
||||
initialized: false,
|
||||
extraArgs: opt.extraArgs,
|
||||
|
||||
+11
-11
@@ -10,14 +10,14 @@ import (
|
||||
"testing"
|
||||
|
||||
v1 "github.com/garethgeorge/resticui/gen/go/v1"
|
||||
test "github.com/garethgeorge/resticui/internal/test/helpers"
|
||||
"github.com/garethgeorge/resticui/test/helpers"
|
||||
)
|
||||
|
||||
func TestResticInit(t *testing.T) {
|
||||
t.Parallel()
|
||||
repo := t.TempDir()
|
||||
|
||||
r := NewRepo(&v1.Repo{
|
||||
r := NewRepo(helpers.ResticBinary(t), &v1.Repo{
|
||||
Id: "test",
|
||||
Uri: repo,
|
||||
Password: "test",
|
||||
@@ -33,7 +33,7 @@ func TestResticBackup(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
|
||||
// create a new repo with cache disabled for testing
|
||||
r := NewRepo(&v1.Repo{
|
||||
r := NewRepo(helpers.ResticBinary(t), &v1.Repo{
|
||||
Id: "test",
|
||||
Uri: repo,
|
||||
Password: "test",
|
||||
@@ -42,8 +42,8 @@ func TestResticBackup(t *testing.T) {
|
||||
t.Fatalf("failed to init repo: %v", err)
|
||||
}
|
||||
|
||||
testData := test.CreateTestData(t)
|
||||
testData2 := test.CreateTestData(t)
|
||||
testData := helpers.CreateTestData(t)
|
||||
testData2 := helpers.CreateTestData(t)
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
@@ -107,7 +107,7 @@ func TestSnapshot(t *testing.T) {
|
||||
|
||||
repo := t.TempDir()
|
||||
|
||||
r := NewRepo(&v1.Repo{
|
||||
r := NewRepo(helpers.ResticBinary(t), &v1.Repo{
|
||||
Id: "test",
|
||||
Uri: repo,
|
||||
Password: "test",
|
||||
@@ -116,7 +116,7 @@ func TestSnapshot(t *testing.T) {
|
||||
t.Fatalf("failed to init repo: %v", err)
|
||||
}
|
||||
|
||||
testData := test.CreateTestData(t)
|
||||
testData := helpers.CreateTestData(t)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err := r.Backup(context.Background(), nil, WithBackupPaths(testData), WithBackupTags(fmt.Sprintf("tag%d", i)))
|
||||
@@ -167,7 +167,7 @@ func TestLs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := t.TempDir()
|
||||
r := NewRepo(&v1.Repo{
|
||||
r := NewRepo(helpers.ResticBinary(t), &v1.Repo{
|
||||
Id: "test",
|
||||
Uri: repo,
|
||||
Password: "test",
|
||||
@@ -176,7 +176,7 @@ func TestLs(t *testing.T) {
|
||||
t.Fatalf("failed to init repo: %v", err)
|
||||
}
|
||||
|
||||
testData := test.CreateTestData(t)
|
||||
testData := helpers.CreateTestData(t)
|
||||
|
||||
snapshot, err := r.Backup(context.Background(), nil, WithBackupPaths(testData))
|
||||
if err != nil {
|
||||
@@ -198,7 +198,7 @@ func TestResticForget(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
repo := t.TempDir()
|
||||
r := NewRepo(&v1.Repo{
|
||||
r := NewRepo(helpers.ResticBinary(t), &v1.Repo{
|
||||
Id: "test",
|
||||
Uri: repo,
|
||||
Password: "test",
|
||||
@@ -207,7 +207,7 @@ func TestResticForget(t *testing.T) {
|
||||
t.Fatalf("failed to init repo: %v", err)
|
||||
}
|
||||
|
||||
testData := test.CreateTestData(t)
|
||||
testData := helpers.CreateTestData(t)
|
||||
|
||||
ids := make([]string, 0)
|
||||
for i := 0; i < 10; i++ {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/garethgeorge/resticui/internal/resticinstaller"
|
||||
)
|
||||
|
||||
func ResticBinary(t *testing.T) string {
|
||||
binPath, err := resticinstaller.FindOrInstallResticBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("find restic binary: %v", err)
|
||||
}
|
||||
return binPath
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package test
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,8 +0,0 @@
|
||||
package static
|
||||
|
||||
import (
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed dist/*.js dist/*.css dist/*.html
|
||||
var FS embed.FS
|
||||
Reference in New Issue
Block a user