Install Camoufox v150:
Run in debian-13-xfce terminal:
sudo apt install python3.13-venv pip python3-pyqt5 python3-pyqt5.qtwebengine
Shutdown debian-13-xfce template.
Run in AppVM terminal:
nano cam.py
write the configuration code according to the Camoufox documentation, for example like this:
from camoufox.utils import launch_options
from playwright.sync_api import sync_playwright
import os
import time
PROFILE_DIR = "/home/user/my_profile"
if not os.path.exists(PROFILE_DIR):
raise FileNotFoundError(f"Профиль не найден: {PROFILE_DIR}")
options = launch_options(
headless=False,
os="windows",
locale="en-US",
window=(1696, 1026),
config={
"timezone": "Europe/Istanbul",
"geolocation:latitude": 41.0082,
"geolocation:longitude": 28.9784,
"navigator.language": "en-US",
"navigator.languages": ["en-US", "en"],
"headers.Accept-Language": "en-US,en;q=0.9",
"screen.width": 1700,
"screen.height": 970,
"screen.availWidth": 1700,
"screen.availHeight": 940,
"screen.colorDepth": 24,
"screen.pixelDepth": 24,
},
i_know_what_im_doing=True,
)
options.pop("executable_path", None)
options.pop("headless", None)
options["viewport"] = {"width": 1700, "height": 970}
custom_args = ["-new-instance", "-no-remote"]
if "args" in options:
custom_args = options.pop("args") + custom_args
with sync_playwright() as p:
context = p.firefox.launch_persistent_context(
user_data_dir=PROFILE_DIR,
executable_path="/home/user/.cache/camoufox/browsers/official/150.0.2-alpha.26/camoufox-bin",
headless=False,
args=custom_args,
**options
)
page = context.pages[0] if context.pages else context.new_page()
try:
page.goto("https://abrahamjuliot.github.io/creepjs";, wait_until="commit", timeout=60000)
except Exception as e:
print(f"Failed to load page: {e}")
while True:
try:
if context.pages is None:
break
pages = context.pages
if not pages:
break
page.evaluate("1")
time.sleep(1)
except Exception:
break
try:
context.close()
except:
pass
then click CTRL + O and CTRL + X
python3 -m venv ~/venvs/camoufox150
source ~/venvs/camoufox150/bin/activate
pip install cloverlabs-camoufox[geoip]
python3 -m camoufox fetch official/stable/v150.0.2-alpha.26
pip install playwright==1.51.0
python3 cam.py
You can also use a profile created in Donut Browser. Profiles are located in /home/user/.local/share/DonutBrowser/profiles/. Copy profile to, for example, /home/user, rename it for simplicity, for example donut_profile, and add profile to Camoufox configuration into PROFILE_DIR = for example, PROFILE_DIR = "/home/user/donut_profile" or add this profile in camoufox gui manager.
Create files .sh for launch this profiles:
nano /home/user/camoufox.sh
#!/usr/bin/env bash
source ~/venvs/camoufox150/bin/activate
python3 cam.py
then click CTRL + O and CTRL + X
chmod +x /home/user/camoufox.sh
You can add GUI profile manager for camoufox v150 GitHub - TechQaiser/camoufox-profile-manager: Manage multiple Camoufox browser profiles with proxy, storage, and fullscreen — GoLogin-style GUI built in PyQt5. · GitHub
git clone https://github.com/TechQaiser/camoufox-profile-manager.git
cd camoufox-profile-manager
python3 -m venv venv
then change py configs for camoufox v150 (default works on camoufox 135):
nano /home/user/camoufox-profile-manager/run.py
add this:
import os
import sys
import subprocess
import importlib.util
from pathlib import Path
REQUIREMENTS = ["PyQt5", "cloverlabs-camoufox[geoip]"]
HERE = Path(__file__).resolve().parent
ENTRY_CANDIDATES = [
"main_window.py",
]
def run(cmd, cwd=None):
print(f"[\~] Running: {' '.join(map(str, cmd))}")
subprocess.check_call(cmd, cwd=str(cwd) if cwd else None)
def pip_install(pkg):
run([sys.executable, "-m", "pip", "install", pkg], cwd=HERE)
def check_and_install():
try:
import pip
except ImportError:
print("[x] pip is not installed. Please install pip and re-run.")
sys.exit(1)
for spec in REQUIREMENTS:
base = spec.split("[", 1)[0]
if importlib.util.find_spec(base) is None:
print(f"[!] Missing {spec}, installing…")
pip_install(spec)
else:
print(f"[✓] {base} already installed")
def ensure_camoufox_browser():
"""Skip browser check"""
print("[✓] Skipping browser auto-install — using manually installed v150.0.2-alpha.26")
return
def find_entry_file() -> Path:
for name in ENTRY_CANDIDATES:
candidate = HERE / name
if candidate.exists():
return candidate
print("[x] Could not find an entry file. Expected one of:")
for n in ENTRY_CANDIDATES:
print(f" - {n} (in {HERE})")
sys.exit(2)
def main():
check_and_install()
ensure_camoufox_browser()
entry = find_entry_file()
print("\n[✓] Environment ready. Launching Camoufox Manager…\n")
try:
run([sys.executable, str(entry.name)], cwd=HERE)
except subprocess.CalledProcessError as e:
print("\n[x] Failed to launch the app.")
print(f" Command: {' '.join(map(str, e.cmd))}")
print(f" Exit code: {e.returncode}")
sys.exit(e.returncode)
if __name__ == "__main__":
main()
then click CTRL + O and CTRL + X
nano /home/user/camoufox-profile-manager/main_window.py
add this:
import json
import os
import sys
import time
from dataclasses import dataclass, asdict, field
from typing import Dict, Any, Optional, List
from pathlib import Path
from PyQt5 import QtCore, QtGui, QtWidgets, uic
# ---- Camoufox ----------------------------------------------------------------
try:
from camoufox.utils import launch_options
CAMOUFOX_OK = True
except Exception:
CAMOUFOX_OK = False
PROFILES_FILE = "profiles.json"
CAMOUFOX_V150_EXE: Optional[str] = "/home/user/.cache/camoufox/browsers/official/150.0.2-alpha.26/camoufox-bin"
if CAMOUFOX_V150_EXE is None and CAMOUFOX_OK:
from platformdirs import user_cache_dir
cache_dir = Path(user_cache_dir("camoufox"))
version_dir = cache_dir / "browsers" / "official" / "150.0.2-alpha.26"
if sys.platform == "linux":
candidate = version_dir / "camoufox-bin"
if candidate.exists():
CAMOUFOX_V150_EXE = str(candidate)
elif sys.platform == "darwin":
candidate = version_dir / "Camoufox.app" / "Contents" / "Windows" / "camoufox"
if candidate.exists():
CAMOUFOX_V150_EXE = str(candidate)
elif sys.platform == "win32":
candidate = version_dir / "camoufox.exe"
if candidate.exists():
CAMOUFOX_V150_EXE = str(candidate)
# ===== Data models =====
@dataclass
class ProxyConfig:
host: str = ""
port: int = 0
username: str = ""
password: str = ""
def to_proxy_dict(self) -> Optional[Dict[str, Any]]:
if not self.host or not self.port:
return None
d = {"server": f"http://{self.host}:{self.port}";}
if self.username:
d["username"] = self.username
if self.password:
d["password"] = self.password
return d
@dataclass
class Profile:
name: str = "Profile"
viewport_width: int = 1700
viewport_height: int = 970
fullscreen: bool = False
persistent_dir: str = ""
use_geoip: bool = False
proxy: ProxyConfig = field(default_factory=ProxyConfig)
def to_dict(self) -> Dict[str, Any]:
d = asdict(self)
d["proxy"] = asdict(self.proxy)
return d
@staticmethod
def from_dict(d: Dict[str, Any]) -> "Profile":
raw_proxy = d.get("proxy", {})
if not isinstance(raw_proxy, dict):
raw_proxy = {}
name = d.get("name", "Profile")
persistent_dir = d.get("persistent_dir", "")
if not persistent_dir:
persistent_dir = os.path.join("C:\\", name)
return Profile(
name=name,
viewport_width=int(d.get("viewport_width", 1700)),
viewport_height=int(d.get("viewport_height", 970)),
fullscreen=bool(d.get("fullscreen", False)),
persistent_dir=persistent_dir,
use_geoip=bool(d.get("use_geoip", False)),
proxy=ProxyConfig(
host=raw_proxy.get("host", ""),
port=int(raw_proxy.get("port", 0) or 0),
username=raw_proxy.get("username", ""),
password=raw_proxy.get("password", ""),
),
)
# ===== Persistence =====
def load_profiles() -> List[Profile]:
if not os.path.exists(PROFILES_FILE):
return []
with open(PROFILES_FILE, "r", encoding="utf-8") as f:
raw = json.load(f)
return [Profile.from_dict(x) for x in raw]
def save_profiles(profiles: List[Profile]) -> None:
with open(PROFILES_FILE, "w", encoding="utf-8") as f:
json.dump([p.to_dict() for p in profiles], f, indent=2)
# ===== Worker thread =====
class CamoufoxWorker(QtCore.QThread):
started_ok = QtCore.pyqtSignal(str)
error = QtCore.pyqtSignal(str)
stopped = QtCore.pyqtSignal(str)
def __init__(self, profile: Profile, launch_size: Optional[tuple[int, int]] = None, parent=None):
super().__init__(parent)
self.profile = profile
self.launch_size = launch_size
self._stop = False
self._context = None
self._playwright = None
def run(self):
if not CAMOUFOX_OK:
self.error.emit(
"Camoufox Python package not available.\n"
"Install: pip install -U 'cloverlabs-camoufox[geoip]'"
)
return
if not CAMOUFOX_V150_EXE or not os.path.exists(CAMOUFOX_V150_EXE):
self.error.emit(
f"Camoufox v150 executable not found.\n"
f"Expected at: {CAMOUFOX_V150_EXE or '<auto-detect failed>'}\n\n"
f"Please set CAMOUFOX_V150_EXE in the script to the correct path.\n"
f"Find it with: python3 -m camoufox list installed --path\n"
f"Or: ls ~/.cache/camoufox/browsers/official/"
)
return
try:
from playwright.sync_api import sync_playwright
VIEWPORT_W, VIEWPORT_H = 1700, 970
options = launch_options(
headless=False,
os="windows",
locale="en-US",
window=(1696, 1026),
config={
"timezone": "Europe/Berlin",
"navigator.language": "en-US",
"navigator.languages": ["en-US", "en"],
"headers.Accept-Language": "en-US,en;q=0.9",
"screen.width": 1700,
"screen.height": 970,
"screen.availWidth": 1700,
"screen.availHeight": 940,
"screen.colorDepth": 24,
"screen.pixelDepth": 24,
},
i_know_what_im_doing=True,
)
options.pop("executable_path", None)
options.pop("headless", None)
options["viewport"] = {"width": VIEWPORT_W, "height": VIEWPORT_H}
custom_args = ["-new-instance", "-no-remote"]
if "args" in options:
custom_args = options.pop("args") + custom_args
user_data_dir = None
if self.profile.persistent_dir:
user_data_dir = os.path.abspath(self.profile.persistent_dir)
os.makedirs(user_data_dir, exist_ok=True)
px = self.profile.proxy.to_proxy_dict()
if px:
options["proxy"] = px
with sync_playwright() as p:
self._playwright = p
self._context = p.firefox.launch_persistent_context(
user_data_dir=user_data_dir,
executable_path=CAMOUFOX_V150_EXE,
headless=False,
args=custom_args,
**options
)
page = self._context.pages[0] if self._context.pages else self._context.new_page()
try:
page.set_viewport_size({"width": VIEWPORT_W, "height": VIEWPORT_H})
except Exception:
pass
try:
page.goto("https://abrahamjuliot.github.io/creepjs";, wait_until="commit", timeout=60000)
except Exception:
pass
if self.profile.fullscreen:
try:
page.keyboard.press("F11")
except Exception:
pass
self.started_ok.emit(f"Session started for '{self.profile.name}'.")
while not self._stop:
time.sleep(0.2)
self._context.close()
except Exception as e:
self.error.emit(f"Failed to start Camoufox: {e}")
finally:
if self._context is not None:
try:
self._context.close()
except Exception:
pass
self.stopped.emit(f"Session stopped for '{self.profile.name}'.")
def request_stop(self):
self._stop = True
# ===== MainWindow Controller =====
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
uic.loadUi("camoufox_manager.ui", self)
self.profileList: QtWidgets.QListWidget
self.newProfileButton: QtWidgets.QPushButton
self.deleteProfileButton: QtWidgets.QPushButton
self.nameEdit: QtWidgets.QLineEdit
self.spinW: QtWidgets.QSpinBox
self.spinH: QtWidgets.QSpinBox
self.fullscreenCheck: QtWidgets.QCheckBox
self.proxyHostEdit: QtWidgets.QLineEdit
self.proxyPortSpin: QtWidgets.QSpinBox
self.proxyUserEdit: QtWidgets.QLineEdit
self.proxyPassEdit: QtWidgets.QLineEdit
self.geoipCheck: QtWidgets.QCheckBox
self.storageEdit: QtWidgets.QLineEdit
self.browseStorageButton: QtWidgets.QPushButton
self.saveButton: QtWidgets.QPushButton
self.launchButton: QtWidgets.QPushButton
self.stopButton: QtWidgets.QPushButton
self.profiles: List[Profile] = load_profiles()
self.current_index: int = -1
self.worker: Optional[CamoufoxWorker] = None
self.profileList.itemSelectionChanged.connect(self._on_select_profile)
self.newProfileButton.clicked.connect(self._new_profile)
self.deleteProfileButton.clicked.connect(self._delete_profile)
self.saveButton.clicked.connect(self._save_changes)
self.browseStorageButton.clicked.connect(self._browse_storage)
self.launchButton.clicked.connect(self._launch)
self.stopButton.clicked.connect(self._stop)
self.launchButton.setObjectName("primary")
self.stopButton.setObjectName("danger")
self._refresh_list()
if self.profiles:
self.profileList.setCurrentRow(0)
self._set_running(False)
QtWidgets.QApplication.setStyle("Fusion")
self._apply_palette()
self.statusbar.showMessage("Ready")
def _apply_palette(self):
p = QtGui.QPalette()
base = QtGui.QColor(248, 249, 251)
text = QtGui.QColor(33, 37, 41)
highlight = QtGui.QColor(76, 110, 245)
p.setColor(QtGui.QPalette.Window, base)
p.setColor(QtGui.QPalette.Base, QtGui.QColor(255, 255, 255))
p.setColor(QtGui.QPalette.AlternateBase, QtGui.QColor(245, 246, 248))
p.setColor(QtGui.QPalette.WindowText, text)
p.setColor(QtGui.QPalette.Text, text)
p.setColor(QtGui.QPalette.ButtonText, text)
p.setColor(QtGui.QPalette.Highlight, highlight)
p.setColor(QtGui.QPalette.HighlightedText, QtGui.QColor(255, 255, 255))
self.setPalette(p)
def _refresh_list(self):
self.profileList.clear()
for p in self.profiles:
self.profileList.addItem(p.name)
def _current(self) -> Optional[Profile]:
if 0 <= self.current_index < len(self.profiles):
return self.profiles[self.current_index]
return None
def _populate_form(self, p: Optional[Profile]):
if not p:
self.nameEdit.setText("")
self.spinW.setValue(1700)
self.spinH.setValue(970)
self.fullscreenCheck.setChecked(False)
self.proxyHostEdit.setText("")
self.proxyPortSpin.setValue(0)
self.proxyUserEdit.setText("")
self.proxyPassEdit.setText("")
self.geoipCheck.setChecked(False)
self.storageEdit.setText("")
return
self.nameEdit.setText(p.name)
self.spinW.setValue(p.viewport_width)
self.spinH.setValue(p.viewport_height)
self.fullscreenCheck.setChecked(p.fullscreen)
self.proxyHostEdit.setText(p.proxy.host)
self.proxyPortSpin.setValue(p.proxy.port)
self.proxyUserEdit.setText(p.proxy.username)
self.proxyPassEdit.setText(p.proxy.password)
self.geoipCheck.setChecked(p.use_geoip)
self.storageEdit.setText(p.persistent_dir)
def _gather_form(self) -> Profile:
p = self._current() or Profile()
p.name = self.nameEdit.text().strip() or "Profile"
p.viewport_width = int(self.spinW.value())
p.viewport_height = int(self.spinH.value())
p.fullscreen = self.fullscreenCheck.isChecked()
p.proxy.host = self.proxyHostEdit.text().strip()
p.proxy.port = int(self.proxyPortSpin.value())
p.proxy.username = self.proxyUserEdit.text().strip()
p.proxy.password = self.proxyPassEdit.text().strip()
p.use_geoip = self.geoipCheck.isChecked()
s = self.storageEdit.text().strip()
if not s:
s = os.path.join("C:\\", p.name)
p.persistent_dir = s
return p
def _set_running(self, running: bool):
self.launchButton.setEnabled(not running)
self.stopButton.setEnabled(running)
for w in [
self.profileList, self.newProfileButton, self.deleteProfileButton,
self.nameEdit, self.spinW, self.spinH, self.fullscreenCheck,
self.proxyHostEdit, self.proxyPortSpin, self.proxyUserEdit, self.proxyPassEdit,
self.geoipCheck, self.storageEdit, self.browseStorageButton, self.saveButton
]:
w.setEnabled(not running)
def _on_select_profile(self):
self.current_index = self.profileList.currentRow()
self._populate_form(self._current())
def _new_profile(self):
p = Profile(name=f"Profile {len(self.profiles)+1}")
p.persistent_dir = os.path.join("C:\\", p.name)
self.profiles.append(p)
save_profiles(self.profiles)
self._refresh_list()
self.profileList.setCurrentRow(len(self.profiles)-1)
self.statusbar.showMessage("New profile created", 3000)
def _delete_profile(self):
row = self.profileList.currentRow()
if row < 0:
return
name = self.profiles[row].name
if QtWidgets.QMessageBox.question(self, "Confirm Delete", f"Delete profile '{name}'?") != QtWidgets.QMessageBox.Yes:
return
del self.profiles[row]
save_profiles(self.profiles)
self._refresh_list()
self._populate_form(None)
self.current_index = -1
self.statusbar.showMessage(f"Deleted '{name}'", 3000)
def _save_changes(self):
if self.current_index == -1:
self._new_profile()
return
self.profiles[self.current_index] = self._gather_form()
save_profiles(self.profiles)
self._refresh_list()
self.profileList.setCurrentRow(self.current_index)
self.statusbar.showMessage("Profile saved", 3000)
def _browse_storage(self):
d = QtWidgets.QFileDialog.getExistingDirectory(self, "Select Storage Directory")
if d:
self.storageEdit.setText(d)
def _launch(self):
if self.worker and self.worker.isRunning():
self.statusbar.showMessage("A session is already running", 3000)
return
if self.current_index == -1:
QtWidgets.QMessageBox.information(self, "No profile", "Create or select a profile first.")
return
prof = self._gather_form()
self.profiles[self.current_index] = prof
save_profiles(self.profiles)
if not CAMOUFOX_OK:
QtWidgets.QMessageBox.warning(
self, "Camoufox not available",
"Install with:\n pip install -U 'cloverlabs-camoufox[geoip]'"
)
return
if not CAMOUFOX_V150_EXE or not os.path.exists(CAMOUFOX_V150_EXE):
QtWidgets.QMessageBox.warning(
self, "Camoufox v150 not found",
f"Executable not found at:\n{CAMOUFOX_V150_EXE or '<auto-detect failed>'}\n\n"
f"Set CAMOUFOX_V150_EXE in the script, or run:\n"
f" python3 -m camoufox list installed --path\n"
f"to find the correct path."
)
return
if prof.fullscreen:
screen = QtWidgets.QApplication.primaryScreen().availableGeometry()
launch_size = (screen.width(), screen.height())
else:
launch_size = (1700, 970)
self.worker = CamoufoxWorker(prof, launch_size)
self.worker.started_ok.connect(lambda m: self.statusbar.showMessage(m, 5000))
self.worker.error.connect(lambda m: QtWidgets.QMessageBox.critical(self, "Session Error", m))
self.worker.stopped.connect(self._on_stopped)
self.worker.start()
self._set_running(True)
def _stop(self):
if self.worker and self.worker.isRunning():
self.worker.request_stop()
self.worker.wait(5000)
self.statusbar.showMessage("Stopping session…", 3000)
else:
self.statusbar.showMessage("No session to stop", 3000)
def _on_stopped(self, msg: str):
self.statusbar.showMessage(msg, 5000)
self._set_running(False)
def apply_qss(app, path="dark.qss"):
full = os.path.abspath(path)
if not os.path.exists(full):
raise FileNotFoundError(f"QSS not found: {full}")
with open(full, "r", encoding="utf-8") as f:
app.setStyleSheet(f.read())
def main():
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
app = QtWidgets.QApplication(sys.argv)
apply_qss(app, "dark.qss")
win = MainWindow()
win.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
then click CTRL + O and CTRL + X
then run:
source venv/bin/activate
pip install PyQt5
Create launch file for GUI manager:
nano camoufox_gui.sh
#!/usr/bin/env bash
cd camoufox-profile-manager
source venv/bin/activate
python3 run.py
then click CTRL + O and CTRL + X
chmod +x /home/user/camoufox_gui.sh
Set this proxy value in user.js to enable internet access in the donor profile:
user_pref("network.proxy.type", 0);
