All checks were successful
CI / lint-and-test (push) Successful in 17s
- Created a new `CHANGELOG.md` file to document all notable changes to the project, adhering to the Keep a Changelog format. - Updated `CONTRIBUTING.md` to include instructions for building and previewing documentation using MkDocs. - Added `mkdocs.yml` configuration for documentation generation, including navigation structure and theme settings. - Enhanced various documentation files, including API reference, architecture overview, configuration reference, and runbook, to provide comprehensive guidance for users and developers. - Included new sections in the README for changelog and documentation links, improving accessibility to project information.
870 lines
24 KiB
HTML
870 lines
24 KiB
HTML
|
||
<!doctype html>
|
||
<html lang="en" class="no-js">
|
||
<head>
|
||
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
|
||
<meta name="description" content="Telegram bot for team duty shift calendar and group reminder">
|
||
|
||
|
||
|
||
<link rel="canonical" href="https://github.com/your-org/duty-teller/runbook/">
|
||
|
||
|
||
<link rel="prev" href="../import-format/">
|
||
|
||
|
||
<link rel="next" href="../api-reference/">
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="icon" href="../assets/images/favicon.png">
|
||
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.2">
|
||
|
||
|
||
|
||
<title>Runbook - Duty Teller</title>
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="../assets/stylesheets/main.484c7ddc.min.css">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
|
||
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
|
||
|
||
|
||
|
||
<link rel="stylesheet" href="../assets/_mkdocstrings.css">
|
||
|
||
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
|
||
|
||
|
||
|
||
|
||
|
||
</head>
|
||
|
||
|
||
<body dir="ltr">
|
||
|
||
|
||
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
|
||
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
|
||
<label class="md-overlay" for="__drawer"></label>
|
||
<div data-md-component="skip">
|
||
|
||
|
||
<a href="#runbook-operational-guide" class="md-skip">
|
||
Skip to content
|
||
</a>
|
||
|
||
</div>
|
||
<div data-md-component="announce">
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<header class="md-header md-header--shadow" data-md-component="header">
|
||
<nav class="md-header__inner md-grid" aria-label="Header">
|
||
<a href=".." title="Duty Teller" class="md-header__button md-logo" aria-label="Duty Teller" data-md-component="logo">
|
||
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
|
||
|
||
</a>
|
||
<label class="md-header__button md-icon" for="__drawer">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
|
||
</label>
|
||
<div class="md-header__title" data-md-component="header-title">
|
||
<div class="md-header__ellipsis">
|
||
<div class="md-header__topic">
|
||
<span class="md-ellipsis">
|
||
Duty Teller
|
||
</span>
|
||
</div>
|
||
<div class="md-header__topic" data-md-component="header-topic">
|
||
<span class="md-ellipsis">
|
||
|
||
Runbook
|
||
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-header__button md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||
</label>
|
||
<div class="md-search" data-md-component="search" role="dialog">
|
||
<label class="md-search__overlay" for="__search"></label>
|
||
<div class="md-search__inner" role="search">
|
||
<form class="md-search__form" name="search">
|
||
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
|
||
<label class="md-search__icon md-icon" for="__search">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
|
||
</label>
|
||
<nav class="md-search__options" aria-label="Search">
|
||
|
||
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
||
</button>
|
||
</nav>
|
||
|
||
</form>
|
||
<div class="md-search__output">
|
||
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
|
||
<div class="md-search-result" data-md-component="search-result">
|
||
<div class="md-search-result__meta">
|
||
Initializing search
|
||
</div>
|
||
<ol class="md-search-result__list" role="presentation"></ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
</nav>
|
||
|
||
</header>
|
||
|
||
<div class="md-container" data-md-component="container">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<main class="md-main" data-md-component="main">
|
||
<div class="md-main__inner md-grid">
|
||
|
||
|
||
|
||
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
|
||
<div class="md-sidebar__scrollwrap">
|
||
<div class="md-sidebar__inner">
|
||
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
|
||
<label class="md-nav__title" for="__drawer">
|
||
<a href=".." title="Duty Teller" class="md-nav__button md-logo" aria-label="Duty Teller" data-md-component="logo">
|
||
|
||
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
|
||
|
||
</a>
|
||
Duty Teller
|
||
</label>
|
||
|
||
<ul class="md-nav__list" data-md-scrollfix>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href=".." class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Home
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../configuration/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Configuration
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../architecture/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Architecture
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../import-format/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Import format
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item md-nav__item--active">
|
||
|
||
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__link md-nav__link--active" for="__toc">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Runbook
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
<span class="md-nav__icon md-icon"></span>
|
||
</label>
|
||
|
||
<a href="./" class="md-nav__link md-nav__link--active">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
Runbook
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
|
||
|
||
|
||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__title" for="__toc">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
Table of contents
|
||
</label>
|
||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#starting-and-stopping" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Starting and stopping
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Starting and stopping">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#local" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Local
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#docker" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Docker
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#health-check" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Health check
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#logs" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Logs
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#common-errors-and-what-to-check" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Common errors and what to check
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Common errors and what to check">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#hash_mismatch-403-from-apiduties-or-miniapp" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
"hash_mismatch" (403 from /api/duties or Miniapp)
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#miniapp-open-in-browser-or-direct-link-access-denied" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Miniapp "Open in browser" or direct link — access denied
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#403-open-from-telegram-no-initdata" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
403 "Open from Telegram" / no initData
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#mini-app-url-redirect-and-broken-auth" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Mini App URL — redirect and broken auth
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#user-not-in-allowlist-403" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
User not in allowlist (403)
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#database-and-migrations" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Database and migrations
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<li class="md-nav__item">
|
||
<a href="../api-reference/" class="md-nav__link">
|
||
|
||
|
||
|
||
<span class="md-ellipsis">
|
||
|
||
|
||
API Reference
|
||
|
||
|
||
|
||
</span>
|
||
|
||
|
||
|
||
</a>
|
||
</li>
|
||
|
||
|
||
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
|
||
<div class="md-sidebar__scrollwrap">
|
||
<div class="md-sidebar__inner">
|
||
|
||
|
||
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<label class="md-nav__title" for="__toc">
|
||
<span class="md-nav__icon md-icon"></span>
|
||
Table of contents
|
||
</label>
|
||
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#starting-and-stopping" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Starting and stopping
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Starting and stopping">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#local" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Local
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#docker" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Docker
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#health-check" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Health check
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#logs" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Logs
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#common-errors-and-what-to-check" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Common errors and what to check
|
||
|
||
</span>
|
||
</a>
|
||
|
||
<nav class="md-nav" aria-label="Common errors and what to check">
|
||
<ul class="md-nav__list">
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#hash_mismatch-403-from-apiduties-or-miniapp" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
"hash_mismatch" (403 from /api/duties or Miniapp)
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#miniapp-open-in-browser-or-direct-link-access-denied" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Miniapp "Open in browser" or direct link — access denied
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#403-open-from-telegram-no-initdata" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
403 "Open from Telegram" / no initData
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#mini-app-url-redirect-and-broken-auth" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Mini App URL — redirect and broken auth
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#user-not-in-allowlist-403" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
User not in allowlist (403)
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
</nav>
|
||
|
||
</li>
|
||
|
||
<li class="md-nav__item">
|
||
<a href="#database-and-migrations" class="md-nav__link">
|
||
<span class="md-ellipsis">
|
||
|
||
Database and migrations
|
||
|
||
</span>
|
||
</a>
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
<div class="md-content" data-md-component="content">
|
||
|
||
<article class="md-content__inner md-typeset">
|
||
|
||
|
||
|
||
|
||
|
||
<h1 id="runbook-operational-guide">Runbook (operational guide)</h1>
|
||
<p>This document covers running the application, checking health, logs, common errors, and database operations.</p>
|
||
<h2 id="starting-and-stopping">Starting and stopping</h2>
|
||
<h3 id="local">Local</h3>
|
||
<ul>
|
||
<li><strong>Start:</strong> From the repository root, with virtualenv activated:
|
||
<code>bash
|
||
python main.py</code>
|
||
Or after <code>pip install -e .</code>: <code>duty-teller</code></li>
|
||
<li><strong>Stop:</strong> <code>Ctrl+C</code></li>
|
||
</ul>
|
||
<h3 id="docker">Docker</h3>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Dev</strong> (code mounted; no rebuild needed for code changes):
|
||
<code>bash
|
||
docker compose -f docker-compose.dev.yml up --build</code>
|
||
Stop: <code>Ctrl+C</code> or <code>docker compose -f docker-compose.dev.yml down</code>.</p>
|
||
</li>
|
||
<li>
|
||
<p><strong>Prod</strong> (built image; restarts on failure):
|
||
<code>bash
|
||
docker compose -f docker-compose.prod.yml up -d --build</code>
|
||
Stop: <code>docker compose -f docker-compose.prod.yml down</code>.</p>
|
||
</li>
|
||
</ul>
|
||
<p>On container start, <code>entrypoint.sh</code> runs Alembic migrations then starts the app as user <code>botuser</code>. Ensure <code>.env</code> (or your orchestrator’s env) contains <code>BOT_TOKEN</code> and any required variables; see <a href="../configuration/">configuration.md</a>.</p>
|
||
<h2 id="health-check">Health check</h2>
|
||
<ul>
|
||
<li><strong>HTTP:</strong> The FastAPI app serves the API and static webapp. A simple way to verify it is up is to open the interactive API docs: <strong><code>GET /docs</code></strong> (e.g. <code>http://localhost:8080/docs</code>). If that page loads, the server is running.</li>
|
||
<li>There is no dedicated <code>/health</code> endpoint; use <code>/docs</code> or a lightweight API call (e.g. <code>GET /api/duties?from=...&to=...</code> with valid auth) as needed.</li>
|
||
</ul>
|
||
<h2 id="logs">Logs</h2>
|
||
<ul>
|
||
<li><strong>Local:</strong> Output goes to stdout/stderr; redirect or use your process manager’s logging (e.g. systemd, supervisord).</li>
|
||
<li><strong>Docker:</strong> Use <code>docker compose logs -f</code> (with the appropriate compose file) to follow application logs. Adjust log level via Python <code>logging</code> if needed (e.g. environment or code).</li>
|
||
</ul>
|
||
<h2 id="common-errors-and-what-to-check">Common errors and what to check</h2>
|
||
<h3 id="hash_mismatch-403-from-apiduties-or-miniapp">"hash_mismatch" (403 from <code>/api/duties</code> or Miniapp)</h3>
|
||
<ul>
|
||
<li><strong>Cause:</strong> The server that serves the Mini App (e.g. production host) uses a <strong>different</strong> <code>BOT_TOKEN</code> than the bot from which users open the Mini App (e.g. test vs production bot). Telegram signs initData with the bot token; if tokens differ, validation fails.</li>
|
||
<li><strong>Check:</strong> Ensure the same <code>BOT_TOKEN</code> is set in <code>.env</code> (or equivalent) on the machine serving <code>/api/duties</code> as the one used by the bot instance whose menu button opens the Miniapp.</li>
|
||
</ul>
|
||
<h3 id="miniapp-open-in-browser-or-direct-link-access-denied">Miniapp "Open in browser" or direct link — access denied</h3>
|
||
<ul>
|
||
<li><strong>Cause:</strong> When users open the calendar via “Open in browser” or a direct URL, Telegram may not send <code>tgWebAppData</code> (initData). The API requires initData (or <code>MINI_APP_SKIP_AUTH</code> / private IP in dev).</li>
|
||
<li><strong>Action:</strong> Users should open the calendar <strong>via the bot’s menu button</strong> (e.g. ⋮ → «Календарь») or a <strong>Web App inline button</strong> so Telegram sends user data.</li>
|
||
</ul>
|
||
<h3 id="403-open-from-telegram-no-initdata">403 "Open from Telegram" / no initData</h3>
|
||
<ul>
|
||
<li><strong>Cause:</strong> Request to <code>/api/duties</code> (or calendar) without valid <code>X-Telegram-Init-Data</code> header. In production, only private IP clients can be allowed without initData (see <code>_is_private_client</code> in <code>api/dependencies.py</code>); behind a reverse proxy, <code>request.client.host</code> is often the proxy (e.g. 127.0.0.1), so the “private IP” bypass may not apply to the real user.</li>
|
||
<li><strong>Check:</strong> Ensure the Mini App is opened from Telegram (menu or inline button). If behind a reverse proxy, see README “Production behind a reverse proxy” (forward real client IP or rely on initData).</li>
|
||
</ul>
|
||
<h3 id="mini-app-url-redirect-and-broken-auth">Mini App URL — redirect and broken auth</h3>
|
||
<ul>
|
||
<li><strong>Cause:</strong> If the Mini App URL is configured <strong>without</strong> a trailing slash (e.g. <code>https://your-domain.com/app</code>) and the server redirects <code>/app</code> → <code>/app/</code>, the browser can drop the fragment Telegram sends, breaking authorization.</li>
|
||
<li><strong>Action:</strong> Configure the bot’s menu button / Web App URL <strong>with a trailing slash</strong>, e.g. <code>https://your-domain.com/app/</code>. See README “Mini App URL”.</li>
|
||
</ul>
|
||
<h3 id="user-not-in-allowlist-403">User not in allowlist (403)</h3>
|
||
<ul>
|
||
<li><strong>Cause:</strong> Telegram user’s username is not in <code>ALLOWED_USERNAMES</code> or <code>ADMIN_USERNAMES</code>, and (if using phone) their phone (set via <code>/set_phone</code>) is not in <code>ALLOWED_PHONES</code> or <code>ADMIN_PHONES</code>.</li>
|
||
<li><strong>Check:</strong> <a href="../configuration/">configuration.md</a> for <code>ALLOWED_USERNAMES</code>, <code>ADMIN_USERNAMES</code>, <code>ALLOWED_PHONES</code>, <code>ADMIN_PHONES</code>. Add the user or ask them to set phone and add it to the allowlist.</li>
|
||
</ul>
|
||
<h2 id="database-and-migrations">Database and migrations</h2>
|
||
<ul>
|
||
<li><strong>Default DB path (SQLite):</strong> <code>data/duty_teller.db</code> (relative to working directory when using default <code>DATABASE_URL=sqlite:///data/duty_teller.db</code>). In Docker, the entrypoint creates <code>/app/data</code> and runs migrations there.</li>
|
||
<li><strong>Migrations (Alembic):</strong> From the repository root:
|
||
<code>bash
|
||
alembic -c pyproject.toml upgrade head</code>
|
||
Config: <code>pyproject.toml</code> → <code>[tool.alembic]</code>; script location <code>alembic/</code>; metadata and URL from <code>duty_teller.config</code> and <code>duty_teller.db.models.Base</code>.</li>
|
||
<li><strong>Rollback:</strong> Use with care; test in a copy of the DB first. Example to go back one revision:
|
||
<code>bash
|
||
alembic -c pyproject.toml downgrade -1</code>
|
||
Always backup the database before downgrading.</li>
|
||
</ul>
|
||
<p>For full list of env vars (including <code>DATABASE_URL</code>), see <a href="../configuration/">configuration.md</a>. For reverse proxy and Mini App URL details, see the main <a href="../README.md">README</a>.</p>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
</article>
|
||
</div>
|
||
|
||
|
||
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<footer class="md-footer">
|
||
|
||
<div class="md-footer-meta md-typeset">
|
||
<div class="md-footer-meta__inner md-grid">
|
||
<div class="md-copyright">
|
||
|
||
|
||
Made with
|
||
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
|
||
Material for MkDocs
|
||
</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</footer>
|
||
|
||
</div>
|
||
<div class="md-dialog" data-md-component="dialog">
|
||
<div class="md-dialog__inner md-typeset"></div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
|
||
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": [], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
|
||
|
||
|
||
<script src="../assets/javascripts/bundle.79ae519e.min.js"></script>
|
||
|
||
|
||
</body>
|
||
</html> |