Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,21 @@ restores the image but never the schema, so keep migrations additive.
(`/srv/www/shared/owner-reports` -- not `shared/reports`, which is the
analytics'), and `internal/boards/survival_test.go` runs every importer
over stored reports.
- `internal/club` -- **the OpenIPC Club**: signing in with Telegram (the bot
takes Start with a code the asking browser shows, then tells the member
what happened to what they sent), GitHub (a member of `CLUB_MAINTAINER_ORG`
reviews) or an emailed link, each only when configured
(`TELEGRAM_BOT_TOKEN`, `GITHUB_OAUTH_*`, `CLUB_SMTP_*`). Mail goes to the
host's own exim at `172.18.0.1:25`, which says HELO as `webber-eu.openipc.org`
(its PTR) and signs DKIM selector `webber2026`; openipc.org's SPF names
37.27.251.71 and DMARC is `p=none` (Hetzner DNS). Accounts and sessions
only: a member's reports, their private dumps, the review queue at
`/club/review` and the stars ledger (`report_stars`, written by a review's
decision, never by an upload) are `internal/reports`'. The board panel's
send form posts to `/api/v1/club/reports` instead of opening a GitHub
issue, and a published report's text and photos become a contributed unit
on its board (`boards.ApplyReportUnits`), as `boards/contributions.yml`
does for what arrived as issues (#365, #366).
- `internal/tools`, `internal/nfsro` — **ipctool for stock firmware**, which
has no curl and no TLS. ipctool's release job pushes each build to
`PUT /api/v1/tools/{name}` (OIDC, `internal/tools/PUSH.md`); nginx serves
Expand Down Expand Up @@ -185,8 +200,10 @@ restores the image but never the schema, so keep migrations additive.

### Conventions & gotchas

- There is no admin and no sign-in (#288). `/admin` answers 410; nothing on the
site sets a cookie.
- There is no admin (#288): `/admin` answers 410. The one sign-in is the
OpenIPC Club (`internal/club`), and its session cookie's path is
`/api/v1/club`: pages stay static and cached, and a visitor who never signs
in is never sent a cookie.
- `deploy/static/reserved-paths` lists the addresses a bundle file must never
shadow (the upload, the firmware download, the service's APIs, nginx's own
locations); `service/deploytest` derives what must be in it and fails when an
Expand Down
129 changes: 129 additions & 0 deletions data/locales/boards.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ en:
fact2: "The MAC, the chip's serial and the cloud ID are replaced with hashes in everything published, in the report and in any log."
fact3_title: "A backup is private unless you say otherwise"
fact3_html: "It holds everything the camera stores, including Wi-Fi keys and passwords. It is published only if you sent it with <code>--public</code>."
club:
eyebrow: 'OpenIPC Club'
title: 'OpenIPC Club'
lede: 'Sign in to follow what you sent to the board catalogue, keep your flash dumps private, and collect stars for what is accepted.'
club_review:
eyebrow: 'OpenIPC Club'
title: 'Review queue'
lede: 'Owner reports waiting for a maintainer''s decision.'
nav:
boards: Camera boards
cameras:
Expand Down Expand Up @@ -373,4 +381,125 @@ en:
new_tab: opens in a new tab
files_title: Photos and files
from_source: 'From {source}'
sent_by: 'Sent by {who}'
known_sources: 'Sources: '
club:
eyebrow: 'OpenIPC Club'
loading: 'Loading…'
load_failed: 'The club could not be reached. Try again in a minute.'
why_title: 'Why sign in'
why_1: 'See whether what you sent was accepted, and where it appears.'
why_2: 'Keep your flash dumps private: only you and OpenIPC''s maintainers can download them.'
why_3: 'Collect stars, and your name next to the boards you documented.'
why_note: 'You don''t need an account to browse or download firmware. The site sets no cookie for anyone who doesn''t sign in.'
tg_button: 'Continue with Telegram'
tg_hint: 'No password: tap Start in the OpenIPC bot'
gh_button: 'Continue with GitHub'
gh_hint: 'Maintainers sign in here'
or: 'or'
email_label: 'We''ll email you a link that signs you in. No password.'
email_placeholder: 'you@example.com'
email_button: 'Email me a link'
email_sent: 'Check {email}: the link signs you in once, within ten minutes.'
unavailable: 'Not available on this site yet.'
tg_title: 'Sign in with Telegram'
tg_step1: 'Scan the code with your phone''s camera, or open Telegram on this computer.'
tg_step2: 'Tap Start in the chat with {bot}, then Yes.'
tg_step3: 'Come back here. This page signs you in by itself.'
tg_open: 'Open Telegram'
tg_other: 'Use GitHub or email instead'
tg_waiting: 'Waiting for Telegram… This code works for {time} more.'
tg_expired: 'The code expired.'
tg_retry: 'Get a new code'
close: 'Close'
signin_expired: 'That sign-in link has expired or was already used. Start again below.'
signin_elsewhere: 'That sign-in belongs to the browser that started it. Start again here.'
signin_failed: 'The sign-in did not complete. Try again, or use another way in.'
pending: '+{n} waiting for review'
ways: 'Signed in with'
link_more: 'Add another way in'
provider_telegram: 'Telegram'
provider_github: 'GitHub'
provider_email: 'Email'
bot_on: 'The OpenIPC bot tells you when something you sent is reviewed.'
bot_muted: 'The bot''s messages are muted.'
mute: 'Mute the bot'
unmute: 'Unmute the bot'
sign_out: 'Sign out'
review_link: 'Review queue'
mine_title: 'My submissions'
mine_empty: 'Nothing sent yet. Open your board in the catalogue and send what it prints.'
boards_link: 'Camera boards'
col_submission: 'Submission'
col_status: 'Status'
col_stars: 'Stars'
status_pending: 'Waiting for review'
status_published: 'Accepted'
status_rejected: 'Not accepted'
status_withdrawn: 'Withdrawn'
duplicate: 'The catalogue already has this dump'
private: 'Only you and maintainers'
kind_backup: 'Full flash dump'
kind_photo: 'Photo'
kind_boot_log: 'Boot log'
kind_uboot_env: 'U-Boot console'
kind_note: 'Note'
kind_document: 'Document'
kind_yaml: 'ipctool report'
no_board: 'No board named'
rules_title: 'How stars work'
rules_item: 'Boot log, U-Boot console, photo, pinout, ipctool report'
rules_dump: 'Full flash dump the catalogue doesn''t have'
rules_none: 'A dump the catalogue already holds, or anything not accepted'
rules_note: 'Stars count when a maintainer accepts what you sent, never on upload.'
send_kinds_label: 'What you are sending'
send_kind_boot_log: 'Boot log'
send_kind_uboot_env: 'U-Boot console'
send_kind_photo: 'Photos'
send_kind_backup: 'Full flash dump'
send_paste: 'Paste the text here, or choose a file below'
send_file: 'File'
send_photos: 'Photos: the front, the back, the UART pins'
send_dump: 'The whole chip, as a programmer read it or as an ipctool backup'
send_public: 'Publish the dump with the report. It holds Wi-Fi keys and passwords: leave this off to keep it private.'
send_note: 'Anything else: where you bought it, what it is sold as'
send_button: 'Send to the catalogue'
sending: 'Sending…'
sent_member: 'Received as {id}. A maintainer reviews it before it is shown; follow it in My submissions.'
sent_guest: 'Received as {id}. A maintainer reviews it before it is shown.'
receipt: 'Receipt'
my_link: 'My submissions'
sign_in_link: 'Sign in'
send_signed_in: 'Signed in as {name}. Stars count once a maintainer accepts it.'
send_guest: 'You can send without signing in. Sign in to collect stars and follow what happens to it.'
send_failed: 'Not sent: {error}'
send_empty: 'Add a file, a photo or some text first.'
reward: '+{n} ★'
send_another: 'Send something else'
review_title: 'Review queue'
review_empty: 'Nothing is waiting.'
review_forbidden: 'Only OpenIPC''s maintainers review. Sign in with GitHub as a member of the OpenIPC organisation.'
review_from: 'From {who}'
review_anon: 'Sent without signing in'
review_board: 'Board named by the sender'
review_guess: 'ipctool says it looks like {board}'
review_models: 'Board ids to publish it on, comma-separated (empty: the board the sender named)'
review_note: 'Note for the sender'
publish: 'Publish'
reject: 'Reject'
review_points: '{points} ★'
review_done_publish: 'Published. {points} ★ to the sender.'
review_done_reject: 'Rejected.'
show_yaml: 'ipctool output'
tab_pending: 'Waiting'
tab_published: 'Published'
tab_rejected: 'Rejected'
header_club: 'Club'
confirm_title: 'Sign in as {who}?'
confirm_text: 'This link was asked for in another browser. Sign in only if {who} is yours: whatever you send while signed in goes to that account.'
confirm_button: 'Sign in'
confirm_cancel: 'Cancel'
rename: 'Change name'
rename_label: 'Your name, as it appears on the boards your reports reach'
rename_save: 'Save'
review_note_label: 'Reviewer'
129 changes: 129 additions & 0 deletions data/locales/boards.ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ ru:
fact2: "MAC, серийный номер чипа и облачный ID во всём опубликованном заменены хешами — и в отчёте, и в логах."
fact3_title: "Бэкап закрыт, если вы не решите иначе"
fact3_html: "В нём всё, что хранит камера, включая ключи Wi-Fi и пароли. Он публикуется, только если вы отправили его с <code>--public</code>."
club:
eyebrow: 'Клуб OpenIPC'
title: 'Клуб OpenIPC'
lede: 'Войдите, чтобы следить за тем, что вы прислали в каталог плат, держать дампы флеш-памяти закрытыми и получать звёзды за принятое.'
club_review:
eyebrow: 'Клуб OpenIPC'
title: 'Очередь проверки'
lede: 'Отчёты владельцев, которые ждут решения мейнтейнера.'
nav:
boards: Платы камер
cameras:
Expand Down Expand Up @@ -414,4 +422,125 @@ ru:
new_tab: откроется в новой вкладке
files_title: Фото и файлы
from_source: 'Источник: {source}'
sent_by: 'Прислано: {who}'
known_sources: 'Источники: '
club:
eyebrow: 'Клуб OpenIPC'
loading: 'Загрузка…'
load_failed: 'Не удалось связаться с клубом. Попробуйте через минуту.'
why_title: 'Зачем входить'
why_1: 'Видеть, приняли ли присланное и где оно появилось.'
why_2: 'Держать дампы флеш-памяти закрытыми: скачать их можете только вы и мейнтейнеры OpenIPC.'
why_3: 'Получать звёзды и видеть своё имя рядом с платами, которые вы описали.'
why_note: 'Чтобы смотреть каталог и скачивать прошивки, учётная запись не нужна. Тем, кто не входит, сайт не ставит ни одной cookie.'
tg_button: 'Войти через Telegram'
tg_hint: 'Без пароля: нажмите «Старт» в боте OpenIPC'
gh_button: 'Войти через GitHub'
gh_hint: 'Мейнтейнеры входят здесь'
or: 'или'
email_label: 'Пришлём на почту ссылку для входа. Без пароля.'
email_placeholder: 'you@example.com'
email_button: 'Прислать ссылку'
email_sent: 'Проверьте {email}: ссылка выполнит вход один раз в течение десяти минут.'
unavailable: 'На этом сайте пока недоступно.'
tg_title: 'Вход через Telegram'
tg_step1: 'Отсканируйте код камерой телефона или откройте Telegram на этом компьютере.'
tg_step2: 'Нажмите «Старт» в чате с {bot}, затем «Да».'
tg_step3: 'Вернитесь сюда: страница выполнит вход сама.'
tg_open: 'Открыть Telegram'
tg_other: 'Войти через GitHub или почту'
tg_waiting: 'Ждём Telegram… Код действует ещё {time}.'
tg_expired: 'Код устарел.'
tg_retry: 'Получить новый код'
close: 'Закрыть'
signin_expired: 'Ссылка для входа устарела или уже использована. Начните заново ниже.'
signin_elsewhere: 'Этот вход принадлежит браузеру, в котором он начат. Начните заново здесь.'
signin_failed: 'Вход не завершился. Попробуйте ещё раз или выберите другой способ.'
pending: '+{n} ждут проверки'
ways: 'Способы входа'
link_more: 'Добавить способ входа'
provider_telegram: 'Telegram'
provider_github: 'GitHub'
provider_email: 'Почта'
bot_on: 'Бот OpenIPC сообщает, когда присланное проверено.'
bot_muted: 'Сообщения бота отключены.'
mute: 'Отключить бота'
unmute: 'Включить бота'
sign_out: 'Выйти'
review_link: 'Очередь проверки'
mine_title: 'Мои материалы'
mine_empty: 'Вы пока ничего не присылали. Откройте свою плату в каталоге и пришлите то, что она выводит.'
boards_link: 'Платы камер'
col_submission: 'Материал'
col_status: 'Статус'
col_stars: 'Звёзды'
status_pending: 'Ждёт проверки'
status_published: 'Принято'
status_rejected: 'Не принято'
status_withdrawn: 'Отозвано'
duplicate: 'Такой дамп в каталоге уже есть'
private: 'Только вам и мейнтейнерам'
kind_backup: 'Полный дамп флеш-памяти'
kind_photo: 'Фото'
kind_boot_log: 'Лог загрузки'
kind_uboot_env: 'Консоль U-Boot'
kind_note: 'Заметка'
kind_document: 'Документ'
kind_yaml: 'Отчёт ipctool'
no_board: 'Плата не указана'
rules_title: 'Как начисляются звёзды'
rules_item: 'Лог загрузки, консоль U-Boot, фото, распиновка, отчёт ipctool'
rules_dump: 'Полный дамп флеш-памяти, которого нет в каталоге'
rules_none: 'Дамп, который уже есть в каталоге, или непринятый материал'
rules_note: 'Звёзды начисляются, когда мейнтейнер принимает присланное, а не при загрузке.'
send_kinds_label: 'Что вы присылаете'
send_kind_boot_log: 'Лог загрузки'
send_kind_uboot_env: 'Консоль U-Boot'
send_kind_photo: 'Фото'
send_kind_backup: 'Полный дамп флеш-памяти'
send_paste: 'Вставьте текст сюда или выберите файл ниже'
send_file: 'Файл'
send_photos: 'Фото: лицевая сторона, обратная, контакты UART'
send_dump: 'Вся микросхема: как её считал программатор, или резервная копия ipctool'
send_public: 'Опубликовать дамп вместе с отчётом. В нём ключи Wi-Fi и пароли: не отмечайте, чтобы он остался закрытым.'
send_note: 'Что-нибудь ещё: где купили, под каким названием продаётся'
send_button: 'Отправить в каталог'
sending: 'Отправляем…'
sent_member: 'Получено, номер {id}. Мейнтейнер проверит перед публикацией; следите в разделе «Мои материалы».'
sent_guest: 'Получено, номер {id}. Мейнтейнер проверит перед публикацией.'
receipt: 'Квитанция'
my_link: 'Мои материалы'
sign_in_link: 'Войти'
send_signed_in: 'Вы вошли как {name}. Звёзды начислятся, когда мейнтейнер примет присланное.'
send_guest: 'Отправить можно и без входа. Войдите, чтобы получать звёзды и следить за проверкой.'
send_failed: 'Не отправлено: {error}'
send_empty: 'Сначала добавьте файл, фото или текст.'
reward: '+{n} ★'
send_another: 'Прислать что-то ещё'
review_title: 'Очередь проверки'
review_empty: 'Ничего не ждёт проверки.'
review_forbidden: 'Проверяют только мейнтейнеры OpenIPC. Войдите через GitHub как участник организации OpenIPC.'
review_from: 'От {who}'
review_anon: 'Прислано без входа'
review_board: 'Плата, указанная отправителем'
review_guess: 'По данным ipctool похоже на {board}'
review_models: 'Id плат для публикации через запятую (пусто — плата, указанная отправителем)'
review_note: 'Пояснение для отправителя'
publish: 'Опубликовать'
reject: 'Отклонить'
review_points: '{points} ★'
review_done_publish: 'Опубликовано. Отправителю {points} ★.'
review_done_reject: 'Отклонено.'
show_yaml: 'Вывод ipctool'
tab_pending: 'Ждут'
tab_published: 'Опубликованы'
tab_rejected: 'Отклонены'
header_club: 'Клуб'
confirm_title: 'Войти как {who}?'
confirm_text: 'Эту ссылку запросили в другом браузере. Входите, только если {who} — это вы: всё, что вы пришлёте после входа, попадёт в эту учётную запись.'
confirm_button: 'Войти'
confirm_cancel: 'Отмена'
rename: 'Изменить имя'
rename_label: 'Ваше имя — так оно показывается у плат, куда попали ваши материалы'
rename_save: 'Сохранить'
review_note_label: 'Проверяющий'
Loading
Loading