UGA HOST Developer Docs

QSSN PaaS is a secure cloud network that hosts and manages UGA HOST, all developed by Gaston Software Solutions LLP. It provides developers with infrastructure to deploy, manage, and scale web applications with enterprise-grade security — handling both frontend sites and backend APIs from a single platform.

🌐 Frontend sites (HTML · React · Vue) ⚡ Backend APIs (Node.js · Python) 🗄️ Turso SQLite auto-provisioned 🔒 Enterprise security & SSL ⚙️ Automatic CI/CD

Overview

UGA HOST is the deployment engine inside QSSN PaaS. It covers two distinct hosting surfaces:

SurfaceWhat you deployHow
BackendNode.js Workers, Python containersugahost CLI — init + deploy
FrontendStatic sites, React/Vue/Angular appsQSSN PaaS dashboard — connect GitHub repo & click Deploy

Every project — backend or frontend — gets a live URL under gss-tec.com, automatic HTTPS, and access to the full management dashboard at qssn-cloud-manager.pages.dev.

Installation

Install the UGA HOST CLI globally via npm:

npm install -g ugahost@latest

Verify the installation:

ugahost --version

The CLI is open source. View the source code, report issues, or contribute on GitHub:

github.com/GSS-creator/UGA-HOST

Authentication

UGA HOST uses a two-layer authentication model. Your account is tied to GitHub OAuth — that is how you sign in to the dashboard. Your CLI and API access is controlled by a personal API key that you generate from the dashboard and paste into ugahost login.

LayerMethodUsed for
Account loginGitHub OAuthDashboard at qssn-cloud-manager.pages.dev
CLI / API accessugahost_ API keyugahost login, all CLI commands, direct API calls

GitHub OAuth Flow

Every UGA HOST account is backed by a GitHub identity. There is no username/password registration — GitHub is the only sign-in method.

Full flow

Go to qssn-cloud-manager.pages.dev/backend and click Sign in with GitHub.
You are redirected to github.com/login/oauth/authorize with scopes user:email and repo. A one-time CSRF state token is stored in the database before the redirect.
GitHub redirects back to /api/auth/github/callback. The platform verifies the state, exchanges the code for a GitHub access token, and fetches your GitHub profile and primary verified email.
Find or create developer account. The platform looks up your email in the developers table. If no account exists it is created automatically with a starter subscription, and a Welcome email is sent to your address.
A signed JWT session token is generated (payload: id, email, tier) and appended to the redirect URL back to the dashboard. A Login notification email is sent with your IP and timestamp.
The dashboard stores the JWT in the browser session. All subsequent dashboard API calls include it as Authorization: Bearer <jwt>.

Emails sent during account lifecycle

EventEmailContents
First sign-in (account created)Welcome emailAccount confirmation, link to dashboard, getting-started guide
Every subsequent sign-inLogin notificationTime of login, IP address / auth method (GitHub OAuth)
API key createdAPI key created emailKey name, preview of first characters, expiry date, permissions
API key revokedAPI key revoked emailKey name, revocation timestamp
Deploy succeedsDeploy success emailProject name, subdomain URL, version ID, deploy source (CLI / Editor)
Deploy failsDeploy failed emailProject name, error message

API Keys

API keys are what authenticate the CLI and any direct HTTP calls you make to the platform API. They are separate from your JWT session token.

Format

Every API key is prefixed with ugahost_ followed by 64 hex characters (32 random bytes):

ugahost_a3f8c2e1d4b7a9f0e2c4d6b8a1f3e5d7c9b2a4e6f8d0b2c4a6e8f0d2b4c6a8

How to create an API key

Sign in to the dashboard at qssn-cloud-manager.pages.dev/backend.
Navigate to Settings → API Keys.
Click Generate New Key. Fill in:
FieldDescription
Key nameA label for this key, e.g. laptop-dev or CI/CD pipeline
ExpiryNumber of days until the key expires — or leave blank for no expiry
Permissionsread_write (default) — full access to deploy, manage env vars, query DB
Copy the full key immediately. The raw value is shown only once. After you close the dialog only the key name and prefix are visible in the dashboard.
An API key created email is sent to your account address with the key name, a short preview, and the expiry date.
🔴
The key is shown in full only once. Copy it before closing the dialog. If you lose it, revoke the old key and generate a new one.

