This document explains how Mau stores and structures data on your filesystem, how JSON-LD works with Schema.org vocabulary, and how files are organized for peer-to-peer synchronization.
Mau adopts the Unix philosophy: everything is a file. Every piece of content you create—a status update, a blog post, a photo, a comment—is stored as a file on your filesystem.
Benefits of file-based storage:
tar your directoryrm a file, it’s gone┌─────────────────────────────────────────────────────────┐
│ Your Filesystem │
│ ┌────────────────────────────────────────────────┐ │
│ │ Mau Directory (e.g., ~/.mau/) │ │
│ │ ├── 5D000B2F.../ (your public key FPR) │ │
│ │ │ ├── hello-world.json.pgp │ │
│ │ │ ├── recipe.json.pgp │ │
│ │ │ └── photo-2026.json.pgp │ │
│ │ ├── A1234567.../ (friend's FPR) │ │
│ │ │ ├── comment.json.pgp │ │
│ │ │ └── like.json.pgp │ │
│ │ └── .mau/ (metadata) │ │
│ │ ├── A1234567....pgp (friend's public key)│ │
│ │ └── config.json │ │
│ └────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Files are:
.pgp extension)Mau stores all data in a single root directory (commonly ~/.mau/):
~/.mau/
├── <your-fingerprint>/ # Your content
├── <friend1-fingerprint>/ # Friend's synced content
├── <friend2-fingerprint>/ # Another friend's content
├── .<blocked-fingerprint>/ # Blocked user (hidden, not synced)
└── .mau/ # Mau metadata directory
Each user (you and your contacts) has a directory named by their PGP key fingerprint (40-character hex, lowercase):
5d000b2f2c040a1675b49d7f0c7cb7dc36999d56/
├── hello-world.json.pgp
├── recipe-pasta.json.pgp
├── recipe-pasta.json.pgp.versions/
│ ├── a7f3b9... (old version)
│ └── d2c1e8... (older version)
├── photo-vacation.json.pgp
└── draft-blog.json.pgp
Key points:
.pgp extension.versions/ subdirectoriesPrefix a directory with . to block a user:
.a1234567890abcdef1234567890abcdef12345678/
.mau/) ¶The .mau/ directory stores system metadata:
.mau/
├── <friend1-fingerprint>.pgp # Friend's public key
├── <friend2-fingerprint>.pgp # Another friend's public key
├── config.json # Local configuration
├── peers.json # Known peer addresses
└── sync-state.json # Sync timestamps
Contents:
.pgp files)In Mau, the filename IS the content ID, including the .pgp extension:
hello-world.json.pgp
└── Content ID: "hello-world.json.pgp"
The .pgp extension is required everywhere:
hello-world.json.pgphello-world.json.pgp (includes .pgp)/p2p/<fingerprint>/hello-world.json.pgp/p2p/<fingerprint>/hello-world.json.pgpWhen you call AddFile(reader, "hello.txt", recipients), Mau automatically appends .pgp if not present, storing it as hello.txt.pgp. All references to the file must include the .pgp extension.
Rules:
.pgp)A content’s full address combines the user fingerprint and filename (with .pgp):
/p2p/<user-fingerprint>/<filename.pgp>
Examples:
/p2p/5d000b2f2c040a1675b49d7f0c7cb7dc36999d56/hello-world.json.pgp
/p2p/5d000b2f2c040a1675b49d7f0c7cb7dc36999d56/recipe-pasta.json.pgp
Good filenames (before .pgp is added automatically):
hello-world.json → stored as hello-world.json.pgp
post-2026-03-08.json → stored as post-2026-03-08.json.pgp
recipe-pasta.json → stored as recipe-pasta.json.pgp
photo-vacation.json → stored as photo-vacation.json.pgp
If you include .pgp manually:
hello-world.json.pgp → stored as hello-world.json.pgp (no double .pgp)
Mau’s AddFile() checks if the name ends with .pgp and adds it only if missing. Either way, the final filename always includes .pgp.
Avoid:
/, \, *, ?, etc.)Tip: Use descriptive names. They act as both IDs and human-readable labels.
Mau uses JSON-LD (JSON for Linked Data) to structure content. JSON-LD extends JSON with:
SocialMediaPosting)Every Mau file (before encryption) looks like this:
1{
2 "@context": "https://schema.org",
3 "@type": "SocialMediaPosting",
4 "headline": "Hello, decentralized world!",
5 "author": {
6 "@type": "Person",
7 "name": "Alice",
8 "identifier": "5d000b2f2c040a1675b49d7f0c7cb7dc36999d56"
9 },
10 "datePublished": "2026-03-08T07:00:00Z",
11 "text": "This is my first Mau post!"
12}
Required fields:
@context - Usually "https://schema.org"@type - Schema.org type (see below)Common fields:
datePublished - ISO 8601 timestampauthor - Person object (usually with fingerprint)headline / text / name - Content textSchema.org provides a shared vocabulary for structured content. Here are common types for social applications:
SocialMediaPosting - Status updates, tweets, posts
1{
2 "@context": "https://schema.org",
3 "@type": "SocialMediaPosting",
4 "headline": "Quick update",
5 "text": "Just implemented a cool feature!",
6 "datePublished": "2026-03-08T10:30:00Z"
7}
Comment - Comments on content
1{
2 "@context": "https://schema.org",
3 "@type": "Comment",
4 "text": "Great post!",
5 "dateCreated": "2026-03-08T10:45:00Z",
6 "parentItem": {
7 "@type": "SocialMediaPosting",
8 "@id": "/p2p/5d000b.../hello-world.json.pgp"
9 }
10}
Message - Private messages
1{
2 "@context": "https://schema.org",
3 "@type": "Message",
4 "text": "Hey, want to grab coffee?",
5 "dateReceived": "2026-03-08T11:00:00Z",
6 "sender": { "@type": "Person", "identifier": "a12345..." },
7 "recipient": { "@type": "Person", "identifier": "5d000b..." }
8}
Article - Blog posts, long-form content
1{
2 "@context": "https://schema.org",
3 "@type": "Article",
4 "headline": "Building P2P Apps with Mau",
5 "articleBody": "In this post, I'll show you...",
6 "author": { "@type": "Person", "name": "Alice" },
7 "datePublished": "2026-03-08T12:00:00Z"
8}
ImageObject - Photos, images
1{
2 "@context": "https://schema.org",
3 "@type": "ImageObject",
4 "name": "Sunset at the beach",
5 "contentUrl": "file:///home/user/.mau/photos/sunset.jpg",
6 "encodingFormat": "image/jpeg",
7 "dateCreated": "2026-03-07T18:30:00Z"
8}
VideoObject - Videos
1{
2 "@context": "https://schema.org",
3 "@type": "VideoObject",
4 "name": "Mau Tutorial",
5 "contentUrl": "file:///home/user/.mau/videos/tutorial.mp4",
6 "duration": "PT10M30S",
7 "uploadDate": "2026-03-08T09:00:00Z"
8}
Recipe - Cooking recipes
1{
2 "@context": "https://schema.org",
3 "@type": "Recipe",
4 "name": "Pasta Carbonara",
5 "recipeIngredient": ["200g pasta", "100g guanciale", "2 eggs", "Pecorino"],
6 "recipeInstructions": "1. Boil pasta. 2. Fry guanciale...",
7 "totalTime": "PT30M"
8}
LikeAction - Likes, favorites
1{
2 "@context": "https://schema.org",
3 "@type": "LikeAction",
4 "agent": { "@type": "Person", "identifier": "5d000b..." },
5 "object": {
6 "@type": "SocialMediaPosting",
7 "@id": "/p2p/a12345.../post.json.pgp"
8 },
9 "startTime": "2026-03-08T13:00:00Z"
10}
FollowAction - Following users
1{
2 "@context": "https://schema.org",
3 "@type": "FollowAction",
4 "agent": { "@type": "Person", "identifier": "5d000b..." },
5 "object": { "@type": "Person", "identifier": "a12345..." },
6 "startTime": "2026-03-08T14:00:00Z"
7}
ShareAction - Sharing/reposting content
1{
2 "@context": "https://schema.org",
3 "@type": "ShareAction",
4 "agent": { "@type": "Person", "identifier": "5d000b..." },
5 "object": {
6 "@type": "Article",
7 "@id": "/p2p/a12345.../article.json.pgp"
8 },
9 "startTime": "2026-03-08T15:00:00Z"
10}
JSON-LD supports multiple languages:
1{
2 "@context": "https://schema.org",
3 "@type": "Article",
4 "headline": {
5 "@value": "Hello World",
6 "@language": "en"
7 },
8 "alternativeHeadline": [
9 { "@value": "مرحبا بالعالم", "@language": "ar" },
10 { "@value": "Hola Mundo", "@language": "es" }
11 ]
12}
You can add custom properties or use other vocabularies:
1{
2 "@context": [
3 "https://schema.org",
4 {
5 "mau": "https://mau-network.org/vocab/",
6 "replyCount": "mau:replyCount",
7 "visibility": "mau:visibility"
8 }
9 ],
10 "@type": "SocialMediaPosting",
11 "headline": "Custom fields example",
12 "mau:replyCount": 5,
13 "mau:visibility": "friends-only"
14}
Guidelines:
Validation:
Libraries:
github.com/piprate/json-goldjsonld.jsPyLDWhen you edit a file, Mau keeps the old version. This enables:
Old versions are stored in a .versions/ subdirectory:
recipe-pasta.json.pgp
recipe-pasta.json.pgp.versions/
├── a7f3b9e2d1c4f8a9b3e7d2c5f1a8b4e9d3c7f2a6 # SHA-256 hash
├── d2c1e8f5a3b9d7c2e6f1a4b8d3c9f7e2a5b1d6 # Older version
└── f1a8b4e9d3c7f2a6b5e1d9c3f8a2b6e4d1c5 # Oldest version
Version filename: SHA-256 hash of the file contents (hex, lowercase)
Full version address:
/p2p/<user-fingerprint>/<filename>.versions/<version-hash>
Example:
/p2p/5d000b.../recipe-pasta.json.versions/a7f3b9e2d1c4f8a9b3e7d2c5f1a8b4e9d3c7f2a6
When you update a file:
.versions/<hash>Example (pseudocode):
1// Update recipe-pasta.json.pgp
2currentContent := readFile("recipe-pasta.json.pgp")
3hash := sha256(currentContent)
4mkdir("recipe-pasta.json.pgp.versions")
5move("recipe-pasta.json.pgp", "recipe-pasta.json.pgp.versions/" + hash)
6writeFile("recipe-pasta.json.pgp", newContent)
Include version references in the current file:
1{
2 "@context": "https://schema.org",
3 "@type": "Recipe",
4 "name": "Pasta Carbonara (updated)",
5 "version": "2",
6 "previousVersion": {
7 "@id": "/p2p/5d000b.../recipe-pasta.json.versions/a7f3b9..."
8 },
9 "dateModified": "2026-03-08T16:00:00Z"
10}
Old versions accumulate. Consider:
Cleanup script example:
1# Keep only the 5 most recent versions
2cd recipe-pasta.json.pgp.versions/
3ls -t | tail -n +6 | xargs rm
Every file is encrypted with PGP before storage:
Public content (encrypted for yourself, readable by anyone):
1gpg --encrypt --recipient 5D000B2F... --output hello-world.json.pgp hello-world.json
Private content (encrypted for yourself only):
1gpg --encrypt --recipient 5D000B2F... --output draft.json.pgp draft.json
Shared content (encrypted for multiple recipients):
1gpg --encrypt \
2 --recipient 5D000B2F... \ # Yourself
3 --recipient A1234567... \ # Friend 1
4 --recipient B9876543... \ # Friend 2
5 --output message.json.pgp message.json
All files should be signed to prevent tampering:
1gpg --sign --encrypt --recipient 5D000B2F... --output post.json.pgp post.json
Benefits:
Decrypt and verify:
1gpg --decrypt --output post.json post.json.pgp
If verification fails, the file has been tampered with.
Encryption determines visibility:
| Encrypted For | Visibility | Use Case |
|---|---|---|
| Yourself only | Private | Drafts, personal notes |
| Yourself + friends | Friends-only | Private messages, shared photos |
| Anyone (public key available) | Public | Blog posts, status updates |
Note: “Public” content is still encrypted—but anyone can request it via the HTTP API (see 07-http-api.md).
Go example:
1import (
2 "github.com/mau-network/mau"
3 "encoding/json"
4)
5
6// Read a file
7content, err := mau.ReadFile("5d000b.../hello-world.json.pgp")
8if err != nil {
9 log.Fatal(err)
10}
11
12// Decrypt (automatic if you have the key)
13decrypted, err := mau.Decrypt(content)
14if err != nil {
15 log.Fatal(err)
16}
17
18// Parse JSON-LD
19var post map[string]interface{}
20json.Unmarshal(decrypted, &post)
21fmt.Println(post["headline"]) // "Hello, decentralized world!"
Go example:
1import (
2 "github.com/mau-network/mau"
3 "encoding/json"
4)
5
6// Create content
7post := map[string]interface{}{
8 "@context": "https://schema.org",
9 "@type": "SocialMediaPosting",
10 "headline": "New post",
11 "datePublished": time.Now().Format(time.RFC3339),
12}
13
14// Serialize
15data, _ := json.Marshal(post)
16
17// Encrypt and sign
18encrypted, err := mau.Encrypt(data, "5d000b...")
19if err != nil {
20 log.Fatal(err)
21}
22
23// Write to file
24mau.WriteFile("5d000b.../new-post.json.pgp", encrypted)
Bash example:
1# List all your files
2ls ~/.mau/5d000b2f2c040a1675b49d7f0c7cb7dc36999d56/
3
4# List friend's files
5ls ~/.mau/a1234567890abcdef1234567890abcdef12345678/
6
7# Count posts
8ls ~/.mau/5d000b.../*.pgp | wc -l
Go example:
1files, err := mau.ListFiles("5d000b...")
2for _, file := range files {
3 fmt.Println(file.Name, file.Size, file.Modified)
4}
Bash:
1# Delete a file (and its versions)
2rm ~/.mau/5d000b.../old-post.json.pgp
3rm -rf ~/.mau/5d000b.../old-post.json.pgp.versions/
Go:
1err := mau.DeleteFile("5d000b.../old-post.json.pgp")
Note: Deletion is local. If peers already synced the file, they keep their copy.
DO:
recipe-carbonara.json, not recipe1.json)photo-2026-03-08-beach.json)my-post.json, not My_Post.json)DON’T:
DO:
@context and @type2026-03-08T10:30:00Z)author with fingerprintdatePublished / dateCreatedDON’T:
DO:
DON’T:
DO:
DON’T:
DO:
DON’T:
DO:
DON’T:
.mau/ metadataNow that you understand storage and data formats:
For complete API reference, see 11-api-reference.md.
Questions? Check 13-troubleshooting.md or open an issue on GitHub.