test: add automated Pale Moon tests (#903)

* test: start work on Pale Moon tests

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(palemoon): rewrite to use ci-images

Signed-off-by: Xe Iaso <me@xeiaso.net>

* ci: enable palemoon tests

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(palemoon): add some variables

Signed-off-by: Xe Iaso <me@xeiaso.net>

* fix: disable tmate

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(palemoon): disable i386 for now

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: spelling

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso 2025-07-25 11:42:08 -04:00 committed by GitHub
parent 0ef3461816
commit bf42014ac3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 646 additions and 9 deletions

View File

@ -18,7 +18,9 @@ jobs:
- git-push
- healthcheck
- i18n
runs-on: ubuntu-24.04
- palemoon/amd64
#- palemoon/i386
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
@ -43,3 +45,14 @@ jobs:
run: |
cd test/${{ matrix.test }}
backoff-retry --try-count 10 ./test.sh
- name: Sanitize artifact name
if: always()
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
- name: Upload artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
if: always()
with:
name: ${{ env.ARTIFACT_NAME }}
path: test/${{ matrix.test }}/var

View File

@ -0,0 +1,33 @@
package internal
import (
"context"
"fmt"
"github.com/docker/docker/client"
)
// GetContainerIPAddress returns the first non-empty IP address of the container with the given name.
// It returns the IP address as a string or an error.
func GetContainerIPAddress(containerName string) (string, error) {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return "", err
}
// Get container details
containerJSON, err := cli.ContainerInspect(ctx, containerName)
if err != nil {
return "", err
}
// Loop through all networks and return the first IP address found
for _, net := range containerJSON.NetworkSettings.Networks {
if net.IPAddress != "" {
return net.IPAddress, nil
}
}
return "", fmt.Errorf("no IP address found for container %q", containerName)
}

View File

@ -0,0 +1,50 @@
package internal
import (
"fmt"
"net"
)
// GetLANIP returns the first non-loopback IPv4 LAN IP address.
func GetLANIP() (net.IP, error) {
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
// Skip down or loopback interfaces
if iface.Flags&(net.FlagUp|net.FlagLoopback) != net.FlagUp {
continue
}
addrs, err := iface.Addrs()
if err != nil {
continue // skip interfaces we can't query
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue // not an IPv4 address
}
return ip, nil
}
}
return nil, fmt.Errorf("no connected LAN IPv4 address found")
}

View File

@ -0,0 +1,34 @@
package internal
import (
"context"
"log"
"os"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
)
// UnbreakDocker connects the container named after the current hostname
// to the specified Docker network.
func UnbreakDocker(networkName string) error {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return err
}
hostname, err := os.Hostname()
if err != nil {
return err
}
err = cli.NetworkConnect(ctx, networkName, hostname, &network.EndpointSettings{})
if err != nil {
return err
}
log.Printf("Connected container %q to network %q\n", hostname, networkName)
return nil
}

114
test/cmd/cipra/main.go Normal file
View File

@ -0,0 +1,114 @@
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/TecharoHQ/anubis/test/cmd/cipra/internal"
"github.com/facebookgo/flagenv"
)
var (
bind = flag.String("bind", ":9090", "TCP host:port to bind HTTP on")
browserBin = flag.String("browser-bin", "palemoon", "browser binary name")
browserContainerName = flag.String("browser-container-name", "palemoon", "browser container name")
composeName = flag.String("compose-name", "", "docker compose base name for resources")
vncServerContainer = flag.String("vnc-container-name", "display", "VNC host:port (NOT a display number)")
)
func main() {
flagenv.Parse()
flag.Parse()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
lanip, err := internal.GetLANIP()
if err != nil {
log.Panic(err)
}
os.Setenv("TARGET", fmt.Sprintf("%s%s", lanip.String(), *bind))
http.HandleFunc("/{$}", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "OK", http.StatusOK)
log.Println("got termination signal", r.RequestURI)
go func() {
time.Sleep(2 * time.Second)
cancel()
}()
})
srv := &http.Server{
Handler: http.DefaultServeMux,
Addr: *bind,
}
go func() {
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
log.Panic(err)
}
}()
if err := RunScript(ctx, "docker", "compose", "up", "-d"); err != nil {
log.Fatalf("can't start project: %v", err)
}
defer RunScript(ctx, "docker", "compose", "down", "-t", "1")
defer RunScript(ctx, "docker", "compose", "rm", "-f")
internal.UnbreakDocker(*composeName + "_default")
if err := RunScript(ctx, "docker", "exec", fmt.Sprintf("%s-%s-1", *composeName, *browserContainerName), "bash", "/hack/scripts/install-cert.sh"); err != nil {
log.Panic(err)
}
if err := RunScript(ctx, "docker", "exec", fmt.Sprintf("%s-%s-1", *composeName, *browserContainerName), *browserBin, "https://relayd"); err != nil {
log.Panic(err)
}
<-ctx.Done()
srv.Close()
time.Sleep(2 * time.Second)
}
func RunScript(ctx context.Context, args ...string) error {
var err error
backoff := 250 * time.Millisecond
for attempt := 0; attempt < 5; attempt++ {
select {
case <-ctx.Done():
return nil
default:
}
log.Printf("Running command: %s", strings.Join(args, " "))
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if exitErr, ok := err.(*exec.ExitError); ok {
log.Printf("attempt=%d code=%d", attempt, exitErr.ExitCode())
}
if err == nil {
return nil
}
log.Printf("Attempt %d failed: %v %T", attempt+1, err, err)
log.Printf("Retrying in %v...", backoff)
time.Sleep(backoff)
backoff *= 2
}
return fmt.Errorf("script failed after 5 attempts: %w", err)
}

