Open Source Signal / Сигнал відкритих джерел is a static publishing toolkit for a bilingual OSINT journal focused on Ukrainian accountability work.
It is built from wartime Kyiv by a Ukrainian soldier. For Ukraine, OSINT is not a hobby or a niche technical craft. It is one of the ways society documents war, resists disinformation, preserves evidence, understands attacks, and keeps public accountability alive under pressure.
The project treats OSINT as a civic skill: how to read sources, verify claims, handle evidence, explain limits, and publish without causing unnecessary harm. The editorial focus is Ukrainian accountability OSINT with an international horizon: war-crimes verification, public-interest investigations, Russian KIA/POW/MIA/missing/wounded OSINT, platform research, geolocation, surveillance infrastructure, and researcher safety.
The visible output is a bilingual digest. The larger goal is reusable open infrastructure: source-aware publishing, editorial workflows, ethical guardrails, verification notes, topic archives, weekly editions, and practical OSINT literacy for Ukrainian and international readers.
Project rules and operational notes live in:
docs/EDITORIAL_POLICY.md
docs/SOURCE_POLICY.md
ROADMAP.md
SECURITY.md
These documents cover editorial scope, source roles, Telegram lead handling, high-risk attribution language, publication boundaries, roadmap, and security reporting.
The project is meant to grow beyond a daily digest into reusable public-interest OSINT infrastructure: weekly editions, evidence and source-risk metadata, topic pages, archive filters, link checking, and cautious Telegram lead collection.
See:
ROADMAP.md
The project currently supports eight working modes.
create_issue.py creates an unfinished issue in drafts/ so it cannot break the published site.
python create_issue.py --date 2026-05-14 --issue 001When the draft is filled, validate and publish it into issues/:
python create_issue.py --publish drafts/2026-05-14.jsonFull instructions are in:
docs/ADD_ISSUE.md
render_issue.py takes one issue JSON file and renders a standalone HTML issue using templates/issue.html.j2.
python render_issue.py issues/2026-05-13.json --out distbuild_site.py takes all JSON files from issues/, renders issue pages into dist/issues/, and builds:
dist/index.html
dist/archive.html
dist/issues/open-source-signal-2026-05-13.html
Run:
python build_site.py --issues issues --templates templates --out dist --config site.jsontelegram_digest.py takes the same issue JSON and renders a short Telegram announcement in English or Ukrainian.
python telegram_digest.py issues/2026-05-13.json \
--lang uk \
--url https://osintsignal.org/issues/open-source-signal-2026-05-13.html \
--out distsend_telegram.py sends a prepared announcement to a Telegram channel or chat. The default parse mode is Telegram HTML, so generated announcements keep bold titles, italic leads, and linked full-issue URLs.
export TELEGRAM_BOT_TOKEN="123456:ABC..."
export TELEGRAM_CHAT_ID="@your_channel_name"
python send_telegram.py \
--text-file dist/telegram-open-source-signal-2026-05-13.uk.txt \
--disable-previewDry run:
python send_telegram.py \
--text-file dist/telegram-open-source-signal-2026-05-13.uk.txt \
--chat-id @your_channel_name \
--dry-runThe repository-ready GitHub Actions workflow is included:
.github/workflows/pages.yml
On every push to main, it installs dependencies, runs tests, builds dist/, uploads the Pages artifact and deploys it. Manual runs are available from the Actions tab.
Setup instructions are in:
docs/GITHUB_PAGES_SETUP.md
A manual Telegram workflow is included:
.github/workflows/telegram.yml
Add repository secrets:
TELEGRAM_BOT_TOKEN
TELEGRAM_CHAT_ID
Then run:
Actions → Publish Telegram announcement → Run workflow
The workflow is manual on purpose, so regular rebuild commits do not spam the channel.
Setup instructions are in:
docs/TELEGRAM_SETUP.md
import_daily_signal.py converts a structured Daily Signal markdown/text file into a complete issues/YYYY-MM-DD.json file.
python import_daily_signal.py drafts/daily_signal_2026-05-15.md \
--date 2026-05-15 \
--issue 002 \
--out issuesA manual GitHub Actions workflow is also included:
.github/workflows/import_daily_signal.yml
Use it from:
Actions → Import Daily Signal → Run workflow
Full instructions are in:
docs/IMPORT_DAILY_SIGNAL.md
The combined publishing workflow is included:
.github/workflows/publish_daily_issue.yml
Use it from:
Actions → Publish Daily Issue → Run workflow
It imports a pasted Daily Signal, runs tests, builds the site, commits the issue, renders the Telegram announcement, checks duplicate protection, sends Telegram if enabled, and records the send log.
Full instructions are in:
docs/PUBLISH_DAILY_ISSUE.md
The project now keeps editorial rules and source registries in the repository:
docs/EDITORIAL_POLICY.md
docs/SOURCE_POLICY.md
data/sources_web.yaml
data/sources_telegram.yaml
Language handling:
- English-language OSINT material is adapted into Ukrainian.
- Ukrainian-only material is adapted back into English for international readers.
- Both versions must read like edited publication text, not literal machine translations.
New Ukrainian-accountability rubrics:
Ukraine Lens / Українська оптика
War Crimes Verification / Верифікація воєнних злочинів
Losses, Captivity & Missing / Втрати, полон, зниклі
Telegram Radar / Telegram-радар
Evocation.info is included as a source pointer, not as standalone proof.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython -m pytest -qThe main editable issue files live in:
issues/
Each item contains both English and Ukrainian adapted text:
- rubric / emoji / theme
- source name, date and URL
- what happened
- why it matters
- how to use it
- limits
- tags
Internal editorial notes are stored in internal_notes inside issue JSON, but they are not rendered on public issue pages.
The public base URL is configured in:
site.json
Default:
https://osintsignal.org
The Telegram workflow uses this base_url when the manual issue_url input is left empty.
The HTML templates use:
- Fraunces for the Latin masthead.
- Source Serif 4 for Ukrainian display typography.
- Inter for body text.
- Arimo for rubric labels and field labels such as
What happened,Why it matters,Що сталося,Чому це важливо. - JetBrains Mono for technical/meta elements.
The manual Telegram workflow records successful sends in data/telegram_sent.json. Re-running the workflow for the same issue/language will not post again unless the force input is set to true.