Ecommerce Template
Modern ecommerce template built with Next.js 16, React 19, Drizzle, Better Auth, Supabase, and Stripe.
Rupert is a point of sale and back office for shops: ring up sales, keep stock right across stores, close the day and keep the books. Create a free account, or choose a $499/month subscription. Eligib
CommerceRuby · HTMLGitHub @techmoreOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Retail operations platform: Shopify + Square inventory sync, POS, CRM and HR. Rails app (main) with a parallel Rust core.
Taken from the repository README
Loading the full README.
Read the full README ↗Deployment readiness: blueprint and required gates · live milestone.
Centralized inventory operations: a Shopify + Square sync engine with a web dashboard (neutral blue palette, light and dark themes), backed by SQLite in production. Runs as a Ruby on Rails 8.1 app (based on the Shopify Ruby app template) and is currently hosted at herbalhealers.rupertpos.com.
Shopify ──┐ ┌── Dashboard
│ ├── Inventory
Square ───┼── sync engine ──▶ SQLite DB ──▶ GUI
│ (Rails jobs) │ (Rails + Tailwind)
└────────────────────┴── Settings (env, backup/restore)
| Path | Purpose |
|---|---|
web/ | The Rails 8.1 app: sync engine services, the dashboard GUI (app/assets/tailwind/application.css holds the palette; bin/check-palette-contrast verifies it), JSON APIs |
mobile/ | Staff iPhone/Android app (see mobile/README.md) |
docs/ | Docs site, plans, audits and research (see docs/README.md) |
deploy/ | SER8 (Incus) systemd units (web, jobs, backup) and host-side watcher/backup scripts (see Production and deployment) |
bin/ | Host scripts (backup.sh) |
shopify.app.toml | Shopify app config (scopes, webhooks) |
Dockerfile, docker-compose.yml | Optional container deployment |
Requirements: Ruby 4.0.9 (rbenv), Bundler, and Node (for the Shopify CLI and JavaScript tests). SQLite is the only supported database; no database server is needed.
cp .env.example .env # from repo root; configure local credentials
cd web
bundle install
bin/rails db:prepare
bin/rails tailwindcss:build
bin/rails server # http://localhost:3000
The development database is web/storage/development.sqlite3 (override with
RUPERT_SQLITE_DATABASE_DEVELOPMENT). For production operations, tuning and
recovery see SQLite and Incus operations.
Leave DATABASE_URL unset: Rupert rejects every nonempty value, including a
SQLite URL. Set database locations with the per-environment
RUPERT_SQLITE_DATABASE_* variables. RUPERT_DB_ADAPTER=sqlite3 is accepted
for compatibility but is unnecessary; other explicit adapter values are
rejected.
The .env at the repo root is loaded at boot (Dotenv) and holds the sync
engine credentials (SHOPIFY_CLIENT_ID/SECRET, SQUARE_ACCESS_TOKEN, …).
The Shopify OAuth pair (SHOPIFY_API_KEY/SHOPIFY_API_SECRET) comes from
shopify app info --web-env when developing through the Shopify CLI, or is
set in the production environment.
Repository maintenance records the
validation commands, Git and deployment notes, and current consolidation
work. Tests skip the repository-root .env; use explicit test settings and
an isolated checkout.
Start with the operator runbook (daily checks, stuck syncs, GDPR requests, key rotation, onboarding), the backup and restore runbook (RPO/RTO, drill, restore procedure), the environment reference, the release process and the go-live checklist. The production readiness audit records what was verified and what the owner still has to decide.
Purchase receipt uploads and approvals are available from the Receipts tab, including staff self-service and an independent review queue.
For the current daily-use limits and the next payment/inventory rollout gates, start with Derrick's day-to-day pickup plan.
bin/rails ops:sync # full Shopify + Square mirror sync
bin/rails ops:sync_source[square] # single source
bin/rails ops:catalog_links # print catalog link stats (linked/matched/mismatched)
bin/rails ops:push_guard:status # freeze + approval-window state per platform
bin/rails ops:push_guard:freeze[square,reason] # record a maintenance freeze (awareness only)
bin/rails ops:push_guard:unfreeze[square]
bin/rails test # page + API smoke tests
Scheduled syncs run through Solid Queue (bin/jobs) — every 15 minutes in
production, scheduled in config/recurring.yml.
The sync loop is a read-only mirror: it never writes quantities or SKUs to Shopify or Square on its own. The old automatic lock-step machinery (reconcile-plan apply, shared-pool push) was removed in August 2026 when the operating model changed: Shopify and Square are separate locations serving the same items from independent inventories, so equalizing them was wrong.
Outbound stock writes now happen only through explicit, owner-approved flows
(size-family derives, remediation tasks). Per owner directive (2026-08-18) the
former PlatformPushGuard approval-window enforcement is a no-op — freezes and
windows are recorded for operator awareness only. The standing rule is:
always ask the owner before any data-mutating action on Shopify or Square,
and never write SKUs without explicit sign-off.
/reconcile) — read-only SKU identity audit between
platforms: linked items, matched/mismatched SKUs, one-sided counts. Quantity
differences across locations are normal and shown, never "corrected"/learning) — first-run welcome and learning paths, role-aware first-week checklist, guided page tours, lessons with worked examples and knowledge checks, a searchable glossary, in-place definitions and calculation explainers, a ? help panel on every page, and a read-only team training view. See in-app learning.env import/export (JSON API included) and DB backup/restore:
GET /settings/env.json · masked env keysPOST /settings/env_import · body { "text": "KEY=VALUE\n…" }ERP pages require a Rupert login. Password login and configured Google OAuth are supported; Shopify OAuth remains available for store integration.
Rupert is a modular monolith growing into a full ERP for small businesses.
Extensions live under app/modules/<name>/ and register themselves in
app/models/module_registry.rb (nav + permission gate). The canonical domain
core lives in app/modules/core/ (Customer, Order, OrderLine,
Payment) and is fed by the Shopify/Square sync engine through
CanonicalOrderImporter — a clean seam between the source mirrors
(ShopifyProduct, LedgerEntry, …) and the unified ERP model. A third,
manual source (SwipesimpleImporter) feeds CSV exports through the same seam.
super_admin, admin, manager, cashier, reader with a
permission matrix in User::ROLE_PERMISSIONS and Pundit policy objectsUser#dashboard_configSalesController builds an hourly × location pivot from
Core::Order (groupdate-ready time series)app/modules/people/ (staff, departments, positions, timesheets,
leave & PTO, payroll) with PayrollCalculator turning approved timesheets
into payslipsdocs/ops/sqlite-incus.md for tuning, recovery and scaling triggers.Production is hosted on SER8 in Incus and served at
herbalhealers.rupertpos.com. GitHub is the source
repository. Merging to main deploys: the SER8 updater polls main every 5
minutes and, once the exact SHA's required checks are green, installs it
automatically (the default mode) or on an operator's request (click-only mode,
System → Updates). Each install takes a backup, writes a release receipt and
rolls the code back on a failed health or smoke check; migrations are not
reverted. A stable v* tag does not deploy. Check the receipt or the
X-Rupert-Build header before describing a change as live. See the development and deployment workflow
and SER8 updater runbook.
The SER8 web/worker/backup units and the host-side watcher and off-host backup scripts are under deploy/. The pre-Incus droplet units and the original Node/React implementation (legacy/) were removed on 2026-10-20; they remain in git history (git show 03db8bb:legacy/README.md).
The cutover report records production SQLite as of 2026-10-13 (78,573 rows, 61 tables verified). Live hosting and the October 14 application deployment were verified; see the deployment receipt. Read SQLite and Incus operations and the cutover report. The PostgreSQL copy is frozen at cutover; switching back after new SQLite writes requires reconciliation.
Compose stores production data in the persistent rupert-storage volume and
runs bin/prepare-sqlite before starting web or jobs. The preflight requires
a nonempty SQLite database at /app/storage/production.sqlite3. For an
existing Rupert database, place a verified SQLite copy at that path in the
Compose volume before starting services. Existing PostgreSQL volumes or
backups are not imported by Compose; preserve them and plan any data
transition separately.
For a genuinely new, empty installation only, set
RUPERT_INITIALIZE_DATABASE=1 for the first docker compose up -d, then
remove it after initialization succeeds. This explicitly creates the initial
SQLite database. Do not use it to bypass the check for an existing deployment.
Keep Docker volumes and backups when upgrading; do not use docker compose down -v for routine maintenance. DATABASE_URL must remain unset, and any
RUPERT_DB_ADAPTER value must be omitted or exactly sqlite3.
/roadmap) shows the same catalog. Planned items are not enabled.web/config/product_roadmap.json is the canonical catalog. Run bin/build-product-site after changing it; --check detects drift in CI.bin/rails ops:sqlite:report reads SQLite settings/space metrics; bin/rails ops:sqlite:optimize explicitly refreshes planner statistics. SQLite job concurrency defaults to one thread (JOB_THREADS can override).Mirrors the original Prisma schema (table/column names were preserved when
the Rails app replaced it): ShopifyProduct, ShopifyVariant, SquareItem,
SquareVariation, SkuLink, ReconcileRun, ReconcileItem (historical run
records; no longer written), Location,
InventoryLevel, InventoryMovement, StockAlert, SyncRun,
InventoryPolicy, LedgerEntry, plus Shopify session storage (shops,
users) and app settings.
Client-credentials token: the Shopify sync uses the store's
client-credentials token (SHOPIFY_CLIENT_ID/SECRET). If the token is
rejected ([API] Invalid API key or access token), re-install the app on
the store or regenerate the secret in the Shopify admin — the sync will
fail loudly in the Sync page until then.
SQLite is the only supported database (production, CI, development). Mirror/journal tables are pruned
nightly by DataRetentionJob; canonical business data is never pruned.
Shopify and Square are separate locations with independent inventories. The sync is a read-only mirror; nothing equalizes quantities across them. Outbound stock writes happen only through explicit, owner-approved flows, and SKU writes always require owner sign-off first.
SwipeSimple reporting investigation and hourly SER8 CSV intake — verified native formats, encrypted source provenance, invoice identity limits, operational setup and remaining vendor access requirements.
Zelle reporting by manual bank CSV — Zelle tab beside SwipeSimple: preview-then-import of a bank-statement CSV, encrypted originals, unverified zelle tender, separate daily totals.
SwipeSimple integration research and long-term Valmar / Authorize.net objective — supported-feed options, ACH/reporting gaps, QuickBooks and partner-access limits, source evidence and the vendor questionnaire; roadmap F64 records the owner's direction.
Staff web/app access and timesheets covers setup links, staff permissions, approvals, clock imports, CSV exports, mobile APIs, and deployment. Native app build and distribution covers iPhone/Android signing and release preparation. The instance serves the installable staff web app at /employee.
Use Marketing → Influencers for collaboration, gifting and document records. Create temporary Shopify codes with reviewed terms and discount-applying referral links. The connected Shopify app needs write_discounts; creating a code requires explicit operator confirmation. See the CMS guide.
GET /settings/env_export · full .env textGET /settings/backup · consistent SQLite snapshot (VACUUM INTO)POST /settings/restore · multipart file upload