Key validation on every request

Every CLI command sends Authorization: Bearer ugahost_…. The platform middleware:

  1. Detects the ugahost_ prefix and routes to APIKeyManager.validateAPIKey()
  2. Queries api_keys WHERE api_key = ? AND is_active = 1
  3. Checks expires_at — rejects if the current time is past expiry
  4. Updates last_used_at = datetime('now') on the matched row
  5. Returns the developer_id and email so the rest of the request proceeds as that developer

Revoking a key

In the dashboard go to Settings → API Keys, find the key row, and click Revoke. This sets is_active = 0 — the key stops working immediately on the next request. A revocation email is sent to your account. The row is kept in the database (soft-delete) so audit history is preserved. Use Delete to remove it permanently.

CLI Login — ugahost login

Run ugahost login to authenticate the CLI. It prompts for your email and API key, validates them against the platform, and saves the credentials locally:

ugahost login

# Prompts:
#   Email:    you@example.com
#   API Key:  ****************  (input masked)

What happens under the hood

The CLI validates the key format — it must start with ugahost_.
A POST request is made to /api/auth/validate-api-key with { email, api_key }. The platform checks the key exists, is active, is not expired, and that the email matches the developer record.
On success, credentials are written to ~/.ugahost/config.json:
{
  "email":  "you@example.com",
  "apiKey": "ugahost_a3f8c2…",
  "apiUrl": "https://qssn-paas-management.gastonsoftwaresolutions234.workers.dev"
}
Every subsequent CLI command reads this file and attaches the key as Authorization: Bearer <apiKey> on every HTTP request to the platform.
⚠️
Keep ~/.ugahost/config.json out of source control. It contains your full API key in plain text. Add it to your global .gitignore: echo ~/.ugahost/config.json >> ~/.gitignore_global

ugahost init

Run ugahost init inside your project directory. It will interactively ask:

PromptDescriptionExample
Project nameDisplay name for your projectmy-api
SubdomainLowercase letters, numbers, hyphens only. Becomes subdomain.gss-tec.commy-api
Languagenodejs or pythonnodejs
PortThe port your server listens on (Python only — used for container routing)3000

This writes a ugahost.json file to your project directory:

{
  "name": "my-api",
  "subdomain": "my-api",
  "language": "nodejs",
  "port": 3000,
  "pythonMode": "standard"
}

After the first deploy, a projectId field is added automatically. This ID is used for all subsequent redeploys and API operations.

Deploy Flow

Running ugahost deploy executes this pipeline:

Read filesindex.js / app.py
→
Validatelanguage checks
→
Upload codePOST /projects
→
Provision DBTurso created
→
Deploy WorkerCloudflare edge
→
Health checkPython only

The CLI distinguishes between a first deploy and a redeploy:

ConditionEndpoint calledEffect
No projectId in ugahost.jsonPOST /api/backend/projectsCreates project, provisions Turso DB, deploys Worker, saves projectId
projectId presentPOST /api/backend/projects/:id/redeployRedeploys with existing env vars + Turso credentials preserved

Deploying a Node.js App

UGA HOST Node.js projects run as Cloudflare Workers — a V8-isolate environment, not a Node.js process. Two coding styles are supported:

Style 1 — Native Cloudflare Worker Recommended

Write a standard export default { fetch(request, env) {} } module. The env object automatically contains all your environment variables (including Turso credentials) as Cloudflare plain-text bindings.

export default {
  async fetch(request, env) {
    const url = new URL(request.url);

    // env.TURSO_DATABASE_URL and env.TURSO_AUTH_TOKEN are injected automatically
    if (url.pathname === '/') {
      return new Response('Hello from UGA HOST!');
    }

    return new Response('Not found', { status: 404 });
  }
};

Style 2 — Express-compatible (polyfilled)

