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