Skip to content
Open
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
85 changes: 85 additions & 0 deletions .github/workflows/build-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Nightly Build & Log

# Builds the signed nightly APK on a schedule and records the result in the
# website build log (website/data/nightly_builds.json). Committing that file to
# main touches website/**, which triggers deploy-website.yml to refresh the
# published site. This complements the push-triggered F-Droid deploy
# (nightlydepolyci.yml); the two can be unified later.

on:
schedule:
- cron: '0 2 * * *' # 02:00 UTC daily
workflow_dispatch:

permissions:
contents: write # commit the updated build log back to main

jobs:
nightly:
name: Build nightly APK and record build log
runs-on: ubuntu-latest
steps:
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
tool-cache: false
android: false # keep Android SDKs for Flutter
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17.x'

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.29.2'

- name: Get dependencies
run: flutter pub get

- name: Decode signing secrets
env:
NIGHTLY_KEYSTORE_B64: ${{ secrets.NIGHTLY_KEYSTORE_B64 }}
NIGHTLY_PROPERTIES_B64: ${{ secrets.NIGHTLY_PROPERTIES_B64 }}
run: |
echo "$NIGHTLY_KEYSTORE_B64" | base64 --decode > android/nightly.jks
echo "$NIGHTLY_PROPERTIES_B64" | base64 --decode > android/key_nightly.properties

- name: Build nightly APK
id: build
run: flutter build apk --flavor nightly --build-number=${{ github.run_number }} --release

- name: Record successful build
if: success()
run: |
python3 scripts/update_build_log.py success \
"https://github.com/${{ github.repository }}/raw/fdroid-repo/repo/nightly.${{ github.run_number }}.apk" \
"${{ github.run_number }}"

- name: Record failed build
if: failure()
run: python3 scripts/update_build_log.py failure "" "${{ github.run_number }}"

- name: Commit build log
if: always()
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
if git diff --quiet -- website/data/nightly_builds.json; then
echo "No build-log changes to commit."
else
git add website/data/nightly_builds.json
git commit -m "chore(nightly): record build ${{ github.run_number }}"
git push
fi
64 changes: 64 additions & 0 deletions .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Deploy Website

# Builds the Hugo site in website/ and publishes it to GitHub Pages.
# Triggered when the site content or this workflow changes (the nightly
# build-log commit made by build-nightly.yml touches website/data/, so a new
# nightly automatically refreshes the published site).

on:
push:
branches: [main]
paths:
- 'website/**'
- '.github/workflows/deploy-website.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment; don't cancel an in-progress production deploy.
concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
env:
HUGO_VERSION: 0.164.0
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # hugo.toml enableGitInfo needs full history

- name: Setup Hugo
uses: peaceiris/actions-hugo@v3
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true

- name: Configure Pages
id: pages
uses: actions/configure-pages@v5

- name: Build site
run: hugo --source website --minify --baseURL "${{ steps.pages.outputs.base_url }}/"

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: website/public

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
7 changes: 7 additions & 0 deletions .github/workflows/nightlydepolyci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ on:
push:
branches:
- main
# Website content and the nightly build-log commit must not trigger a full
# APK rebuild + F-Droid redeploy (see build-nightly.yml / deploy-website.yml).
paths-ignore:
- 'website/**'
- 'scripts/**'
- '.github/workflows/deploy-website.yml'
- '.github/workflows/build-nightly.yml'

jobs:
build-and-deploy:
Expand Down
61 changes: 61 additions & 0 deletions scripts/update_build_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env python3
"""Record a nightly build in the website's build-log data file.

Prepends an entry describing the current commit to
``website/data/nightly_builds.json`` (newest first) and keeps only the most
recent ``MAX_ENTRIES``. The Hugo ``nightly-builds`` shortcode renders this file.

Run from the repository root (as the CI workflow does):

python3 scripts/update_build_log.py <status> <artifact_url> <build_number>

All arguments are optional:
status "success" (default) or "failure"
artifact_url link to the built APK ("" if none)
build_number CI run number ("" if unknown)
"""

import json
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path

LOG = Path("website/data/nightly_builds.json")
MAX_ENTRIES = 90


def _git(*args: str) -> str:
return subprocess.check_output(["git", *args]).decode().strip()


def main(status: str = "success", artifact: str = "", build_number: str = "") -> None:
entry = {
"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"sha": _git("rev-parse", "HEAD")[:8],
"msg": _git("log", "-1", "--pretty=%s"),
"status": status,
"artifact": artifact,
"build": build_number,
}

entries = []
if LOG.exists():
try:
entries = json.loads(LOG.read_text() or "[]")
except json.JSONDecodeError:
entries = []

entries.insert(0, entry)
entries = entries[:MAX_ENTRIES]

LOG.parent.mkdir(parents=True, exist_ok=True)
LOG.write_text(json.dumps(entries, indent=2) + "\n")
print(
f"Recorded nightly build {entry['sha']} ({status}); "
f"{len(entries)} entr{'y' if len(entries) == 1 else 'ies'} in {LOG}."
)


if __name__ == "__main__":
main(*sys.argv[1:])
4 changes: 4 additions & 0 deletions website/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Hugo build output and caches
/public/
/resources/
.hugo_build.lock
74 changes: 74 additions & 0 deletions website/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# TaskWarrior Mobile — Project Website