Standard Express apps also work. The platform injects a require() polyfill that intercepts require('express'), require('cors'), and require('dotenv'). Remove app.listen() — it is stripped automatically by the CLI before upload.

const express = require('express');
const app = express();
app.use(express.json());

app.get('/', (req, res) => {
  res.json({ status: 'ok' });
});

// DO NOT call app.listen() — stripped by the CLI
// Access env vars via process.env.MY_VAR
ℹ️
When to use each style: If your app uses env.SOME_VAR (Cloudflare-native) use Style 1. If it uses process.env.SOME_VAR (Node-style) use Style 2 with the Express polyfill.

Entry file

The CLI always reads index.js from the current directory. No other entry point is supported for Node.js.

Deploying a Python App

Python projects run as isolated containers managed by the UGA HOST Python Runtime. The container starts on the first request and stays warm as long as it receives traffic.

Entry file

The CLI reads app.py from the current directory.

Standard mode (default) Recommended

Start a regular HTTP server. The platform routes HTTP traffic to the port specified in PORT environment variable.

from flask import Flask, jsonify
import os

app = Flask(__name__)

@app.route('/')
def index():
    return jsonify({ 'status': 'ok' })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=int(os.environ['PORT']))

Supported frameworks

FrameworkDetection pattern
FlaskFlask(...) or app.run(...)
FastAPIFastAPI(...) or uvicorn.run(...)
StarletteStarlette(...)
DjangoDjango(...)
BottleBottle(...)
Standard libraryHTTPServer(...) / ThreadingHTTPServer(...)

Dependencies — requirements.txt

Place a requirements.txt file in the same directory. It is uploaded alongside app.py and installed into the container automatically.

flask==3.0.3
requests==2.32.3
libsql-client==0.3.1

Health check after deploy

After uploading, the CLI calls GET /runtime-health to verify the container started. Cold-start timeouts (503) are non-fatal — the deploy is still considered successful and the container will start on the first real request.

Redeploying

Once ugahost.json contains a projectId, every subsequent ugahost deploy is a redeploy. The platform:

✅
Env vars are never lost on redeploy. Any variables you set with ugahost env set persist across all future deploys automatically.

Python Pre-deploy Validation

Before any Python code reaches the platform, the CLI runs a local validation pass. If any check fails, the deploy is aborted with a descriptive error message — no code is uploaded.

Check pipeline

Syntax check — The code is written to a temp file and parsed via python3 -c "import ast; ast.parse(...)". If no local Python is found, this step is skipped silently.
HTTP server check — Verifies that at least one supported server/framework is instantiated (Flask, FastAPI, HTTPServer, uvicorn.run, etc.).
PORT reference check — Verifies the code references the PORT environment variable. The platform assigns a port dynamically; hardcoding a port number will break routing.
Dangerous pattern check — Blocks sys.exit() at the top level (kills the server immediately) and import __main__ at the top level (causes infinite loops).
API crossover check — Ensures standard mode code does not use Cloudflare / Pyodide Worker APIs (from workers import, WorkerEntrypoint, pyodide.http, on_fetch).

All validation rules

RuleConditionResult
SyntaxAST parse fails✗ Blocked
HTTP serverNo Flask/FastAPI/HTTPServer/uvicorn found✗ Blocked
PORT usageString PORT not present in code✗ Blocked
sys.exit()Top-level sys.exit() call✗ Blocked
import __main__Top-level import __main__✗ Blocked
Worker APIs in std modefrom workers import, WorkerEntrypoint, pyodide✗ Blocked
All pass—✓ Deploy proceeds
⚠️
Always read the PORT from the environment.
port = int(os.environ.get('PORT', '8080')) is the correct pattern. Never hardcode a port number.

Node.js Pre-deploy Processing

Node.js code is not validated as strictly as Python, but the CLI applies two transformations before upload:

1. app.listen() removal

Any app.listen(...) call (including multi-line callback blocks) is stripped from the code. Workers do not have a listening port — the platform handles all routing.

2. Export injection

If the code contains none of export default, module.exports, or globalThis.app, the following is appended automatically:

