AIOZ Storage Skill
Deploy static websites to AIOZ Storage with built-in templates or custom sites.
Like a lobster shell, security has layers — review code before you run it.
License
Runtime requirements
SKILL.md
AIOZ Static Website Deploy
Deploy a static website to AIOZ decentralized storage. Supports 4 built-in templates or user's own static site.
FLOW OVERVIEW
1. Login (email + password) → Bearer token + accountId
2. Choose template or custom site
3. Clone template & customize CONFIG
4. Get bucket info (name + 12-word passphrase)
5. Get rootZKey from API
6. Generate grant via grant-cli.ts
7. Register S3 credentials from grant
8. Upload files to S3
9. Create static website via API
10. Site live at https://<bucket>.sites.aiozstorage.app
STEP 1: LOGIN
Ask user for AIOZ Storage email and password.
curl -s 'https://api.aiozstorage.network/api/v1/login' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'origin: https://aiozstorage.network' \
-H 'referer: https://aiozstorage.network/' \
--data-raw '{"email":"<EMAIL>","password":"<PASSWORD>"}'
Response: data.access_token (Bearer token), data.account.id (accountId).
Store BEARER_TOKEN and ACCOUNT_ID.
STEP 2: CHOOSE TEMPLATE
Ask user which template or if they have their own static site.
Templates from https://github.com/AIOZStorage/aioz-storage-docs/tree/main/examples:
- landing — OLED dark theme, X/Twitter style landing page. CONFIG in
assets/main.js. - landing-alt — Purple gradient landing page. CONFIG in
assets/main.js. Supports*italic*gradient text in headlines. - portfolio — Clean minimal portfolio for devs/designers. CONFIG in
assets/main.js. Image carousel for projects. - documents — Full documentation site with Markdown content. CONFIG in
assets/js/config.js. Search, TOC, syntax highlighting. - Custom — User provides their own static files.
STEP 3: CLONE TEMPLATE & CUSTOMIZE
Clone the chosen template:
git clone --depth 1 https://github.com/AIOZStorage/aioz-storage-docs.git /tmp/aioz-storage-docs
cp -r /tmp/aioz-storage-docs/examples/<template_name> ./<project_folder>
Then customize the CONFIG object based on template type:
Template: landing
Config: assets/main.js → CONFIG object. Theme: "dark" (OLED black) or "light".
Key fields to customize:
brand.name— brand/product namemeta.title,meta.description— SEOhero.eyebrow— pill badge (or null to hide),hero.title,hero.subhero.primaryCta—{ label, href },hero.secondaryCtasections.*— toggle: highlights, logos, features, useCases, testimonials, pricingTop, pricingBot, faqhighlights[]—{ number, label }stats (numbers animate on scroll)logos.items[]— company name stringsfeatures—{ label, title, sub, items: [{ title, desc }] }useCases—{ label, title, sub, items: [{ tag, title, desc }] }testimonials[]—{ quote, name, role, company, avatar: { initials, bg } }pricing.plans[]—{ name, price, period, desc, isPopular, cta: { label, href }, features[] }faq.items[]—{ q, a }cta.form.mode—"redirect"|"mailto"|"webhook"footer.brandDesc,footer.columns[],footer.socials[]- Colors in
assets/style.css:root→--color-primary: #1d9bf0
Template: landing-alt
Config: assets/main.js → CONFIG object. Theme: "light" | "dark" | "system".
Key fields to customize:
brand.name,brand.taglinehero.badge— pill badge (or null),hero.headline(wrap words in*asterisks*for gradient italic text),hero.subheadlinehero.primaryCta,hero.secondaryCta,hero.ctaNotesections.*— toggle: stats, problemSolution, features, useCases, logos, testimonials, pricing, faqstats[]—{ number, label }(numbers animate on scroll)problemSolution—{ problem: { label, title, items[] }, solution: { label, title, items[] } }features.items[]—{ size, gradient, kicker, title, desc, tag, visual }(bento grid, size: "wide"|"narrow"|"half"|"third"|"full")pricing.plans[]—{ name, price, period, desc, isPopular, cta, features[] }cta.form.mode—"redirect"|"mailto"|"webhook"- Colors in
assets/style.css→--color-primary: #635bff,--color-gradient-start,--color-gradient-end
Template: portfolio
Config: assets/main.js → CONFIG object. Theme: "light" | "dark" | "auto".
Key fields to customize:
personal.firstName,personal.lastName,personal.title,personal.emailpersonal.location—{ city, country }about.subtitle,about.bio(array of paragraphs),about.focusAreas(array of skill strings)experience[]—{ company, tagline, period, position, description, longDescription, location, industry, website: { label, url } }projects[]—{ title, tags[], year, images[], description, techNote, link: { label, url } }(multiple images = carousel)social[]—{ platform, url }theme.defaultMode,theme.showTogglesections— toggle: showAbout, showExperience, showProjects, showFocusAreasfooter.cta,footer.copyright(null = auto-generate)meta.title,meta.description,meta.ogImage- Images in
assets/images/(1600×900 for projects, 1200×630 for OG)
Template: documents
Config: assets/js/config.js → CONFIG object. Theme: "light" | "dark" | "auto".
Key fields to customize:
site.title,site.description,site.versionsite.logo.light,site.logo.darksidebar[]—{ group, collapsed, items: [{ label, path }] }(path = filename without .md)header.nav[]—{ label, href },header.links[]—{ icon, href }footer.copyright,footer.columns[]features— toggle: search, tableOfContents, feedback, prevNext, copyCodeButton, scrollToTopfeatures.editLink—{ enabled, baseUrl, text }features.tocDepth— 2 or 3
Content: create .md files in /docs/ with YAML front matter:
---
title: Page Title
description: Brief description
order: 1
hidden: false
---
Callouts: > [!NOTE], > [!TIP], > [!WARNING], > [!DANGER]
Code blocks with syntax highlighting (javascript, typescript, python, bash, json, html, css, yaml, sql, go, rust, java, etc.)
Hash routing: index.html#/page-name → loads docs/page-name.md
STEP 4: GET BUCKET INFO
Ask user for:
- Bucket name — their bucket on aiozstorage.network
- 12-word seed phrase — BIP39 passphrase from bucket creation
IMPORTANT: AIOZ Storage does NOT store passphrases. If lost, bucket access is lost forever.
See: https://aiozstorage.network/docs/tutorials/manage-buckets
STEP 5: GET ROOT ZKEY
curl -s 'https://api.aiozstorage.network/api/v1/zkeys' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'authorization: Bearer <BEARER_TOKEN>' \
-H 'origin: https://aiozstorage.network' \
-H 'referer: https://aiozstorage.network/' \
--data-raw '{"name":"deploy-key","force":true}'
Extract: ROOT_ZKEY = data.zkey
STEP 6: GENERATE GRANT
Prerequisites (one-time setup in {baseDir})
cd {baseDir} && npm install
This installs argon2-browser and ts-node. No build:sjcl needed — the CLI uses Node.js built-in crypto.
Run grant-cli.ts (RECOMMENDED: JSON output for bot)
Two grants are needed:
- Upload grant — permissions
1,2,3(Read+Write+List) for S3 upload - Website grant — permissions
1,3(Read+List only) for website creation
Upload grant:
npx ts-node {baseDir}/grant-cli.ts \
--mode per-bucket \
--zkey "<ROOT_ZKEY>" \
--account "<ACCOUNT_ID>" \
--url w3s \
--duration 0 \
--bucket "<BUCKET_NAME>" \
--passphrase "<12_WORD_SEED_PHRASE>" \
--permissions 1,2,3 \
--output json \
--quiet
Website grant:
npx ts-node {baseDir}/grant-cli.ts \
--mode per-bucket \
--zkey "<ROOT_ZKEY>" \
--account "<ACCOUNT_ID>" \
--url w3s \
--duration 0 \
--bucket "<BUCKET_NAME>" \
--passphrase "<12_WORD_SEED_PHRASE>" \
--permissions 1,3 \
--output json \
--quiet
Output (single JSON line to stdout):
{"grant":"<GRANT_STRING>","zkey":"<ZKEY_STRING>"}
Parse JSON to extract UPLOAD_GRANT and WEBSITE_GRANT.
IMPORTANT: Website API rejects grants with Write (2) or Delete (4) permissions.
Alternative: config file
echo '{"mode":"per-bucket","rootZKey":"<ROOT_ZKEY>","accountId":"<ACCOUNT_ID>","url":"w3s","duration":0,"buckets":[{"name":"<BUCKET_NAME>","passphrase":"<PASSPHRASE>","permissions":["1","2","3"]}]}' > /tmp/upload-grant-config.json
npx ts-node {baseDir}/grant-cli.ts --config /tmp/upload-grant-config.json --output json --quiet
echo '{"mode":"per-bucket","rootZKey":"<ROOT_ZKEY>","accountId":"<ACCOUNT_ID>","url":"w3s","duration":0,"buckets":[{"name":"<BUCKET_NAME>","passphrase":"<PASSPHRASE>","permissions":["1","3"]}]}' > /tmp/website-grant-config.json
npx ts-node {baseDir}/grant-cli.ts --config /tmp/website-grant-config.json --output json --quiet
CLI flags
| Flag | Required | Description |
|---|---|---|
--mode | Yes | per-bucket or all-buckets |
--zkey | Yes | Root ZKey (base64url) |
--account | Yes | Account UUID |
--url | No | Service URL (default: w3s) |
--duration | No | ms, 0 = no expiry (default: 0) |
--bucket | per-bucket | Bucket name |
--passphrase | Yes | 12-word seed phrase |
--permissions | per-bucket | 1,2,3,4 (1=Read 2=Write 3=List 4=Delete) |
--config | No | Path to JSON config file |
--output | No | json for JSON output |
--quiet | No | Suppress logs, stdout only |
STEP 7: REGISTER S3 CREDENTIALS
Use the upload grant (permissions 1,2,3):
curl -s 'https://reg-api.aiozstorage.network/api/v1/access' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'authorization: Bearer <BEARER_TOKEN>' \
-H 'origin: https://aiozstorage.network' \
-H 'referer: https://aiozstorage.network/' \
--data-raw '{"grant":"<GRANT>","public":false}'
Extract: ACCESS_KEY_ID = data.access_key_id, SECRET_KEY = data.secret_key
STEP 8: UPLOAD FILES TO S3
S3 endpoint: https://s3.aiozstorage.network | Region: us-east-1 | PathStyle: true
Using AWS CLI
AWS_ACCESS_KEY_ID="<ACCESS_KEY_ID>" \
AWS_SECRET_ACCESS_KEY="<SECRET_KEY>" \
aws s3 sync ./<project_folder>/ s3://<BUCKET_NAME>/ \
--endpoint-url https://s3.aiozstorage.network \
--region us-east-1
Using Node.js (@aws-sdk/client-s3)
Use S3Client with { region: "us-east-1", endpoint: "https://s3.aiozstorage.network", forcePathStyle: true, credentials: { accessKeyId, secretAccessKey } }.
Upload each file with PutObjectCommand, setting correct Content-Type:
.html→text/html .css→text/css .js→application/javascript .json→application/json .svg→image/svg+xml .png→image/png .jpg/.jpeg→image/jpeg .webp→image/webp .ico→image/x-icon .md→text/markdown .woff2→font/woff2 default→application/octet-stream
STEP 9: CREATE STATIC WEBSITE
Use the website grant (permissions 1,3 — Read+List only):
curl -s 'https://api.aiozstorage.app/api/v1/websites' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
-H 'authorization: Bearer <BEARER_TOKEN>' \
-H 'origin: https://aiozstorage.network' \
-H 'referer: https://aiozstorage.network/' \
--data-raw '{
"bucket_name":"<BUCKET_NAME>",
"enabled":true,
"index_document":"index.html",
"error_document":"404.html",
"use_default_error":true,
"error_pages":{},
"grant":"<WEBSITE_GRANT>"
}'
IMPORTANT: Website API rejects grants with Write (2) or Delete (4) permissions. Use --permissions 1,3 for this grant.
API domain is api.aiozstorage.app (not .network).
STEP 10: DONE
Site is live at: https://<BUCKET_NAME>.sites.aiozstorage.app
API DOMAINS
api.aiozstorage.network— login, zkeysreg-api.aiozstorage.network— S3 credential registrations3.aiozstorage.network— S3 upload endpointapi.aiozstorage.app— static website creation<bucket>.sites.aiozstorage.app— static website URL
ERROR HANDLING
- Login fails → check email/password
- ZKey creation fails → Bearer token expired, re-login
- Grant generation fails → verify rootZKey, accountId, bucket name, passphrase
- S3 upload fails → check credentials, endpoint, bucket name
- Website creation fails → check grant, bucket name, Bearer token
- Site 404 → ensure index.html at bucket root (not in subfolder)
IMPORTANT NOTES
grantencodes both authorization (Macaroon/ZKey) and encryption key (EKey from passphrase)- Two grants needed: upload grant (permissions 1,2,3) and website grant (permissions 1,3)
- Website API rejects grants with Write (2) or Delete (4) permissions
- AIOZ Storage is S3-compatible — any AWS SDK works
- Bearer tokens expire — re-login on 401
- grant-cli.ts requires Node.js >= 18
- argon2-browser WASM loading is auto-patched in the CLI
- All template asset paths are relative (
./assets/...) — works from any base URL - For documents template, content is in
/docs/*.mdwith YAML front matter - For landing/landing-alt/portfolio, all content is in CONFIG object in
assets/main.js
Files
4 totalComments
Loading comments…
