vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software: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 · 11d 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/config/config.go +4
| @@ -264,6 +264,10 @@ func (c *Config) SpoolDir() string { return filepath.Join(c.DataDir, "queue", "s | |||
| 264 | 264 | // every repository: the same artifact pushed twice costs one copy. | |
| 265 | 265 | func (c *Config) LFSDir() string { return filepath.Join(c.DataDir, "lfs") } | |
| 266 | 266 | ||
| 267 | // LogsDir holds one file per CI attempt. Logs are not content-addressed and are | ||
| 268 | // not shared, so they live outside the LFS pool. | ||
| 269 | func (c *Config) LogsDir() string { return filepath.Join(c.DataDir, "logs") } | ||
| 270 | |||
| 267 | 271 | // Lease returns how long a runner may hold a job without a heartbeat. | |
| 268 | 272 | func (c *Config) Lease() time.Duration { | |
| 269 | 273 | if c.LeaseSeconds <= 0 { | |