Compare commits

..

6 Commits

Author SHA1 Message Date
b766c9ef20 Improve current Arch Linux run log
All checks were successful
Alpine 3.22 Success
2025-11-23 19:38:51 +01:00
6868bde5e6 Reject unknown DB versions
All checks were successful
Alpine 3.21 Success
2025-09-06 09:12:27 +02:00
d3a046d85d Avoid disaster with DB migrations
All checks were successful
Alpine 3.21 Success
2025-09-04 10:38:31 +02:00
6622ea0e1c Improve formatting of durations
All checks were successful
Alpine 3.20 Success
Since "m" could stand for both "minute" and "month",
and months vary in length, let's stop at days.
2025-01-02 00:36:03 +01:00
a492b3b668 Clean up 2024-12-28 00:27:46 +01:00
280114a5d3 Unify our usage of the local shell 2024-12-27 02:16:14 +01:00
4 changed files with 48 additions and 32 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2024 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

53
acid.go
View File

@@ -30,7 +30,6 @@ import (
"syscall"
ttemplate "text/template"
"time"
"unicode"
_ "github.com/mattn/go-sqlite3"
"github.com/pkg/sftp"
@@ -154,6 +153,14 @@ var shellFuncs = ttemplate.FuncMap{
// --- Utilities ---------------------------------------------------------------
func localShell() string {
if shell := os.Getenv("SHELL"); shell != "" {
return shell
}
// The os/user package doesn't store the parsed out shell field.
return "/bin/sh"
}
func giteaSign(b []byte) string {
payloadHmac := hmac.New(sha256.New, []byte(getConfig().Secret))
payloadHmac.Write(b)
@@ -306,9 +313,7 @@ function get(id) {
return document.getElementById(id)
}
function getLog(id) {
const header = document.getElementById(id)
const log = document.getElementById(id + 'log')
const text = log.textContent
const header = get(id), log = get(id + 'log'), text = log.textContent
// lines[-1] is an implementation detail of terminalWriter.Serialize,
// lines[-2] is the actual last line.
const last = Math.max(0, text.split('\n').length - 2)
@@ -326,8 +331,7 @@ function refreshLog(log, top, changed) {
log.log.hidden = empty
}
let refresher = setInterval(() => {
let run = getLog('run'), task = getLog('task'), deploy = getLog('deploy')
const run = getLog('run'), task = getLog('task'), deploy = getLog('deploy')
const url = new URL(window.location.href)
url.search = ''
url.searchParams.set('json', '')
@@ -366,8 +370,8 @@ let refresher = setInterval(() => {
if (!data.IsRunning)
clearInterval(refresher)
}).catch(error => {
alert(error)
clearInterval(refresher)
alert(error)
})
}, 1000 /* For faster updates than this, we should use WebSockets. */)
</script>
@@ -842,7 +846,7 @@ func notifierRunCommand(ctx context.Context, task Task) {
return
}
cmd := exec.CommandContext(ctx, "sh")
cmd := exec.CommandContext(ctx, localShell())
cmd.Stdin = script
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -1166,14 +1170,6 @@ func executorDownload(client *ssh.Client, remoteRoot, localRoot string) error {
return nil
}
func executorLocalShell() string {
if shell := os.Getenv("SHELL"); shell != "" {
return shell
}
// The os/user package doesn't store the parsed out shell field.
return "/bin/sh"
}
func executorTmpDir(fallback string) string {
// See also: https://systemd.io/TEMPORARY_DIRECTORIES/
if tmp := os.Getenv("TMPDIR"); tmp != "" {
@@ -1209,9 +1205,10 @@ func executorDeploy(
return err
}
cmd := exec.CommandContext(ctx, executorLocalShell(), "-c", script.String())
cmd := exec.CommandContext(ctx, localShell())
cmd.Env = rt.localEnv()
cmd.Dir = dir
cmd.Stdin = script
cmd.Stdout = &rt.DeployLog
cmd.Stderr = &rt.DeployLog
return cmd.Run()
@@ -1605,18 +1602,15 @@ func (t *Task) CloneURL() string {
}
func shortDurationString(d time.Duration) string {
rs := []rune(d.Truncate(time.Second).String())
for i, r := range rs {
if !unicode.IsLetter(r) {
continue
}
i++
for i < len(rs) && unicode.IsLetter(rs[i]) {
i++
}
return string(rs[:i])
if d.Abs() >= 24*time.Hour {
return strconv.FormatInt(int64(d/time.Hour/24), 10) + "d"
} else if d.Abs() >= time.Hour {
return strconv.FormatInt(int64(d/time.Hour), 10) + "h"
} else if d.Abs() >= time.Minute {
return strconv.FormatInt(int64(d/time.Minute), 10) + "m"
} else {
return strconv.FormatInt(int64(d/time.Second), 10) + "s"
}
return string(rs)
}
func (t *Task) Created() *time.Time {
@@ -1757,8 +1751,11 @@ func dbOpen(path string) error {
`task`, `duration`, `INTEGER NOT NULL DEFAULT 0`); err != nil {
return err
}
fallthrough
case 2:
// The next migration goes here, remember to increment the number below.
default:
return fmt.Errorf("unsupported database version: %d", version)
}
if _, err = tx.Exec(

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"testing"
ttemplate "text/template"
"time"
)
func TestTemplateQuote(t *testing.T) {
@@ -30,3 +31,19 @@ func TestTemplateQuote(t *testing.T) {
}
}
}
func TestShortDurationString(t *testing.T) {
for _, test := range []struct {
d time.Duration
expect string
}{
{72 * time.Hour, "3d"},
{-3 * time.Hour, "-3h"},
{12 * time.Minute, "12m"},
{time.Millisecond, "0s"},
} {
if sd := shortDurationString(test.d); sd != test.expect {
t.Errorf("%s = %s; want %s\n", test.d, sd, test.expect)
}
}
}

View File

@@ -210,11 +210,13 @@ func (tw *terminalWriter) processParsedCSI(
if len(params) == 0 {
tw.line = tw.lineTop
tw.column = 0
} else if len(params) >= 2 && params[0] != 0 && params[1] != 0 {
} else if len(params) < 2 || params[0] <= 0 || params[1] <= 0 {
return false
} else if params[0] >= 32766 && params[1] >= 32766 {
// Ignore attempts to scan terminal bounds.
} else {
tw.line = tw.lineTop + params[0] - 1
tw.column = params[1] - 1
} else {
return false
}
return true
case final == 'J': // Erase in Display