Scheduler de apps com system tray, dashboard live e logs Grafana Loki
Project description
bgo_scheduler
Scheduler de apps para Windows com ícone de system tray, dashboard web live e logs em formato Grafana Loki. Distribuído como wheel Python.
📋 Histórico de alterações em CHANGELOG.md.
Objetivos
- Scheduler Python de zero dependências — o runtime não instala nada além da biblioteca padrão.
- Avisos que tiram partido do Windows — ícone de system tray (Win32 nativo) e notificações (toasts) para o estado das execuções.
- Sleep hours transversais e específicas por app — períodos em que o agendamento fica em pausa, definidos globalmente e/ou por aplicação.
- Encadeamento de execuções — uma app corre depois de outra terminar com
sucesso (ex.:
app02corre apósapp01). - Visão agregada e por app — uma vista geral de todas as execuções e o detalhe/histórico de cada aplicação.
Screenshots
Visão geral de todas as apps — indicadores no topo, lista de apps e o histórico agregado com estado, RC, notificações e mensagens.
Detalhe de uma app — abas Monitorização / Configuração, histórico com duração e origem, e "Executar agora".
Definições globais e sleep hours transversais, editáveis no dashboard:
Instalação
pip install dist\bgo_scheduler-1.9.10-py3-none-any.whl
Sem dependências de runtime: o ícone de tray é nativo do Windows (Win32
via ctypes, carrega ficheiros .ico) e o dashboard usa só a stdlib.
Fica com dois comandos:
bgo-scheduler— com consola (bom para debug); aceita--headlesspara correr sem ícone de tray (só scheduler + dashboard).bgo-scheduler-tray— sem janela de consola; é este que deves usar no arranque do Windows (atalho na pastashell:startup).
Para reconstruir o wheel: python -m pip wheel . --no-deps -w dist.
Em desenvolvimento (sem instalar): python systray_icon.py.
Estrutura de pastas e ficheiros
Há duas zonas distintas: a pasta de configuração/dados do scheduler e a(s) pasta(s) de apps. Não têm de ser a mesma (embora no primeiro arranque coincidam — ver "Configuração").
Pasta de configuração/dados
Por omissão %APPDATA%\bgo_scheduler (ou onde apontar o --config /
BGO_SCHEDULER_CONFIG). É aqui que o scheduler guarda tudo o que produz:
%APPDATA%\bgo_scheduler\
├── scheduler.ini ← configuração GLOBAL do scheduler
├── notification_rules.json ← regras de notificação e mensagens
├── logs\ ← logs JSON-lines (formato Grafana Loki)
│ ├── scheduler.log ← log do próprio scheduler
│ ├── hello1.log ← um ficheiro por app
│ └── hello2.log
└── history\ ← histórico de execuções (persiste entre reinícios)
├── hello1.jsonl ← uma linha JSON por execução
└── hello2.jsonl
| Ficheiro / pasta | Para que serve |
|---|---|
scheduler.ini |
Configuração global: onde estão as apps ([Apps] roots), o dashboard (host/port), max_parallel, sleep hours transversais, atalhos [Links]. Parte é editável no dashboard (⚙ Definições), o resto à mão. |
notification_rules.json |
Padrões que disparam notificações de erro e mensagens de sucesso/warning. Gerido pelo dashboard (aba Configuração de cada app) — normalmente não se edita à mão. |
logs\<app>.log |
Um ficheiro por app; cada linha é um objeto JSON (ts, level, app, event, msg) pronto para o Grafana Loki. Escrito à medida que a app corre. Rotação automática 5 MB × 3. |
logs\scheduler.log |
Log do próprio scheduler: arranque, rescan, avisos de configuração, encadeamentos. |
history\<app>.jsonl |
Uma linha JSON por execução (início, fim, duração, código de saída, estado, origem, excerto do output, notificações). Carregado no arranque para o dashboard mostrar o histórico logo após reiniciar. Compactado acima de 2 MB. |
A pasta logs\ pode ser movida com [Logs] dir no INI (ex.: para uma pasta que
o Promtail/Alloy já vigie); a history\ fica sempre ao lado do INI.
Pasta de apps
Cada sub-pasta de uma raiz ([Apps] roots) é uma app. O nome da app é o
nome da pasta (é o que aparece no tray e no dashboard). A pasta tem de conter
main.py (preferido) ou main.bat, e opcionalmente um schedule.ini.
C:\Users\asus\Desktop\bgo_apps\ ← uma raiz (em [Apps] roots)
├── hello1\ ← app "hello1"
│ ├── main.py ← o que é executado
│ └── schedule.ini ← periodicidade/opções (opcional)
├── relatorios\ ← app "relatorios"
│ ├── main.py
│ ├── schedule.ini
│ └── .venv\ ← venv próprio (opcional; ver python_exe)
└── backup\ ← app "backup"
└── main.bat ← app sem Python: corre o .bat (com logs à mesma)
- A app corre com a própria pasta como diretório de trabalho (caminhos
relativos dentro do
main.pysão resolvidos a partir daí). - Sem
schedule.ini, a app fica ativa e corre a cada 60 min. - Código de saída:
0= ok;≠ 0marca a execução como "erro" e (por omissão) dispara notificação.
Exemplo de hello1\main.py:
import sys
print("a processar…") # vai para logs/hello1.log (event=stdout)
# ... trabalho ...
print("42 registos processados") # pode virar "mensagem de sucesso" no dashboard
sys.exit(0) # 0 = ok; != 0 = erro
Exemplos de schedule.ini (todas as opções também são editáveis no dashboard):
; de 30 em 30 minutos
[Schedule]
interval_minutes = 30
; dias úteis às 09:00, aborta se passar de 10 min
[Schedule]
cron = 0 9 * * 1-5
timeout_minutes = 10
; interpretador próprio (venv da app) e nunca dorme (ignora sleep hours)
[Schedule]
interval_minutes = 60
python_exe = .venv\Scripts\python.exe
ignore_sleep_hours = true
; encadeamento: corre quando a app "extrair" termina com sucesso
[Schedule]
run_after = extrair
O detalhe completo de cada opção está mais abaixo, em schedule.ini (por app).
Configuração
O scheduler.ini é procurado por esta ordem:
--config C:\caminho\scheduler.ini- variável de ambiente
BGO_SCHEDULER_CONFIG %APPDATA%\bgo_scheduler\scheduler.ini— criado automaticamente no primeiro arranque
No primeiro arranque (sem --config e sem INI existente), o
scheduler.ini é criado em %APPDATA%\bgo_scheduler com roots a apontar
para essa mesma pasta. Ou seja, funciona logo: basta criares sub-pastas com as
apps dentro de %APPDATA%\bgo_scheduler. Podes depois trocar os roots no INI
(ou no dashboard) para outras pastas.
Ao lado do INI vivem o notification_rules.json (editado no dashboard) e a
pasta logs\ (alterável em [Logs] dir).
Onde estão as apps
Na secção [Apps] do INI indicas uma ou mais pastas, uma por linha:
[Apps]
roots =
C:\Users\asus\Desktop\bgo_apps
D:\outros_jobs
Cada sub-pasta de cada raiz com main.py (ou, em alternativa, main.bat) é
uma app. Se duas raízes tiverem apps com o mesmo nome, a primeira ganha e
fica um aviso no log do scheduler. Para testes pontuais:
bgo-scheduler --apps-root C:\outra\pasta (repetível; ignora as roots do INI
nessa execução).
scheduler.ini completo
[Dashboard]—host,port(por omissão 8765),open_on_start.[Apps]—roots(ver acima) eexclude(pastas a ignorar).[Execution]—max_parallel(0 = sem limite): número máximo de apps a executar em simultâneo. Quando o limite é atingido, as restantes ficam em fila e arrancam à medida que abrem lugares (evita picos quando muitas apps partilham o mesmo horário).[Logs]—dir(vazio =logs\ao lado do INI).[Links]— cadaNome = URLvira item de menu no tray e link no topo do dashboard (Hello1, Hello2, …).[SleepHours]— período diário em que as apps não são executadas automaticamente (ver abaixo).
Sleep hours (pausa do agendamento)
Período em que as execuções agendadas e cron ficam em pausa (a app retoma
quando o período termina). Suporta janelas que atravessam a meia-noite
(ex.: 22:00–07:00). A execução manual continua sempre disponível.
Tudo editável no dashboard — não é preciso mexer nos ficheiros à mão:
- Transversal (a todas as apps): clica no indicador "sleep hours" no
topo do dashboard. Guarda em
scheduler.ini [SleepHours].[SleepHours] enabled = true start = 22:00 end = 07:00
- Por app (no detalhe da app, secção "Sleep hours desta app"), com três
modos, guardados no
schedule.inida app:- Herdar a transversal (por omissão);
- Ignorar — app crítica, nunca dorme (
ignore_sleep_hours = true); - Horário próprio — janela só desta app (
sleep_hours = 23:00-06:00).
As alterações aplicam-se de imediato, sem reiniciar. O dashboard mostra um indicador no topo e marca cada app em pausa.
schedule.ini (por app, opcional)
[Schedule]
enabled = true
; agendamento por intervalo:
interval_minutes = 60
; OU agendamento cron (tem prioridade sobre interval_minutes):
; cron = 0 9 * * 1-5 <- dias úteis às 09:00 (hora local)
; aborta a execução ao fim de N minutos (0 = sem limite)
timeout_minutes = 0
; interpretador Python próprio (venv da app); caminho absoluto
; ou relativo à pasta da app
; python_exe = .venv\Scripts\python.exe
; ignorar o período de sleep hours (ver scheduler.ini) para esta app
; ignore_sleep_hours = false
; encadeamento: corre quando a(s) app(s) a montante terminam com sucesso,
; em vez de por intervalo/cron (vários nomes separados por vírgula)
; run_after = extrair, transformar
Sem schedule.ini: ativa, a cada 60 minutos. Em modo intervalo, conta a
partir do fim da execução anterior e há uma execução no arranque do
scheduler; em modo cron respeita-se apenas o horário definido. A app corre
com a própria pasta como diretório de trabalho e execuções sobrepostas são
ignoradas (fica registado no log).
Todo o agendamento é editável no dashboard (detalhe da app → secção "Agendamento"): modo intervalo/cron, timeout e o encadeamento — sem editar ficheiros nem reiniciar.
Encadeamento de apps (pipelines)
Com run_after, uma app deixa de correr por tempo e passa a correr quando a(s)
app(s) indicada(s) terminam com sucesso — útil para pipelines de dados
(ex.: extrair → transformar → carregar). As execuções mostram a origem
"dependência (…)" no histórico e no log (evento chain_trigger). O scheduler
deteta e quebra ciclos (com aviso) e ignora referências a apps inexistentes.
A execução manual continua sempre disponível.
Sintaxe cron (5 campos): minuto hora dia-do-mês mês dia-da-semana, com
*, listas 1,2,3, intervalos 1-5 e passos */15. Dia da semana: 0 e 7
= domingo. Exemplos: */30 * * * * (de 30 em 30 min), 0 9 * * 1-5 (dias
úteis às 09:00), 0 7 1 * * (dia 1 de cada mês às 07:00).
Ligar/desligar o agendamento no dashboard fica gravado no schedule.ini
da app (comentários preservados).
Dashboard (http://127.0.0.1:8765/)
- Homepage com a visão geral das execuções de todas as apps (app, início/fim, RC, estado, notificações e mensagens destacadas).
- O detalhe de cada app separa consulta de edição em duas abas:
- Monitorização (por omissão): estado, histórico com output e log viewer.
- Configuração: agendamento, sleep hours e regras de notificação/mensagens, com um único "Guardar configuração" (só grava as secções alteradas) e aviso de "alterações por gravar" / confirmação ao sair.
- Ações rápidas sempre no topo do detalhe: Executar agora e ligar/desligar o agendamento (gravado no schedule.ini). ↻ Redetetar apps sem reiniciar.
- ⚙ Definições (topo): edita
host/port/open_on_start/max_parallel, as raízes de apps ([Apps] roots) e os[Links]noscheduler.ini. As raízes (redeteta as apps ao guardar), os links e oopen_on_startaplicam-se ao vivo;host/port/max_parallelficam gravados mas só depois de reiniciar (o dashboard avisa). - Notificações de erro: por omissão notifica (toast do Windows) quando o código de saída ≠ 0; podes acrescentar padrões (texto ou regex) por app ou globais que disparam notificação quando aparecem no output.
- Mensagens de sucesso/warning: padrões que destacam linhas do output na homepage e no histórico (sem toast).
- Menu do tray: cada app tem "Abrir dashboard" e "Executar agora"; item "Redetetar apps"; duplo-clique no ícone abre o dashboard geral.
Logs
Cada linha de logs\<app>.log é um objeto JSON (mesmo para apps main.bat,
porque é o scheduler que captura o stdout/stderr):
{"ts": "2026-07-08T16:38:34.229+01:00", "level": "error", "app": "app_fail",
"event": "stderr", "msg": "ERRO: falha ao ligar à base de dados"}
O ts é a hora local com offset explícito (RFC3339): legível diretamente
nos ficheiros e sem ambiguidade para o Promtail/Grafana.
As linhas de stdout/stderr são escritas à medida que a app corre
(logs live no Loki), não só no fim da execução.
Eventos: run_start, stdout, stderr, run_end (com status,
returncode, duration_s), run_timeout, run_skipped, run_queued,
notify. Rotação automática: 5 MB × 3 backups. O log do próprio scheduler é
logs\scheduler.log.
Exemplo de scrape no Promtail (ou equivalente no Grafana Alloy):
scrape_configs:
- job_name: bgo_scheduler
static_configs:
- targets: [localhost]
labels:
job: bgo_scheduler
__path__: C:\Users\asus\AppData\Roaming\bgo_scheduler\logs\*.log
pipeline_stages:
- json:
expressions: { ts: ts, level: level, app: app, event: event }
- labels: { app: "", level: "" }
- timestamp: { source: ts, format: RFC3339 }
Estrutura do projeto
bgo_scheduler\
├── pyproject.toml ← metadata do package + entry points
├── systray_icon.py ← arranque em modo dev (sem instalar)
└── src\bgo_scheduler\
├── cli.py ← bgo-scheduler / bgo-scheduler-tray
├── config.py ← resolução e leitura do scheduler.ini
├── scheduler_core.py ← deteção de apps, agendamento, execução, regras
├── loki_logger.py ← JSON-lines para o Loki
├── web_dashboard.py ← API + servidor do dashboard (stdlib)
├── cron.py ← parser de expressões cron
├── tray.py ← ícone de system tray (Win32 nativo via ctypes)
├── dashboard.html ← página do dashboard (package data)
└── icons\ ← ok/run/err .ico do tray (package data)
Avisos de configuração
Erros no scheduler.ini ou num schedule.ini (porto inválido, cron mal
formada, python_exe inexistente, raiz de apps em falta, etc.) já não são
silenciosos: aparecem num banner no topo do dashboard e por app (⚠), além de
irem para logs\scheduler.log (evento config_warning).
Desenvolvimento
pip install -e ".[dev]" :: instala pytest + ruff
ruff check .
pytest -q
A suite (tests\, ~70 testes) cobre cron, config/sleep hours/avisos, regras,
execução, streaming de logs, timeout, concorrência, histórico persistente,
rescan, toggle e a API HTTP do dashboard. O CI (GitHub Actions,
.github\workflows\ci.yml) corre ruff + pytest em Windows (Python 3.9 e 3.12)
a cada push/PR.
To Do
- Tornar a aplicação multi-língua (i18n do dashboard e das mensagens).
- Tornar a aplicação cross-platform (Linux/macOS): abstrair o tray e as notificações, hoje específicos do Windows.
Licença
MIT — ver LICENSE. Copyright © 2026 Bruno Oliveira. Podes usar, copiar, modificar e distribuir livremente, desde que mantenhas o aviso de copyright e a licença; o software é fornecido "tal como está", sem garantias.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bgo_scheduler-1.9.16.tar.gz.
File metadata
- Download URL: bgo_scheduler-1.9.16.tar.gz
- Upload date:
- Size: 85.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
577f57fc6cc2810921bf4549457c2bbe90735a14c8f620822bcae4e6c1835dc7
|
|
| MD5 |
46d3fdad2e9d07574a11a5b93a1d9a95
|
|
| BLAKE2b-256 |
5c3a6c42c88cdd9ecbbac860a0cd21544f3641b696653c71efd06252fc026564
|
Provenance
The following attestation bundles were made for bgo_scheduler-1.9.16.tar.gz:
Publisher:
release.yml on brunogoncalooliveira/bgo_scheduler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bgo_scheduler-1.9.16.tar.gz -
Subject digest:
577f57fc6cc2810921bf4549457c2bbe90735a14c8f620822bcae4e6c1835dc7 - Sigstore transparency entry: 2206722702
- Sigstore integration time:
-
Permalink:
brunogoncalooliveira/bgo_scheduler@33bc9fd7ccf322c2edb3359fc238f507706cef5f -
Branch / Tag:
refs/tags/v1.9.16 - Owner: https://github.com/brunogoncalooliveira
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@33bc9fd7ccf322c2edb3359fc238f507706cef5f -
Trigger Event:
push
-
Statement type:
File details
Details for the file bgo_scheduler-1.9.16-py3-none-any.whl.
File metadata
- Download URL: bgo_scheduler-1.9.16-py3-none-any.whl
- Upload date:
- Size: 61.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43a001ddc4915bb16f2147bfe611a4a4722ce88d3fd53de3548668e99785449b
|
|
| MD5 |
063244d1f4336e9384216d644d1246b0
|
|
| BLAKE2b-256 |
3ca29e372295cf645f52fc356016e845d414582daad3ef0150675c6f0917e499
|
Provenance
The following attestation bundles were made for bgo_scheduler-1.9.16-py3-none-any.whl:
Publisher:
release.yml on brunogoncalooliveira/bgo_scheduler
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bgo_scheduler-1.9.16-py3-none-any.whl -
Subject digest:
43a001ddc4915bb16f2147bfe611a4a4722ce88d3fd53de3548668e99785449b - Sigstore transparency entry: 2206722711
- Sigstore integration time:
-
Permalink:
brunogoncalooliveira/bgo_scheduler@33bc9fd7ccf322c2edb3359fc238f507706cef5f -
Branch / Tag:
refs/tags/v1.9.16 - Owner: https://github.com/brunogoncalooliveira
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@33bc9fd7ccf322c2edb3359fc238f507706cef5f -
Trigger Event:
push
-
Statement type: