Compare commits

..

2 Commits

Author SHA1 Message Date
user
5d87d386c3 ci: pin actions to commit SHAs to prevent RCE
Some checks failed
Check / check (pull_request) Failing after 5m27s
Pin actions/checkout and actions/setup-go to their full commit
SHAs instead of mutable tags, per review feedback.

- actions/checkout@v4 → 34e114876b0b11c390a56381ad16ebd13914f8d5
- actions/setup-go@v5 → 40f1582b2485089dde7abd97c1529aa768e1baff
2026-02-19 20:25:23 -08:00
user
f65e3887b2 ci: add Gitea Actions workflow for make check (fixes #96) 2026-02-19 20:24:46 -08:00
12 changed files with 52 additions and 27 deletions

View File

@@ -0,0 +1,26 @@
name: Check
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
with:
go-version-file: go.mod
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- name: Run make check
run: make check

View File

@@ -51,7 +51,7 @@ type Config struct {
MaintenanceMode bool
MetricsUsername string
MetricsPassword string
SessionSecret string //nolint:gosec // not a hardcoded credential, loaded from env/file
SessionSecret string
CORSOrigins string
params *Params
log *slog.Logger

View File

@@ -480,20 +480,6 @@ func (c *Client) CloneRepo(
return c.performClone(ctx, cfg)
}
// RemoveImage removes a Docker image by ID or tag.
// It returns nil if the image was successfully removed or does not exist.
func (c *Client) RemoveImage(ctx context.Context, imageID string) error {
_, err := c.docker.ImageRemove(ctx, imageID, image.RemoveOptions{
Force: true,
PruneChildren: true,
})
if err != nil && !client.IsErrNotFound(err) {
return fmt.Errorf("failed to remove image %s: %w", imageID, err)
}
return nil
}
func (c *Client) performBuild(
ctx context.Context,
opts BuildImageOptions,
@@ -754,6 +740,20 @@ func (c *Client) connect(ctx context.Context) error {
return nil
}
// RemoveImage removes a Docker image by ID or tag.
// It returns nil if the image was successfully removed or does not exist.
func (c *Client) RemoveImage(ctx context.Context, imageID string) error {
_, err := c.docker.ImageRemove(ctx, imageID, image.RemoveOptions{
Force: true,
PruneChildren: true,
})
if err != nil && !client.IsErrNotFound(err) {
return fmt.Errorf("failed to remove image %s: %w", imageID, err)
}
return nil
}
func (c *Client) close() error {
if c.docker != nil {
err := c.docker.Close()

View File

@@ -76,7 +76,7 @@ func deploymentToAPI(d *models.Deployment) apiDeploymentResponse {
func (h *Handlers) HandleAPILoginPOST() http.HandlerFunc {
type loginRequest struct {
Username string `json:"username"`
Password string `json:"password"` //nolint:gosec // request field, not a hardcoded credential
Password string `json:"password"`
}
type loginResponse struct {

View File

@@ -499,7 +499,7 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
return
}
_, _ = writer.Write([]byte(logs)) //nolint:gosec // response Content-Type is text/plain, not rendered as HTML
_, _ = writer.Write([]byte(logs))
}
}
@@ -582,7 +582,7 @@ func (h *Handlers) HandleDeploymentLogDownload() http.HandlerFunc {
}
// Check if file exists
_, err := os.Stat(logPath) //nolint:gosec // logPath is constructed by deploy service, not from user input
_, err := os.Stat(logPath)
if os.IsNotExist(err) {
http.NotFound(writer, request)

View File

@@ -726,7 +726,6 @@ func (svc *Service) cleanupCancelledDeploy(
} else {
svc.log.Info("cleaned up build dir from cancelled deploy",
"app", app.Name, "path", dirPath)
_ = deployment.AppendLog(ctx, "Cleaned up build directory")
}
}

View File

@@ -32,7 +32,7 @@ func TestCleanupCancelledDeploy_RemovesBuildDir(t *testing.T) {
require.NoError(t, os.MkdirAll(deployDir, 0o750))
// Create a file inside to verify full removal
require.NoError(t, os.WriteFile(filepath.Join(deployDir, "work"), []byte("test"), 0o600))
require.NoError(t, os.WriteFile(filepath.Join(deployDir, "work"), []byte("test"), 0o640))
// Also create a dir for a different deployment (should NOT be removed)
otherDir := filepath.Join(buildDir, "99-xyz789")

View File

@@ -52,10 +52,10 @@ func NewTestServiceWithConfig(log *slog.Logger, cfg *config.Config, dockerClient
// cleanupCancelledDeploy for testing. It removes build directories matching
// the deployment ID prefix.
func (svc *Service) CleanupCancelledDeploy(
_ context.Context,
ctx context.Context,
appName string,
deploymentID int64,
_ string,
imageID string,
) {
// We can't create real models.App/Deployment in tests easily,
// so we test the build dir cleanup portion directly.

View File

@@ -260,7 +260,7 @@ func (svc *Service) sendNtfy(
request.Header.Set("Title", title)
request.Header.Set("Priority", svc.ntfyPriority(priority))
resp, err := svc.client.Do(request) //nolint:gosec // URL constructed from trusted config, not user input
resp, err := svc.client.Do(request)
if err != nil {
return fmt.Errorf("failed to send ntfy request: %w", err)
}
@@ -352,7 +352,7 @@ func (svc *Service) sendSlack(
request.Header.Set("Content-Type", "application/json")
resp, err := svc.client.Do(request) //nolint:gosec // URL from trusted webhook config
resp, err := svc.client.Do(request)
if err != nil {
return fmt.Errorf("failed to send slack request: %w", err)
}

View File

@@ -12,7 +12,7 @@ import (
// KeyPair contains an SSH key pair.
type KeyPair struct {
PrivateKey string //nolint:gosec // field name describes SSH key material, not a hardcoded secret
PrivateKey string
PublicKey string
}

View File

@@ -369,7 +369,7 @@ document.addEventListener("alpine:init", () => {
init() {
// Read initial logs from script tag (avoids escaping issues)
const initialLogsEl = this.$el.querySelector(".initial-logs");
this.logs = initialLogsEl?.dataset.logs || "Loading...";
this.logs = initialLogsEl?.textContent || "Loading...";
// Set up scroll tracking
this.$nextTick(() => {

View File

@@ -98,7 +98,7 @@
title="Scroll to bottom"
>↓ Follow</button>
</div>
{{if .Logs.Valid}}<div hidden class="initial-logs" data-logs="{{.Logs.String}}"></div>{{end}}
{{if .Logs.Valid}}<script type="text/plain" class="initial-logs">{{.Logs.String}}</script>{{end}}
</div>
{{end}}
</div>