Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Stream run logs over the runner's SSH channel

5ece70a50ebda392626b92bdf29d28bc9b444954 · Keenan Nemetz · 10d ago · parent fdf2340f

Stream run logs over the runner's SSH channel

The runner wrote the log to a temp file and uploaded it as one LFS object
when the job ended, so a running job had no log to read and a log was stored
like a shared artifact even though it belongs to one attempt.

The runner sends 128 KiB chunks over the same runner channel that holds the
lease. The server appends them to data/logs/<repo>/<run>/<attempt>.log and
records the size while the run is still running. The finish record carries
the final size; the server checks the file length, syncs it, and stores the
result. A partial or failed stream leaves no log on the record.

Logs are no longer LFS objects: they are not shared, not content-addressed,
and the web serves the file directly, with range and step slices. GC no
longer treats them as LFS roots.

forge/instance.go +5−1

@@ -14,6 +14,7 @@ import (
14 14 "path/filepath"
15 15 "strings"
16 16
17 "github.com/rodent-software/vole/ci/logs"
17 18 "github.com/rodent-software/vole/ci/runs"
18 19 "github.com/rodent-software/vole/core/lfs"
19 20 "github.com/rodent-software/vole/forge/access"
@@ -38,6 +39,7 @@ type Instance struct {
38 39 ACLs *access.Store
39 40 Locks *lfs.LockStore
40 41 LFS *lfs.Store
42 Logs *logs.Store
41 43 PageViews *pageview.Store
42 44
43 45 // ImportedACLs is how many legacy refs/vole/acl rule sets were imported by
@@ -51,7 +53,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
51 53 // Directories that hold secrets are created at 0700. The repositories tree is
52 54 // content, not secrets, but it lives inside the data directory and there is no
53 55 // reason for it to be wider than the directory that contains it.
54 for _, dir := range []string{cfg.DataDir, cfg.ReposDir(), cfg.SpoolDir(), cfg.LFSDir()} {
56 for _, dir := range []string{cfg.DataDir, cfg.ReposDir(), cfg.SpoolDir(), cfg.LFSDir(), cfg.LogsDir()} {
55 57 if err := secure.Mkdir(dir); err != nil {
56 58 return nil, fmt.Errorf("data directory %s: %w", dir, err)
57 59 }
@@ -152,6 +154,7 @@ func Open(cfg *config.Config, binPath string) (*Instance, error) {
152 154 ACLs: acls,
153 155 Locks: lk,
154 156 LFS: lfs.New(cfg.LFSDir(), cfg.LFSObjectMaxBytes),
157 Logs: logs.New(cfg.LogsDir()),
155 158 PageViews: views,
156 159 ImportedACLs: imported,
157 160 }, nil
@@ -172,6 +175,7 @@ func TightenSecrets(cfg *config.Config) ([]string, error) {
172 175 cfg.ReposDir(),
173 176 cfg.SpoolDir(),
174 177 cfg.LFSDir(),
178 cfg.LogsDir(),
175 179 cfg.UsersDB(), cfg.UsersDB() + "-wal", cfg.UsersDB() + "-shm",
176 180 cfg.IndexDB(), cfg.IndexDB() + "-wal", cfg.IndexDB() + "-shm",
177 181 cfg.RunsDB(), cfg.RunsDB() + "-wal", cfg.RunsDB() + "-shm",