From bf42014ac39dabeb0cb0a3e2a46389e17283269b Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Fri, 25 Jul 2025 11:42:08 -0400 Subject: [PATCH] test: add automated Pale Moon tests (#903) * test: start work on Pale Moon tests Signed-off-by: Xe Iaso * test(palemoon): rewrite to use ci-images Signed-off-by: Xe Iaso * ci: enable palemoon tests Signed-off-by: Xe Iaso * test(palemoon): add some variables Signed-off-by: Xe Iaso * fix: disable tmate Signed-off-by: Xe Iaso * test(palemoon): disable i386 for now Signed-off-by: Xe Iaso * chore: spelling Signed-off-by: Xe Iaso --------- Signed-off-by: Xe Iaso --- .github/workflows/smoke-tests.yml | 15 ++- test/cmd/cipra/internal/containerip.go | 33 +++++++ test/cmd/cipra/internal/getlanip.go | 50 ++++++++++ test/cmd/cipra/internal/unbreakdocker.go | 34 +++++++ test/cmd/cipra/main.go | 114 +++++++++++++++++++++++ test/go.mod | 25 ++++- test/go.sum | 53 ++++++++++- test/i18n/var/.gitignore | 2 + test/lib/lib.sh | 51 ++++++++++ test/palemoon/README.md | 5 + test/palemoon/amd64/docker-compose.yml | 50 ++++++++++ test/palemoon/amd64/test.sh | 30 ++++++ test/palemoon/amd64/var/.gitignore | 2 + test/palemoon/anubis/anubis.yaml | 12 +++ test/palemoon/i386/docker-compose.yml | 44 +++++++++ test/palemoon/i386/test.sh | 30 ++++++ test/palemoon/i386/var/.gitignore | 2 + test/palemoon/scripts/install-cert.sh | 103 ++++++++++++++++++++ 18 files changed, 646 insertions(+), 9 deletions(-) create mode 100644 test/cmd/cipra/internal/containerip.go create mode 100644 test/cmd/cipra/internal/getlanip.go create mode 100644 test/cmd/cipra/internal/unbreakdocker.go create mode 100644 test/cmd/cipra/main.go create mode 100644 test/i18n/var/.gitignore create mode 100644 test/lib/lib.sh create mode 100644 test/palemoon/README.md create mode 100644 test/palemoon/amd64/docker-compose.yml create mode 100755 test/palemoon/amd64/test.sh create mode 100644 test/palemoon/amd64/var/.gitignore create mode 100644 test/palemoon/anubis/anubis.yaml create mode 100644 test/palemoon/i386/docker-compose.yml create mode 100755 test/palemoon/i386/test.sh create mode 100644 test/palemoon/i386/var/.gitignore create mode 100755 test/palemoon/scripts/install-cert.sh diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml index c598a2a..b574aaf 100644 --- a/.github/workflows/smoke-tests.yml +++ b/.github/workflows/smoke-tests.yml @@ -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 diff --git a/test/cmd/cipra/internal/containerip.go b/test/cmd/cipra/internal/containerip.go new file mode 100644 index 0000000..2550afb --- /dev/null +++ b/test/cmd/cipra/internal/containerip.go @@ -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) +} diff --git a/test/cmd/cipra/internal/getlanip.go b/test/cmd/cipra/internal/getlanip.go new file mode 100644 index 0000000..cf11dd4 --- /dev/null +++ b/test/cmd/cipra/internal/getlanip.go @@ -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") +} diff --git a/test/cmd/cipra/internal/unbreakdocker.go b/test/cmd/cipra/internal/unbreakdocker.go new file mode 100644 index 0000000..68e289e --- /dev/null +++ b/test/cmd/cipra/internal/unbreakdocker.go @@ -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 +} diff --git a/test/cmd/cipra/main.go b/test/cmd/cipra/main.go new file mode 100644 index 0000000..e6f3f7e --- /dev/null +++ b/test/cmd/cipra/main.go @@ -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) +} diff --git a/test/go.mod b/test/go.mod index 4f44f40..9919f50 100644 --- a/test/go.mod +++ b/test/go.mod @@ -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 diff --git a/test/go.sum b/test/go.sum index 243047f..a80b17b 100644 --- a/test/go.sum +++ b/test/go.sum @@ -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= diff --git a/test/i18n/var/.gitignore b/test/i18n/var/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/test/i18n/var/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/test/lib/lib.sh b/test/lib/lib.sh new file mode 100644 index 0000000..2fc0b11 --- /dev/null +++ b/test/lib/lib.sh @@ -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 " + 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 +} diff --git a/test/palemoon/README.md b/test/palemoon/README.md new file mode 100644 index 0000000..397182d --- /dev/null +++ b/test/palemoon/README.md @@ -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. diff --git a/test/palemoon/amd64/docker-compose.yml b/test/palemoon/amd64/docker-compose.yml new file mode 100644 index 0000000..9403df3 --- /dev/null +++ b/test/palemoon/amd64/docker-compose.yml @@ -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 diff --git a/test/palemoon/amd64/test.sh b/test/palemoon/amd64/test.sh new file mode 100755 index 0000000..dc093cc --- /dev/null +++ b/test/palemoon/amd64/test.sh @@ -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 || : diff --git a/test/palemoon/amd64/var/.gitignore b/test/palemoon/amd64/var/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/test/palemoon/amd64/var/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/test/palemoon/anubis/anubis.yaml b/test/palemoon/anubis/anubis.yaml new file mode 100644 index 0000000..c85e797 --- /dev/null +++ b/test/palemoon/anubis/anubis.yaml @@ -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 diff --git a/test/palemoon/i386/docker-compose.yml b/test/palemoon/i386/docker-compose.yml new file mode 100644 index 0000000..cda915d --- /dev/null +++ b/test/palemoon/i386/docker-compose.yml @@ -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 diff --git a/test/palemoon/i386/test.sh b/test/palemoon/i386/test.sh new file mode 100755 index 0000000..dc093cc --- /dev/null +++ b/test/palemoon/i386/test.sh @@ -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 || : diff --git a/test/palemoon/i386/var/.gitignore b/test/palemoon/i386/var/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/test/palemoon/i386/var/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/test/palemoon/scripts/install-cert.sh b/test/palemoon/scripts/install-cert.sh new file mode 100755 index 0000000..b6dd411 --- /dev/null +++ b/test/palemoon/scripts/install-cert.sh @@ -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."