commit
e8dd3b9f90
14 changed files with 409 additions and 0 deletions
@ -0,0 +1,9 @@
|
||||
FLASK_APP=run.py |
||||
FLASK_ENV=development |
||||
SECRET_KEY=change-me |
||||
MARIADB_HOST=127.0.0.1 |
||||
MARIADB_PORT=3306 |
||||
MARIADB_DB=otb_cloud |
||||
MARIADB_USER=otb_cloud |
||||
MARIADB_PASSWORD=change-me |
||||
STORAGE_ROOT=/tank/backups/otb-cloud |
||||
@ -0,0 +1,59 @@
|
||||
# Python |
||||
__pycache__/ |
||||
*.pyc |
||||
*.pyo |
||||
*.pyd |
||||
.pytest_cache/ |
||||
.mypy_cache/ |
||||
.ruff_cache/ |
||||
.coverage |
||||
htmlcov/ |
||||
|
||||
# Virtual environments |
||||
venv/ |
||||
.venv/ |
||||
env/ |
||||
|
||||
# Flask / runtime |
||||
instance/*.db |
||||
instance/*.sqlite |
||||
instance/*.sqlite3 |
||||
*.log |
||||
*.pid |
||||
tmp/ |
||||
uploads_tmp/ |
||||
|
||||
# Secrets / env |
||||
.env |
||||
.env.* |
||||
!.env.example |
||||
|
||||
# Build artifacts |
||||
dist/ |
||||
build/ |
||||
*.egg-info/ |
||||
|
||||
# Editors / OS |
||||
.vscode/ |
||||
.idea/ |
||||
*.swp |
||||
*~ |
||||
.DS_Store |
||||
Thumbs.db |
||||
|
||||
# Backups / scratch |
||||
*.bak |
||||
*.old |
||||
*.orig |
||||
|
||||
# Generated exports / tarballs |
||||
exports/ |
||||
tarballs/ |
||||
|
||||
# Android (future) |
||||
android/.gradle/ |
||||
android/local.properties |
||||
android/app/build/ |
||||
|
||||
# Node (future) |
||||
node_modules/ |
||||
@ -0,0 +1,71 @@
|
||||
# PROJECT_STATE.md |
||||
|
||||
## Project |
||||
OTB Cloud |
||||
|
||||
## Current version |
||||
v0.1.0 |
||||
|
||||
## Build date |
||||
2026-04-12 |
||||
|
||||
## Host |
||||
vault3 |
||||
|
||||
## App path |
||||
/opt/otb_cloud |
||||
|
||||
## Purpose |
||||
Portal-authenticated secure backup and storage platform for customer files, including images, videos, documents, and other uploaded data. |
||||
|
||||
## Core requirements locked in |
||||
- Shared OTB branding, nav, footer, favicon |
||||
- Portal login / auth handoff through OTB Billing |
||||
- No unauthenticated file/account access |
||||
- MariaDB backend |
||||
- Vault3 storage root at `/tank/backups/otb-cloud` |
||||
- Tenant-isolated storage |
||||
- Device-defined source directories |
||||
- Immutable originals |
||||
- Derived-file processing workflow |
||||
- Search by filename and date |
||||
- Bulk zip export |
||||
- Audit logging |
||||
- Owner-approved admin support access using one-time token |
||||
|
||||
## Device organization model |
||||
Per-tenant storage will be organized by named devices, for example: |
||||
- laptop |
||||
- phone |
||||
- tablet |
||||
- workpc |
||||
- homepc |
||||
|
||||
Each device should have: |
||||
- originals/ |
||||
- derived/ |
||||
- exports/ |
||||
- deleted/ |
||||
- tmp/ |
||||
|
||||
## Initial app modules planned |
||||
- auth |
||||
- main |
||||
- files |
||||
- jobs |
||||
- admin |
||||
- audit |
||||
- services |
||||
- models |
||||
|
||||
## Immediate next tasks |
||||
1. Add Flask app factory and blueprint registration |
||||
2. Add MariaDB config and SQL bootstrap schema |
||||
3. Add shared portal template integration |
||||
4. Add storage bootstrap script for vault3 |
||||
5. Add service card integration plan for OTB Billing portal |
||||
|
||||
## Notes |
||||
Original uploaded files should remain preserved and effectively read-only. |
||||
Any user-facing edits or processing outputs should create derivative files. |
||||
Admin access should require owner-issued one-time support authorization. |
||||
@ -0,0 +1,64 @@
|
||||
# OTB Cloud |
||||
|
||||
## v0.1.0 - 2026-04-12 |
||||
- Initial scaffold created on vault3 at /opt/otb_cloud |
||||
- MariaDB-backed architecture selected |
||||
- Modular Flask app structure created |
||||
- Device-based tenant storage model defined |
||||
- Shared OTB portal template architecture planned |
||||
- Core project documentation files added |
||||
|
||||
--- |
||||
|
||||
## Summary |
||||
OTB Cloud is a private portal-authenticated backup and storage platform for Outsidethebox.top. |
||||
|
||||
Primary goals: |
||||
- Secure backup and storage for documents, images, videos, and uploaded files |
||||
- Per-customer tenant isolation |
||||
- Device-based organization (laptop, phone, tablet, workpc, homepc, etc.) |
||||
- Immutable original uploads |
||||
- Derived file workflow for processing and edits |
||||
- Searchable file library |
||||
- Bulk upload and bulk export support |
||||
- Audit logging |
||||
- Owner-approved admin support access using one-time token workflow |
||||
|
||||
## Planned host and path |
||||
- Host: vault3 |
||||
- App path: `/opt/otb_cloud` |
||||
- Domain: `otb-cloud.outsidethebox.top` |
||||
- Storage root: `/tank/backups/otb-cloud` |
||||
|
||||
## Planned backend stack |
||||
- Flask |
||||
- MariaDB |
||||
- Jinja templates with shared portal base |
||||
- Background job processing for media conversions |
||||
- FFmpeg for video/audio processing |
||||
- Nginx reverse proxy |
||||
|
||||
## Security goals |
||||
- Portal-authenticated access only |
||||
- No unauthenticated file access |
||||
- Tenant-isolated storage and database access |
||||
- Audit logging for login attempts and file actions |
||||
- Encrypted storage at rest |
||||
- HTTPS/TLS in transit |
||||
- Immutable originals by default |
||||
|
||||
## Device model |
||||
Each tenant may define logical upload devices such as: |
||||
- laptop |
||||
- phone |
||||
- tablet |
||||
- workpc |
||||
- homepc |
||||
|
||||
Uploads are organized by source device to preserve context. |
||||
|
||||
## Documentation policy |
||||
This repository uses: |
||||
- `README.md` for version/change log summary |
||||
- `PROJECT_STATE.md` for current status and working notes |
||||
- `VERSION` for current version |
||||
@ -0,0 +1,23 @@
|
||||
import os |
||||
from flask import Flask |
||||
from dotenv import load_dotenv |
||||
|
||||
def create_app(): |
||||
load_dotenv() |
||||
|
||||
app = Flask(__name__, instance_relative_config=True) |
||||
|
||||
app.config.from_mapping( |
||||
SECRET_KEY=os.getenv("SECRET_KEY", "change-me"), |
||||
MARIADB_HOST=os.getenv("MARIADB_HOST", "127.0.0.1"), |
||||
MARIADB_PORT=int(os.getenv("MARIADB_PORT", "3306")), |
||||
MARIADB_DB=os.getenv("MARIADB_DB", "otb_cloud"), |
||||
MARIADB_USER=os.getenv("MARIADB_USER", "otb_cloud"), |
||||
MARIADB_PASSWORD=os.getenv("MARIADB_PASSWORD", "change-me"), |
||||
STORAGE_ROOT=os.getenv("STORAGE_ROOT", "/tank/backups/otb-cloud"), |
||||
) |
||||
|
||||
from .main.routes import bp as main_bp |
||||
app.register_blueprint(main_bp) |
||||
|
||||
return app |
||||
@ -0,0 +1,7 @@
|
||||
from flask import Blueprint, render_template |
||||
|
||||
bp = Blueprint("main", __name__) |
||||
|
||||
@bp.route("/") |
||||
def index(): |
||||
return render_template("cloud/index.html") |
||||
@ -0,0 +1 @@
|
||||
# SQL schema is currently managed in app/models/schema.sql |
||||
@ -0,0 +1,103 @@
|
||||
CREATE TABLE IF NOT EXISTS users ( |
||||
id INT AUTO_INCREMENT PRIMARY KEY, |
||||
portal_user_id INT NULL, |
||||
email VARCHAR(255) NOT NULL UNIQUE, |
||||
display_name VARCHAR(255) NULL, |
||||
is_active TINYINT(1) NOT NULL DEFAULT 1, |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
last_login_at DATETIME NULL |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS tenants ( |
||||
id INT AUTO_INCREMENT PRIMARY KEY, |
||||
owner_user_id INT NOT NULL, |
||||
slug VARCHAR(100) NOT NULL UNIQUE, |
||||
storage_root VARCHAR(500) NOT NULL, |
||||
service_status VARCHAR(50) NOT NULL DEFAULT 'active', |
||||
retention_mode VARCHAR(50) NOT NULL DEFAULT 'standard', |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
CONSTRAINT fk_tenants_owner FOREIGN KEY (owner_user_id) REFERENCES users(id) |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS devices ( |
||||
id INT AUTO_INCREMENT PRIMARY KEY, |
||||
tenant_id INT NOT NULL, |
||||
device_name VARCHAR(100) NOT NULL, |
||||
device_type VARCHAR(50) NOT NULL, |
||||
relative_path VARCHAR(255) NOT NULL, |
||||
is_active TINYINT(1) NOT NULL DEFAULT 1, |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
CONSTRAINT uq_devices_tenant_name UNIQUE (tenant_id, device_name), |
||||
CONSTRAINT fk_devices_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id) |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS files ( |
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
||||
tenant_id INT NOT NULL, |
||||
device_id INT NOT NULL, |
||||
parent_file_id BIGINT NULL, |
||||
file_kind VARCHAR(20) NOT NULL, |
||||
relative_path VARCHAR(1000) NOT NULL, |
||||
directory_path VARCHAR(1000) NOT NULL, |
||||
original_filename VARCHAR(255) NOT NULL, |
||||
basename VARCHAR(255) NOT NULL, |
||||
extension VARCHAR(50) NOT NULL, |
||||
mime_type VARCHAR(255) NULL, |
||||
size_bytes BIGINT NOT NULL DEFAULT 0, |
||||
sha256 CHAR(64) NULL, |
||||
capture_date DATETIME NULL, |
||||
uploaded_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
is_immutable TINYINT(1) NOT NULL DEFAULT 1, |
||||
is_deleted TINYINT(1) NOT NULL DEFAULT 0, |
||||
deleted_at DATETIME NULL, |
||||
CONSTRAINT fk_files_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id), |
||||
CONSTRAINT fk_files_device FOREIGN KEY (device_id) REFERENCES devices(id), |
||||
CONSTRAINT fk_files_parent FOREIGN KEY (parent_file_id) REFERENCES files(id) |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS jobs ( |
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
||||
tenant_id INT NOT NULL, |
||||
file_id BIGINT NOT NULL, |
||||
job_type VARCHAR(100) NOT NULL, |
||||
options_json LONGTEXT NULL, |
||||
status VARCHAR(50) NOT NULL DEFAULT 'queued', |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
started_at DATETIME NULL, |
||||
completed_at DATETIME NULL, |
||||
output_file_id BIGINT NULL, |
||||
log_text LONGTEXT NULL, |
||||
CONSTRAINT fk_jobs_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id), |
||||
CONSTRAINT fk_jobs_file FOREIGN KEY (file_id) REFERENCES files(id) |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs ( |
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
||||
tenant_id INT NULL, |
||||
user_id INT NULL, |
||||
actor_type VARCHAR(20) NOT NULL, |
||||
event_type VARCHAR(100) NOT NULL, |
||||
file_id BIGINT NULL, |
||||
job_id BIGINT NULL, |
||||
ip_address VARCHAR(64) NULL, |
||||
user_agent VARCHAR(500) NULL, |
||||
event_detail LONGTEXT NULL, |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
INDEX idx_audit_tenant_created (tenant_id, created_at), |
||||
INDEX idx_audit_event_type (event_type) |
||||
); |
||||
|
||||
CREATE TABLE IF NOT EXISTS admin_access_tokens ( |
||||
id BIGINT AUTO_INCREMENT PRIMARY KEY, |
||||
tenant_id INT NOT NULL, |
||||
issued_by_user_id INT NOT NULL, |
||||
used_by_admin_id INT NULL, |
||||
token_hash CHAR(64) NOT NULL, |
||||
purpose VARCHAR(255) NOT NULL, |
||||
status VARCHAR(50) NOT NULL DEFAULT 'issued', |
||||
expires_at DATETIME NOT NULL, |
||||
used_at DATETIME NULL, |
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
||||
CONSTRAINT fk_admin_token_tenant FOREIGN KEY (tenant_id) REFERENCES tenants(id), |
||||
CONSTRAINT fk_admin_token_owner FOREIGN KEY (issued_by_user_id) REFERENCES users(id) |
||||
); |
||||
@ -0,0 +1,19 @@
|
||||
{% extends "portal_base.html" %} |
||||
|
||||
{% block title %}OTB Cloud{% endblock %} |
||||
|
||||
{% block content %} |
||||
<div class="container py-4"> |
||||
<div class="card shadow-sm rounded-4"> |
||||
<div class="card-body"> |
||||
<h1 class="h3 mb-3">OTB Cloud</h1> |
||||
<p class="mb-2"> |
||||
Private backup and storage platform for customer files, device uploads, and media processing. |
||||
</p> |
||||
<p class="mb-0 text-muted"> |
||||
Initial scaffold is in place. Portal auth handoff, tenant isolation, searchable library, and processing jobs come next. |
||||
</p> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
{% endblock %} |
||||
@ -0,0 +1,3 @@
|
||||
Flask==3.1.0 |
||||
PyMySQL==1.1.1 |
||||
python-dotenv==1.0.1 |
||||
@ -0,0 +1,6 @@
|
||||
from app import create_app |
||||
|
||||
app = create_app() |
||||
|
||||
if __name__ == "__main__": |
||||
app.run(host="127.0.0.1", port=5090, debug=True) |
||||
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash |
||||
set -e |
||||
|
||||
BASE="/tank/backups/otb-cloud" |
||||
|
||||
echo "===== ensuring base storage root exists =====" |
||||
sudo mkdir -p "$BASE/tenants" |
||||
sudo mkdir -p "$BASE/system/job-output" |
||||
sudo mkdir -p "$BASE/system/logs" |
||||
sudo mkdir -p "$BASE/system/quarantine" |
||||
|
||||
echo "===== base storage root prepared =====" |
||||
echo "===== script completed successfully =====" |
||||
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash |
||||
set -e |
||||
|
||||
if [ $# -lt 2 ]; then |
||||
echo "usage: $0 <tenant_slug> <device_name> [device_name ...]" |
||||
exit 1 |
||||
fi |
||||
|
||||
BASE="/tank/backups/otb-cloud/tenants" |
||||
TENANT="$1" |
||||
shift |
||||
|
||||
TENANT_ROOT="$BASE/$TENANT" |
||||
|
||||
echo "===== creating tenant root for $TENANT =====" |
||||
sudo mkdir -p "$TENANT_ROOT" |
||||
|
||||
for DEVICE in "$@"; do |
||||
sudo mkdir -p "$TENANT_ROOT/devices/$DEVICE/originals" |
||||
sudo mkdir -p "$TENANT_ROOT/devices/$DEVICE/derived" |
||||
sudo mkdir -p "$TENANT_ROOT/devices/$DEVICE/exports" |
||||
sudo mkdir -p "$TENANT_ROOT/devices/$DEVICE/deleted" |
||||
sudo mkdir -p "$TENANT_ROOT/devices/$DEVICE/tmp" |
||||
done |
||||
|
||||
sudo mkdir -p "$TENANT_ROOT/logs" |
||||
sudo mkdir -p "$TENANT_ROOT/support" |
||||
|
||||
echo "===== tenant layout created =====" |
||||
echo "===== script completed successfully =====" |
||||
Loading…
Reference in new issue