파이어폭스 확장 프로그램, 자동 설치하다가 멘붕? 아키텍처 이해가 답이다!
2026. 4. 12.
파이어폭스 확장 프로그램, 자동 설치하다가 멘붕? 아키텍처 이해가 답이다!
파이어폭스 확장 프로그램 설치를 자동화하려다 좌절해본 경험, 다들 한 번쯤 있으실 겁니다. 테스트 파이프라인이나 표준화된 개발 환경을 구축하려던 분들도 있겠고, 저처럼 모든 것을 스크립트로 처리하고 싶어 하는 강박(?)을 가진 분들도 계실 테죠. 사실 파이어폭스는 확장 프로그램 자동 설치를 쉽게 허락하지 않는데, 그 이유를 들여다보면 브라우저 아키텍처 관점에서 꽤 흥미로운 지점들이 많습니다.
최근 해커 뉴스에서 모든 파이어폭스 확장 프로그램을 설치하려는 괴짜 실험을 보고 한동안 이 문제에 깊이 파고들었습니다. 확장 프로그램 설치의 실제 메커니즘과 수많은 확장 프로그램을 일괄 관리하는 게 왜 그렇게 어려운지 고민하게 된 계기였죠. 저도 실무에서 특정 확장 프로그램들이 설치된 개발 환경을 배포할 때마다 비슷한 문제에 부딪히며 애를 먹었던 기억이 생생합니다.
문제: 파이어폭스 확장 프로그램은 단순한 파일이 아니다
대부분의 개발자들이 처음 품는 순진한 가정이 있습니다. "확장 프로그램은 그저 .xpi 파일(사실 ZIP 압축 파일이죠)이니, 적당한 폴더에 넣어두면 알아서 설치될 거야!"
하지만 현실은 냉혹합니다.
파이어폭스는 서명 검증, 매니페스트 파싱, 호환성 확인, 그리고 설치 상태를 추적하는 전용 데이터베이스를 포함하는 다층적인 확장 프로그램 시스템을 갖추고 있습니다. 만약 여러분이 그저 XPI 파일을 프로필 디렉터리에 복사만 한다면, 파이어폭스는 이들을 완전히 무시하거나, 더 나쁘게는 손상된 파일로 플래그를 지정할 겁니다. 제가 실무에서 이 부분을 테스트해봤을 때, 단순히 XPI 파일을 복사하는 방식이 작동하지 않아 몇 번이나 시간을 낭비했던 기억이 있네요. 결국 이런 근본적인 아키텍처를 이해하는 게 중요하더라고요.
파이어폭스의 확장 프로그램 아키텍처 이해하기
파이어폭스는 몇 가지 핵심 구성 요소를 통해 확장 프로그램을 관리합니다.
extensions.json: 프로필 디렉터리 내에 있는 데이터베이스 파일로, 모든 설치된 확장 프로그램의 상태와 메타데이터를 추적합니다.addons.json: AMO (addons.mozilla.org)에서 가져온 애드온 메타데이터의 캐시 파일입니다.extensions/디렉터리: 압축이 풀린 확장 프로그램 파일들이 실제로 프로필 내에 저장되는 곳이죠.- 서명 검증 (Signature verification): 파이어폭스 43 버전부터는 제한적인 예외를 제외하고 모든 확장 프로그램이 모질라의 서명을 받아야 합니다.
확장 프로그램 설치 흐름은 대략 이렇습니다. 파이어폭스가 XPI를 다운로드하고, 서명을 검증하며, manifest.json (혹은 레거시 install.rdf)을 확인한 다음, 압축을 해제하고, extensions.json에 등록한 후, 마지막으로 로드합니다. 이 과정 중 어느 한 단계라도 건너뛰면 모든 것이 틀어지게 됩니다.
1단계: AMO에서 확장 프로그램 다운로드하기
모질라 애드온(AMO) API는 사실 문서화가 꽤 잘 되어 있습니다. API를 활용하면 확장 프로그램을 프로그램적으로 조회하고 다운로드할 수 있죠.
import requests
import os
def download_extension(slug, download_dir="./extensions"):
"""AMO 슬러그를 이용해 파이어폭스 확장 프로그램을 다운로드합니다."""
# AMO v5 API 확장 프로그램 상세 정보 엔드포인트
api_url = f"https://addons.mozilla.org/api/v5/addons/addon/{slug}/"
resp = requests.get(api_url)
resp.raise_for_status() # HTTP 오류 발생 시 예외 처리
data = resp.json()
# 최신 버전의 다운로드 URL 가져오기
current = data["current_version"]
xpi_url = current["file"]["url"]
# 실제 XPI 파일 다운로드
xpi_resp = requests.get(xpi_url)
os.makedirs(download_dir, exist_ok=True) # 디렉터리가 없으면 생성
filename = f"{slug}-{current['version']}.xpi"
filepath = os.path.join(download_dir, filename)
with open(filepath, "wb") as f:
f.write(xpi_resp.content)
return filepath
만약 작업할 확장 프로그램 목록을 얻고 싶다면, 검색 엔드포인트가 큰 도움이 될 겁니다.
def search_extensions(query, page_size=25):
"""AMO에서 검색 쿼리에 일치하는 확장 프로그램을 찾습니다."""
url = "https://addons.mozilla.org/api/v5/addons/search/"
params = {
"q": query,
"type": "extension",
"page_size": page_size,
"sort": "users" # 인기도 순 정렬
}
resp = requests.get(url, params=params)
resp.raise_for_status()
return resp.json()["results"]
여기까지는 문제가 없습니다. 하지만 다운로드한 확장 프로그램을 실제로 설치하려고 할 때부터 머리가 아파지기 시작합니다.
2단계: 확장 프로그램을 프로그램적으로 설치하는 올바른 방법
몇 가지 합법적인 접근 방식이 있으며, 어떤 방식을 사용할지는 여러분의 사용 사례에 따라 달라집니다.
옵션 A: 파이어폭스 정책 사용 (관리되는 환경에 최적)
파이어폭스는 확장 프로그램을 강제로 설치할 수 있도록 기업 정책(Enterprise Policies)을 지원합니다. policies.json 파일을 생성해보세요.
{
"policies": {
"ExtensionSettings": {
"uBlock0@raymondhill.net": {
"installation_mode": "force_installed",
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/ublock-origin/latest.xpi"
},
"addon@darkreader.org": {
"installation_mode": "force_installed",
"install_url": "https://addons.mozilla.org/firefox/downloads/latest/darkreader/latest.xpi"
}
}
}
}
이 파일은 macOS에서는 Firefox.app/Contents/Resources/distribution/에, Linux에서는 /usr/lib/firefox/distribution/ 또는 /etc/firefox/policies/에, Windows에서는 Firefox 설치 디렉터리 아래 distribution/에 위치해야 합니다.
이 방법은 파이어폭스가 모든 검증 및 등록 과정을 스스로 처리하기 때문에 개발 환경에서 가장 깔끔한 접근 방식입니다.
옵션 B: Selenium/WebDriver 사용 (테스팅에 최적)
자동화된 테스트를 위해 확장 프로그램을 설정하는 경우, WebDriver 접근 방식이 직관적입니다.
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
options = Options()
# install_addon은 나중에 제거할 수 있도록 애드온 ID를 반환합니다.
options.add_argument("-profile") # 프로필 인자를 추가 (새 프로필 또는 기존 프로필 지정)
driver = webdriver.Firefox(options=options)
# 로컬 XPI 파일에서 설치
driver.install_addon("/path/to/extension.xpi", temporary=True)
# temporary=True는 세션 종료 후 확장 프로그램이 유지되지 않음을 의미합니다.
# 영구적으로 유지하려면 temporary=False를 사용하세요.
옵션 C: 직접 프로필 조작 (취약하지만 때로는 필요)
만약 파이어폭스 프로필에 확장 프로그램을 미리 구워 넣어야 한다면 (예: Docker 컨테이너나 휴대용 개발 환경을 위해), 서명된 XPI 파일을 프로필의 extensions/ 디렉터리에 직접 배치할 수 있습니다. 단, 파일 이름이 확장 프로그램의 ID여야 한다는 점을 명심해야 합니다.
# 먼저, XPI의 manifest에서 확장 프로그램 ID를 찾습니다.
unzip -p extension.xpi manifest.json | python3 -c "
import sys, json
manifest = json.load(sys.stdin)
# ID는 browser_specific_settings.gecko.id에 있습니다.
print(manifest.get('browser_specific_settings', {}).get('gecko', {}).get('id', 'NO_ID_FOUND'))
"
# 그 다음, 해당 ID를 파일 이름으로 XPI를 복사합니다.
cp extension.xpi ~/.mozilla/firefox/YOUR_PROFILE/extensions/{extension-id}.xpi
파이어폭스는 다음 시작 시 새 파일을 감지하고 자체 검증 과정을 거칩니다. 확장 프로그램이 제대로 서명되어 있다면 설치될 것입니다. 하지만 이 방식은 파이어폭스의 내부 상태가 꼬이거나 혼란스러워지면 작동하지 않으며, 실패했을 때 좋은 오류 보고 기능이 없다는 단점이 있습니다.
대량 설치가 실패하는 이유
수많은 확장 프로그램을 한꺼번에 설치하려고 할 때 문제가 발생합니다. 여러 가지 이유가 복합적으로 작용하죠.
메모리 및 시작 시간. 각 확장 프로그램은 오버헤드를 추가합니다. 파이어폭스는 시작 시 모든 확장 프로그램을 로드하며, 각 확장 프로그램은 자체 컨텍스트에서 실행됩니다. 수백 개를 설치하면 파이어폭스 시작에 몇 분이 걸리거나, 아예 시작조차 하지 못할 수 있습니다.
확장 프로그램 충돌. 동일한 웹 API를 수정하거나 동일한 페이지를 대상으로 하는 콘텐츠 스크립트를 가진 확장 프로그램들은 서로 싸우게 됩니다. 두 개의 광고 차단기는 서로 간섭할 것이고, 여러 암호 관리자는 동일한 양식 필드를 채우려고 경쟁할 것입니다.
extensions.json 병목 현상. 파이어폭스의 확장 프로그램 데이터베이스는 단일 JSON 파일입니다. 수백 개의 항목이 있으면 이를 파싱하고 업데이트하는 것이 느려지고, 파이어폭스가 깔끔하게 종료되지 않을 경우 파일 손상 가능성이 커집니다.
AMO 속도 제한 (Rate limiting). 확장 프로그램을 프로그램적으로 대량 다운로드할 경우, 모질라 서버가 속도 제한을 걸 수 있습니다. API는 구체적인 제한을 게시하지 않지만, 제 경험상 몇 백 번의 요청을 연속으로 보내면 429 응답을 받기 시작합니다. 다운로드 사이에 지연 시간을 추가해주는 것이 좋습니다.
import time
def download_with_backoff(slug, max_retries=3):
"""백오프를 사용하여 확장 프로그램을 다운로드합니다."""
for attempt in range(max_retries):
try:
return download_extension(slug)
except requests.exceptions.HTTPError as e:
if e.response.status_code == 429:
wait = 2 ** attempt * 10 # 지수 백오프
print(f"Rate limited. Waiting {wait}s...")
time.sleep(wait)
else:
raise
raise Exception(f"Failed to download {slug} after {max_retries} retries")
예방: 재현 가능한 브라우저 환경 구축하기
실제 목표가 여러 머신에서 일관된 확장 프로그램 세트를 유지하는 것이라면 (이 문제의 현실적인 버전이죠), 다음을 권장합니다.
- 관리되거나 팀 환경에서는 파이어폭스 정책을 사용하세요.
policies.json파일을 여러분의 닷파일(dotfiles) 저장소에 체크인하는 것이 좋습니다. - CI/테스팅을 위해서는 Selenium의
install_addon()메서드를 임시 설치와 함께 사용하세요. 프로필을 미리 만들어 두려고 시도하지 마세요. - Docker 기반 테스팅의 경우, 확장 프로그램이 설치된 프로필을 한 번 구축하고 제대로 작동하는지 확인한 다음, 전체 프로필 디렉터리를 이미지로 복사하세요.
- 항상 확장 프로그램 버전을 고정하세요. AMO API를 사용하면 특정 버전을 다운로드할 수 있습니다. 항상 최신 버전을 가져오기보다는 이 기능을 활용하세요.
파이어폭스 엔터프라이즈 문서는 정책 접근 방식을 자세히 다루고 있으며, AMO API 문서는 프로그램적 접근을 위한 좋은 참고 자료입니다.
결론 및 실무 팁
파이어폭스의 확장 프로그램 시스템은 사용자가 브라우저 UI를 통해 몇 개의 애드온을 설치하는 시나리오에 맞춰 설계되었습니다. 이 기본적인 경로에서 벗어나 자동화를 시도하면, 브라우저 아키텍처에 내재된 가정들과 씨름하게 될 수밖에 없습니다. 이것이 반드시 나쁜 것만은 아닙니다. 이러한 가정들은 보안상의 이유로 존재하니까요. 하지만 이는 파일을 옮기는 것 이상의 내부 메커니즘을 이해해야만 자동화가 가능하다는 의미이기도 합니다.
그럼에도 불구하고 정책 기반 접근 방식(Policies approach)은 정말 훌륭합니다. 만약 여러분의 팀 개발 환경에서 아직 이 방식을 사용하고 있지 않다면, 10분 정도 투자해서 설정해보는 것을 강력히 추천합니다. 한 번의 설정으로 팀원 모두의 시간을 절약해줄 수 있는 좋은 투자니까요!
원문: https://dev.to/alanwest/how-to-programmatically-install-firefox-extensions-and-why-it-breaks-2b01 수집일: 2026-04-12 06:10:14