No description
  • HTML 63.9%
  • C# 26.7%
  • CSS 3.4%
  • Python 3.3%
  • JavaScript 1.8%
  • Other 0.9%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
monerica-project 27ff29529c fav icon
2026-08-05 15:55:39 -04:00
deploy Video indexing: one video per watch page + robust thumbnails 2026-08-04 07:04:38 -04:00
MoneroAcademy.Data Leaderboard: add a graduation-date column 2026-07-08 20:29:42 -04:00
MoneroAcademy.Web fav icon 2026-08-05 15:55:39 -04:00
tools TTS: pronounce 'monerod' as 'Monero D' in lesson narration 2026-07-02 09:38:18 -04:00
.gitignore Course 1: narrated lesson videos on Bunny Stream + per-course/per-lesson video toggles 2026-06-30 11:18:43 -04:00
monerica-donate-qr-code.png Initial commit: Monero Academy site 2026-06-29 17:33:39 -04:00
monero academy.png Initial commit: Monero Academy site 2026-06-29 17:33:39 -04:00
MoneroAcademy.sln Initial commit: Monero Academy site 2026-06-29 17:33:39 -04:00
README.md Initial commit: Monero Academy site 2026-06-29 17:33:39 -04:00

Monero Academy

Free, ad-supported Monero education site. ASP.NET Core (.NET 10) Razor Pages + PostgreSQL, videos and images on Bunny.net, sponsors pulled live from Monerica's active sponsor feed, Tor-accessible, no user accounts, no JS required for visitors (the only script is optional watch-progress tracking).

Features

  • Courses → ordered lessons with Beginner / Intermediate / Advanced levels, tags, descriptions, optional transcripts, prev/next navigation and a course playlist sidebar.
  • Quizzes — anonymous, scored server-side, per-question explanations, attempt stats.
  • Admin panel (/admin) — manage courses, lessons (upload/replace/delete Bunny videos, thumbnails, related/affiliate links, tags), quizzes, comment moderation, feedback inbox, analytics (views, average watch %, completion rate), change password.
  • Ad-supported — sponsor slots fed by https://app.monerica.com/sponsoredlisting/activesponsor.json (cached 15 min, expired entries filtered). Featured (primary-tier) sponsors render prominently sorted by soonest expiry; everyone else shows as a low-key list near the page foot. Which sponsorshipType values count as primary is set in Sponsors:PrimaryTypes.
  • Downloads — per-lesson toggleable download button → short-lived signed MP4 link (302 redirect, no JS).
  • SEO — canonical URLs, Open Graph, JSON-LD (Course / VideoObject), dynamic sitemap.xml, robots.txt.
  • Privacy — no accounts, no third-party scripts, CSP locked down (only the Bunny player iframe), IPs only stored as salted hashes on comments, onion service with Onion-Location meta.
  • Comments with admin approval, honeypot + per-IP throttle + a no-JS captcha; all output Razor-encoded (XSS-safe).
  • Feedback & course suggestions form (also captcha-protected) feeding the admin inbox.

Project layout

MoneroAcademy/
├── MoneroAcademy.sln
├── MoneroAcademy.Data/             # class library: entities + EF DbContext
│   ├── Models/Entities.cs
│   └── AppDbContext.cs             # EnsureCreated() — no migrations needed
├── MoneroAcademy.Web/              # the ASP.NET Core app
│   ├── Program.cs                  # composition root, security headers, minimal APIs
│   ├── appsettings.json            # config template (override in appsettings.Production.json)
│   ├── Services/
│   │   ├── BunnyService.cs         # Stream + Storage + signed URLs
│   │   ├── SponsorService.cs       # Monerica feed: featured (by expiry) + secondary tiers
│   │   ├── CaptchaService.cs       # no-JS, no-cookie, self-hosted captcha
│   │   └── Helpers.cs              # slugs, PBKDF2 hashing, comment throttle
│   ├── Pages/                      # public + /admin pages
│   └── wwwroot/                    # css, lesson-track.js, favicon
└── deploy/                         # deploy.sh, setup-vps.sh, systemd, nginx, torrc

The data layer is a separate class library so the schema/entities can be reused and the web app depends on them by project reference (MoneroAcademy.WebMoneroAcademy.Data). The Npgsql provider is wired up in the web project; the Data project only references Microsoft.EntityFrameworkCore.

Bunny.net setup (one-time)

  1. Stream library (videos)
    • Create a Video Library; note the Library IDBunny:StreamLibraryId.
    • API → copy the library API keyBunny:StreamApiKey.
    • Note the library CDN hostname (e.g. vz-xxxxxxxx-xxx.b-cdn.net) → Bunny:StreamCdnHost.
    • Enable "MP4 Fallback" (Encoding settings) — required for the download button.
    • (Recommended) Security → enable Embed view token authentication; copy the key → Bunny:EmbedTokenAuthKey. Also enable CDN token authentication on the library's pull zone and copy that key → Bunny:CdnTokenAuthKey (signs the short-lived download links). Leave either key empty to serve unsigned URLs.
  2. Storage zone (thumbnails/images)
    • Create a Storage Zone → Bunny:StorageZoneName; copy its password/API key → Bunny:StorageApiKey.
    • If the zone is not in the default region, set Bunny:StorageRegionHost (e.g. ny.storage.bunnycdn.com).
    • Connect a Pull Zone to the storage zone; its hostname → Bunny:StorageHost.