// Export for UGA HOST
globalThis.app = app;

If your code already has export default { fetch(...) }, no injection occurs and the native Worker entry point is used as-is.

Turso Database — Auto Provisioning

Every project gets a dedicated Turso (libSQL / SQLite) database provisioned automatically on first deploy. You never need to create one manually.

What happens on first deploy

Platform creates a Turso database named ugahost-{subdomain} in the default group of your organisation.
A full-access, never-expiring auth token is generated for the database.
Both credentials are saved to the project record (turso_database_url, turso_auth_token) and to backend_env_vars as secrets.
The credentials are injected into the deployed Worker as Cloudflare plain-text bindings so they appear on the env object at runtime.

Credentials available in your app

VariableAvailable inDescription
TURSO_DATABASE_URLenv.TURSO_DATABASE_URL (Node.js native)
process.env.TURSO_DATABASE_URL (Express)
os.environ['TURSO_DATABASE_URL'] (Python)
libSQL URL — libsql://…turso.io
TURSO_AUTH_TOKENSame patterns as aboveJWT auth token for Turso HTTP API
DATABASE_URLSame patterns as aboveAlias for TURSO_DATABASE_URL
DATABASE_AUTH_TOKENSame patterns as aboveAlias for TURSO_AUTH_TOKEN
✅
Credentials survive every redeploy. They are stored in backend_env_vars and re-injected automatically on every ugahost deploy. You never need to re-enter them.

Accessing the Database in Your App

Node.js — native Worker (recommended)

Use the Turso HTTP API directly. The credentials are already on env:

async function query(env, sql, args = []) {
  const url = env.TURSO_DATABASE_URL
    .replace('libsql://', 'https://') + '/v2/pipeline';
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.TURSO_AUTH_TOKEN}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      requests: [
        { type: 'execute', stmt: { sql, args: args.map(v => ({ type: 'text', value: String(v) })) } },
        { type: 'close' }
      ]
    })
  });
  const data = await res.json();
  return data.results?.[0];
}

Python — using libsql-client

import os, libsql_client

client = libsql_client.create_client_sync(
    url=os.environ['TURSO_DATABASE_URL'],
    auth_token=os.environ['TURSO_AUTH_TOKEN']
)

rows = client.execute("SELECT * FROM users").rows

Schema initialization pattern

Because Workers and containers cold-start, the recommended pattern is to run CREATE TABLE IF NOT EXISTS on every startup:

async function initDb(env) {
  await query(env, `CREATE TABLE IF NOT EXISTS users (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    username      TEXT    NOT NULL UNIQUE,
    created_at    TEXT    NOT NULL DEFAULT (datetime('now'))
  )`);
}

Database CLI Commands

The ugahost db subcommand gives you full database access from the terminal. All commands must be run from a directory containing a deployed ugahost.json.

CommandDescription
ugahost db infoShow DB type, URL, tables and row counts
ugahost db tablesList all tables with row counts
ugahost db query "<SQL>"Run any raw SQL statement
ugahost db find <table> [where]SELECT rows, optional JSON where filter
ugahost db get <table> <id>SELECT single row by id
ugahost db insert <table> <json>INSERT a new row
ugahost db update <table> <where> <set>UPDATE rows matching where clause
ugahost db delete <table> <where>DELETE rows matching where clause
ugahost db drop <table>DROP TABLE (with confirmation prompt)
ugahost db count <table> [where]COUNT rows
ugahost db migrate <file>Run a .sql or .json migration file against the live database
ugahost db export <table> [-o file]Export all rows to a JSON file
ugahost db import <table> <file>Bulk-insert rows from a JSON array file

Quick examples

# Create a table with a single SQL statement
ugahost db query "CREATE TABLE posts (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, body TEXT)"

# Insert a row
ugahost db insert posts '{"title":"Hello","body":"World"}'

# SELECT with a filter (JSON key=value)
ugahost db find posts '{"title":"Hello"}'

# Raw SQL with JOIN
ugahost db query "SELECT u.username, COUNT(p.id) as posts FROM users u LEFT JOIN posts p ON p.user_id = u.id GROUP BY u.id"

