remove deprecated legacy launcher

This commit is contained in:
oneflux 2025-04-21 18:34:57 -07:00
parent a5a458a3f4
commit 591f056478
14 changed files with 6 additions and 949 deletions

View file

@ -1,6 +0,0 @@
## Deprecated Assets
##### 2024-11-21
- Old launcher has been deprecated due to it not supporting non-Linux platforms, and for using FF's debugging protocol to load addons [#90](https://github.com/daijro/camoufox/issues/90).
- `generate-locales.sh` (based on [LibreWolf's locale build system](https://gitlab.com/librewolf-community/browser/source/-/blob/3dc56de7b0665724bf3842198cebe961c42a81e0/scripts/generate-locales.sh)) was deprecated due to "Camoufox" leaking to the page [#90](https://github.com/daijro/camoufox/issues/90).

View file

@ -1,34 +0,0 @@
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: $0 <arch> <os>"
echo "arch: x86_64, i686, arm64"
echo "os: linux, windows, macos"
exit 1
fi
ARCH=$1
OS=$2
case $ARCH in
x86_64) GOARCH=amd64 ;;
i686) GOARCH=386 ;;
arm64) GOARCH=arm64 ;;
*) echo "Invalid architecture"; exit 1 ;;
esac
case $OS in
linux) GOOS=linux ;;
windows) GOOS=windows ;;
macos) GOOS=darwin ;;
*) echo "Invalid OS"; exit 1 ;;
esac
[ "$OS" = "windows" ] && OUTPUT="launch.exe" || OUTPUT="launch"
rm -rf ./dist launch launch.exe
echo Building launcher...
GOOS=$GOOS GOARCH=$GOARCH go build -o dist/$OUTPUT || exit 1
echo "Complete: launcher/dist/$OUTPUT"

File diff suppressed because one or more lines are too long

View file

@ -1,166 +0,0 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"syscall"
)
func getExecutableName() string {
// Get the executable name based on the OS
switch normalizeOS(runtime.GOOS) {
case "linux":
return getPath("camoufox-bin")
case "macos":
return getPath("Camoufox.app")
case "windows":
return getPath("camoufox.exe")
default:
// This should never be reached due to the check in normalizeOS
return ""
}
}
func setExecutablePermissions(execPath string) error {
// Set executable permissions if needed
switch normalizeOS(runtime.GOOS) {
case "macos":
return filepath.Walk(execPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
return maybeSetPermission(path, 0755)
})
case "linux":
return maybeSetPermission(execPath, 0755)
}
return nil
}
func maybeSetPermission(path string, mode os.FileMode) error {
info, err := os.Stat(path)
if err != nil {
return err
}
currentMode := info.Mode().Perm()
if currentMode != mode {
return os.Chmod(path, mode)
}
return nil
}
func filterOutput(r io.Reader, w io.Writer) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if !ExclusionRegex.MatchString(line) {
fmt.Fprintln(w, line)
}
}
}
// Run Camoufox
func runCamoufox(execName string, args []string, addonsList []string, stderrPath string) {
// If addons are specified, get the debug port
var debugPortInt int
if len(addonsList) > 0 {
debugPortInt = getDebugPort(&args)
}
// For macOS, use "open" command to launch the app
if normalizeOS(runtime.GOOS) == "macos" {
args = append([]string{"-a", execName}, args...)
execName = "open"
}
// Print args
cmd := exec.Command(execName, args...)
setProcessGroupID(cmd)
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Printf("Error creating stdout pipe: %v\n", err)
os.Exit(1)
}
stderr, err := cmd.StderrPipe()
if err != nil {
fmt.Printf("Error creating stderr pipe: %v\n", err)
os.Exit(1)
}
// Set up signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
if err := cmd.Start(); err != nil {
fmt.Printf("Error starting %s: %v\n", execName, err)
os.Exit(1)
}
if len(addonsList) > 0 {
go tryLoadAddons(debugPortInt, addonsList)
}
// Channel to signal when the subprocess has finished
subprocessDone := make(chan struct{})
// Start a goroutine to handle signals
go func() {
select {
case <-sigChan:
killProcessGroup(cmd)
case <-subprocessDone:
// Subprocess has finished, exit the Go process
os.Exit(0)
}
}()
done := make(chan bool)
go func() {
filterOutput(stdout, os.Stdout)
done <- true
}()
go func() {
// If stderrPath is not empty, write to the file
fmt.Printf("Setting stderr to file: %s\n", stderrPath)
if stderrPath != "" {
file, err := os.Create(stderrPath)
if err != nil {
fmt.Printf("Error creating stderr file: %v\n", err)
os.Exit(1)
}
defer file.Close()
filterOutput(stderr, file)
}
filterOutput(stderr, os.Stderr)
}()
<-done
<-done
// Wait for the command to finish
if err := cmd.Wait(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// If the subprocess exited with an error, use its exit code
os.Exit(exitErr.ExitCode())
} else {
fmt.Printf("Error running %s: %v\n", execName, err)
os.Exit(1)
}
}
// Signal that the subprocess has finished
close(subprocessDone)
// Wait here to allow the signal handling goroutine to exit the process
select {}
}