Local development

# PostgreSQL
sudo -u postgres psql -c "CREATE ROLE moneroacademy LOGIN PASSWORD 'dev';"
sudo -u postgres psql -c "CREATE DATABASE moneroacademy OWNER moneroacademy;"

# config: edit MoneroAcademy.Web/appsettings.json (connection string, Bunny keys), then
dotnet run --project MoneroAcademy.Web

Schema is created automatically on first start (EnsureCreated) and the initial admin account is seeded from Admin:InitialUsername / Admin:InitialPassword. Log in at /admin and change the password immediately (it's only used for seeding).

VPS deployment

# 1. On the VPS (Ubuntu/Debian), as root:
scp -r deploy/ user@vps:~ && ssh user@vps
sudo bash deploy/setup-vps.sh        # postgres, nginx, tor, systemd, prod config

# 2. DNS A/AAAA record -> VPS, then on the VPS:
sudo certbot --nginx -d moneroacademy.com -d www.moneroacademy.com

# 3. Fill in Bunny keys + donation address in /var/www/moneroacademy/appsettings.Production.json

# 4. From your dev machine (edit SSH_HOST in deploy/deploy.sh first):
./deploy/deploy.sh

deploy.sh publishes a self-contained linux-x64 build (no .NET runtime needed on the VPS), rsyncs it, and restarts the moneroacademy systemd service. The production config and data-protection keys on the server are never overwritten.

Tor

setup-vps.sh configures the hidden service. Get the address with cat /var/lib/tor/moneroacademy/hostname, put it (with http://) into Site:OnionUrl, and redeploy/restart — the site then advertises it via the Onion-Location meta tag. The site works fully without JavaScript, so it behaves well in Tor Browser on "Safest".

Operations notes

  • Uploads: admin video uploads stream through nginx → Kestrel → Bunny (limit 8 GB, proxy_request_buffering off). Replacing a video deletes the old Bunny video only after the new one uploads successfully. Duration shows pending until Bunny finishes encoding — use Refresh duration from Bunny on the lesson edit page.
  • Watch tracking: anonymous random session id in sessionStorage; furthest-watched percent is beaconed to /api/track. No cookies, no fingerprinting. Analytics shows views, average watch %, and ≥90% completion rate per lesson.
  • Sponsors: fetched server-side from Monerica and cached (Sponsors:CacheMinutes), so no visitor request ever touches a third-party server.
  • Backups: pg_dump moneroacademy covers everything except the videos/images, which live on Bunny.
  • Comments/feedback throttle is in-memory per instance (45 s per IP) — fine for a single VPS.

Config reference (appsettings.json)

Key Purpose
ConnectionStrings:Default PostgreSQL connection string
Site:BaseUrl canonical https URL, used in sitemap/SEO
Site:OnionUrl onion address for Onion-Location (optional)
Site:DonationAddress XMR address shown in footer + /donate
Admin:InitialUsername/Password seed credentials (first run only)
Bunny:StreamLibraryId/StreamApiKey Stream library + API key
Bunny:StreamCdnHost library CDN host (vz-….b-cdn.net)
Bunny:EmbedTokenAuthKey embed token key (optional, signs player URLs)
Bunny:CdnTokenAuthKey CDN token key (optional, signs download links)
Bunny:StorageZoneName/StorageApiKey storage zone for images
Bunny:StorageHost pull-zone host serving the images
Bunny:StorageRegionHost storage API endpoint (region-specific)
Bunny:DownloadLinkMinutes download link lifetime (default 15)
Sponsors:Url/CacheMinutes Monerica sponsor feed + cache
Sponsors:PrimaryTypes comma list of sponsorshipType values to feature prominently (case-insensitive); unmatched ones show as secondary. If none match, everything is featured by expiry.

Notes on a few behaviours

  • Captcha: self-hosted arithmetic challenge sealed in a DataProtection-signed token (no cookies, no JS, no third party). Keys persist to MoneroAcademy.Web/keys/ (excluded from deploy rsync) so tokens and auth cookies survive restarts/deploys. Stops naive bots; layer a PoW gateway (Anubis/mCaptcha) in front if you need more.
  • Lowercase URLs: all generated links are lowercase and any mixed-case GET path 301-redirects to lowercase (query string untouched) for clean canonical SEO. Routing stays case-insensitive.
  • Sponsor tiers: set Sponsors:PrimaryTypes to the exact tier names in your feed.