View File

@ -6,6 +6,7 @@ replace github.com/TecharoHQ/anubis => ..
require (
github.com/TecharoHQ/anubis v1.19.1
github.com/docker/docker v28.0.1+incompatible
github.com/facebookgo/flagenv v0.0.0-20160425205200-fcd59fca7456
github.com/google/uuid v1.6.0
)
@ -13,27 +14,38 @@ require (
require (
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.6-20250425153114-8976f5be98c1.1 // indirect
cel.dev/expr v0.24.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/TecharoHQ/thoth-proto v0.4.0 // indirect
github.com/a-h/templ v0.3.906 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.8.4 // indirect
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gaissmai/bart v0.20.5 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/cel-go v0.25.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/jsha/minica v1.1.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lum8rjack/go-ja4h v0.0.0-20250606032308-3a989c6635be // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.22.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
@ -45,15 +57,22 @@ require (
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.etcd.io/bbolt v1.4.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect
go.opentelemetry.io/otel/metric v1.37.0 // indirect
go.opentelemetry.io/otel/trace v1.37.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gotest.tools/v3 v3.5.2 // indirect
k8s.io/apimachinery v0.33.2 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/yaml v1.5.0 // indirect

View File

@ -24,6 +24,8 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@ -32,7 +34,6 @@ github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpS
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -61,6 +62,7 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gaissmai/bart v0.20.5 h1:ehoWZWQ7j//qt0K0Zs4i9hpoPpbgqsMQiR8W2QPJh+c=
github.com/gaissmai/bart v0.20.5/go.mod h1:cEed+ge8dalcbpi8wtS9x9m2hn/fNJH5suhdGQOHnYk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -83,10 +85,14 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jsha/minica v1.1.0 h1:O2ZbzAN75w4RTB+5+HfjIEvY5nxRqDlwj3ZlLVG5JD8=
github.com/jsha/minica v1.1.0/go.mod h1:dxC3wNmD+gU1ewXo/R8jB2ihB6wNpyXrG8aUk5Iuf/k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -164,6 +170,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I=
@ -174,6 +182,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA=
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
@ -182,28 +194,57 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI=
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.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
@ -214,6 +255,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY=
k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=

2
test/i18n/var/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

51
test/lib/lib.sh Normal file
View File

@ -0,0 +1,51 @@
REPO_ROOT=$(git rev-parse --show-toplevel)
(cd $REPO_ROOT && go install ./utils/cmd/...)
function cleanup() {
pkill -P $$
if [ -f "docker-compose.yaml" ]; then
docker compose down -t 1 || :
docker compose rm -f || :
fi
}
trap cleanup EXIT SIGINT
function build_anubis_ko() {
(
cd $REPO_ROOT && npm ci && npm run assets
)
(
cd $REPO_ROOT &&
VERSION=devel ko build \
--platform=all \
--base-import-paths \
--tags="latest" \
--image-user=1000 \
--image-annotation="" \
--image-label="" \
./cmd/anubis \
--local
)
}
function mint_cert() {
if [ "$#" -ne 1 ]; then
echo "Usage: mint_cert <domain.name>"
fi
domainName="$1"
# If the transient local TLS certificate doesn't exist, mint a new one
if [ ! -f "${REPO_ROOT}/test/pki/${domainName}/cert.pem" ]; then
# Subshell to contain the directory change
(
cd ${REPO_ROOT}/test/pki &&
mkdir -p "${domainName}" &&
go tool minica -domains "${domainName}" &&
cd "${domainName}" &&
chmod 666 *
)
fi
}

5
test/palemoon/README.md Normal file
View File

@ -0,0 +1,5 @@
# Pale Moon CI tests
Pale Moon has exposed [some pretty bad bugs](https://anubis.techaro.lol/blog/release/v1.21.1#fix-event-loop-thrashing-when-solving-a-proof-of-work-challenge) in Anubis. As such, we're running Pale Moon against Anubis in CI to ensure that it keeps working.
This test is a fork of [dtinth/xtigervnc-docker](https://github.com/dtinth/xtigervnc-docker) but focused on Pale Moon.

View File

@ -0,0 +1,50 @@
services:
display:
image: ghcr.io/techarohq/ci-images/xserver:latest
pull_policy: always
ports:
- 5900:5900
anubis:
image: ko.local/anubis
environment:
BIND: ":3000"
TARGET: http://$TARGET
POLICY_FNAME: /cfg/anubis.yaml
SLOG_LEVEL: DEBUG
volumes:
- ../anubis:/cfg
depends_on:
- relayd
relayd:
image: ghcr.io/xe/x/relayd
environment:
BIND: :443
CERT_DIR: /techaro/pki
CERT_FNAME: cert.pem
KEY_FNAME: key.pem
PROXY_TO: http://anubis:3000
volumes:
- ../../pki/relayd:/techaro/pki:ro
# novnc:
# image: geek1011/easy-novnc
# command: -a :5800 -h display --no-url-password
# ports:
# - 5800:5800
palemoon:
platform: linux/amd64
init: true
image: ghcr.io/techarohq/ci-images/palemoon:latest
command: sleep inf
environment:
DISPLAY: display:0
volumes:
- ../../pki:/usr/local/share/ca-certificates/minica:ro
- ../scripts:/hack/scripts:ro
depends_on:
- anubis
- relayd
- display

30
test/palemoon/amd64/test.sh Executable file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
export VERSION=$GITHUB_COMMIT-test
export KO_DOCKER_REPO=ko.local
function capture_vnc_snapshots() {
sudo apt-get update && sudo apt-get install -y gvncviewer
mkdir -p ./var
while true; do
timestamp=$(date +"%Y%m%d%H%M%S")
gvnccapture localhost:0 ./var/snapshot_$timestamp.png 2>/dev/null
sleep 1
done
}
source ../../lib/lib.sh
if [ "$GITHUB_ACTIONS" = "true" ]; then
capture_vnc_snapshots &
fi
set -euo pipefail
build_anubis_ko
mint_cert relayd
go run ../../cmd/cipra/ --compose-name $(basename $(pwd))
docker compose down -t 1 || :
docker compose rm -f || :

2
test/palemoon/amd64/var/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,12 @@
bots:
- name: palemoon
user_agent_regex: PaleMoon
action: CHALLENGE
challenge:
difficulty: 2
report_as: 2
algorithm: fast
status_codes:
CHALLENGE: 401
DENY: 403

View File

@ -0,0 +1,44 @@
services:
display:
image: ghcr.io/techarohq/ci-images/xserver:latest
pull_policy: always
ports:
- 5900:5900
anubis:
image: ko.local/anubis
environment:
BIND: ":3000"
TARGET: http://$TARGET
POLICY_FNAME: /cfg/anubis.yaml
SLOG_LEVEL: DEBUG
volumes:
- ../anubis:/cfg
relayd:
image: ghcr.io/xe/x/relayd
environment:
BIND: :443
CERT_DIR: /techaro/pki
CERT_FNAME: cert.pem
KEY_FNAME: key.pem
PROXY_TO: http://anubis:3000
volumes:
- ../../pki/relayd:/techaro/pki:ro
# novnc:
# image: geek1011/easy-novnc
# command: -a :5800 -h display --no-url-password
# ports:
# - 5800:5800
palemoon:
platform: linux/386
init: true
image: ghcr.io/techarohq/ci-images/palemoon:latest
command: sleep inf
environment:
DISPLAY: display:0
volumes:
- ../../pki:/usr/local/share/ca-certificates/minica:ro
- ../scripts:/hack/scripts:ro

30
test/palemoon/i386/test.sh Executable file
View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
export VERSION=$GITHUB_COMMIT-test
export KO_DOCKER_REPO=ko.local
function capture_vnc_snapshots() {
sudo apt-get update && sudo apt-get install -y gvncviewer
mkdir -p ./var
while true; do
timestamp=$(date +"%Y%m%d%H%M%S")
gvnccapture localhost:0 ./var/snapshot_$timestamp.png 2>/dev/null
sleep 1
done
}
source ../../lib/lib.sh
if [ "$GITHUB_ACTIONS" = "true" ]; then
capture_vnc_snapshots &
fi
set -euo pipefail
build_anubis_ko
mint_cert relayd
go run ../../cmd/cipra/ --compose-name $(basename $(pwd))
docker compose down -t 1 || :
docker compose rm -f || :

2
test/palemoon/i386/var/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1,103 @@
#!/usr/bin/env bash
set -euo pipefail
CERT_PATH="/usr/local/share/ca-certificates/minica/minica.pem"
CERT_NAME="minica"
TRUST_FLAGS="C,,"
FIREFOX_DIR="$HOME/.mozilla/firefox"
PALEMOON_DIR="$HOME/.moonchild productions/pale moon"
echo "🔄 Updating system CA certificates..."
update-ca-certificates
# 🌀 Trigger Pale Moon to create its profile if needed
if command -v palemoon &>/dev/null; then
echo "🚀 Launching Pale Moon to initialize profile..."
palemoon &>/dev/null &
PALEMOON_PID=$!
# Wait up to 20 seconds for prefs.js to be created
for i in {1..20}; do
set +e
PROFILE_DIR=$(grep Path ~/.moonchild\ productions/pale\ moon/profiles.ini | cut -d= -f2)
PREFS_FILE="$HOME/.moonchild productions/pale moon/$PROFILE_DIR/prefs.js"
if [[ -f "$PREFS_FILE" ]]; then
set -e
echo "✅ prefs.js found at: $PREFS_FILE"
break
fi
sleep 5
done
kill $PALEMOON_PID 2>/dev/null || true
wait $PALEMOON_PID 2>/dev/null || true
if [[ ! -f "$PREFS_FILE" ]]; then
echo "❌ prefs.js not found. Pale Moon did not fully initialize."
exit 1
fi
else
echo "⚠️ Pale Moon is not installed or not in PATH. Skipping profile bootstrap."
fi
echo 'user_pref("security.cert_pinning.enforcement_level", 0);' >>"$PREFS_FILE"
echo "✅ TLS cert validation disabled in Pale Moon profile: $PROFILE_DIR"
# 🔧 Ensure certutil is installed
if ! command -v certutil &>/dev/null; then
if [ -f /etc/debian_version ]; then
echo "🔧 'certutil' not found. Installing via apt..."
apt-get update
apt-get install -y libnss3-tools
else
echo "❌ 'certutil' not found and install is only supported on Debian-based systems."
exit 1
fi
fi
import_cert_to_profiles() {
local base_dir="$1"
local browser_name="$2"
local profile_glob="$3"
if [ ! -d "$base_dir" ]; then
echo "⚠️ $browser_name profile directory not found: $base_dir"
return
fi
echo "📌 Searching for $browser_name profiles in: $base_dir"
local found=0
for profile in "$base_dir"/$profile_glob; do
if [ ! -d "$profile" ]; then
continue
fi
found=1
local db_path="sql:$profile"
echo "🔍 Processing $browser_name profile: $profile"
if certutil -L -d "$db_path" | grep -q "^$CERT_NAME"; then
echo " ✅ Certificate '$CERT_NAME' already exists in profile."
continue
fi
certutil -A -n "$CERT_NAME" -t "$TRUST_FLAGS" -i "$CERT_PATH" -d "$db_path"
echo " Added certificate '$CERT_NAME' to $browser_name profile."
done
if [ "$found" -eq 0 ]; then
echo "⚠️ No $browser_name profiles found in: $base_dir"
fi
}
import_cert_to_profiles "$FIREFOX_DIR" "Firefox" "*.default*"
import_cert_to_profiles "$PALEMOON_DIR" "Pale Moon" "*.*"
echo "✅ Done. Firefox and Pale Moon profiles updated with '$CERT_NAME' certificate."