Migrations — ugahost db migrate

ugahost db migrate <file> runs a script of SQL statements (or a structured JSON migration) against your live Turso database. Every statement is executed individually, with a real-time progress line printed for each one — so you can see exactly which step succeeded or failed.

✅
This is the recommended way to create tables and seed initial data. Write your schema once in a .sql file, commit it to source control, and run it against any project with one command.

How it works

The file is read and split on ; — each non-empty segment becomes one statement. Comments (--) and blank lines are ignored.
Statements are sent to your database one by one via the /database/query API endpoint. A spinner shows [1/N] CREATE TABLE … live in the terminal.
Each statement either shows ✅ succeeded or ❌ failed: <error message>. Failures do not stop the run — remaining statements are still attempted.
A summary line is printed at the end: Migration complete: 5/5 succeeded or Migration finished: 4 succeeded, 1 failed.

SQL migration file (.sql) Most common

Create a plain .sql file with one or more statements separated by ;:

-- migrations/001_init.sql
-- Creates the core schema for the auth API

CREATE TABLE IF NOT EXISTS users (
  id            INTEGER  PRIMARY KEY AUTOINCREMENT,
  username      TEXT     NOT NULL UNIQUE,
  email         TEXT     UNIQUE,
  salt          TEXT     NOT NULL,
  password_hash TEXT     NOT NULL,
  role          TEXT     NOT NULL DEFAULT 'user',
  created_at    TEXT     NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS sessions (
  id         INTEGER  PRIMARY KEY AUTOINCREMENT,
  token      TEXT     NOT NULL UNIQUE,
  user_id    INTEGER  NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  created_at TEXT     NOT NULL DEFAULT (datetime('now')),
  expires_at TEXT
);

CREATE INDEX IF NOT EXISTS idx_sessions_token  ON sessions(token);
CREATE INDEX IF NOT EXISTS idx_users_username  ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email     ON users(email)

Run it:

ugahost db migrate ./migrations/001_init.sql

Terminal output:

🔄 Running migration: 001_init.sql
   5 statement(s)

  ✅ [1/5] CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY…
  ✅ [2/5] CREATE TABLE IF NOT EXISTS sessions ( id INTEGER PRIMARY K…
  ✅ [3/5] CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(…
  ✅ [4/5] CREATE INDEX IF NOT EXISTS idx_users_username ON users(use…
  ✅ [5/5] CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)

✅ Migration complete: 5/5 succeeded

Seed data in the same file

You can mix DDL (schema) and DML (data) in one file. Statements run in order:

-- migrations/002_seed.sql

INSERT INTO users (username, email, salt, password_hash, role)
VALUES ('admin', 'admin@example.com', 'abc123', 'hashed_value', 'admin');

INSERT INTO users (username, email, salt, password_hash, role)
VALUES ('alice', 'alice@example.com', 'def456', 'hashed_value', 'user');

INSERT INTO users (username, email, salt, password_hash, role)
VALUES ('bob', 'bob@example.com', 'ghi789', 'hashed_value', 'user')
ugahost db migrate ./migrations/002_seed.sql

JSON migration file (.json)

For programmatically generated migrations, use the JSON format. Each entry in operations has a sql field:

{
  "version": "003",
  "description": "Add posts table",
  "operations": [
    { "sql": "CREATE TABLE IF NOT EXISTS posts (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, title TEXT NOT NULL, body TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE)" },
    { "sql": "CREATE INDEX IF NOT EXISTS idx_posts_user_id ON posts(user_id)" }
  ]
}
ugahost db migrate ./migrations/003_posts.json

Recommended project layout

my-api/
├── index.js          # or app.py
├── ugahost.json
└── migrations/
    ├── 001_init.sql  # schema — tables, indexes
    ├── 002_seed.sql  # initial data
    └── 003_posts.sql # subsequent changes
ℹ️
Use CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS so migration files are safe to re-run without errors. UGA HOST does not track which migrations have already been applied — idempotent SQL is the simplest solution.
⚠️
Failures do not roll back. If statement 3 of 5 fails, statements 1–2 are already committed. Write migrations that are safe to partially apply, or use BEGIN; … COMMIT; as a single statement for atomic changes.

Import & Export

Export — ugahost db export

Dumps all rows from a table to a local JSON file. Useful for backups or moving data between projects.

# Export to auto-named file: users-export-1234567890.json
ugahost db export users

# Export to a specific file
ugahost db export users -o ./backups/users.json

Import — ugahost db import

Bulk-inserts rows from a JSON array file into a table. The id field is stripped automatically — the database assigns new IDs.

# Import from a previously exported file
ugahost db import users ./backups/users.json

# Import from a handwritten seed file
ugahost db import products ./seeds/products.json

The seed file must be a JSON array of objects whose keys match the table's column names:

[
  { "name": "Widget A", "price": 9.99, "stock": 100 },
  { "name": "Widget B", "price": 14.99, "stock": 50 }
]
ℹ️
Import vs Migrate for seeding: Use db import when you have a JSON dataset to load into an existing table. Use db migrate when you want to write INSERT statements in SQL — both work equally well for seeding.

Environment Variables

Env vars are stored securely in the platform database and injected into your Worker on every deploy. They are available in your code via the normal patterns for each language.

How injection works

When the platform deploys or redeploys your Worker it:

  1. Reads all rows from backend_env_vars for your project
  2. Merges them with Turso credentials from the project record
  3. Passes the full set as Cloudflare plain_text bindings in the Worker metadata — making them available on env.VAR_NAME
  4. Also sets them on globalThis.process.env.VAR_NAME (polyfill, for Express-style apps)
ℹ️
Setting an env var triggers an automatic redeploy of the Worker so the new value takes effect immediately. For Python containers, the container is restarted instead.

Env Commands

CommandDescription
ugahost env listList all environment variable keys (values hidden for secrets)
ugahost env set KEY VALUESet a variable. Prompts interactively if args omitted.
ugahost env set KEY VALUE --secretMark as secret — value is stored encrypted and shown as *** in logs
ugahost env unset KEYRemove a variable and redeploy

Examples

# Set a plain variable
ugahost env set NODE_ENV production

# Set a secret (API key, password, etc.)
ugahost env set STRIPE_SECRET_KEY sk_live_... --secret

# Interactive mode — prompts for key, value, and secret flag
ugahost env set

# Remove a variable
ugahost env unset NODE_ENV
⚠️
Do not set TURSO_DATABASE_URL or TURSO_AUTH_TOKEN manually. These are provisioned and managed automatically. Overwriting them with incorrect values will break your database connection.

Logs

All console.log, console.error, and console.warn calls in your app are captured and stored per-project.

# Fetch the last 100 log lines
ugahost logs

# Fetch more lines
ugahost logs --lines 500

Log levels are colour-coded in the terminal:

LevelSourceColour
INFOconsole.log / console.infoCyan
WARNconsole.warnYellow
ERRORconsole.errorRed
DEBUGconsole.debugGray

Status

ugahost status

Displays project details and current quota usage:

FieldDescription
NameProject display name
URLLive URL — https://subdomain.gss-tec.com
Languagenodejs or python
WorkerInternal Cloudflare worker script name
StatusRUNNING / STOPPED / FAILED
Created / DeployedTimestamps
QuotaApps used/max, storage MB, requests today

Full CLI Reference

CommandDescription
ugahost loginAuthenticate with your API key
ugahost initInitialise a new project in the current directory
ugahost deployFirst deploy or redeploy the current project
ugahost statusShow project status and quota
ugahost logs [--lines N]Fetch application logs
ugahost env listList environment variable keys
ugahost env set [KEY] [VALUE] [--secret]Set an environment variable
ugahost env unset KEYRemove an environment variable
ugahost db infoShow database info and table list
ugahost db tablesList tables with row counts
ugahost db query "<SQL>"Run a raw SQL statement
ugahost db find <table> [where]SELECT rows with optional filter
ugahost db get <table> <id>SELECT single row by id
ugahost db insert <table> <json>INSERT a row
ugahost db update <table> <where> <set>UPDATE rows
ugahost db delete <table> <where>DELETE rows
ugahost db drop <table>DROP a table
ugahost db count <table> [where]COUNT rows
ugahost db migrate <file>Run a SQL or JSON migration file
ugahost db export <table> [-o file]Export table rows to JSON
ugahost db import <table> <file>Bulk-insert from a JSON file

UgaFront Frontend Deployment

Deploy static sites and frontend apps to the global edge via the QSSN PaaS dashboard. Connect your GitHub repo and go live in seconds.

⚙️ Automatic CI/CD 🌍 Global edge deployment 🔒 Enterprise security & SSL 📦 Static HTML/CSS/JS ⚛️ React · Vue · Angular

Prerequisites

Repository Requirements

RequirementDetails
index.html Landing PageYour repository must contain an index.html file as the main landing page
GitHub RepositoryPublic or private GitHub repository containing your project code
Static AssetsCSS, JavaScript, images, and other static files referenced by index.html

Account Requirements

RequirementDetails
QSSN PaaS AccountRegister for a free QSSN PaaS developer account at qssn-cloud-manager.pages.dev
GitHub IntegrationConnect your GitHub account for repository access during deployment setup
ℹ️
index.html is mandatory. The platform uses this file as the entry point for your site. Projects missing index.html at the repository root will fail to deploy.

Deployment Steps

Prepare Your Repository

Ensure your GitHub repository contains an index.html file as the main landing page. Example structure:

my-project/
├── index.html     ← required
├── css/style.css
├── js/script.js
└── README.md
Connect GitHub Account

In the QSSN PaaS dashboard, click Create Project and connect your GitHub account to access your repositories. Sign in at qssn-cloud-manager.pages.dev/frontend.

Select Repository

Browse your repositories and select the one you want to deploy. The system will automatically detect your project type and suggest build settings.

Auto-DetectedSupported Project Types
Build command
Output directory
Framework type
Static HTML / CSS / JS
React, Vue, Angular
Node.js apps
Configure Deployment

Set your project name, subdomain, and build configuration.

SettingTypeDescription
Project NameRequiredDisplay name shown in the dashboard
SubdomainRequiredYour site will be live at yourapp.gss-tec.com
Build CommandRequirede.g. npm run build — leave blank for plain static sites
Output DirectoryRequirede.g. dist or build — leave blank for repo root
Environment VariablesOptionalKey/value pairs injected at build time
Custom DomainOptionalMap your own domain (CNAME/A record setup required)
Auto-deploy on PushOptionalRedeploy automatically on every git push to the linked branch
Deploy & Monitor

Click Deploy from Git to launch your application. Real-time build logs and a live preview URL are available immediately after the build completes.

Best Practices

Repository Structure

PracticeDetails
Use index.html as entry pointAlways ensure your main page is named index.html — the platform serves this file first
Organize assets with relative pathsUse relative paths for CSS, JS, and images so they resolve correctly on any subdomain
Optimize file sizesCompress images and minify CSS/JS for faster global edge delivery

Deployment Tips

TipDetails
Test locally firstVerify your site works locally (npx serve . or a local dev server) before connecting to QSSN PaaS
Use meaningful commit messagesClear commits make it easy to trace which deployment introduced a change via the build history
Monitor build logsCheck the real-time build log in the dashboard if a deployment fails — error messages pinpoint the exact step
✅
Enable Auto-deploy to ship every git push automatically. Combined with a proper branching strategy (main = production), this gives you a zero-touch CI/CD pipeline with no extra tooling.

Need Help?

The QSSN PaaS support team is available to help you succeed with your deployment.

ChannelDetails
Email supportsupport@gss-tec.com
UGA HOST supportugahost@gss-tec.com

About Gaston Software Solutions LLP

DetailInfo
CompanyGaston Software Solutions LLP
Registration No.80041130611335
LocationKampala, Uganda
Websitewww.gss-tec.com
WhatsApp+256 755 274 944
LinkedInGaston Software Solutions LLP
Website WhatsApp LinkedIn