Wdrażanie Chimery na serwerze (VPS)
Chimera działa jako długo żyjący proces gateway. Dodaj --cron, a będzie też odpalać
zaplanowane zadania wg prawdziwego zegara, więc działa w czasie (nie tylko gdy dostanie
wiadomość). Ten przewodnik pokrywa wdrożenie na VPS za 5$ na dwa sposoby: Docker Compose
(zalecane) lub systemd.
Stan — pamięć długoterminowa, zadania cron, trajektorie, dziennik audytu — żyje w CHIMERA_HOME
(katalogu). Zachowaj go trwale (wolumen Dockera albo prawdziwa ścieżka), a agent przetrwa
restarty.
0. Wymagania wstępne
- Linuksowy VPS (1 vCPU / 1 GB RAM w zupełności wystarczy dla pojedynczego agenta).
- Co najmniej jeden klucz providera. Najtańszy start to klucz OpenRouter.
- Dla publicznych przychodzących webhooków (WhatsApp Cloud API,
POST /webhook/<hook>), domena + reverse proxy z TLS (Caddy lub nginx). Niepotrzebne dla Discorda/Telegrama/Slacka/ Signala, które łączą się wychodząco.
Stwórz swój plik env z szablonu i wypełnij klucz:
cp .env.example .env
# edit .env — e.g. set CHIMERA_OPENROUTER_KEYS=sk-or-...
1. Docker Compose (zalecane)
docker compose up -d # build + run; restarts on crash and on reboot
docker compose logs -f # watch it
docker compose ps # status + health
To uruchamia chimera serve --host 0.0.0.0 --cron: bramę HTTP (/chat, /webhook/<hook>,
/health) oraz daemon cron. Stan jest zachowywany w wolumenie chimera-data.
Podpięcie platformy czatu (na przykładzie Discorda) — ustaw token w .env, następnie nadpisz
komendę w docker-compose.yml:
command: ["serve", "--host", "0.0.0.0", "--cron", "--discord"]
i ponownie docker compose up -d. (Telegram/Slack/Signal działają tak samo przez swoje flagi;
każdy potrzebuje swojego dopasowanego tokenu CHIMERA_* — zobacz .env.example.)
Aktualizacja do nowej wersji:
git pull && docker compose up -d --build
2. systemd (bez Dockera)
Zainstaluj w virtualenv na hoście:
git clone https://github.com/brcampidelli/chimera-agent.git /opt/chimera
cd /opt/chimera
python3 -m venv .venv && . .venv/bin/activate
pip install '.[messaging,mcp]'
cp .env.example .env # then edit it
Stwórz /etc/systemd/system/chimera.service:
[Unit]
Description=Chimera Agent gateway + cron daemon
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/chimera
EnvironmentFile=/opt/chimera/.env
Environment=CHIMERA_HOME=/opt/chimera/state
ExecStart=/opt/chimera/.venv/bin/chimera serve --host 0.0.0.0 --cron
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now chimera
sudo systemctl status chimera
journalctl -u chimera -f
3. Planowanie pracy proaktywnej (daemon --cron)
--cron tylko uruchamia zadania, które zaplanowałeś. Dodaj je przez CLI (zachowują się w
CHIMERA_HOME):
chimera cron add "morning-brief" "0 8 * * *" "Summarize overnight news and post it."
chimera cron add "nightly-backup" "0 3 * * *" "Back up the important files."
chimera cron list
Wewnątrz Dockera:
docker compose exec chimera chimera cron add "morning-brief" "0 8 * * *" "..."
Daemon tyka co --cron-tick sekund (domyślnie 30) i wysyła akcję każdego należnego zadania przez
agenta, gdy nadejdzie jego czas. Nieudane zadanie jest logowane i nigdy nie zatrzymuje daemona.
Zapytać harmonogram o to, czego nie mówi
chimera cron doctor
Harmonogram może zamilknąć na dwa sposoby, a dopóki nie zapytasz, oba wyglądają tak samo — jak harmonogram, w którym nic nie jest należne:
- Nic się nie wykonało. Daemon umarł, kontener nigdy nie został zrestartowany, host zasnął. Żadnego wyjątku, żadnej linii logu, żadnego werdyktu. Każdy inny mechanizm uczciwości tutaj stoi poniżej faktu, że przebieg się odbył, więc żaden z nich nie dostaje swojej kolejki.
- Wszystko się wykonało i wszystko przepadło. Daemon żyje,
last_runbył minutę temu, harmonogram idzie do przodu — a każde wysłanie zawodzi od miesiąca. Ten przypadek czyta się jako zdrowszy od pierwszego, bo pole, które wygląda na oznakę zdrowia, zapisuje próbę, a nie wynik.
cron doctor zadaje oba pytania i daje różne rady, bo naprawy nie mają ze sobą nic wspólnego:
spóźnienie dotyczy daemona, zawodzenie dotyczy zadania. chimera cron list wypisuje linię, gdy
cokolwiek zawodzi, więc nie musisz wiedzieć, że ta komenda istnieje, żeby się o tym dowiedzieć.
Czym to nie jest. To pytanie, a nie stróż: dopóki proces leży, nikt tutaj niczego nie zauważa, z tego samego powodu, dla którego proces, który się wywalił, nie może zalogować własnego upadku. Odpowiada uczciwie w chwili, gdy cokolwiek zapyta — powłoka, aplikacja, następny start. Prawdziwy stróż potrzebuje własnego zegara i własnej żywotności, co jest osobną decyzją i jest śledzone jako issue #26. Jeśli chcesz alertu, a nie odpowiedzi, uruchamiaj to z crona samego hosta:
*/30 * * * * cd /opt/chimera && out=$(.venv/bin/chimera cron doctor --check) || echo "$out" | mail -s "chimera" you@example.com
To działa, bo nadzoruje to coś innego niż Chimera — i o to właśnie chodzi.
--check kończy się kodem 1 tylko wtedy, gdy zadanie jest spóźnione albo zawodzi — a od teraz także gdy bicie serca demona jest nieświeże, więc linia host-cron powyżej wyłapie martwego demona przy kolejnym przebiegu, niezależnie od harmonogramu zadania; mail przychodzi tylko wtedy. cron doctor bez niego zawsze kończy się kodem 0, bo odpowiadanie nie jest porażką.
Bicie serca demona zamyka okno, którego zadania zamknąć nie potrafią. Przy każdym ticku demon zapisuje swój znak życia do <CHIMERA_HOME>/scheduler/heartbeat.json (atomowo, obok jobs.json). Doctor czyta je najpierw i odpowiada na pytanie, którego same zadania zadać nie potrafią: martwy demon z dziennym zadaniem wygląda zdrowo z perspektywy zadań przez ~23 godziny — zadanie jeszcze nie jest spóźnione — podczas gdy bicie zatrzymuje się w chwili śmierci demona. Werdykt jest oceniany względem trzech ticków własnego interwału zapisanego przez bicie i wypisywany razem z używanym limitem, więc możesz się z liczbą spierać, zamiast zgadywać, skąd się wzięła. Trzy uczciwe odczyty: alive/stale są oceniane; unknown znaczy, że bicie istnieje, ale nie zapisało interwału ticku (starszy zapisujący), więc świeżości nie da się ocenić bez wymyślenia liczby — a czytelnik odmawia; none znaczy brak bicia w ogóle, co jest “brakiem sygnału”, nie “śmiercią” — demon, który nigdy nie działał, nie zostawił żadnego dowodu. To liveness, nie taint: powierzchnia schedulera nadal nie śledzi taint (patrz SECURITY.md).
4. Zdrowie, kopie zapasowe, bezpieczeństwo
- Zdrowie:
GET /healthzwraca{"ok": true}. Compose ma podpięty healthcheck. - Kopie zapasowe: kopiuj wolumen
chimera-data(Docker) lub katalogCHIMERA_HOME(systemd) — to cały trwały stan. Przykład:docker run --rm -v chimera-data:/d -v $PWD:/b busybox tar czf /b/chimera-state.tgz -C /d . - Sekrety: trzymaj klucze w
.env(ignorowanym przez git); nigdy nie wypiekaj ich do obrazu. - Ekspozycja: wiąż bramę z
0.0.0.0tylko za firewallem/reverse proxy. UstawCHIMERA_SERVER_TOKEN, by wymagaćAuthorization: Bearer <token>na bramie HTTP i API desktopowym (token jest automatycznie przekazywany UI desktopowemu tylko dla klientów loopback, więc instancja wystawiona zdalnie pozostaje za twoją własną autoryzacją). Autoryzacja jest opt-in i domyślnie pusta, więc bez tej zmiennej jej nie ma — ogranicz port albo wystaw tylko ścieżkę webhooka. Jak dotrzeć do tej instancji z aplikacji desktopowej, zobacz §5. - Sandboxing: ustaw
CHIMERA_SANDBOX=docker, by uruchamiać narzędzia shell/kod w jednorazowym kontenerze zamiast na hoście. - Wykonanie bez nadzoru na hoście: od 2026-07-20 przebieg headless odmawia komend
hostowych pod domyślnym
CHIMERA_HOST_EXEC=ask(nie ma TTY do potwierdzenia). Wdrożenie, które naprawdę potrzebuje, by agent uruchamiał shell na hoście, ustawiaCHIMERA_HOST_EXEC=allowświadomie; bezpieczniejszą opcją jestCHIMERA_SANDBOX=docker, gdzie ta bramka jest pomijana, bo kontener naprawdę izoluje. Podobnie serwer API uzbraja zawężanie skażenia (CHIMERA_TAINT_NARROW=1): po tym, jak agent przeczyta niezaufaną treść, narzędzia wykonania/zapisu/wychodzące zawodzą na bezpieczną stronę (fail closed). Ustaw na0, by dalej działać autonomicznie.
5. Dotarcie do tej instancji z aplikacji desktopowej
Aplikacja desktopowa domyślnie rozmawia z tą Chimerą, którą sama uruchamia na twojej maszynie. Od v0.44 może też wskazywać na taką, którą prowadzisz sam — ten VPS — i wtedy aplikacja staje się oknem na agenta, który i tak całą noc wykonuje twoje zadania cron.
Przeczytaj tę część, zanim otworzysz port. To, co wystawiasz, nie jest panelem podglądu. Każdy ekran tej aplikacji jest powierzchnią sterowania: uruchamia powłokę, edytuje pliki, rozdziela tablicę autonomicznych zadań i zmienia ustawienia. Instancja osiągalna z internetu bez tokenu to nie „Chimera, na którą ktoś mógłby popatrzeć" — to maszyna, na której każdy, kto znajdzie adres, może uruchamiać polecenia, opłacane twoimi kluczami providera.
Muszą być spełnione trzy rzeczy, a bez dwóch pierwszych aplikacja odmawia połączenia:
1 — TLS. Postaw ją za reverse proxy z prawdziwym certyfikatem (Caddy załatwi go za ciebie):
chimera.seudominio.com {
reverse_proxy 127.0.0.1:8765
}
Aplikacja odmawia adresu bez https poza twoją własną maszyną, bo token jedzie w nagłówku
Authorization przy każdym żądaniu — po zwykłym http to poświadczenie wręczone każdemu
przeskokowi między tobą a serwerem, a nic na ekranie nie wyglądałoby przy tym źle.
2 — Token. Autoryzacja jest opt-in i domyślnie pusta:
CHIMERA_SERVER_TOKEN=$(openssl rand -hex 32)
Wpisz go do .env, zrestartuj i wklej tę samą wartość do aplikacji. Aplikacja odmawia zdalnego
adresu bez tokenu z powodu powyżej: instancja bez niego stoi otworem dla każdego, kto ją znajdzie.
Zwróć uwagę, czego serwer celowo nie robi: gdy zdalny klient prosi o UI, serwuje stronę bez tokenu. Token nigdy nie jest wydawany przez sieć — kopiujesz go do własnego klienta, raz, poza pasmem. Dlatego aplikacja ma na niego pole.
3 — Origin twojej aplikacji. Aplikację serwuje jej własny lokalny sidecar, więc jej żądania do tej instancji są cross-origin, a przeglądarka odrzuca odpowiedzi, dopóki ta instancja nie nazwie tego originu:
CHIMERA_ALLOWED_ORIGINS=http://127.0.0.1:45813
Aplikacja pokazuje dokładną wartość, gdy połączenie się nie uda — jest w komunikacie błędu, gotowa do skopiowania. Port jest stały dla instalacji (od v0.43 jest pamiętany między uruchomieniami), więc ustawia się to raz na każdą maszynę, z której się łączysz. Kilka oddziela się przecinkami.
To ustawienie nie jest granicą bezpieczeństwa i nie wolno go tak czytać. CORS decyduje, która
strona może odczytać odpowiedź; o tym, kto może wołać, nie decyduje nic. Bramką jest token.
Nazwanie originu bez ustawienia tokenu niczego nie chroni — sprawia tylko, że niezabezpieczona
instancja jest osiągalna z przeglądarki, a nie tylko z curl.
Domyślnie puste, więc instancja, której nikt nie skonfigurował, zachowuje się dokładnie tak jak wcześniej.
Co aplikacja mówi, gdy się nie uda
- „Token został odrzucony" — adres i origin są dobre; wartość jest zła.
- „Nie udało się dosięgnąć" — albo adres jest zły, albo origin nie jest dozwolony. Przeglądarka celowo odmawia powiedzenia, który z nich, więc aplikacja wymienia oba, zamiast zgadywać, i podaje ci origin do dopuszczenia.
- Ostrzeżenie o wersji — aplikacja porównuje wersję własnego backendu z tą i podaje obie liczby. Nie odmawia: serwer o jedno wydanie z tyłu zwykle działa, a odmowa zostawiłaby cię na tym samym ekranie, który byłby potrzebny do naprawy. Niektóre endpointy mogą po starszej stronie nie istnieć.
Jeszcze bezpieczniej
Pomiń publiczny port całkowicie: dosięgnij VPS-a przez WireGuard albo tailnet Tailscale i skieruj aplikację na adres prywatny. Token nadal ma znaczenie — tailnet to mniejszy pokój, a nie pusty.
6. Proxy, własne CA i zaglądanie do działającej instancji
Proxy i własne CA
Ruch wychodzący — wywołania dostawców i integracje — odczytuje standardowe zmienne proxy i
certyfikatów ze środowiska procesu Chimery. Co da się dziś sprawdzić (httpx 0.28, LiteLLM 1.99):
httpx respektuje HTTP_PROXY, HTTPS_PROXY i NO_PROXY, a pakiet CA bierze z SSL_CERT_FILE /
SSL_CERT_DIR; LiteLLM czyta SSL_CERT_FILE. Żaden z nich nie czyta REQUESTS_CA_BUNDLE, która
dotyczy tylko kodu zbudowanego na bibliotece requests — ustaw ją dodatkowo tylko wtedy, gdy któraś
z twoich integracji używa requests. Adaptery dostawców wewnątrz LiteLLM się różnią, więc udowodnij
ścieżkę, na której polegasz, sondą poniżej, zamiast ją zakładać. Nic z tego nie zmienia
przychodzącego listenera Chimery ani autoryzacji jej API.
Dla powłoki lub środowiska systemd:
export HTTP_PROXY=http://proxy.example:8080
export HTTPS_PROXY=http://proxy.example:8080
export NO_PROXY=localhost,127.0.0.1,.internal.example
export SSL_CERT_FILE=/etc/ssl/certs/company-ca.pem
# Optional, only for requests-based code:
# export REQUESTS_CA_BUNDLE=/etc/ssl/certs/company-ca.pem
chimera doctor --probe
W docker-compose.yml, pod environment: usługi (plik CA musi istnieć wewnątrz kontenera):
environment:
HTTP_PROXY: http://proxy.example:8080
HTTPS_PROXY: http://proxy.example:8080
NO_PROXY: localhost,127.0.0.1,.internal.example
SSL_CERT_FILE: /etc/ssl/certs/company-ca.pem
# REQUESTS_CA_BUNDLE: /etc/ssl/certs/company-ca.pem # optional, requests-based code only
volumes:
- ./company-ca.pem:/etc/ssl/certs/company-ca.pem:ro
chimera doctor --probe wykonuje jedno prawdziwe wywołanie dostawcy przez skonfigurowaną trasę i
łańcuch certyfikatów. Sonda, która przechodzi, weryfikuje tego dostawcę i to wywołanie; nie
certyfikuje każdej integracji. Trzymaj dane uwierzytelniające poza adresami URL proxy, gdzie to
możliwe: wartości środowiska widzi każdy, kto może podejrzeć proces.
chimera api i chimera logs
Dwa polecenia terminala dla działającej instancji, z których żadne nie uruchamia serwera:
chimera api GET /api/health # the app's API: status and body, token never printed
chimera api POST /api/... -d '{}' --yes # changing state needs --yes
docker exec chimera chimera api GET /health # the HTTP gateway of plain `chimera serve`
docker exec chimera chimera logs --level ERROR --lines 50
docker exec chimera chimera logs --follow --run <run-id>
chimera api wysyła CHIMERA_SERVER_TOKEN ze środowiska. Znajduje chimera app przez URL, który
aplikacja ogłasza w CHIMERA_HOME; w przeciwnym razie używa http://127.0.0.1:8765, domyślnego
portu chimera serve, którego brama odpowiada na GET /health i POST /chat (nie ma tam /api).
serve --discord/--telegram/--slack/--signal nie otwiera żadnego portu HTTP, więc w takim
wdrożeniu chimera api nie ma do czego się dostać; chimera logs nadal działa. --url nadpisuje
adres. Wywołanie przez loopback ignoruje powyższe zmienne proxy, więc token nigdy nie trafia do
proxy, a polecenie odmawia wysłania tokenu na zdalny URL http://. O każdym żądaniu nadal decydują
autoryzacja i zatwierdzenia samego serwera.
chimera logs czyta CHIMERA_HOME/logs/backend.log: ostrzeżenia i błędy zapisane przez chimera serve i chimera app, rotowane przy 1 MB, z zamaskowanymi sekretami. Traceback zostaje przy
wpisie, do którego należy, pod --level i --run, a --follow czyta dalej przez rotację.
7. Uczciwy status
Chimera jest w fazie alpha. To się wdraża i działa, a daemon cron czyni ją proaktywną. Jej
przebieg produkcyjny to jedno wdrożenie: własny VPS autora, który od lipca 2026 przez całą dobę
uruchamia crony i bota Discord. To realne użycie, nie flota, i nie zetknęło się z twoim obciążeniem.
Zacznij od cronów niskiego ryzyka, obserwuj logs, i miej na uwadze zabezpieczenia governance
(--guard przy solve, CHIMERA_SANDBOX=docker) przy wszystkim, co dotyka prawdziwych systemów.
Gdzie te strony są publikowane
Te pliki są źródłem dla dokumentacji na chimeraagent.space, która renderuje je bezpośrednio z tego katalogu w czasie builda. Edytuj markdown tutaj, a strona podąży za tym; nie ma drugiej kopii do utrzymywania w zgodzie.
Konfiguracja MkDocs, która kiedyś żyła w mkdocs.yml, została usunięta. Była kompletna —
motyw, nawigacja, dziesięć stron — i nigdy nie została opublikowana: nie było workflow ani gałęzi
gh-pages, więc instrukcje wdrożenia, które kiedyś stały w tym miejscu, opisywały stronę, która
nie istniała. Konfiguracja, której nikt nie uruchamia, jest gorsza niż brak konfiguracji, bo
następna osoba edytuje jej nawigację i nie potrafi ustalić, dlaczego nic się nie zmienia.