View file

@ -1,7 +0,0 @@
module launch
go 1.23.0
require github.com/mileusna/useragent v1.3.4
require github.com/goccy/go-json v0.10.3

View file

@ -1,4 +0,0 @@
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk=
github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=

View file

@ -1,157 +0,0 @@
package main
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
)
// Gets the debug port from the args, or creates a new one if not provided
func getDebugPort(args *[]string) int {
debugPort := parseArgs("-start-debugger-server", "", args, false)
var debugPortInt int
var err error
if debugPort == "" {
// Create new debugger server port
debugPortInt = getOpenPort()
// Add -start-debugger-server {debugPort} to args
*args = append(*args, "-start-debugger-server", strconv.Itoa(debugPortInt))
} else {
debugPortInt, err = strconv.Atoi(debugPort)
if err != nil {
fmt.Printf("Error parsing debug port. Must be an integer: %v\n", err)
os.Exit(1)
}
}
return debugPortInt
}
// Confirm paths are valid
func confirmPaths(paths []string) {
for _, path := range paths {
if _, err := os.Stat(path); err != nil {
fmt.Printf("Error: %s is not a valid addon path.\n", path)
os.Exit(1)
}
}
}
// Generate an open port
func getOpenPort() int {
ln, err := net.Listen("tcp", ":0") // listen on a random port
if err != nil {
return 0
}
defer ln.Close()
addr := ln.Addr().(*net.TCPAddr) // type assert to *net.TCPAddr to get the Port
return addr.Port
}
// Waits for the server to start, then loads the addons
func tryLoadAddons(debugPortInt int, addonsList []string) {
// Wait for the server to be open
for {
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", debugPortInt))
if err == nil {
conn.Close()
break
}
time.Sleep(10 * time.Millisecond)
}
// Load addons
for _, addon := range addonsList {
go loadFirefoxAddon(debugPortInt, addon)
}
}
// Firefox addon loader
// Ported from this Nodejs implementation:
// https://github.com/microsoft/playwright/issues/7297#issuecomment-1211763085
func loadFirefoxAddon(port int, addonPath string) bool {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", "localhost", port))
if err != nil {
return false
}
defer conn.Close()
success := false
send := func(data map[string]string) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
_, err = fmt.Fprintf(conn, "%d:%s", len(jsonData), jsonData)
return err
}
err = send(map[string]string{
"to": "root",
"type": "getRoot",
})
if err != nil {
return false
}
onMessage := func(message map[string]interface{}) bool {
if addonsActor, ok := message["addonsActor"].(string); ok {
err := send(map[string]string{
"to": addonsActor,
"type": "installTemporaryAddon",
"addonPath": addonPath,
})
if err != nil {
return true
}
}
if _, ok := message["addon"]; ok {
success = true
return true
}
if _, ok := message["error"]; ok {
return true
}
return false
}
reader := bufio.NewReader(conn)
for {
lengthStr, err := reader.ReadString(':')
if err != nil {
break
}
length, err := strconv.Atoi(strings.TrimSuffix(lengthStr, ":"))
if err != nil {
break
}
jsonData := make([]byte, length)
_, err = reader.Read(jsonData)
if err != nil {
break
}
var message map[string]interface{}
err = json.Unmarshal(jsonData, &message)
if err != nil {
break
}
if onMessage(message) {
break
}
}
return success
}

View file

