Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Pages

# Publishes the project site to GitHub Pages: the landing page (site/index.html) at the root
# Publishes the project site to GitHub Pages: the landing page (site/public/) at the root
# and the guides (docs/*.md, CHANGELOG.md) under /docs/, built by site/build.sh with MkDocs.
# Needs Settings → Pages → Source: GitHub Actions.

Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ adb logcat -s MeshAnd MeshAnd/SDK
- **Releases:** `.github/workflows/release.yml` runs on a `v*` tag. It requires the tag to equal `versionName`, signs using the secrets `MESHAND_KEYSTORE_BASE64` and `MESHAND_KEYSTORE_PASSWORD`, and publishes `MeshAnd-<version>.apk` to GitHub Releases.
- **Website:** the project site at https://meshand.app/, published by `.github/workflows/pages.yml` on pushes to `main` that touch `site/`, `docs/` or `CHANGELOG.md`, or by hand (Run workflow). The repo's Settings → Pages → Source must be **GitHub Actions**.
- Domain: `meshand.app`, registered and DNS-hosted on Cloudflare (account "P13 Private", 2026-10-22). Apex `A`/`AAAA` records point to GitHub Pages (`185.199.108–111.153`, `2606:50c0:8000–8003::153`) and `www` is a `CNAME` to `mategogiberidze.github.io`, all **DNS only** (not proxied), so GitHub issues the HTTPS certificate. The custom domain is set in Settings → Pages; with Actions deploys there is no `CNAME` file.
- `/` is `site/index.html`, one hand-written static page. `/docs/` holds the guides: `site/build.sh` copies `docs/*.md`, `CHANGELOG.md` (as `changelog.md`) and `site/guides/` (overview page, logo, CSS) into `build/site-docs/` and renders them with MkDocs Material (`site/mkdocs.yml`, versions pinned in `site/requirements.txt`, `--strict`). The `.md` files stay the only source; don't copy guide text into `site/`.
- `/` is `site/public/`: `index.html` (one hand-written static page), `404.html`, `robots.txt`, `sitemap.xml` (the landing page only; the guides have MkDocs' `/docs/sitemap.xml`, and `robots.txt` lists both) and `apple-touch-icon.png`. `build.sh` adds `favicon.svg` (the logo) and `images/social-preview.png` from `docs/images/`. `/docs/` holds the guides: `site/build.sh` copies `docs/*.md`, `CHANGELOG.md` (as `changelog.md`) and `site/guides/` (overview page, logo, CSS) into `build/site-docs/` and renders them with MkDocs Material (`site/mkdocs.yml`, versions pinned in `site/requirements.txt`, `--strict`). The `.md` files stay the only source; don't copy guide text into `site/`.
- SEO: the landing page has a meta description, Open Graph tags, JSON-LD (`WebSite` + `SoftwareApplication`) and a visible FAQ. Each guide gets its own `<title>` and description from `site/hooks.py` (so `docs/*.md` need no front matter) and Open Graph tags from `site/theme/main.html`. Keep JSON-LD and FAQ answers true to the app; don't add ratings or reviews.
- Material for MkDocs is in maintenance mode and incompatible with MkDocs 2.0, so keep `mkdocs` pinned below 2.0.
- Preview: `pip install -r site/requirements.txt && ./site/build.sh`, then `python3 -m http.server -d build/site`.
- A link between guides on GitHub (`../CHANGELOG.md`) is rewritten by `build.sh`; a new kind of cross-folder link needs the same, or `--strict` fails.
Expand Down
10 changes: 7 additions & 3 deletions site/build.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env bash
# Builds the GitHub Pages site into build/site:
# / the landing page (site/index.html)
# / the landing page and other root files (site/public/), plus the favicon and the
# social preview image from docs/images and the logo
# /docs/ the guides: docs/*.md and CHANGELOG.md rendered by MkDocs Material (site/mkdocs.yml)
# Needs the packages in site/requirements.txt. Used by .github/workflows/pages.yml.
set -euo pipefail
cd "$(dirname "$0")/.."

rm -rf build/site build/site-docs
mkdir -p build/site build/site-docs
mkdir -p build/site/images build/site-docs

# The guides' sources: our overview page and assets, the docs, and the changelog.
cp -R site/guides/. build/site-docs/
Expand All @@ -19,4 +20,7 @@ rm build/site-docs/*.bak

# mkdocs is pinned below 2.0 in requirements.txt, so Material's MkDocs 2.0 warning doesn't apply.
NO_MKDOCS_2_WARNING=true mkdocs build --strict --config-file site/mkdocs.yml --site-dir "$PWD/build/site/docs"
cp site/index.html build/site/

cp -R site/public/. build/site/
cp site/guides/assets/logo.svg build/site/favicon.svg
cp docs/images/social-preview.png build/site/images/
37 changes: 37 additions & 0 deletions site/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""MkDocs hook: search-friendly titles and descriptions for the guides.

They live here rather than as front matter, so the Markdown files in docs/ stay plain on GitHub.
Keys are the file names in build/site-docs (see build.sh).
"""

PAGES = {
"index.md": (
"Guides for Meshtastic teams on OsmAnd",
"Guides for MeshAnd, the Android app that shows your Meshtastic team live on the OsmAnd "
"map: radio setup, release notes and building the app.",
),
"radio-setup.md": (
"Meshtastic radio setup for live team tracking",
"Channel, role and position settings that make Meshtastic teammates move smoothly on the "
"map: a private channel, faster position updates and the Client role.",
),
"changelog.md": (
"Release notes",
"What changed in each version of MeshAnd, the Android app that shows your Meshtastic team "
"on the OsmAnd map.",
),
"development.md": (
"Building MeshAnd from source",
"Build MeshAnd with Android Studio, see how the code is organised, read the logs, publish "
"a release and debug Bluetooth.",
),
}


def on_page_markdown(markdown, page, **kwargs):
title, description = PAGES.get(page.file.src_uri, (None, None))
if title:
page.meta.setdefault("title", title)
if description:
page.meta.setdefault("description", description)
return markdown
9 changes: 7 additions & 2 deletions site/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# MkDocs Material config for the guides under /docs/ on the project site.
# Don't run mkdocs directly: site/build.sh gathers docs/*.md and CHANGELOG.md into
# build/site-docs first and puts the result next to the landing page.
# hooks.py sets each page's <title> and description; theme/main.html adds social-preview tags.

site_name: MeshAnd guides
site_description: Guides for MeshAnd, the Android app that shows your Meshtastic team on the OsmAnd map.
site_name: MeshAnd
site_description: Guides for MeshAnd, the Android app that shows your Meshtastic team live on the OsmAnd map.
site_url: https://meshand.app/docs/
repo_url: https://github.com/mategogiberidze/MeshAnd
repo_name: mategogiberidze/MeshAnd
Expand All @@ -20,6 +21,7 @@ nav:

theme:
name: material
custom_dir: theme
logo: assets/logo.svg
favicon: assets/logo.svg
# System fonts, as on the landing page: no requests to Google Fonts.
Expand Down Expand Up @@ -57,6 +59,9 @@ extra:
link: https://github.com/mategogiberidze/MeshAnd
name: MeshAnd on GitHub

hooks:
- hooks.py

extra_css:
- assets/extra.css

Expand Down
41 changes: 41 additions & 0 deletions site/public/404.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page not found · MeshAnd</title>
<meta name="robots" content="noindex">
<meta name="theme-color" content="#1F5A47">
<meta name="color-scheme" content="light dark">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<style>
:root { --bg: #1F5A47; --text: #F2F4F3; --muted: #C9DDD4; --ember: #F07B4F; }
@media (prefers-color-scheme: dark) { :root { --bg: #133A2E; --muted: #B7CEC4; } }
* { box-sizing: border-box; }
body {
margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 32px 16px;
background: var(--bg); color: var(--text); text-align: center;
font: 17px/1.6 ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
img { width: 72px; height: 72px; border-radius: 16px; box-shadow: 0 0 0 1px rgb(255 255 255 / .14); }
h1 { font-size: clamp(1.8rem, 1.3rem + 2vw, 2.6rem); letter-spacing: -.03em; margin: 24px 0 8px; }
p { color: var(--muted); margin: 0 auto; max-width: 28em; }
nav { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 28px; }
a { display: inline-flex; align-items: center; min-height: 44px; padding: 10px 20px; border-radius: 999px; font-weight: 700; text-decoration: none; }
a.primary { background: var(--ember); color: #2A1208; }
a.ghost { color: #fff; box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / .35); }
a:focus-visible { outline: 3px solid var(--ember); outline-offset: 3px; }
</style>
</head>
<body>
<main>
<img src="/favicon.svg" alt="">
<h1>This page is off the map</h1>
<p>The page you were looking for doesn't exist or has moved.</p>
<nav>
<a class="primary" href="/">MeshAnd home</a>
<a class="ghost" href="/docs/">Guides</a>
</nav>
</main>
</body>
</html>
Binary file added site/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 106 additions & 7 deletions site/index.html → site/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,63 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>MeshAnd: your Meshtastic team on the OsmAnd map</title>
<meta name="description" content="MeshAnd is a free Android app that connects to your Meshtastic radio over Bluetooth and shows your whole team live on the OsmAnd map. No internet or mobile coverage needed.">
<title>MeshAnd: your Meshtastic team live on the OsmAnd map</title>
<meta name="description" content="Free, open-source Android app: connect your Meshtastic radio over Bluetooth and see your whole team live on the OsmAnd map. Works offline, no coverage needed.">
<meta name="theme-color" content="#1F5A47">
<meta name="color-scheme" content="light dark">
<link rel="canonical" href="https://meshand.app/">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='18 18 72 72'%3E%3Crect x='18' y='18' width='72' height='72' rx='14' fill='%231F5A47'/%3E%3Cpath d='M35 73L54 55L73 73Z' fill='none' stroke='%23fff' stroke-width='5' stroke-linejoin='round'/%3E%3Cpath d='M54 55L45.3 43.7A11 11 0 1 1 62.7 43.7ZM49.5 37a4.5 4.5 0 1 0 9 0a4.5 4.5 0 1 0-9 0Z' fill='%23F07B4F' fill-rule='evenodd'/%3E%3Ccircle cx='35' cy='73' r='5.5' fill='%23fff'/%3E%3Ccircle cx='73' cy='73' r='5.5' fill='%23fff'/%3E%3C/svg%3E">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<meta property="og:type" content="website">
<meta property="og:title" content="MeshAnd: your Meshtastic team on the OsmAnd map">
<meta property="og:site_name" content="MeshAnd">
<meta property="og:locale" content="en_GB">
<meta property="og:title" content="MeshAnd: your Meshtastic team live on the OsmAnd map">
<meta property="og:description" content="Free Android app: see everyone in your Meshtastic mesh live on the OsmAnd map. No internet or mobile coverage needed.">
<meta property="og:url" content="https://meshand.app/">
<meta property="og:image" content="https://raw.githubusercontent.com/mategogiberidze/MeshAnd/main/docs/images/social-preview.png">
<meta property="og:image" content="https://meshand.app/images/social-preview.png">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="640">
<meta property="og:image:alt" content="The MeshAnd logo next to a phone showing teammates as coloured dots on a topographic map.">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://meshand.app/#website",
"name": "MeshAnd",
"url": "https://meshand.app/",
"inLanguage": "en"
},
{
"@type": "SoftwareApplication",
"@id": "https://meshand.app/#app",
"name": "MeshAnd",
"url": "https://meshand.app/",
"description": "Free, open-source Android app that connects to your Meshtastic radio over Bluetooth and shows your whole team live on the OsmAnd map, without internet or mobile coverage.",
"applicationCategory": "TravelApplication",
"operatingSystem": "Android 7.0 or newer",
"image": "https://meshand.app/images/social-preview.png",
"downloadUrl": "https://github.com/mategogiberidze/MeshAnd/releases/latest",
"releaseNotes": "https://meshand.app/docs/changelog/",
"softwareHelp": "https://meshand.app/docs/",
"license": "https://www.gnu.org/licenses/gpl-3.0.html",
"isAccessibleForFree": true,
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "USD" },
"featureList": [
"Teammates on the OsmAnd map, updated live",
"Team list inside OsmAnd, nearest first, with navigation",
"Trails of the last 30 minutes to 6 hours",
"Shared map pins",
"GPS health check",
"Alerts when a teammate hasn't been heard"
],
"sameAs": ["https://github.com/mategogiberidze/MeshAnd"]
}
]
}
</script>
<style>
:root {
--pine: #1F5A47;
Expand Down Expand Up @@ -202,6 +245,15 @@
.know div { border-top: 2px solid var(--line); padding-top: 16px; }
.know p { color: var(--muted); margin-top: 6px; font-size: .97rem; }

/* FAQ */
.faq { margin-top: 40px; max-width: 820px; display: grid; gap: 10px; }
.faq details { background: var(--surface); border: 1px solid var(--line); border-radius: 14px; }
.faq summary { cursor: pointer; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 16px; padding: 16px 20px; font-weight: 700; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; flex: none; font-size: 1.4rem; font-weight: 400; line-height: 1; color: var(--accent); }
.faq details[open] summary::after { content: "\2212"; }
.faq details p { padding: 0 20px 18px; color: var(--muted); }

/* Links */
.links { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; margin-top: 40px; }
.links a { display: block; text-decoration: none; background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 18px 20px; color: var(--text); transition: border-color .15s ease, transform .15s ease; }
Expand Down Expand Up @@ -532,14 +584,61 @@ <h2>Before your first trip</h2>
</div>
</section>

<section id="docs" class="alt">
<section id="faq" class="alt">
<div class="wrap">
<p class="eyebrow">Questions</p>
<h2>Frequently asked</h2>
<div class="faq">
<details>
<summary>Does MeshAnd need internet or mobile coverage?</summary>
<p>No. Positions travel between the radios over LoRa, and your phone talks to your own
radio over Bluetooth. OsmAnd works offline once you've downloaded the maps for your
region. MeshAnd's only internet use is its update check, which you can switch off.</p>
</details>
<details>
<summary>Which Meshtastic radios work with MeshAnd?</summary>
<p>MeshAnd was built and tested with the LILYGO T-Beam Supreme. Other Meshtastic radios
with Bluetooth and GPS should work the same way, including ones you build yourself.
Radios without GPS still appear, but have no position to put on the map.</p>
</details>
<details>
<summary>Do I need OsmAnd Pro?</summary>
<p>No. The free OsmAnd from Google Play is enough, and OsmAnd Pro works the same way. The
first time, allow MeshAnd in <b>OsmAnd → Menu → Plugins</b>.</p>
</details>
<details>
<summary>Do I still need the Meshtastic app?</summary>
<p>Only to set up the radios: region, a private channel, role and position settings.
MeshAnd never changes your radio's settings. Day to day, MeshAnd is the app connected
to your radio, and a radio talks to only one app at a time.</p>
</details>
<details>
<summary>How often do teammates move on the map?</summary>
<p>As often as their radios send a position. With the default settings that's only about
once an hour; with the settings in the <a href="docs/radio-setup/">radio setup guide</a>,
a walking teammate updates about every minute.</p>
</details>
<details>
<summary>Is there an iPhone version?</summary>
<p>No. Only OsmAnd for Android lets other apps draw on its map. Teammates with an iPhone
can use the Meshtastic iOS app, and their radios still show up in MeshAnd.</p>
</details>
<details>
<summary>Is MeshAnd free?</summary>
<p>Yes. It's free software under the GPL-3.0, with no account and no server. The source
code is on <a href="https://github.com/mategogiberidze/MeshAnd">GitHub</a>.</p>
</details>
</div>
</div>
</section>

<section id="docs">
<div class="wrap">
<p class="eyebrow">Guides and source</p>
<h2>Learn more</h2>
<div class="links">
<a href="docs/radio-setup/"><b>Radio setup guide</b><span>Channel, role and position settings for a moving team.</span></a>
<a href="docs/changelog/"><b>What's new</b><span>Changes in every version, written for app users.</span></a>
<a href="docs/"><b>All guides</b><span>Radio setup, what's new and developing, in one place.</span></a>
<a href="https://github.com/mategogiberidze/MeshAnd/releases"><b>All releases</b><span>Every version as a ready-to-install APK.</span></a>
<a href="https://github.com/mategogiberidze/MeshAnd"><b>Source code</b><span>The full app on GitHub. Pull requests welcome.</span></a>
<a href="docs/development/"><b>Developer guide</b><span>Building it yourself and how the code is organised.</span></a>
Expand Down
5 changes: 5 additions & 0 deletions site/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
User-agent: *
Allow: /

Sitemap: https://meshand.app/sitemap.xml
Sitemap: https://meshand.app/docs/sitemap.xml
7 changes: 7 additions & 0 deletions site/public/sitemap.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- The landing page. The guides have their own sitemap at /docs/sitemap.xml (made by MkDocs). -->
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://meshand.app/</loc>
</url>
</urlset>
19 changes: 19 additions & 0 deletions site/theme/main.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{#- Adds social-preview tags to every guide page. Titles and descriptions come from hooks.py. -#}
{% extends "base.html" %}

{% block extrahead %}
{%- set og_title = (page.meta.title if page.meta and page.meta.title else page.title) | striptags -%}
{%- set og_description = page.meta.description if page.meta and page.meta.description else config.site_description -%}
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<meta property="og:type" content="article">
<meta property="og:site_name" content="MeshAnd">
<meta property="og:locale" content="en_GB">
<meta property="og:title" content="{{ og_title }} - {{ config.site_name }}">
<meta property="og:description" content="{{ og_description }}">
<meta property="og:url" content="{{ page.canonical_url }}">
<meta property="og:image" content="https://meshand.app/images/social-preview.png">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="640">
<meta property="og:image:alt" content="The MeshAnd logo next to a phone showing teammates as coloured dots on a topographic map.">
<meta name="twitter:card" content="summary_large_image">
{% endblock %}
Loading