3 & Done: система задач для разработчика
Разбор системы задач 3 & Done — хранение дел, голосовой ввод, приватность и инфраструктура на PostgreSQL. Как убрать рутину и сосредоточиться на важном.
Зачем нужна своя система задач
Проблема избытка инструментов
Сейчас на рынке столько таск-менеджеров, что кажется — каждый захотел построить свой замок Писателя. Notion, ClickUp, Todoist, Trello, Asana, Monday.com, Linear, Jira… Перебороть их все — целая работа. Я сам с этим боролся пять лет: каждый новый сервис обещал «сделать жизнь проще», а на деле просто добавлял ещё одну вкладку в браузер, где нужно было логиниться, помнить пароль и постоянно синхронизировать состояние.
Ситуация ровно такая же, как с хранилищами. Помните, как в 2010-х каждая контора качала свой облачный S3-совместимый бакет? Через пять лет все сели на MinIO или сами поднимали [развертывание DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti). С таск-менеджерами та же история — избыток приводит к хаосу.
Я остановился на принципе: один источник правды, один интерфейс, один способ добавлять задачу. Ни одного «а вдруг тут круто». Если что-то не вписывается в эту схему — оно не нужное. Особенно когда речь идёт о личных данных.
Как рутина отвлекает от работы
Рутина — это не про «лень». Это про расход мышечного внимания. Каждая задача, которую нужно вручную распределять по проектам, помечать тегами, перетаскивать между колонками — это микроскопический удар по тем же самым нейронам, что отвечают за концентрацию на коде.
Мне кажется, вы знаете это ощущение: сел писать фичу, а через полчаса понимаешь, что вместо реализации ты раз в пятом пытаешь вспомнить, где та задача про рефакторинг старого эндпоинта. Или наоборот — забыл, что нужно срочно ответить на письмо клиенту, потому что оно «где-то там» в системе, которая не напомнит.
Вот почему я сделал ставку на предсказуемость. Задачи попадают в PostgreSQL через один и тот же канал — CLI, Telegram-бот или голосовой ввод. Никаких «а я её в Notion записал, а потом забыл синхронизировать». Один движок, одна модель данных, один набор правил. Остальное — только шум.
Архитектура 3 & Done
Архитектура 3 & Done
Система построена вокруг трёх принципов: данные должны быть предсказуемы, доступ к ним — быстрый, а инфраструктура — минимальной. Вот как это выглядит на практике.
Хранилище на PostgreSQL
PostgreSQL здесь не просто база — он становится единой точкой синхронизации между веб-интерфейсом, мобильным клиентом и CLI-утилитами. Все задачи, метаданные и ссылки на вложения хранятся в одной таблице tasks, что упрощает резервное копирование и миграции.
Схема таблицы выглядит так:
sqlCREATE TABLE tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title TEXT NOT NULL,
description TEXT,
status SMALLINT DEFAULT 0,
priority SMALLINT DEFAULT 1,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
due_date TIMESTAMP WITH TIME ZONE,
tags TEXT[]
);
Индекс по status и priority даёт предсказуемость при фильтрации:
sqlCREATE INDEX idx_tasks_status_priority ON tasks (status, priority);
Для резервного копирования используем стандартный pg_dump с сжатием:
bashpg_dump -U postgres -h localhost -F c -Z 9 3anddone > backup_$(date +%F).dump
А вот как подключаемся из приложения через psycopg2 в Python:
pythonimport psycopg2
conn = psycopg2.connect(
host="localhost",
database="3anddone",
user="app",
password="secure_password"
)
Если нужна отказоустойчивость — смотрите [развёртывание DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti) для настройки репликации и мониторинга.
Голосовой ввод и текстовые заметки
Голосовой ввод работает через локальный Whisper-модуль, а не через облако. Это важно для приватности: ни одно аудио не покидает устройство.
Конфиг для whisper.cpp в Docker Compose:
yamlservices:
stt:
image: ghcr.io/ggerganov/whisper.cpp:latest
volumes:
- ./models:/models
- ./audio:/audio
command: -m /models/ggml-base.en-q5_0.bin -f /audio/input.wav -otxt
Запуск распознавания:
bashdocker compose run --rm stt
Результат сохраняется в /audio/output.txt, откуда скрипт на Python парсит текст и отправляет в PostgreSQL:
pythonwith open('/audio/output.txt', 'r') as f:
text = f.read().strip()
cur.execute(
"INSERT INTO tasks (title, description) VALUES (%s, %s)",
(text[:100], text)
)
conn.commit()
Для текстовых заметок используется простой Markdown-парсер. Пример заметки:
markdown# Встреча с командой
- Обсудить дедлайны
- Распределить задачи
- Согласовать бюджет
Эта заметка автоматически разбивается на подзадачи и сохраняется в PostgreSQL через тот же pipeline.
Если интересна временная шкала событий — смотрите масштабируемую временную шкалу, где мы решали похожую задачу для 4 млн записей.
А для тех, кто хочет всё это развернуть локально — [локальный стек DB, Ollama и SQL](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) покажет, как собрать полный стек без облака.
Приватность и локальное хранение
Почему данные не в облаке
Скажу сразу — я не против облаков. Работал с AWS, GCP, Azure, где удобно — там оставлял. Но личные задачи — это не проект, где можно «потом поднять». Это каждый день, и если облако ляжет, вы потеряете не просто данные — вы потеряете ритм.
В 2020 году у меня был планировщик на базе Notion. Работало как часы три года. Потом — аутсайдерский апдейт, и на два дня все данные превратились в кашу из дубликатов. Восстанавливать из бэкапа пришлось вручную, а потом ещё разобраться, что изменилось за выходные. С тех пор принцип: если это мои данные и я ими пользуюсь каждый день — они лежат у меня.
Говоря о предсказуемости: локальный PostgreSQL на Пятачке (да, так я назвал ноду, где дома стоит R730) даёт гарантию, что кроме меня никто не лезет в базу. Ни администратор, ни сисадмин из Notion, ни сбой в их инфраструктуре.
bash# Проверка, что PostgreSQL слушает только localhost
ss -tlnp | grep 5432
# Ожидаем: 127.0.0.1:5432
Если порт открыт наружу — это уже не ваш домашний сервер, это сервер с доступом из интернета. И тогда «приватность» становится формальностью.
Связанная идея: [размещение на домашних серверах AI: локальный стек DB, Ollama и SQL](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) — когда всё вместе, в одной сети, без пересечения с внешними сервисами.
Контроль доступа и шифрование
Доступ к базе делится на два уровня: кто может подключиться, и кто может что делать внутри.
Уровень подключения. PostgreSQL по умолчанию слушает только localhost. Если вы хотите, чтобы приложение 3 & Done подключалось с другой машины — не открывайте порт наружу. Настраивайте SSH-туннель или WireGuard-сеть между нодами.
bash# SSH-туннель с клиентской машины к серверу с PostgreSQL
ssh -L 5432:localhost:5432 alexey@пятачок.local
Теперь клиент подключается к localhost:5432, а запросы идут через туннель. Никто извне не видит порт.
Уровень прав. В PostgreSQL создаю отдельную роль для приложения:
sqlCREATE ROLE three_and_done WITH LOGIN PASSWORD 'сгенерированный_пароль_из_256_символов';
GRANT CONNECT ON DATABASE tasks TO three_and_done;
GRANT USAGE ON SCHEMA public TO three_and_done;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO three_and_done;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO three_and_done;
Пароль храню в pass — шифруется GPG-ключом, синхронизируется через приватный Git-репозиторий. Никогда не в .env-файле рядом с кодом.
Шифрование данных. База лежит на диске с LUKS-шифрованием. Это не защита от злоумышленника, который уже в системе — это защита от кражи диска. Если вынесете SSD из сервера — без ключа он бесполезен.
bash# Проверка статуса LUKS
cryptsetup status /dev/sdb3
Для бэкапов использую pg_dump с --compress и сразу шифрую gpg:
bashpg_dump -U postgres tasks | gzip | gpg --encrypt --recipient alexey@домашний.сервер > backup_$(date +%F).sql.gz.gpg
Бэкапы кладутся на отдельный том TrueNAS, который реплицируется на вторую ноду. Если вдруг Пятачок загорится — данные не пропадут.
Система не идеальна, но она предсказуема. Вы знаете, где ваши данные, кто к ним имеет доступ, и как их восстановить. В отличие от облака, где «всё работает, пока работает».
Инфраструктура и развёртывание
Docker Compose для сервисов
Собирать 3 & Done вручную на каждой машине — это путь в тот же подход, который я критикую в статье. Инфраструктура должна быть предсказуемой, а значит — описана в коде.
Вот docker-compose.yml, который я использую в продакшене. База данных, бэкенд и веб-интерфейс идут в отдельных контейнерах, а важные детали — в комментариях.
yamlversion: "3.9"
services:
db:
image: postgres:16-alpine
container_name: 3done-db
restart: unless-stopped
volumes:
- ./pgdata:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
environment:
POSTGRES_DB: thrivedone
POSTGRES_USER: thrivedone
POSTGRES_PASSWORD: ${DB_PASSWORD}
networks:
- backend
backend:
build:
context: .
dockerfile: Dockerfile.backend
container_name: 3done-backend
restart: unless-stopped
depends_on:
- db
env_file: .env.backend
networks:
- backend
- frontend
web:
image: nginx:1.25-alpine
container_name: 3done-web
restart: unless-stopped
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./static:/usr/share/nginx/html:ro
depends_on:
- backend
networks:
- frontend
networks:
backend:
frontend:
Пара слов о выборе. PostgreSQL 16 — актуальная LTS на сегодняшний день, а Alpine-образы экономят мегагигабайты по сравнению с Debian-версиями. Разделение сетей на backend и frontend гарантирует, что веб-сервер не «заглянет» в базу напрямую — только через бэкенд.
Переменные окружения вынесены в .env-файлы. Это не просто удобно — это безопасность. Файл .env.backend выглядит так:
envDB_HOST=db DB_PORT=5432 DB_NAME=thrivedone JWT_SECRET=здесь_только_длинная_строка_из_32_символов LOG_LEVEL=info
А пароль к базе данных — в .env в корне проекта:
envDB_PASSWORD=сгенерированный_пароль_из_64_символов
Совет: генерировать пароль один раз и хранить в менеджере паролей. Я использую pass на своей машине — [связанная статья про размещение на домашних серверах AI-стек](/articles/размещение на домашних серверах-ai-workflow-db-ollama-sql) тоже опирается на локальную инфраструктуру, так что подходы схожи.
Сам бэкенд собирается из Dockerfile, который я тоже пишу руками. Никаких готовых образов с чужими зависимостями — только тот софт, который действительно нужен:
dockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
Если вы работаете с PostgreSQL регулярно, можете также посмотреть мой разбор [локального DBA-агента DeepSQL](/guides/razvertyvanie-deepsql-размещение на домашних серверах-dba-agent-dlya-postgresql-i-mysql-uf6iti) — там есть примеры настройки мониторинга и алертинга прямо внутри контейнера.
Резервное копирование и миграции
База данных — это не просто файлы в pgdata. Это состояние, которое нужно сохранять предсказуемо. Я делаю два уровня бэкапа: логический и физический.
Логический бэкап — pg_dump с флагом --clean, чтобы при восстановлении не было конфликтов со старыми объектами:
bashpg_dump -U thrivedone -h db -Fc thrivedone > backup_$(date +%Y%m%d_%H%M%S).dump
Флаг -Fc создаёт кастомный формат, который потом удобно восстанавливать частично:
bashpg_restore -U thrivedone -h db --clean --if-exists -d thrivedone backup_20240115_120000.dump
Физический бэкап — pg_basebackup. Он нужен, когда вы хотите восстановить кластер целиком, включая все WAL-файлы:
bashpg_basebackup -U replicator -h db -D /backup/pg_base/ -P -R -X stream
Для автоматизации я использую systemd-таймер, а не cron. Почему? Потому что systemd даёт предсказуемость: вы видите в journalctl, когда именно задача запустилась, с каким результатом, и можете повесить алерт, если что-то пошло не так.
Вот юнит /etc/systemd/system/3done-backup.service:
ini[Unit]
Description=3 & Done database backup
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/3done-backup.sh
User=backup
И таймер /etc/systemd/system/3done-backup.timer:
ini[Unit]
Description=Daily 3 & Done backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Активируем:
bashsystemctl daemon-reload
systemctl enable --now 3done-backup.timer
Скрипт 3done-backup.sh прост — он делает логический дамп, копирует его на NFS-хранилище и удаляет старые бэкапы старше 30 дней:
bash#!/bin/bash
set -euo pipefail
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/mnt/nfs/backups/3done"
CONTAINER="3done-db"
mkdir -p "$BACKUP_DIR"
docker exec "$CONTAINER" pg_dump -U thrivedone thrivedone | gzip > "$BACKUP_DIR/thrivedone_$DATE.sql.gz"
# Удаляем файлы старше 30 дней
find "$BACKUP_DIR" -name "thrivedone_*.sql.gz" -mtime +30 -delete
Миграции — это тоже часть инфраструктуры. Я не использую миграции в классическом Django-смысле. Вместо этого всё схемы хранятся в SQL-файлах в каталоге migrations/, и применяются последовательно через psql:
bashpsql -U thrivedone -d thrivedone -f migrations/001_create_tables.sql psql -U thrivedone -d thrivedone -f migrations/002_add_indexes.sql
Это даёт полный контроль. Вы видите каждый шаг, можете откатить на любом этапе, и никто не «навязывает» вам свою ORM-специфику.
Если у вас уже есть данные и вы хотите перенести их из старой системы — эта статья про временные метки с 4 млн событий покажет, как оптимизировать миграцию больших объёмов данных в PostgreSQL без простоев.
Главное правило: если вы не можете восстановить сервис из бэкапа за 30 минут, ваша стратегия резервного копирования не работает. Проверяйте восстановление регулярно — не реже месяца.
Часто задаваемые вопросы
Почему именно PostgreSQL, а не SQLite или облачный сервис?
Потому что PostgreSQL даёт предсказуемость, которой не хватает ни SQLite, ни облачным таск-менеджерам. SQLite не выдерживает конкурентных запросов, если вы работаете с задачами из Telegram-бота, CLI и веб-интерфейса одновременно — блокировки начинают каскадировать. Облачные сервисы, даже если они и «бесплатны», требуют синхронизации состояния, а значит — задержек и потерь данных при плохом соединении. PostgreSQL с JSONB-полями для метаданных и транзакциями для атомарности операций работает локально, без задержек, и не требует выхода в интернет. Если вы уже знакомы с SQL — вы сможете писать запросы для анализа своей продуктивности без сторонних API.
Как система обрабатывает голосовые заметки без передачи данных третьим лицам?
Голосовой ввод работает через локальный распознаватель речи — Vosk или Whisper.cpp — прямо на вашем сервере. Аудио не покидает устройство: запись захватывается микрофоном, отправляется в локальный процесс распознавания, результат преобразуется в текст и сразу записывается в PostgreSQL. Никаких запросов к Google Speech API, Яндекс.Распознаванию или другим облачным сервисам. Это значит, что даже если вы говорите «переписать роутер на /api/v1/users», эта информация не попадает ни в какую базу данных третьих сторон. Для большинства задач достаточно Vosk — он легче, быстрее и не требует GPU.
Что делать, если нужно срочно добавить задачу, не садясь за компьютер?
Telegram-бот решает эту задачу. Вы пишете сообщение в чат с ботом — «срочно: проверить логи на Проде» — и задача автоматически попадает в PostgreSQL с пометкой «срочно» и временной меткой. Бот работает через webhook или long-polling, не требует постоянного соединения с вашим ноутбомом, и может быть развёрнут на той же ноде, где лежит база данных. Главное — не нужно помнить, в каком из пяти сервисов вы оставляли напоминание. Один канал ввода, один источник правды.
Как система помогает не терять задачи в потоке дел?
Принцип «3 & Done» прост: вы добавляете задачу, система автоматически назначает ей приоритет на основе ключевых слов и времени создания, а через три дня — если задача не завершена — она попадает в сегодняшний список с пометкой «просрочено». Никаких колонок «в процессе», «на паузе», «отложено» — только актуальные задачи, которые реально нужно сделать сегодня. Это убирает когнитивную нагрузку на выбор: вы видите три самые важные задачи и начинаете с них. Остальное — в архиве, но с доступом по одному клику, если вдруг вспомните, что-то упустили.
Можно ли использовать эту систему в команде, а не только для себя?
Да, но с оговоркой. PostgreSQL поддерживает роли и права доступа, так что вы можете создать отдельные схемы или таблицы для каждого участника команды, а также общие представления для совместных задач. Однако система 3 & Done изначально проектировалась как персональная — без сложных workflow, уведомлений и интеграций с CI/CD. Если вам нужна командная коллаборация с аппрувами, ревью и распределением нагрузки — лучше рассмотреть Linear или Jira. А если цель — убрать рутину из личной работы и сосредоточиться на коде, эта система работает как часы.