@ -1,219 +0,0 @@
package main
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"unicode/utf8"
json "github.com/goccy/go-json"
"github.com/mileusna/useragent"
)
func main() {
args := os.Args[1:]
configPath := parseArgs("--config", "{}", &args, true)
addons := parseArgs("--addons", "[]", &args, true)
excludeAddons := parseArgs("--exclude-addons", "[]", &args, true)
stderrPath := parseArgs("--stderr", "", &args, true)
//*** PARSE CONFIG ***//
// Read and parse the config file
var configMap map[string]interface{}
parseJson(configPath, &configMap)
validateConfig(configMap)
// Add "debug: True" to the config
if stderrPath != "" {
configMap["debug"] = true
}
//*** PARSE ADDONS ***//
// If addons are passed, handle them
var addonsList []string
parseJson(addons, &addonsList)
// Confirm addon paths are valid
confirmPaths(addonsList)
// Add the default addons, excluding the ones specified in --exclude-addons
var excludeAddonsList []string
parseJson(excludeAddons, &excludeAddonsList)
addDefaultAddons(excludeAddonsList, &addonsList)
//*** FONTS ***//
// Determine the target OS
userAgentOS := determineUserAgentOS(configMap)
// Add OS specific fonts
updateFonts(configMap, userAgentOS)
//*** LAUNCH ***//
setEnvironmentVariables(configMap, userAgentOS)
// Run the Camoufox executable
execName := getExecutableName()
if err := setExecutablePermissions(execName); err != nil {
fmt.Printf("Error setting executable permissions: %v\n", err)
os.Exit(1)
}
runCamoufox(execName, args, addonsList, stderrPath)
}
// Returns the absolute path relative to the launcher
func getPath(path string) string {
execPath, err := os.Executable()
if err != nil {
fmt.Printf("Error getting executable path: %v\n", err)
os.Exit(1)
}
execDir := filepath.Dir(execPath)
addonPath := filepath.Join(execDir, path)
return addonPath
}
// Parses & removes an argument from the args list
func parseArgs(param string, defaultValue string, args *[]string, removeFromArgs bool) string {
for i := 0; i < len(*args); i++ {
if (*args)[i] != param {
continue
}
if i+1 < len(*args) {
value := (*args)[i+1]
if removeFromArgs {
*args = append((*args)[:i], (*args)[i+2:]...)
}
return value
}
fmt.Printf("Error: %s flag requires a value\n", param)
os.Exit(1)
}
return defaultValue
}
// fileExists checks if a file exists
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// Parses a JSON string or file into a map
func parseJson(argv string, target interface{}) {
// Unmarshal the config input into a map
var data []byte
var err error
// Check if the input is a file path or inline JSON
if fileExists(argv) {
data, err = os.ReadFile(argv)
if err != nil {
fmt.Printf("Error reading JSON file: %v\n", err)
os.Exit(1)
}
} else {
// Assume it's inline JSON
data = []byte(argv)
}
if err := json.Unmarshal(data, target); err != nil {
fmt.Printf("Invalid JSON: %v\n", err)
os.Exit(1)
}
}
// Determines the target OS from the user agent string if provided
func determineUserAgentOS(configMap map[string]interface{}) string {
// Determine the OS from the user agent string if provided
defaultOS := normalizeOS(runtime.GOOS)
if ua, ok := configMap["navigator.userAgent"].(string); ok {
parsedUA := useragent.Parse(ua)
if parsedUA.OS != "" {
return normalizeOS(parsedUA.OS)
}
}
return defaultOS
}
// Get the OS name as {macos, windows, linux}
func normalizeOS(osName string) string {
osName = strings.ToLower(osName)
switch {
case osName == "darwin" || strings.Contains(osName, "mac"):
return "macos"
case strings.Contains(osName, "win"):
return "windows"
default:
return "linux"
}
}
// Add fonts associated with the OS to the config map
func updateFonts(configMap map[string]interface{}, userAgentOS string) {
fonts, ok := configMap["fonts"].([]interface{})
if !ok {
fonts = []interface{}{}
}
existingFonts := make(map[string]bool)
for _, font := range fonts {
if f, ok := font.(string); ok {
existingFonts[f] = true
}
}
for _, font := range FontsByOS[userAgentOS] {
if !existingFonts[font] {
fonts = append(fonts, font)
}
}
configMap["fonts"] = fonts
}
// Update the config map with the fonts and environment variables
func setEnvironmentVariables(configMap map[string]interface{}, userAgentOS string) {
updatedConfigData, err := json.Marshal(configMap)
if err != nil {
fmt.Printf("Error updating config: %v\n", err)
os.Exit(1)
}
// Validate utf8
if !utf8.Valid(updatedConfigData) {
fmt.Println("Config is not valid UTF-8")
os.Exit(1)
}
// Split the config into chunks of 2047 characters if the OS is Windows,
// otherwise split into 32767 characters
var chunkSize int
if normalizeOS(runtime.GOOS) == "windows" {
chunkSize = 2047
} else {
chunkSize = 32767
}
configStr := string(updatedConfigData)
for i := 0; i < len(configStr); i += chunkSize {
end := i + chunkSize
if end > len(configStr) {
end = len(configStr)
}
chunk := configStr[i:end]
envName := fmt.Sprintf("CAMOU_CONFIG_%d", (i/chunkSize)+1)
if err := os.Setenv(envName, chunk); err != nil {
fmt.Printf("Error setting %s: %v\n", envName, err)
os.Exit(1)
}
}
if normalizeOS(runtime.GOOS) == "linux" {
fontconfigPath := getPath(filepath.Join("fontconfig", userAgentOS))
os.Setenv("FONTCONFIG_PATH", fontconfigPath)
}
}