A self-contained [Hugo](https://gohugo.io) static site for the TaskWarrior Mobile
app: landing page, downloads (with an auto-updated **nightly build log**), and a
documentation section. It has **no external theme** — all layouts live in
`layouts/`, so it builds from the single Hugo binary with no submodules or network
fetches.

## Local development

Requires Hugo **extended** (pinned to the version in
[`.github/workflows/deploy-website.yml`](../.github/workflows/deploy-website.yml)):

```bash
# from this directory
hugo server # live-reload dev server at http://localhost:1313
hugo --minify # production build into ./public
```

## Layout

```
website/
├── hugo.toml # site config (baseURL, menus, params)
├── content/ # Markdown pages (_index, downloads, docs)
├── layouts/ # self-contained templates (no theme)
│ ├── _default/ # baseof / single / list
│ ├── index.html # homepage
│ └── shortcodes/
│ └── nightly-builds.html # renders data/nightly_builds.json
├── data/
│ └── nightly_builds.json # build log (populated by CI; ships empty)
└── static/
├── css/style.css
└── CNAME # custom domain
```

## Nightly build log

[`scripts/update_build_log.py`](../scripts/update_build_log.py) prepends an entry
`{ts, sha, msg, status, artifact, build}` to `data/nightly_builds.json` (keeping the
90 most recent). The `{{</* nightly-builds */>}}` shortcode on the downloads page
renders it. The file ships as `[]`; CI fills it in.

## CI

- **`build-nightly.yml`** — daily at 02:00 UTC (and on demand): builds the signed
nightly APK, records the result via `update_build_log.py`, and commits the updated
log to `main`.
- **`deploy-website.yml`** — on any push touching `website/**` (including the nightly
log commit): builds the site with Hugo and deploys it to GitHub Pages.
- `nightlydepolyci.yml` now ignores `website/**` and `scripts/**` so a build-log
commit does not trigger a redundant APK rebuild.

## One-time infrastructure setup (needs repo-admin / DNS access)

These cannot be done from code and must be configured by a maintainer:

1. **Enable GitHub Pages** → repo *Settings → Pages → Build and deployment →
Source: **GitHub Actions***.
2. **Custom domain / DNS** — point `taskwarrior.ccextractor.org` at GitHub Pages
with a `CNAME` DNS record to `<org-or-user>.github.io`. The site already ships a
`static/CNAME`, so Pages will pick up the domain on first deploy.
3. **Branch protection** — if `main` is protected, allow `github-actions[bot]` to
push the nightly build-log commit (or point that commit at an unprotected branch).
4. The nightly signing secrets (`NIGHTLY_KEYSTORE_B64`, `NIGHTLY_PROPERTIES_B64`) are
the same ones the existing F-Droid workflow already uses.

> **Note on the deploy target.** The proposal described renaming the `fdroid-repo`
> branch to `public-site` and serving from a branch. This implementation instead uses
> the modern **GitHub Actions Pages deployment** (`actions/deploy-pages`), which keeps
> the site source in `website/` on `main` and needs no dedicated deploy branch — fewer
> moving parts and no history juggling. The F-Droid APK repo on `fdroid-repo` is
> untouched.
12 changes: 12 additions & 0 deletions website/content/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: "TaskWarrior Mobile"
---

**TaskWarrior Mobile** is the cross-platform [Flutter](https://flutter.dev) client for
[TaskWarrior](https://taskwarrior.org), maintained by [CCExtractor](https://ccextractor.org).
It brings TaskWarrior's fast, unobtrusive task management to Android, iOS, Windows,
macOS, and Linux, backed by [TaskChampion](https://github.com/GothenburgBitFactory/taskchampion)
for native, offline-first sync.

New here? Head to the [downloads page](downloads/) to install the latest build, or browse
the [nightly build history](downloads/#nightly-builds) to see what has changed recently.
26 changes: 26 additions & 0 deletions website/content/docs/_index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: "Documentation"
description: "Developer and user documentation for TaskWarrior Mobile."
---

Documentation for TaskWarrior Mobile. This section is a living skeleton that grows
alongside the app.

## Architecture at a glance

- **UI** — Flutter (Dart) with GetX for state management and routing.
- **Core** — a Rust FFI bridge (`tc_helper`, via `flutter_rust_bridge`) that wraps
[TaskChampion](https://github.com/GothenburgBitFactory/taskchampion) for task storage.
- **Sync** — offline-first: changes hit a local SQLite/TaskChampion replica immediately
and synchronise with a TaskChampion sync server when connectivity is available.

## Getting the app

See the [downloads page](../downloads/) for stable releases and nightly builds.

## Contributing

Contributions are welcome. Start with the
[CONTRIBUTING guide](https://github.com/CCExtractor/taskwarrior-flutter/blob/main/CONTRIBUTING.md)
in the repository, and join the [CCExtractor community](https://ccextractor.org/) on Slack
or Zulip.
20 changes: 20 additions & 0 deletions website/content/downloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: "Downloads"
description: "Install TaskWarrior Mobile — stable releases and automatically-built nightly APKs."
---

## Stable releases

Tagged releases are published on GitHub. Download the latest signed APK from the
[releases page](https://github.com/CCExtractor/taskwarrior-flutter/releases).

## Nightly builds {#nightly-builds}

A signed nightly APK is built automatically from the `main` branch every night and
published to the project's F-Droid repository. Each entry below links to the APK produced
by that build, newest first.

{{< nightly-builds limit="20" >}}

_Nightly builds are unstable snapshots intended for testing. For everyday use, prefer a
stable release above._
1 change: 1 addition & 0 deletions website/data/nightly_builds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[]
Loading
Loading