{"id":42500539,"url":"https://github.com/omercnet/doh1","last_synced_at":"2026-01-28T13:05:11.202Z","repository":{"id":330818469,"uuid":"1124053961","full_name":"omercnet/doh1","owner":"omercnet","description":null,"archived":false,"fork":false,"pushed_at":"2025-12-28T09:07:37.000Z","size":48,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-30T16:42:36.628Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omercnet.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-12-28T08:15:02.000Z","updated_at":"2025-12-28T09:07:41.000Z","dependencies_parsed_at":"2025-12-31T00:07:56.517Z","dependency_job_id":null,"html_url":"https://github.com/omercnet/doh1","commit_stats":null,"previous_names":["omercnet/doh1"],"tags_count":null,"template":true,"template_full_name":null,"purl":"pkg:github/omercnet/doh1","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fdoh1","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fdoh1/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fdoh1/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fdoh1/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omercnet","download_url":"https://codeload.github.com/omercnet/doh1/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omercnet%2Fdoh1/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28845792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T13:02:32.985Z","status":"ssl_error","status_checked_at":"2026-01-28T13:02:04.945Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-28T13:05:10.502Z","updated_at":"2026-01-28T13:05:11.195Z","avatar_url":"https://github.com/omercnet.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🪖 דו\"ח 1 - IDF Daily Attendance Automation\n\nAutomated daily attendance form submission for IDF reservists using Playwright.\n\n\u003e **📋 This is a template repo!** Click [**Use this template**](https://github.com/omercnet/doh1/generate) to create your own.\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│  🇮🇱                                                   🇮🇱  │\n│   ╔═══╗ ╔═══╗ ╔═╗ ╔═╗   ╔═══╗                              │\n│   ║   ║ ║   ║ ║ ║ ║ ║     ║                                │\n│   ║   ║ ║   ║ ║ ╚═╝ ║     ║                                │\n│   ║   ║ ║   ║ ║ ╔═╗ ║     ║                                │\n│   ╚═══╝ ╚═══╝ ╚═╝ ╚═╝     ║                                │\n│                                                             │\n│   ⭐ Automate your מעקב נוכחות יומי ⭐                      │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n```\n\n## What it does\n\n- 🔐 Logs into miluim.idf.il with Azure AD authentication\n- 📞 Handles MFA via phone call (you approve, it continues)\n- 📝 Submits daily attendance form (דו\"ח 1)\n- ✅ Detects if already submitted today and skips gracefully\n- 💾 Caches auth session to skip MFA on subsequent runs\n- ⏰ Runs on schedule via GitHub Actions\n- 📢 Notifies via Slack on success/failure\n\n## 🛠️ Local Development\n\n### Prerequisites\n\n- [mise](https://mise.jdx.dev/) - Runtime version manager\n- [fnox](https://github.com/jdx/fnox) - Secret management\n\n### Setup\n\n1. **Install dependencies:**\n\n   ```bash\n   mise install\n   pnpm install\n   npx playwright install chromium\n   ```\n\n2. **Configure secrets:**\n\n   ```bash\n   # Generate an age key if you don't have one\n   age-keygen -o ~/.config/fnox/age.txt\n\n   # Copy the PUBLIC key (starts with age1...) for fnox.toml\n   cat ~/.config/fnox/age.txt | grep \"public key\"\n\n   # Create your fnox.toml\n   cp fnox.toml.example fnox.toml\n   # Edit fnox.toml - replace YOUR_AGE_PUBLIC_KEY with your public key\n\n   # Add your secrets\n   fnox set IDF_ID\n   fnox set IDF_PASSWORD\n   ```\n\n3. **Run locally (headed):**\n\n   ```bash\n   fnox run -- pnpm test:headed\n   ```\n\n   When MFA triggers, approve the phone call.\n\n4. **Run headless:**\n   ```bash\n   fnox run -- pnpm test\n   ```\n\n## 🚀 GitHub Actions Setup\n\n### Required Secrets\n\n| Secret               | Description                                      |\n| -------------------- | ------------------------------------------------ |\n| `IDF_ID`             | Your ID number (ת.ז.)                            |\n| `IDF_PASSWORD`       | Your Azure AD password                           |\n| `TS_OAUTH_CLIENT_ID` | Tailscale OAuth client ID                        |\n| `TS_OAUTH_SECRET`    | Tailscale OAuth client secret                    |\n| `TS_EXIT_NODE`       | Tailscale exit node hostname (your home machine) |\n\n### Optional Secrets\n\n| Secret              | Description                                                                                                       |\n| ------------------- | ----------------------------------------------------------------------------------------------------------------- |\n| `AGE_RECIPIENT`     | Age public key (starts with `age1...`) for encrypting failed test artifacts. Get it from `~/.config/fnox/age.txt` |\n| `SLACK_WEBHOOK_URL` | Slack webhook for notifications                                                                                   |\n\n### Tailscale Setup\n\nThe IDF portal blocks access from cloud IPs (Azure AD Conditional Access). CI routes traffic through your home network via Tailscale.\n\n1. **Enable exit node on your home machine:**\n\n   ```bash\n   sudo tailscale up --advertise-exit-node\n   ```\n\n   Then approve it in the [Tailscale admin console](https://login.tailscale.com/admin/machines).\n\n2. **Add ACL tag for CI nodes:**\n   Add to your [Tailscale ACL policy](https://login.tailscale.com/admin/acls):\n\n   ```json\n   \"tagOwners\": {\n     \"tag:ci\": [\"autogroup:admin\"]\n   }\n   ```\n\n3. **Create OAuth client:**\n   - Go to [Tailscale Settings → OAuth clients](https://login.tailscale.com/admin/settings/oauth)\n   - Click \"Generate OAuth client\"\n   - Add `tag:ci` tag\n   - Scopes: `auth_keys` (writable)\n   - Copy the **Client ID** and **Client Secret**\n\n4. **Add GitHub repository secrets:**\n   - Go to repo Settings → Secrets and variables → Actions → Secrets\n   - Add `TS_OAUTH_CLIENT_ID` with the client ID (without `api.tailscale.com/` prefix)\n   - Add `TS_OAUTH_SECRET` with the client secret\n   - Add `TS_EXIT_NODE` with your home machine's Tailscale hostname\n\n\u003e **Note:** The age private key stays local in `~/.config/fnox/age.txt`. Only the public key goes to GitHub for encrypting artifacts you can decrypt locally.\n\n### Schedule\n\nRuns automatically Sunday-Thursday at 9:00 AM Israel time.\n\nTo run manually: Actions → Daily Attendance Form → Run workflow\n\n### First Run\n\nThe first CI run (or after session expires) will require MFA:\n\n1. Trigger the workflow\n2. Wait for the phone call\n3. Approve MFA\n4. Session gets cached for future runs\n\n## 🔧 Troubleshooting\n\n### Decrypting Failed Test Artifacts\n\nIf a run fails, traces are encrypted and uploaded as artifacts.\n\n```bash\n# Download the artifact, then:\nage -d -i ~/.config/fnox/age.txt test-results.tar.gz.age \u003e test-results.tar.gz\ntar -xzf test-results.tar.gz\nnpx playwright show-trace test-results/*/trace.zip\n```\n\n### Force Re-authentication\n\nIf the cached session is stale:\n\n1. Go to Actions → Daily Attendance Form\n2. Run workflow with \"Force new login\" checked\n\n### Common Issues\n\n| Issue                 | Solution                                                   |\n| --------------------- | ---------------------------------------------------------- |\n| MFA timeout           | Be ready to approve the call within 3 minutes              |\n| Session expired       | Force re-login via workflow dispatch                       |\n| Already submitted     | This is fine! Test passes with \"Already submitted today ✓\" |\n| Selectors changed     | Update selectors in `src/form/fill-attendance.ts`          |\n| \"Cannot access\" error | Azure AD blocking cloud IPs - check Tailscale exit node    |\n| Tailscale timeout     | Ensure home machine is online and exit node is approved    |\n\n## 📁 Project Structure\n\n```\nsrc/\n├── config.ts              # Environment config\n├── auth/\n│   └── login.ts           # Azure AD + MFA login flow\n├── form/\n│   └── fill-attendance.ts # Form submission logic\n└── utils/\n    └── notify.ts          # Slack/email notifications\n\ntests/\n└── test-1.spec.ts         # Main test orchestrator\n\n.github/workflows/\n└── playwright.yml         # CI workflow\n```\n\n## 📜 License\n\nISC\n\n---\n\n🎖️ _Made for מילואימניקים by מילואימניקים_ 🎖️\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fdoh1","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomercnet%2Fdoh1","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomercnet%2Fdoh1/lists"}