View file

@ -1,17 +0,0 @@
//go:build !windows
// +build !windows
package main
import (
"os/exec"
"syscall"
)
func setProcessGroupID(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
}
func killProcessGroup(cmd *exec.Cmd) {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}

View file

@ -1,16 +0,0 @@
//go:build windows
// +build windows
package main
import (
"os/exec"
)
func setProcessGroupID(cmd *exec.Cmd) {
// Windows doesn't support process groups in the same way
}
func killProcessGroup(cmd *exec.Cmd) {
cmd.Process.Kill()
}

View file

@ -1,75 +0,0 @@
package main
import (
"fmt"
"math"
"os"
"reflect"
"runtime"
)
type Property struct {
Property string `json:"property"`
Type string `json:"type"`
}
func validateConfig(configMap map[string]interface{}) {
properties := loadProperties()
// Create a map for quick lookup of property types
propertyTypes := make(map[string]string)
for _, prop := range properties {
propertyTypes[prop.Property] = prop.Type
}
for key, value := range configMap {
expectedType, exists := propertyTypes[key]
if !exists {
fmt.Printf("Warning: Unknown property %s in config\n", key)
continue
}
if !validateType(value, expectedType) {
fmt.Printf("Invalid type for property %s. Expected %s, got %T\n", key, expectedType, value)
os.Exit(1)
}
}
}
func loadProperties() []Property {
// Get the path to the properties.json file
var propertiesPath string
if normalizeOS(runtime.GOOS) == "macos" {
propertiesPath = getPath("Camoufox.app/Contents/Resources/properties.json")
} else {
propertiesPath = getPath("properties.json")
}
var properties []Property
// Parse the JSON file
parseJson(propertiesPath, &properties)
return properties
}
func validateType(value interface{}, expectedType string) bool {
switch expectedType {
case "str":
_, ok := value.(string)
return ok
case "int":
v, ok := value.(float64)
return ok && v == math.Trunc(v)
case "uint":
v, ok := value.(float64)
return ok && v == math.Trunc(v) && v >= 0
case "double":
_, ok := value.(float64)
return ok
case "bool":
_, ok := value.(bool)
return ok
case "array":
return reflect.TypeOf(value).Kind() == reflect.Slice
default:
return false
}
}

View file

