Repository: restic/restic
Pull request #4480 (2023-09-26 16:56:58)

Support reading basic auth credentials for REST server from environment variables
What does this PR change? What problem does it solve?
-----------------------------------------------------
This PR adds an `ApplyEnvironment` function to the REST backend, similar to other backends, that reads `REST_USERNAME` and `REST_PASSWORD` variables if the repository URL does not already contain the username or password. This makes it possible to pass secrets to restic without including them in the repository string or repository file. 

I and several other people have run into this problem when configuring restic in NixOS configurations that apply to more than one system. In such cases, the secrets cannot be provided in the repository file, since the repository string is templated at build time to include the target system's hostname (for example: `repository = "rest:http://10.20.1.2:8000/${config.networking.hostName}/";`). The secrets also can't be written in the repository string itself, because the repository string appears in world-readable files in `/nix/store`, and would be publically accessible from the git repository that contains the configuration files.

Checklist
---------

<!--
You do not need to check all the boxes below all at once. Feel free to take
your time and add more commits. If you're done and ready for review, please
check the last box. Enable a checkbox by replacing [ ] with [x].
-->

- [x] I have read the [contribution guidelines](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#providing-patches).
- [x] I have [enabled maintainer edits](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- [ ] I have added tests for all code changes. (note: I looked into adding tests, but none of the other backends test `ApplyEnvironment`, so I'm unsure if I should)
- [x] I have added documentation for relevant changes (in the manual).
- [x] There's a new file in `changelog/unreleased/` that describes the changes for our users (see [template](https://github.com/restic/restic/blob/master/changelog/TEMPLATE)).
- [x] I have run `gofmt` on the code in all commits.
- [x] All commit messages are formatted in the same style as [the other commits in the repo](https://github.com/restic/restic/blob/master/CONTRIBUTING.md#git-commits).
- [x] I'm done! This pull request is ready for review.


MichaelEischer: Although, I'm wondering: if you can safely pass `$REST_USERNAME` etc. to restic, then using `$RESTIC_REPOSITORY` (including username and password) should also work? Maybe it would be better to extend the nix package? (Can you provide a link to it?)
AgathaSorceress: > Although, I'm wondering: if you can safely pass `$REST_USERNAME` etc. to restic, then using `$RESTIC_REPOSITORY` (including username and password) should also work? Maybe it would be better to extend the nix package? (Can you provide a link to it?)
> 
> E.g. `--repository-file <( echo "${VARIABLE}")` could be used to let bash/zsh/other shells generate the URL at runtime and pass it to restic using a temporary file descriptor.

I'm using the [`services.restic.backups`](https://search.nixos.org/options?channel=23.05&from=0&size=50&sort=relevance&type=packages&query=services.restic.backups) module to configure restic, which provides an `environmentFile` option. Using that option, I can have a file like
```env
REST_USERNAME=username
REST_PASSWORD=foobar123
```
which gets rsynced during deployment to the target system, and does not end up in the nix store or the git repo, due to only being referenced by a quoted absolute path.

the NixOS restic module creates a systemd service and timer, and in theory it can be hacked together by disabling the upstream module and making my own, where the service's `ExecStart` is set to run something like `sh -c 'RESTIC_REPOSITORY=rest:http://username:$(cat /var/lib/secrets/restic-password)@10.20.1.2:8000/${config.networking.hostName}/ ${ (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ]) ++ pruneCmd }'` and removing the `Environment="RESTIC_REPOSITORY=..."` line, but that feels like significantly more layers of duct tape than having 2 separate env variables for it, and would also mean that the module needs to be manually updated every time the upstream module changes.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md
index 532983773..b0741e3f8 100644
--- a/.github/ISSUE_TEMPLATE/Bug.md
+++ b/.github/ISSUE_TEMPLATE/Bug.md
@@ -32,30 +32,23 @@ Output of `restic version`
 --------------------------
 
 
-
-What backend/service did you use to store the repository?
----------------------------------------------------------
-
-
-
-Problem description / Steps to reproduce
-----------------------------------------
+How did you run restic exactly?
+-------------------------------
 
 <!--
 This section should include at least:
 
- * A description of the problem you are having with restic.
-
  * The complete command line and any environment variables you used to
    configure restic's backend access. Make sure to replace sensitive values!
 
  * The output of the commands, what restic prints gives may give us much
    information to diagnose the problem!
-
- * The more time you spend describing an easy way to reproduce the behavior (if
-   this is possible), the easier it is for the project developers to fix it!
 -->
 
+What backend/server/service did you use to store the repository?
+----------------------------------------------------------------
+
+
 
 Expected behavior
 -----------------
@@ -72,13 +65,23 @@ In this section, please try to concentrate on observations, so only describe
 what you observed directly.
 -->
 
-Do you have any idea what may have caused this?
------------------------------------------------
+Steps to reproduce the behavior
+-------------------------------
 
 <!--
-Did something noteworthy happen on your system, Internet connection, backend services, etc?
+The more time you spend describing an easy way to reproduce the behavior (if
+this is possible), the easier it is for the project developers to fix it!
 -->
 
+Do you have any idea what may have caused this?
+-----------------------------------------------
+
+
+
+Do you have an idea how to solve the issue?
+-------------------------------------------
+
+
 
 Did restic help you today? Did it make you happy in any way?
 ------------------------------------------------------------
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index dba696d44..b9398ee86 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -22,10 +22,10 @@ jobs:
 
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v4
+        uses: actions/checkout@v3
 
       - name: Log in to the Container registry
-        uses: docker/login-action@b4bedf8053341df3b5a9f9e0f2cf4e79e27360c6
+        uses: docker/login-action@cf8514a65188af1d4f94f8c28a7a4153af1088ce
         with:
           registry: ${{ env.REGISTRY }}
           username: ${{ github.actor }}
@@ -42,10 +42,10 @@ jobs:
             type=semver,pattern={{major}}.{{minor}}
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3
+        uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226
+        uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c
 
       - name: Build and push Docker image
         uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4
diff --git a/changelog/unreleased/issue-4128 b/changelog/unreleased/issue-4128
deleted file mode 100644
index 7d331b6b3..000000000
--- a/changelog/unreleased/issue-4128
+++ /dev/null
@@ -1,8 +0,0 @@
-Enhancement: Automatically adjust GOMAXPROCS in resource-constrained containers
-
-When running restic in a Linux container with CPU-usage limits, restic now
-automatically adjusts GOMAXPROCS. This helps to reduce the memory consumption
-on hosts with many CPU cores.
-
-https://github.com/restic/restic/issues/4128
-https://github.com/restic/restic/pull/4485
diff --git a/changelog/unreleased/issue-4513 b/changelog/unreleased/issue-4513
deleted file mode 100644
index aa79f2848..000000000
--- a/changelog/unreleased/issue-4513
+++ /dev/null
@@ -1,8 +0,0 @@
-Bugfix: Make `key list` command honor `--no-lock`
-
-This allows to determine which keys a repo can be accessed by without the
-need for having write access (e.g., read-only sftp access, filesystem
-snapshot).
-
-https://github.com/restic/restic/issues/4513
-https://github.com/restic/restic/pull/4514
diff --git a/changelog/unreleased/pull-299 b/changelog/unreleased/pull-299
deleted file mode 100644
index aae5c0e05..000000000
--- a/changelog/unreleased/pull-299
+++ /dev/null
@@ -1,6 +0,0 @@
-Enhancement: Show progress bar while loading the index
-
-Restic did not provide any feedback while loading index files. Now there is a progress bar for the index loading process. 
-
-https://github.com/restic/restic/issues/229
-https://github.com/restic/restic/pull/4419
diff --git a/changelog/unreleased/pull-4480 b/changelog/unreleased/pull-4480
new file mode 100644
index 000000000..bafbf62e8
--- /dev/null
+++ b/changelog/unreleased/pull-4480
@@ -0,0 +1,10 @@
+Enhancement: Allow setting REST password and username via environment variables
+
+Previously, it was only possible to specify the REST server username and
+password in the repository URL, or using the `--repository-file` option. This
+meant it was not possible to use authentication in contexts where the repository
+URL is public and parts of it are templated by other software. Restic now
+allows setting the username and password using the `RESTIC_REST_USERNAME` and
+`RESTIC_REST_PASSWORD` variables.
+
+https://github.com/restic/restic/pull/4480
diff --git a/changelog/unreleased/pull-4511 b/changelog/unreleased/pull-4511
deleted file mode 100644
index 60c5659b6..000000000
--- a/changelog/unreleased/pull-4511
+++ /dev/null
@@ -1,7 +0,0 @@
-Enhancement: Include inode numbers in JSON output for find and ls
-
-Restic used to omit the inode numbers in the JSON messages emitted for nodes by
-the `ls` command as well as for matches by the `find` command. It now includes
-those values whenever they are available.
-
-https://github.com/restic/restic/pull/4511
diff --git a/cmd/restic/cmd_backup.go b/cmd/restic/cmd_backup.go
index e476ae7b8..6b5706855 100644
--- a/cmd/restic/cmd_backup.go
+++ b/cmd/restic/cmd_backup.go
@@ -546,10 +546,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
 	if !gopts.JSON {
 		progressPrinter.V("load index files")
 	}
-
-	bar := newIndexTerminalProgress(gopts.Quiet, gopts.JSON, term)
-
-	err = repo.LoadIndex(ctx, bar)
+	err = repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_cat.go b/cmd/restic/cmd_cat.go
index 238614cd0..97789d271 100644
--- a/cmd/restic/cmd_cat.go
+++ b/cmd/restic/cmd_cat.go
@@ -169,8 +169,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
 		return err
 
 	case "blob":
-		bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-		err = repo.LoadIndex(ctx, bar)
+		err = repo.LoadIndex(ctx)
 		if err != nil {
 			return err
 		}
@@ -198,8 +197,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
 			return errors.Fatalf("could not find snapshot: %v\n", err)
 		}
 
-		bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-		err = repo.LoadIndex(ctx, bar)
+		err = repo.LoadIndex(ctx)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/restic/cmd_check.go b/cmd/restic/cmd_check.go
index 5f03c446b..3c4c9daa9 100644
--- a/cmd/restic/cmd_check.go
+++ b/cmd/restic/cmd_check.go
@@ -226,8 +226,7 @@ func runCheck(ctx context.Context, opts CheckOptions, gopts GlobalOptions, args
 	}
 
 	Verbosef("load indexes\n")
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	hints, errs := chkr.LoadIndex(ctx, bar)
+	hints, errs := chkr.LoadIndex(ctx)
 
 	errorsFound := false
 	suggestIndexRebuild := false
diff --git a/cmd/restic/cmd_copy.go b/cmd/restic/cmd_copy.go
index f31c17adb..eaa0ef81a 100644
--- a/cmd/restic/cmd_copy.go
+++ b/cmd/restic/cmd_copy.go
@@ -99,13 +99,12 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
 	}
 
 	debug.Log("Loading source index")
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err := srcRepo.LoadIndex(ctx, bar); err != nil {
+	if err := srcRepo.LoadIndex(ctx); err != nil {
 		return err
 	}
-	bar = newIndexProgress(gopts.Quiet, gopts.JSON)
+
 	debug.Log("Loading destination index")
-	if err := dstRepo.LoadIndex(ctx, bar); err != nil {
+	if err := dstRepo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_debug.go b/cmd/restic/cmd_debug.go
index bf05c448d..a54200c45 100644
--- a/cmd/restic/cmd_debug.go
+++ b/cmd/restic/cmd_debug.go
@@ -134,7 +134,7 @@ func printPacks(ctx context.Context, repo *repository.Repository, wr io.Writer)
 }
 
 func dumpIndexes(ctx context.Context, repo restic.Repository, wr io.Writer) error {
-	return index.ForAllIndexes(ctx, repo.Backend(), repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
+	return index.ForAllIndexes(ctx, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
 		Printf("index_id: %v\n", id)
 		if err != nil {
 			return err
@@ -469,8 +469,7 @@ func runDebugExamine(ctx context.Context, gopts GlobalOptions, args []string) er
 		}
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	err = repo.LoadIndex(ctx, bar)
+	err = repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_diff.go b/cmd/restic/cmd_diff.go
index 125904068..accd55db2 100644
--- a/cmd/restic/cmd_diff.go
+++ b/cmd/restic/cmd_diff.go
@@ -363,8 +363,8 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
 	if !gopts.JSON {
 		Verbosef("comparing snapshot %v to %v:\n\n", sn1.ID().Str(), sn2.ID().Str())
 	}
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_dump.go b/cmd/restic/cmd_dump.go
index 8b9fa9624..325607017 100644
--- a/cmd/restic/cmd_dump.go
+++ b/cmd/restic/cmd_dump.go
@@ -152,8 +152,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
 		return errors.Fatalf("failed to find snapshot: %v", err)
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	err = repo.LoadIndex(ctx, bar)
+	err = repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_find.go b/cmd/restic/cmd_find.go
index abcf4f829..181d8595d 100644
--- a/cmd/restic/cmd_find.go
+++ b/cmd/restic/cmd_find.go
@@ -126,6 +126,7 @@ func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
 
 		// Make the following attributes disappear
 		Name               byte `json:"name,omitempty"`
+		Inode              byte `json:"inode,omitempty"`
 		ExtendedAttributes byte `json:"extended_attributes,omitempty"`
 		Device             byte `json:"device,omitempty"`
 		Content            byte `json:"content,omitempty"`
@@ -588,8 +589,8 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
 	if err != nil {
 		return err
 	}
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_key.go b/cmd/restic/cmd_key.go
index ab41b4be3..62521d762 100644
--- a/cmd/restic/cmd_key.go
+++ b/cmd/restic/cmd_key.go
@@ -212,13 +212,10 @@ func runKey(ctx context.Context, gopts GlobalOptions, args []string) error {
 
 	switch args[0] {
 	case "list":
-		if !gopts.NoLock {
-			var lock *restic.Lock
-			lock, ctx, err = lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
-			defer unlockRepo(lock)
-			if err != nil {
-				return err
-			}
+		lock, ctx, err := lockRepo(ctx, repo, gopts.RetryLock, gopts.JSON)
+		defer unlockRepo(lock)
+		if err != nil {
+			return err
 		}
 
 		return listKeys(ctx, repo, gopts)
diff --git a/cmd/restic/cmd_list.go b/cmd/restic/cmd_list.go
index 5974da9ac..bd02cedc7 100644
--- a/cmd/restic/cmd_list.go
+++ b/cmd/restic/cmd_list.go
@@ -63,7 +63,7 @@ func runList(ctx context.Context, cmd *cobra.Command, gopts GlobalOptions, args
 	case "locks":
 		t = restic.LockFile
 	case "blobs":
-		return index.ForAllIndexes(ctx, repo.Backend(), repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
+		return index.ForAllIndexes(ctx, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
 			if err != nil {
 				return err
 			}
diff --git a/cmd/restic/cmd_ls.go b/cmd/restic/cmd_ls.go
index fa2f9fbc2..256c9e002 100644
--- a/cmd/restic/cmd_ls.go
+++ b/cmd/restic/cmd_ls.go
@@ -87,7 +87,6 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
 		ModTime     time.Time   `json:"mtime,omitempty"`
 		AccessTime  time.Time   `json:"atime,omitempty"`
 		ChangeTime  time.Time   `json:"ctime,omitempty"`
-		Inode       uint64      `json:"inode,omitempty"`
 		StructType  string      `json:"struct_type"` // "node"
 
 		size uint64 // Target for Size pointer.
@@ -103,7 +102,6 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
 		ModTime:     node.ModTime,
 		AccessTime:  node.AccessTime,
 		ChangeTime:  node.ChangeTime,
-		Inode:       node.Inode,
 		StructType:  "node",
 	}
 	// Always print size for regular files, even when empty,
@@ -175,8 +173,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
 		return err
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_mount.go b/cmd/restic/cmd_mount.go
index 04c072daf..ec3662d5c 100644
--- a/cmd/restic/cmd_mount.go
+++ b/cmd/restic/cmd_mount.go
@@ -130,8 +130,7 @@ func runMount(ctx context.Context, opts MountOptions, gopts GlobalOptions, args
 		}
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	err = repo.LoadIndex(ctx, bar)
+	err = repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_prune.go b/cmd/restic/cmd_prune.go
index 638a0de5e..e4c2c7b29 100644
--- a/cmd/restic/cmd_prune.go
+++ b/cmd/restic/cmd_prune.go
@@ -187,8 +187,7 @@ func runPruneWithRepo(ctx context.Context, opts PruneOptions, gopts GlobalOption
 
 	Verbosef("loading indexes...\n")
 	// loading the index before the snapshots is ok, as we use an exclusive lock here
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	err := repo.LoadIndex(ctx, bar)
+	err := repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_recover.go b/cmd/restic/cmd_recover.go
index 63084dd5f..85dcc23d7 100644
--- a/cmd/restic/cmd_recover.go
+++ b/cmd/restic/cmd_recover.go
@@ -58,8 +58,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error {
 	}
 
 	Verbosef("load index files\n")
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
@@ -74,7 +73,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions) error {
 	})
 
 	Verbosef("load %d trees\n", len(trees))
-	bar = newProgressMax(!gopts.Quiet, uint64(len(trees)), "trees loaded")
+	bar := newProgressMax(!gopts.Quiet, uint64(len(trees)), "trees loaded")
 	for id := range trees {
 		tree, err := restic.LoadTree(ctx, repo, id)
 		if err != nil {
diff --git a/cmd/restic/cmd_repair_index.go b/cmd/restic/cmd_repair_index.go
index 622c77801..b1905836a 100644
--- a/cmd/restic/cmd_repair_index.go
+++ b/cmd/restic/cmd_repair_index.go
@@ -88,7 +88,7 @@ func rebuildIndex(ctx context.Context, opts RepairIndexOptions, gopts GlobalOpti
 	} else {
 		Verbosef("loading indexes...\n")
 		mi := index.NewMasterIndex()
-		err := index.ForAllIndexes(ctx, repo.Backend(), repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
+		err := index.ForAllIndexes(ctx, repo, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
 			if err != nil {
 				Warnf("removing invalid index %v: %v\n", id, err)
 				obsoleteIndexes = append(obsoleteIndexes, id)
diff --git a/cmd/restic/cmd_repair_snapshots.go b/cmd/restic/cmd_repair_snapshots.go
index 720523762..03736795c 100644
--- a/cmd/restic/cmd_repair_snapshots.go
+++ b/cmd/restic/cmd_repair_snapshots.go
@@ -89,8 +89,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
 		return err
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err := repo.LoadIndex(ctx, bar); err != nil {
+	if err := repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go
index 494c6b86a..9bce90a26 100644
--- a/cmd/restic/cmd_restore.go
+++ b/cmd/restic/cmd_restore.go
@@ -173,8 +173,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
 		return errors.Fatalf("failed to find snapshot: %v", err)
 	}
 
-	bar := newIndexTerminalProgress(gopts.Quiet, gopts.JSON, term)
-	err = repo.LoadIndex(ctx, bar)
+	err = repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/restic/cmd_rewrite.go b/cmd/restic/cmd_rewrite.go
index 9d9ab5d5d..c08797c48 100644
--- a/cmd/restic/cmd_rewrite.go
+++ b/cmd/restic/cmd_rewrite.go
@@ -212,8 +212,7 @@ func runRewrite(ctx context.Context, opts RewriteOptions, gopts GlobalOptions, a
 		return err
 	}
 
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/cmd_stats.go b/cmd/restic/cmd_stats.go
index f16b59d3c..a7ecd438f 100644
--- a/cmd/restic/cmd_stats.go
+++ b/cmd/restic/cmd_stats.go
@@ -98,8 +98,8 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
 	if err != nil {
 		return err
 	}
-	bar := newIndexProgress(gopts.Quiet, gopts.JSON)
-	if err = repo.LoadIndex(ctx, bar); err != nil {
+
+	if err = repo.LoadIndex(ctx); err != nil {
 		return err
 	}
 
diff --git a/cmd/restic/main.go b/cmd/restic/main.go
index 8b5e64f44..17b9c468d 100644
--- a/cmd/restic/main.go
+++ b/cmd/restic/main.go
@@ -9,13 +9,13 @@ import (
 	"runtime"
 	godebug "runtime/debug"
 
-	"github.com/spf13/cobra"
-	_ "go.uber.org/automaxprocs"
-
 	"github.com/restic/restic/internal/debug"
-	"github.com/restic/restic/internal/errors"
 	"github.com/restic/restic/internal/options"
 	"github.com/restic/restic/internal/restic"
+
+	"github.com/spf13/cobra"
+
+	"github.com/restic/restic/internal/errors"
 )
 
 // cmdRoot is the base command when no other command has been specified.
diff --git a/cmd/restic/progress.go b/cmd/restic/progress.go
index fe4f654d0..4b6025a54 100644
--- a/cmd/restic/progress.go
+++ b/cmd/restic/progress.go
@@ -29,12 +29,13 @@ func calculateProgressInterval(show bool, json bool) time.Duration {
 	return interval
 }
 
-// newTerminalProgressMax returns a progress.Counter that prints to stdout or terminal if provided.
-func newGenericProgressMax(show bool, max uint64, description string, print func(status string)) *progress.Counter {
+// newProgressMax returns a progress.Counter that prints to stdout.
+func newProgressMax(show bool, max uint64, description string) *progress.Counter {
 	if !show {
 		return nil
 	}
 	interval := calculateProgressInterval(show, false)
+	canUpdateStatus := stdoutCanUpdateStatus()
 
 	return progress.NewCounter(interval, max, func(v uint64, max uint64, d time.Duration, final bool) {
 		var status string
@@ -46,28 +47,14 @@ func newGenericProgressMax(show bool, max uint64, description string, print func
 				ui.FormatDuration(d), ui.FormatPercent(v, max), v, max, description)
 		}
 
-		print(status)
+		printProgress(status, canUpdateStatus)
 		if final {
 			fmt.Print("\n")
 		}
 	})
 }
 
-func newTerminalProgressMax(show bool, max uint64, description string, term *termstatus.Terminal) *progress.Counter {
-	return newGenericProgressMax(show, max, description, func(status string) {
-		term.SetStatus([]string{status})
-	})
-}
-
-// newProgressMax calls newTerminalProgress without a terminal (print to stdout)
-func newProgressMax(show bool, max uint64, description string) *progress.Counter {
-	return newGenericProgressMax(show, max, description, printProgress)
-}
-
-func printProgress(status string) {
-
-	canUpdateStatus := stdoutCanUpdateStatus()
-
+func printProgress(status string, canUpdateStatus bool) {
 	w := stdoutTerminalWidth()
 	if w > 0 {
 		if w < 3 {
@@ -96,11 +83,3 @@ func printProgress(status string) {
 
 	_, _ = os.Stdout.Write([]byte(clear + status + carriageControl))
 }
-
-func newIndexProgress(quiet bool, json bool) *progress.Counter {
-	return newProgressMax(!quiet && !json, 0, "index files loaded")
-}
-
-func newIndexTerminalProgress(quiet bool, json bool, term *termstatus.Terminal) *progress.Counter {
-	return newTerminalProgressMax(!quiet && !json, 0, "index files loaded", term)
-}
diff --git a/doc/030_preparing_a_new_repo.rst b/doc/030_preparing_a_new_repo.rst
index 04c189d07..02c406e52 100644
--- a/doc/030_preparing_a_new_repo.rst
+++ b/doc/030_preparing_a_new_repo.rst
@@ -209,6 +209,14 @@ are some more examples:
     $ restic -r rest:https://user:pass@host:8000/ init
     $ restic -r rest:https://user:pass@host:8000/my_backup_repo/ init
 
+The server username and password can be specified using environment
+variables as well:
+
+.. code-block:: console
+
+    $ export RESTIC_REST_USERNAME=<MY_REST_SERVER_USERNAME>
+    $ export RESTIC_REST_PASSWORD=<MY_REST_SERVER_PASSWORD>
+
 If you use TLS, restic will use the system's CA certificates to verify the
 server certificate. When the verification fails, restic refuses to proceed and
 exits with an error. If you have your own self-signed certificate, or a custom
diff --git a/doc/040_backup.rst b/doc/040_backup.rst
index 1655e7eed..a3b280476 100644
--- a/doc/040_backup.rst
+++ b/doc/040_backup.rst
@@ -593,9 +593,16 @@ environment variables. The following lists these environment variables:
     AWS_PROFILE                         Amazon credentials profile (alternative to specifying key and region)
     AWS_SHARED_CREDENTIALS_FILE         Location of the AWS CLI shared credentials file (default: ~/.aws/credentials)
 
-    ST_AUTH                             Auth URL for keystone v1 authentication
-    ST_USER                             Username for keystone v1 authentication
-    ST_KEY                              Password for keystone v1 authentication
+    AZURE_ACCOUNT_NAME                  Account name for Azure
+    AZURE_ACCOUNT_KEY                   Account key for Azure
+    AZURE_ACCOUNT_SAS                   Shared access signatures (SAS) for Azure
+    AZURE_ENDPOINT_SUFFIX               Endpoint suffix for Azure Storage (default: core.windows.net)
+
+    B2_ACCOUNT_ID                       Account ID or applicationKeyId for Backblaze B2
+    B2_ACCOUNT_KEY                      Account Key or applicationKey for Backblaze B2
+
+    GOOGLE_PROJECT_ID                   Project ID for Google Cloud Storage
+    GOOGLE_APPLICATION_CREDENTIALS      Application Credentials for Google Cloud Storage (e.g. $HOME/.config/gs-secret-restic-key.json)
 
     OS_AUTH_URL                         Auth URL for keystone authentication
     OS_REGION_NAME                      Region name for keystone authentication
@@ -619,18 +626,14 @@ environment variables. The following lists these environment variables:
     OS_STORAGE_URL                      Storage URL for token authentication
     OS_AUTH_TOKEN                       Auth token for token authentication
 
-    B2_ACCOUNT_ID                       Account ID or applicationKeyId for Backblaze B2
-    B2_ACCOUNT_KEY                      Account Key or applicationKey for Backblaze B2
+    RCLONE_BWLIMIT                      rclone bandwidth limit
 
-    AZURE_ACCOUNT_NAME                  Account name for Azure
-    AZURE_ACCOUNT_KEY                   Account key for Azure
-    AZURE_ACCOUNT_SAS                   Shared access signatures (SAS) for Azure
-    AZURE_ENDPOINT_SUFFIX               Endpoint suffix for Azure Storage (default: core.windows.net)
+    RESTIC_REST_USERNAME                Restic REST Server username
+    RESTIC_REST_PASSWORD                Restic REST Server password
 
-    GOOGLE_PROJECT_ID                   Project ID for Google Cloud Storage
-    GOOGLE_APPLICATION_CREDENTIALS      Application Credentials for Google Cloud Storage (e.g. $HOME/.config/gs-secret-restic-key.json)
-
-    RCLONE_BWLIMIT                      rclone bandwidth limit
+    ST_AUTH                             Auth URL for keystone v1 authentication
+    ST_USER                             Username for keystone v1 authentication
+    ST_KEY                              Password for keystone v1 authentication
 
 See :ref:`caching` for the rules concerning cache locations when
 ``RESTIC_CACHE_DIR`` is not set.
diff --git a/doc/075_scripting.rst b/doc/075_scripting.rst
index 71ecd4c2b..9b9d676a9 100644
--- a/doc/075_scripting.rst
+++ b/doc/075_scripting.rst
@@ -277,8 +277,6 @@ Match object
 +-----------------+----------------------------------------------+
 | ``group``       | Name of group                                |
 +-----------------+----------------------------------------------+
-| ``inode``       | Inode number                                 |
-+-----------------+----------------------------------------------+
 | ``mode``        | UNIX file mode, shorthand of ``permissions`` |
 +-----------------+----------------------------------------------+
 | ``device_id``   | OS specific device identifier                |
@@ -472,8 +470,6 @@ node
 +-----------------+--------------------------+
 | ``ctime``       | Node creation time       |
 +-----------------+--------------------------+
-| ``inode``       | Inode number of node     |
-+-----------------+--------------------------+
 
 
 restore
diff --git a/doc/080_examples.rst b/doc/080_examples.rst
index a31af5686..74ce3c0b3 100644
--- a/doc/080_examples.rst
+++ b/doc/080_examples.rst
@@ -220,7 +220,7 @@ repository:
 
 .. code-block:: console
 
-   $ restic init
+   $ ./restic init
    created restic backend b5c661a86a at s3:https://s3.amazonaws.com/restic-demo
 
    Please note that knowledge of your password is required to access
@@ -236,14 +236,14 @@ restic is now ready to be used with Amazon S3. Try to create a backup:
    10+0 records out
    10485760 bytes (10 MB, 10 MiB) copied, 0,0891322 s, 118 MB/s
 
-   $ restic backup test.bin
+   $ ./restic backup test.bin
    scan [/home/philip/restic-demo/test.bin]
    scanned 0 directories, 1 files in 0:00
    [0:04] 100.00%  2.500 MiB/s  10.000 MiB / 10.000 MiB  1 / 1 items ... ETA 0:00 
    duration: 0:04, 2.47MiB/s
    snapshot 10fdbace saved
 
-   $ restic snapshots
+   $ ./restic snapshots
    ID        Date                 Host        Tags        Directory
    ----------------------------------------------------------------------
    10fdbace  2017-03-26 16:41:50  blackbox                /home/philip/restic-demo/test.bin
@@ -252,7 +252,7 @@ A snapshot was created and stored in the S3 bucket. By default backups to Amazon
 
 .. code-block:: console
 
-  $ restic backup -o s3.storage-class=REDUCED_REDUNDANCY test.bin
+  $ ./restic backup -o s3.storage-class=REDUCED_REDUNDANCY test.bin
 
 This snapshot may now be restored:
 
@@ -260,7 +260,7 @@ This snapshot may now be restored:
 
    $ mkdir restore
 
-   $ restic restore 10fdbace --target restore
+   $ ./restic restore 10fdbace --target restore
    restoring <Snapshot 10fdbace of [/home/philip/restic-demo/test.bin] at 2017-03-26 16:41:50.201418102 +0200 CEST by philip@blackbox> to restore
 
    $ ls restore/
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 978da7960..ecc283f8a 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -11,7 +11,7 @@ RUN go run build.go
 
 FROM alpine:latest AS restic
 
-RUN apk add --no-cache ca-certificates fuse openssh-client tzdata jq
+RUN apk add --update --no-cache ca-certificates fuse openssh-client tzdata jq
 
 COPY --from=builder /go/src/github.com/restic/restic/restic /usr/bin
 
diff --git a/docker/Dockerfile.release b/docker/Dockerfile.release
index 42302f203..ccf80376a 100644
--- a/docker/Dockerfile.release
+++ b/docker/Dockerfile.release
@@ -13,6 +13,6 @@ RUN mv /output/restic_${TARGETOS}_${TARGETARCH} /output/restic
 FROM alpine:latest
 
 COPY --from=helper /output/restic /usr/bin
-RUN apk add --no-cache ca-certificates fuse openssh-client tzdata jq
+RUN apk add --update --no-cache ca-certificates fuse openssh-client tzdata jq
 
 ENTRYPOINT ["/usr/bin/restic"]
diff --git a/go.mod b/go.mod
index 9b230ca69..490bf889b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,7 +1,7 @@
 module github.com/restic/restic
 
 require (
-	cloud.google.com/go/storage v1.33.0
+	cloud.google.com/go/storage v1.31.0
 	github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0
 	github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0
@@ -9,9 +9,10 @@ require (
 	github.com/cenkalti/backoff/v4 v4.2.1
 	github.com/cespare/xxhash/v2 v2.2.0
 	github.com/elithrar/simple-scrypt v1.3.0
-	github.com/go-ole/go-ole v1.3.0
+	github.com/go-ole/go-ole v1.2.6
 	github.com/google/go-cmp v0.5.9
-	github.com/hashicorp/golang-lru/v2 v2.0.7
+	github.com/hashicorp/golang-lru/v2 v2.0.6
+	github.com/juju/ratelimit v1.0.2
 	github.com/klauspost/compress v1.16.7
 	github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5
 	github.com/minio/minio-go/v7 v7.0.61
@@ -24,15 +25,13 @@ require (
 	github.com/restic/chunker v0.4.0
 	github.com/spf13/cobra v1.7.0
 	github.com/spf13/pflag v1.0.5
-	go.uber.org/automaxprocs v1.5.3
-	golang.org/x/crypto v0.13.0
-	golang.org/x/net v0.15.0
-	golang.org/x/oauth2 v0.12.0
+	golang.org/x/crypto v0.12.0
+	golang.org/x/net v0.14.0
+	golang.org/x/oauth2 v0.11.0
 	golang.org/x/sync v0.3.0
 	golang.org/x/sys v0.12.0
 	golang.org/x/term v0.12.0
-	golang.org/x/text v0.13.0
-	golang.org/x/time v0.3.0
+	golang.org/x/text v0.12.0
 	google.golang.org/api v0.138.0
 )
 
@@ -58,7 +57,6 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
 	github.com/kr/fs v0.1.0 // indirect
-	github.com/kr/text v0.2.0 // indirect
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/minio/md5-simd v1.1.2 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
diff --git a/go.sum b/go.sum
index 64fc2af39..82f0b2e9c 100644
--- a/go.sum
+++ b/go.sum
@@ -8,8 +8,8 @@ cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGB
 cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
 cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
 cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
-cloud.google.com/go/storage v1.33.0 h1:PVrDOkIC8qQVa1P3SXGpQvfuJhN2LHOoyZvWs8D2X5M=
-cloud.google.com/go/storage v1.33.0/go.mod h1:Hhh/dogNRGca7IWv1RC2YqEn0c0G77ctA/OxflYkiD8=
+cloud.google.com/go/storage v1.31.0 h1:+S3LjjEN2zZ+L5hOwj4+1OkGCsLVe0NzpXKQ1pSdTCI=
+cloud.google.com/go/storage v1.31.0/go.mod h1:81ams1PrhW16L4kF7qg+4mTq7SRs5HsbDTM0bWvrwJ0=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 h1:8q4SaHjFsClSvuVne0ID/5Ka8u3fcIHyqkLjcFpNRHQ=
 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
@@ -44,7 +44,6 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 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=
@@ -64,8 +63,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
 github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
 github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
-github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -113,13 +112,15 @@ github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko
 github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
 github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
-github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
-github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
+github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
 github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
+github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
 github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
 github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -127,9 +128,6 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q
 github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5 h1:OUlGa6AAolmjyPtILbMJ8vHayz5wd4wBUloheGcMhfA=
 github.com/kurin/blazer v0.5.4-0.20230113224640-3887e1ec64b5/go.mod h1:4FCXMUWo9DllR2Do4TtBd377ezyAJ51vB5uTBjt0pGU=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
@@ -159,7 +157,6 @@ github.com/pkg/xattr v0.4.10-0.20221120235825-35026bbbd013 h1:aqByeeNnF7NiEbXCi7
 github.com/pkg/xattr v0.4.10-0.20221120235825-35026bbbd013/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
 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/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/restic/chunker v0.4.0 h1:YUPYCUn70MYP7VO4yllypp2SjmsRhRJaad3xKu1QFRw=
 github.com/restic/chunker v0.4.0/go.mod h1:z0cH2BejpW636LXw0R/BGyv+Ey8+m9QGiOanDHItzyw=
@@ -193,16 +190,14 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
 go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
-go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
-go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
 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.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
-golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
+golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@@ -223,12 +218,12 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
-golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
+golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
-golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
+golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
+golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -240,6 +235,7 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -253,7 +249,6 @@ golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -267,10 +262,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
-golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
-golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
-golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -324,8 +317,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
 google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/internal/backend/limiter/static_limiter.go b/internal/backend/limiter/static_limiter.go
index c09a12de0..9fb8fbf24 100644
--- a/internal/backend/limiter/static_limiter.go
+++ b/internal/backend/limiter/static_limiter.go
@@ -1,16 +1,15 @@
 package limiter
 
 import (
-	"context"
 	"io"
 	"net/http"
 
-	"golang.org/x/time/rate"
+	"github.com/juju/ratelimit"
 )
 
 type staticLimiter struct {
-	upstream   *rate.Limiter
-	downstream *rate.Limiter
+	upstream   *ratelimit.Bucket
+	downstream *ratelimit.Bucket
 }
 
 // Limits represents static upload and download limits.
@@ -24,16 +23,16 @@ type Limits struct {
 // download rate cap
 func NewStaticLimiter(l Limits) Limiter {
 	var (
-		upstreamBucket   *rate.Limiter
-		downstreamBucket *rate.Limiter
+		upstreamBucket   *ratelimit.Bucket
+		downstreamBucket *ratelimit.Bucket
 	)
 
 	if l.UploadKb > 0 {
-		upstreamBucket = rate.NewLimiter(rate.Limit(toByteRate(l.UploadKb)), int(toByteRate(l.UploadKb)))
+		upstreamBucket = ratelimit.NewBucketWithRate(toByteRate(l.UploadKb), int64(toByteRate(l.UploadKb)))
 	}
 
 	if l.DownloadKb > 0 {
-		downstreamBucket = rate.NewLimiter(rate.Limit(toByteRate(l.DownloadKb)), int(toByteRate(l.DownloadKb)))
+		downstreamBucket = ratelimit.NewBucketWithRate(toByteRate(l.DownloadKb), int64(toByteRate(l.DownloadKb)))
 	}
 
 	return staticLimiter{
@@ -96,55 +95,18 @@ func (l staticLimiter) Transport(rt http.RoundTripper) http.RoundTripper {
 	})
 }
 
-func (l staticLimiter) limitReader(r io.Reader, b *rate.Limiter) io.Reader {
+func (l staticLimiter) limitReader(r io.Reader, b *ratelimit.Bucket) io.Reader {
 	if b == nil {
 		return r
 	}
-	return &rateLimitedReader{r, b}
+	return ratelimit.Reader(r, b)
 }
 
-type rateLimitedReader struct {
-	reader io.Reader
-	bucket *rate.Limiter
-}
-
-func (r *rateLimitedReader) Read(p []byte) (int, error) {
-	n, err := r.reader.Read(p)
-	if err := consumeTokens(n, r.bucket); err != nil {
-		return n, err
-	}
-	return n, err
-}
-
-func (l staticLimiter) limitWriter(w io.Writer, b *rate.Limiter) io.Writer {
+func (l staticLimiter) limitWriter(w io.Writer, b *ratelimit.Bucket) io.Writer {
 	if b == nil {
 		return w
 	}
-	return &rateLimitedWriter{w, b}
-}
-
-type rateLimitedWriter struct {
-	writer io.Writer
-	bucket *rate.Limiter
-}
-
-func (w *rateLimitedWriter) Write(buf []byte) (int, error) {
-	if err := consumeTokens(len(buf), w.bucket); err != nil {
-		return 0, err
-	}
-	return w.writer.Write(buf)
-}
-
-func consumeTokens(tokens int, bucket *rate.Limiter) error {
-	// bucket allows waiting for at most Burst() tokens at once
-	maxWait := bucket.Burst()
-	for tokens > maxWait {
-		if err := bucket.WaitN(context.Background(), maxWait); err != nil {
-			return err
-		}
-		tokens -= maxWait
-	}
-	return bucket.WaitN(context.Background(), tokens)
+	return ratelimit.Writer(w, b)
 }
 
 func toByteRate(val int) float64 {
diff --git a/internal/backend/rest/config.go b/internal/backend/rest/config.go
index ba42a0220..8458b0df2 100644
--- a/internal/backend/rest/config.go
+++ b/internal/backend/rest/config.go
@@ -2,10 +2,12 @@ package rest
 
 import (
 	"net/url"
+	"os"
 	"strings"
 
 	"github.com/restic/restic/internal/errors"
 	"github.com/restic/restic/internal/options"
+	"github.com/restic/restic/internal/restic"
 )
 
 // Config contains all configuration necessary to connect to a REST server.
@@ -70,3 +72,19 @@ func prepareURL(s string) string {
 	}
 	return s
 }
+
+var _ restic.ApplyEnvironmenter = &Config{}
+
+// ApplyEnvironment saves values from the environment to the config.
+func (cfg *Config) ApplyEnvironment(prefix string) {
+	username := cfg.URL.User.Username()
+	_, pwdSet := cfg.URL.User.Password()
+
+	// Only apply env variable values if neither username nor password are provided.
+	if username == "" && !pwdSet {
+		envName := os.Getenv(prefix + "RESTIC_REST_USERNAME")
+		envPwd := os.Getenv(prefix + "RESTIC_REST_PASSWORD")
+
+		cfg.URL.User = url.UserPassword(envName, envPwd)
+	}
+}
diff --git a/internal/checker/checker.go b/internal/checker/checker.go
index a5bb43731..b2512969e 100644
--- a/internal/checker/checker.go
+++ b/internal/checker/checker.go
@@ -113,42 +113,13 @@ func computePackTypes(ctx context.Context, idx restic.MasterIndex) map[restic.ID
 }
 
 // LoadIndex loads all index files.
-func (c *Checker) LoadIndex(ctx context.Context, p *progress.Counter) (hints []error, errs []error) {
+func (c *Checker) LoadIndex(ctx context.Context) (hints []error, errs []error) {
 	debug.Log("Start")
 
-	indexList, err := backend.MemorizeList(ctx, c.repo.Backend(), restic.IndexFile)
-	if err != nil {
-		// abort if an error occurs while listing the indexes
-		return hints, append(errs, err)
-	}
-
-	if p != nil {
-		var numIndexFiles uint64
-		err := indexList.List(ctx, restic.IndexFile, func(fi restic.FileInfo) error {
-			_, err := restic.ParseID(fi.Name)
-			if err != nil {
-				debug.Log("unable to parse %v as an ID", fi.Name)
-				return nil
-			}
-
-			numIndexFiles++
-			return nil
-		})
-		if err != nil {
-			return hints, append(errs, err)
-		}
-		p.SetMax(numIndexFiles)
-		defer p.Done()
-	}
-
 	packToIndex := make(map[restic.ID]restic.IDSet)
-	err = index.ForAllIndexes(ctx, indexList, c.repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
+	err := index.ForAllIndexes(ctx, c.repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
 		debug.Log("process index %v, err %v", id, err)
 
-		if p != nil {
-			p.Add(1)
-		}
-
 		if oldFormat {
 			debug.Log("index %v has old format", id)
 			hints = append(hints, &ErrOldIndexFormat{id})
diff --git a/internal/fuse/snapshots_dirstruct.go b/internal/fuse/snapshots_dirstruct.go
index a3cd11b14..3080d4de8 100644
--- a/internal/fuse/snapshots_dirstruct.go
+++ b/internal/fuse/snapshots_dirstruct.go
@@ -328,7 +328,7 @@ func (d *SnapshotsDirStructure) updateSnapshots(ctx context.Context) error {
 		return nil
 	}
 
-	err = d.root.repo.LoadIndex(ctx, nil)
+	err = d.root.repo.LoadIndex(ctx)
 	if err != nil {
 		return err
 	}
diff --git a/internal/index/index_parallel.go b/internal/index/index_parallel.go
index d505d756e..e7e46e88a 100644
--- a/internal/index/index_parallel.go
+++ b/internal/index/index_parallel.go
@@ -11,7 +11,7 @@ import (
 // ForAllIndexes loads all index files in parallel and calls the given callback.
 // It is guaranteed that the function is not run concurrently. If the callback
 // returns an error, this function is cancelled and also returns that error.
-func ForAllIndexes(ctx context.Context, lister restic.Lister, repo restic.Repository,
+func ForAllIndexes(ctx context.Context, repo restic.Repository,
 	fn func(id restic.ID, index *Index, oldFormat bool, err error) error) error {
 
 	// decoding an index can take quite some time such that this can be both CPU- or IO-bound
@@ -19,7 +19,7 @@ func ForAllIndexes(ctx context.Context, lister restic.Lister, repo restic.Reposi
 	workerCount := repo.Connections() + uint(runtime.GOMAXPROCS(0))
 
 	var m sync.Mutex
-	return restic.ParallelList(ctx, lister, restic.IndexFile, workerCount, func(ctx context.Context, id restic.ID, size int64) error {
+	return restic.ParallelList(ctx, repo.Backend(), restic.IndexFile, workerCount, func(ctx context.Context, id restic.ID, size int64) error {
 		var err error
 		var idx *Index
 		oldFormat := false
diff --git a/internal/repository/packer_manager.go b/internal/repository/packer_manager.go
index 2e2368aad..4422e3418 100644
--- a/internal/repository/packer_manager.go
+++ b/internal/repository/packer_manager.go
@@ -70,19 +70,14 @@ func (r *packerManager) SaveBlob(ctx context.Context, t restic.BlobType, id rest
 
 	var err error
 	packer := r.packer
-	// use separate packer if compressed length is larger than the packsize
-	// this speeds up the garbage collection of oversized blobs and reduces the cache size
-	// as the oversize blobs are only downloaded if necessary
-	if len(ciphertext) >= int(r.packSize) || r.packer == nil {
+	if r.packer == nil {
 		packer, err = r.newPacker()
 		if err != nil {
 			return 0, err
 		}
-		// don't store packer for oversized blob
-		if r.packer == nil {
-			r.packer = packer
-		}
 	}
+	// remember packer
+	r.packer = packer
 
 	// save ciphertext
 	// Add only appends bytes in memory to avoid being a scaling bottleneck
@@ -96,10 +91,8 @@ func (r *packerManager) SaveBlob(ctx context.Context, t restic.BlobType, id rest
 		debug.Log("pack is not full enough (%d bytes)", packer.Size())
 		return size, nil
 	}
-	if packer == r.packer {
-		// forget full packer
-		r.packer = nil
-	}
+	// forget full packer
+	r.packer = nil
 
 	// call while holding lock to prevent findPacker from creating new packers if the uploaders are busy
 	// else write the pack to the backend
diff --git a/internal/repository/repository.go b/internal/repository/repository.go
index 8ec3c598e..653c1f774 100644
--- a/internal/repository/repository.go
+++ b/internal/repository/repository.go
@@ -581,41 +581,15 @@ func (r *Repository) SetIndex(i restic.MasterIndex) error {
 }
 
 // LoadIndex loads all index files from the backend in parallel and stores them
-func (r *Repository) LoadIndex(ctx context.Context, p *progress.Counter) error {
+// in the master index. The first error that occurred is returned.
+func (r *Repository) LoadIndex(ctx context.Context) error {
 	debug.Log("Loading index")
 
-	indexList, err := backend.MemorizeList(ctx, r.Backend(), restic.IndexFile)
-	if err != nil {
-		return err
-	}
-
-	if p != nil {
-		var numIndexFiles uint64
-		err := indexList.List(ctx, restic.IndexFile, func(fi restic.FileInfo) error {
-			_, err := restic.ParseID(fi.Name)
-			if err != nil {
-				debug.Log("unable to parse %v as an ID", fi.Name)
-				return nil
-			}
-
-			numIndexFiles++
-			return nil
-		})
-		if err != nil {
-			return err
-		}
-		p.SetMax(numIndexFiles)
-		defer p.Done()
-	}
-
-	err = index.ForAllIndexes(ctx, indexList, r, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
+	err := index.ForAllIndexes(ctx, r, func(id restic.ID, idx *index.Index, oldFormat bool, err error) error {
 		if err != nil {
 			return err
 		}
 		r.idx.Insert(idx)
-		if p != nil {
-			p.Add(1)
-		}
 		return nil
 	})
 
diff --git a/internal/restic/repository.go b/internal/restic/repository.go
index a651f9906..6990200e4 100644
--- a/internal/restic/repository.go
+++ b/internal/restic/repository.go
@@ -24,7 +24,7 @@ type Repository interface {
 	Key() *crypto.Key
 
 	Index() MasterIndex
-	LoadIndex(context.Context, *progress.Counter) error
+	LoadIndex(context.Context) error
 	SetIndex(MasterIndex) error
 	LookupBlobSize(ID, BlobType) (uint, bool)
 
diff --git a/internal/ui/restore/text.go b/internal/ui/restore/text.go
index 2647bb28b..e6465eed0 100644
--- a/internal/ui/restore/text.go
+++ b/internal/ui/restore/text.go
@@ -22,7 +22,7 @@ func (t *textPrinter) Update(filesFinished, filesTotal, allBytesWritten, allByte
 	formattedAllBytesWritten := ui.FormatBytes(allBytesWritten)
 	formattedAllBytesTotal := ui.FormatBytes(allBytesTotal)
 	allPercent := ui.FormatPercent(allBytesWritten, allBytesTotal)
-	progress := fmt.Sprintf("[%s] %s  %v files/dirs %s, total %v files/dirs %v",
+	progress := fmt.Sprintf("[%s] %s  %v files %s, total %v files %v",
 		timeLeft, allPercent, filesFinished, formattedAllBytesWritten, filesTotal, formattedAllBytesTotal)
 
 	t.terminal.SetStatus([]string{progress})
@@ -36,10 +36,10 @@ func (t *textPrinter) Finish(filesFinished, filesTotal, allBytesWritten, allByte
 
 	var summary string
 	if filesFinished == filesTotal && allBytesWritten == allBytesTotal {
-		summary = fmt.Sprintf("Summary: Restored %d files/dirs (%s) in %s", filesTotal, formattedAllBytesTotal, timeLeft)
+		summary = fmt.Sprintf("Summary: Restored %d Files (%s) in %s", filesTotal, formattedAllBytesTotal, timeLeft)
 	} else {
 		formattedAllBytesWritten := ui.FormatBytes(allBytesWritten)
-		summary = fmt.Sprintf("Summary: Restored %d / %d files/dirs (%s / %s) in %s",
+		summary = fmt.Sprintf("Summary: Restored %d / %d Files (%s / %s) in %s",
 			filesFinished, filesTotal, formattedAllBytesWritten, formattedAllBytesTotal, timeLeft)
 	}
 


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 995fcf86b..2ec4591f0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -135,7 +135,7 @@ jobs:
         if: matrix.os == 'windows-latest'
 
       - name: Check out code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v3
 
       - name: Build with build.go
         run: |
@@ -231,7 +231,7 @@ jobs:
           go-version: ${{ env.latest_go }}
 
       - name: Check out code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v3
 
       - name: Cross-compile for subset ${{ matrix.subset }}
         run: |
@@ -249,7 +249,7 @@ jobs:
           go-version: ${{ env.latest_go }}
 
       - name: Check out code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v3
 
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
@@ -289,7 +289,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Check out code
-        uses: actions/checkout@v4
+        uses: actions/checkout@v3
 
       - name: Docker meta
         id: meta
@@ -309,10 +309,10 @@ jobs:
             type=sha
 
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v2
 
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v2
 
       - name: Build and push
         id: docker_build
diff --git a/cmd/restic/cmd_backup_integration_test.go b/cmd/restic/cmd_backup_integration_test.go
index 76c227e3d..fb7bef633 100644
--- a/cmd/restic/cmd_backup_integration_test.go
+++ b/cmd/restic/cmd_backup_integration_test.go
@@ -252,7 +252,7 @@ func TestBackupTreeLoadError(t *testing.T) {
 
 	r, err := OpenRepository(context.TODO(), env.gopts)
 	rtest.OK(t, err)
-	rtest.OK(t, r.LoadIndex(context.TODO(), nil))
+	rtest.OK(t, r.LoadIndex(context.TODO()))
 	treePacks := restic.NewIDSet()
 	r.Index().Each(context.TODO(), func(pb restic.PackedBlob) {
 		if pb.Type == restic.TreeBlob {
diff --git a/cmd/restic/integration_helpers_test.go b/cmd/restic/integration_helpers_test.go
index 2afdbb938..b7cb5b333 100644
--- a/cmd/restic/integration_helpers_test.go
+++ b/cmd/restic/integration_helpers_test.go
@@ -258,7 +258,7 @@ func removePacksExcept(gopts GlobalOptions, t testing.TB, keep restic.IDSet, rem
 	rtest.OK(t, err)
 
 	// Get all tree packs
-	rtest.OK(t, r.LoadIndex(context.TODO(), nil))
+	rtest.OK(t, r.LoadIndex(context.TODO()))
 
 	treePacks := restic.NewIDSet()
 	r.Index().Each(context.TODO(), func(pb restic.PackedBlob) {
diff --git a/internal/backend/limiter/static_limiter_test.go b/internal/backend/limiter/static_limiter_test.go
index 8a839518f..564b6a00a 100644
--- a/internal/backend/limiter/static_limiter_test.go
+++ b/internal/backend/limiter/static_limiter_test.go
@@ -9,7 +9,6 @@ import (
 	"testing"
 
 	"github.com/restic/restic/internal/test"
-	"golang.org/x/time/rate"
 )
 
 func TestLimiterWrapping(t *testing.T) {
@@ -34,38 +33,6 @@ func TestLimiterWrapping(t *testing.T) {
 	}
 }
 
-func TestReadLimiter(t *testing.T) {
-	reader := bytes.NewReader(make([]byte, 300))
-	limiter := rate.NewLimiter(rate.Limit(10000), int(100))
-	limReader := rateLimitedReader{reader, limiter}
-
-	n, err := limReader.Read([]byte{})
-	test.OK(t, err)
-	test.Equals(t, n, 0)
-
-	n, err = limReader.Read(make([]byte, 300))
-	test.OK(t, err)
-	test.Equals(t, n, 300)
-
-	n, err = limReader.Read([]byte{})
-	test.Equals(t, err, io.EOF)
-	test.Equals(t, n, 0)
-}
-
-func TestWriteLimiter(t *testing.T) {
-	writer := &bytes.Buffer{}
-	limiter := rate.NewLimiter(rate.Limit(10000), int(100))
-	limReader := rateLimitedWriter{writer, limiter}
-
-	n, err := limReader.Write([]byte{})
-	test.OK(t, err)
-	test.Equals(t, n, 0)
-
-	n, err = limReader.Write(make([]byte, 300))
-	test.OK(t, err)
-	test.Equals(t, n, 300)
-}
-
 type tracedReadCloser struct {
 	io.Reader
 	Closed bool
diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go
index ee7e2867c..6405ecfbd 100644
--- a/internal/checker/checker_test.go
+++ b/internal/checker/checker_test.go
@@ -77,7 +77,7 @@ func TestCheckRepo(t *testing.T) {
 	repo := repository.TestOpenLocal(t, repodir)
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -103,7 +103,7 @@ func TestMissingPack(t *testing.T) {
 	test.OK(t, repo.Backend().Remove(context.TODO(), packHandle))
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -136,7 +136,7 @@ func TestUnreferencedPack(t *testing.T) {
 	test.OK(t, repo.Backend().Remove(context.TODO(), indexHandle))
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -178,7 +178,7 @@ func TestUnreferencedBlobs(t *testing.T) {
 	sort.Sort(unusedBlobsBySnapshot)
 
 	chkr := checker.New(repo, true)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -258,7 +258,7 @@ func TestModifiedIndex(t *testing.T) {
 	}
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) == 0 {
 		t.Fatalf("expected errors not found")
 	}
@@ -279,7 +279,7 @@ func TestDuplicatePacksInIndex(t *testing.T) {
 	repo := repository.TestOpenLocal(t, repodir)
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(hints) == 0 {
 		t.Fatalf("did not get expected checker hints for duplicate packs in indexes")
 	}
@@ -347,7 +347,7 @@ func TestCheckerModifiedData(t *testing.T) {
 
 	chkr := checker.New(checkRepo, false)
 
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -408,7 +408,7 @@ func TestCheckerNoDuplicateTreeDecodes(t *testing.T) {
 	}
 
 	chkr := checker.New(checkRepo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -524,7 +524,7 @@ func TestCheckerBlobTypeConfusion(t *testing.T) {
 		delayRepo.Unblock()
 	}()
 
-	hints, errs := chkr.LoadIndex(ctx, nil)
+	hints, errs := chkr.LoadIndex(ctx)
 	if len(errs) > 0 {
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
 	}
@@ -553,7 +553,7 @@ func loadBenchRepository(t *testing.B) (*checker.Checker, restic.Repository, fun
 	repo := repository.TestOpenLocal(t, repodir)
 
 	chkr := checker.New(repo, false)
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) > 0 {
 		defer cleanup()
 		t.Fatalf("expected no errors, got %v: %v", len(errs), errs)
diff --git a/internal/checker/testing.go b/internal/checker/testing.go
index fe1679393..0668406d8 100644
--- a/internal/checker/testing.go
+++ b/internal/checker/testing.go
@@ -11,7 +11,7 @@ import (
 func TestCheckRepo(t testing.TB, repo restic.Repository) {
 	chkr := New(repo, true)
 
-	hints, errs := chkr.LoadIndex(context.TODO(), nil)
+	hints, errs := chkr.LoadIndex(context.TODO())
 	if len(errs) != 0 {
 		t.Fatalf("errors loading index: %v", errs)
 	}
diff --git a/internal/index/index_parallel_test.go b/internal/index/index_parallel_test.go
index 86be46473..760374510 100644
--- a/internal/index/index_parallel_test.go
+++ b/internal/index/index_parallel_test.go
@@ -29,7 +29,7 @@ func TestRepositoryForAllIndexes(t *testing.T) {
 	// check that all expected indexes are loaded without errors
 	indexIDs := restic.NewIDSet()
 	var indexErr error
-	rtest.OK(t, index.ForAllIndexes(context.TODO(), repo.Backend(), repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
+	rtest.OK(t, index.ForAllIndexes(context.TODO(), repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
 		if err != nil {
 			indexErr = err
 		}
@@ -42,7 +42,7 @@ func TestRepositoryForAllIndexes(t *testing.T) {
 	// must failed with the returned error
 	iterErr := errors.New("error to pass upwards")
 
-	err := index.ForAllIndexes(context.TODO(), repo.Backend(), repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
+	err := index.ForAllIndexes(context.TODO(), repo, func(id restic.ID, index *index.Index, oldFormat bool, err error) error {
 		return iterErr
 	})
 
diff --git a/internal/index/master_index_test.go b/internal/index/master_index_test.go
index bf8ec3f41..5260a11a0 100644
--- a/internal/index/master_index_test.go
+++ b/internal/index/master_index_test.go
@@ -357,7 +357,7 @@ func TestIndexSave(t *testing.T) {
 func testIndexSave(t *testing.T, version uint) {
 	repo := createFilledRepo(t, 3, version)
 
-	err := repo.LoadIndex(context.TODO(), nil)
+	err := repo.LoadIndex(context.TODO())
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -382,7 +382,7 @@ func testIndexSave(t *testing.T, version uint) {
 		t.Error(err)
 	}
 
-	hints, errs := checker.LoadIndex(context.TODO(), nil)
+	hints, errs := checker.LoadIndex(context.TODO())
 	for _, h := range hints {
 		t.Logf("hint: %v\n", h)
 	}
diff --git a/internal/repository/packer_manager_test.go b/internal/repository/packer_manager_test.go
index 8984073da..90f716e0d 100644
--- a/internal/repository/packer_manager_test.go
+++ b/internal/repository/packer_manager_test.go
@@ -89,24 +89,6 @@ func testPackerManager(t testing.TB) int64 {
 	return int64(bytes)
 }
 
-func TestPackerManagerWithOversizeBlob(t *testing.T) {
-	packFiles := int(0)
-	sizeLimit := uint(512 * 1024)
-	pm := newPackerManager(crypto.NewRandomKey(), restic.DataBlob, sizeLimit, func(ctx context.Context, tp restic.BlobType, p *Packer) error {
-		packFiles++
-		return nil
-	})
-
-	for _, i := range []uint{sizeLimit / 2, sizeLimit, sizeLimit / 3} {
-		_, err := pm.SaveBlob(context.TODO(), restic.DataBlob, restic.ID{}, make([]byte, i), 0)
-		test.OK(t, err)
-	}
-	test.OK(t, pm.Flush(context.TODO()))
-
-	// oversized blob must be stored in a separate packfile
-	test.Equals(t, packFiles, 2)
-}
-
 func BenchmarkPackerManager(t *testing.B) {
 	// Run testPackerManager if it hasn't run already, to set totalSize.
 	once.Do(func() {
diff --git a/internal/repository/repack_test.go b/internal/repository/repack_test.go
index c8570a9d4..bb31eba77 100644
--- a/internal/repository/repack_test.go
+++ b/internal/repository/repack_test.go
@@ -213,7 +213,7 @@ func reloadIndex(t *testing.T, repo restic.Repository) {
 		t.Fatal(err)
 	}
 
-	if err := repo.LoadIndex(context.TODO(), nil); err != nil {
+	if err := repo.LoadIndex(context.TODO()); err != nil {
 		t.Fatalf("error loading new index: %v", err)
 	}
 }
diff --git a/internal/repository/repository_test.go b/internal/repository/repository_test.go
index d362b0b5e..f26bf46f2 100644
--- a/internal/repository/repository_test.go
+++ b/internal/repository/repository_test.go
@@ -255,7 +255,7 @@ func TestRepositoryLoadIndex(t *testing.T) {
 	defer cleanup()
 
 	repo := repository.TestOpenLocal(t, repodir)
-	rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
+	rtest.OK(t, repo.LoadIndex(context.TODO()))
 }
 
 // loadIndex loads the index id from backend and returns it.
@@ -324,7 +324,7 @@ func TestRepositoryLoadUnpackedRetryBroken(t *testing.T) {
 	err = repo.SearchKey(context.TODO(), rtest.TestPassword, 10, "")
 	rtest.OK(t, err)
 
-	rtest.OK(t, repo.LoadIndex(context.TODO(), nil))
+	rtest.OK(t, repo.LoadIndex(context.TODO()))
 }
 
 func BenchmarkLoadIndex(b *testing.B) {
diff --git a/internal/ui/restore/text_test.go b/internal/ui/restore/text_test.go
index fc03904ff..2a8c90878 100644
--- a/internal/ui/restore/text_test.go
+++ b/internal/ui/restore/text_test.go
@@ -23,19 +23,19 @@ func TestPrintUpdate(t *testing.T) {
 	term := &mockTerm{}
 	printer := NewTextProgress(term)
 	printer.Update(3, 11, 29, 47, 5*time.Second)
-	test.Equals(t, []string{"[0:05] 61.70%  3 files/dirs 29 B, total 11 files/dirs 47 B"}, term.output)
+	test.Equals(t, []string{"[0:05] 61.70%  3 files 29 B, total 11 files 47 B"}, term.output)
 }
 
 func TestPrintSummaryOnSuccess(t *testing.T) {
 	term := &mockTerm{}
 	printer := NewTextProgress(term)
 	printer.Finish(11, 11, 47, 47, 5*time.Second)
-	test.Equals(t, []string{"Summary: Restored 11 files/dirs (47 B) in 0:05"}, term.output)
+	test.Equals(t, []string{"Summary: Restored 11 Files (47 B) in 0:05"}, term.output)
 }
 
 func TestPrintSummaryOnErrors(t *testing.T) {
 	term := &mockTerm{}
 	printer := NewTextProgress(term)
 	printer.Finish(3, 11, 29, 47, 5*time.Second)
-	test.Equals(t, []string{"Summary: Restored 3 / 11 files/dirs (29 B / 47 B) in 0:05"}, term.output)
+	test.Equals(t, []string{"Summary: Restored 3 / 11 Files (29 B / 47 B) in 0:05"}, term.output)
 }