@ -1,146 +0,0 @@
package main
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
// Downloads and extracts the default addons
func addDefaultAddons(excludeList []string, addonsList *[]string) {
// Build a map from DefaultAddons, excluding keys found in excludeAddonsList
addonsMap := make(map[string]string)
for name, url := range DefaultAddons {
if len(excludeList) == 0 || !contains(excludeList, name) {
addonsMap[name] = url
}
}
// Download if not already downloaded
maybeDownloadAddons(addonsMap, addonsList)
}
// Downloads and extracts the addon
func downloadAndExtract(url, extractPath string) error {
// Create a temporary file to store the downloaded zip
tempFile, err := os.CreateTemp("", "camoufox-addon-*.zip")
if err != nil {
return fmt.Errorf("failed to create temp file: %w", err)
}
defer os.Remove(tempFile.Name()) // Clean up the temp file when done
// Download the zip file
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to download addon: %w", err)
}
defer resp.Body.Close()
// Write the body to the temp file
_, err = io.Copy(tempFile, resp.Body)
if err != nil {
return fmt.Errorf("failed to write addon to temp file: %w", err)
}
// Close the file before unzipping
tempFile.Close()
// Extract the zip file
err = unzip(tempFile.Name(), extractPath)
if err != nil {
return fmt.Errorf("failed to extract addon: %w", err)
}
return nil
}
// Extracts the zip file
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// Checks if a slice contains a string
func contains(slice []string, item string) bool {
for _, s := range slice {
if s == item {
return true
}
}
return false
}
// Returns the absolute path to the target addon location
func getAddonPath(addonName string) string {
return getPath(filepath.Join("addons", addonName))
}
// Downloads and extracts the addons
func maybeDownloadAddons(addons map[string]string, addonsList *[]string) {
for addonName, url := range addons {
// Get the addon path
addonPath := getAddonPath(addonName)
// Check if the addon is already extracted
if _, err := os.Stat(addonPath); !os.IsNotExist(err) {
// Add the existing addon path to addonsList
*addonsList = append(*addonsList, addonPath)
continue
}
// Addon doesn't exist, create directory and download
err := os.MkdirAll(addonPath, 0755)
if err != nil {
fmt.Printf("Failed to create directory for %s: %v\n", addonName, err)
continue
}
err = downloadAndExtract(url, addonPath)
if err != nil {
fmt.Printf("Failed to download and extract %s: %v\n", addonName, err)
} else {
fmt.Printf("Successfully downloaded and extracted %s\n", addonName)
// Add the new addon directory path to addonsList
*addonsList = append(*addonsList, addonPath)
}
}
}

View file

@ -1,49 +0,0 @@
#!/usr/bin/bash
if [ ! -f browser/locales/shipped-locales ]; then
echo "ERROR: Run this script from the root of the Camoufox source code"
exit 1
fi
rm -rf browser/locales/l10n
mkdir browser/locales/l10n
N=8
for i in $(seq $N); do echo; done
total=$(wc -l < browser/locales/shipped-locales)
echo_status() {
printf "\033[$((($N - $n) + 1))A$@ %40s\r\033[$((($N - $n) + 1))B"
}
generate_locale() {
if echo " en-US ca ja " | grep -q " $1 "; then
echo_status "Skipping locale \"$1\""
sleep 1
echo_status
return
fi
echo_status "Downloading locale \"$1\""
wget -q -O browser/locales/l10n/$1.zip https://hg.mozilla.org/l10n-central/$1/archive/tip.zip
echo_status "Extracting locale \"$1\""
7z x -y -obrowser/locales/l10n browser/locales/l10n/$1.zip > /dev/null
mv browser/locales/l10n/$1-*/ browser/locales/l10n/$1/
rm -f browser/locales/l10n/$1.zip
echo_status "Generating locale \"$1\""
mv browser/locales/l10n/$1/browser/branding/official browser/locales/l10n/$1/browser/branding/camoufox
find browser/locales/l10n/$1 -type f -exec sed -i -e 's/Mozilla Firefox/Camoufox/g' {} \;
find browser/locales/l10n/$1 -type f -exec sed -i -e 's/Mozilla/Camoufox/g' {} \;
find browser/locales/l10n/$1 -type f -exec sed -i -e 's/Firefox/Camoufox/g' {} \;
echo_status "Done"
sleep 0.3
echo_status
}
while read in; do
((n=n%N)); ((n++==0)) && wait
generate_locale $in &
done < browser/locales/shipped-locales
wait
printf "\033[$(($N))A\rGenerated $total locales %-40s\n"

View file

@ -668,11 +668,11 @@ defaultPref("dom.ipc.keepProcessesAlive.web", 0);
defaultPref("javascript.options.wasm_shared_memory", false);
// Disable TLS 1.0 and 1.1
user_pref("security.tls.version.min", 3); // 3 = TLS 1.2
user_pref("security.tls.version.max", 4); // 4 = TLS 1.3
defaultPref("security.tls.version.min", 3); // 3 = TLS 1.2
defaultPref("security.tls.version.max", 4); // 4 = TLS 1.3
// Disable weak ciphers
user_pref("security.ssl3.rsa_rc4_128_sha", false);
user_pref("security.ssl3.rsa_des_ede3_sha", false);
user_pref("security.ssl3.dhe_rsa_aes_128_sha", false);
user_pref("security.ssl3.dhe_rsa_aes_256_sha", false);
defaultPref("security.ssl3.rsa_rc4_128_sha", false);
defaultPref("security.ssl3.rsa_des_ede3_sha", false);
defaultPref("security.ssl3.dhe_rsa_aes_128_sha", false);
defaultPref("security.ssl3.dhe_rsa_aes_256_sha", false);