tabby-web testing

This commit is contained in:
mjallen18
2025-09-03 20:01:19 -05:00
parent bb96cf2406
commit d2c60d8157
6 changed files with 681 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
# Tabby Web Service Module
This module provides a NixOS service for running the Tabby Web terminal application server.
## Features
- Systemd service with automatic startup
- User and group management
- Database migration on startup
- Configurable environment variables
- Security hardening
- Firewall integration
- Support for PostgreSQL and SQLite databases
- Social authentication configuration
## Basic Usage
```nix
{
mjallen.services.tabby-web = {
enable = true;
port = 9000;
openFirewall = true;
};
}
```
## Advanced Configuration
```nix
{
mjallen.services.tabby-web = {
enable = true;
port = 8080;
openFirewall = true;
# Use PostgreSQL instead of SQLite
databaseUrl = "postgresql://tabby:password@localhost:5432/tabby";
# Use S3 for app distribution storage
appDistStorage = "s3://my-bucket/tabby-dist";
# Configure social authentication
socialAuth = {
github = {
key = "your-github-oauth-key";
secret = "your-github-oauth-secret";
};
gitlab = {
key = "your-gitlab-oauth-key";
secret = "your-gitlab-oauth-secret";
};
};
# Performance tuning
workers = 8;
timeout = 300;
# Additional environment variables
extraEnvironment = {
DEBUG = "0";
LOG_LEVEL = "info";
};
};
}
```
## Configuration Options
### Basic Options
- `enable`: Enable the tabby-web service
- `port`: Port to run the server on (default: 9000)
- `openFirewall`: Whether to open the firewall port (default: false)
- `user`: User to run the service as (default: "tabby-web")
- `group`: Group to run the service as (default: "tabby-web")
- `dataDir`: Data directory (default: "/var/lib/tabby-web")
### Database Configuration
- `databaseUrl`: Database connection URL
- SQLite: `"sqlite:///var/lib/tabby-web/tabby.db"` (default)
- PostgreSQL: `"postgresql://user:password@host:port/database"`
### Storage Configuration
- `appDistStorage`: Storage URL for app distributions
- Local: `"file:///var/lib/tabby-web/dist"` (default)
- S3: `"s3://bucket-name/path"`
- GCS: `"gcs://bucket-name/path"`
### Social Authentication
Configure OAuth providers:
```nix
socialAuth = {
github = {
key = "oauth-key";
secret = "oauth-secret";
};
gitlab = {
key = "oauth-key";
secret = "oauth-secret";
};
microsoftGraph = {
key = "oauth-key";
secret = "oauth-secret";
};
googleOauth2 = {
key = "oauth-key";
secret = "oauth-secret";
};
};
```
### Performance Options
- `workers`: Number of gunicorn worker processes (default: 4)
- `timeout`: Worker timeout in seconds (default: 120)
### Additional Configuration
- `extraEnvironment`: Additional environment variables as an attribute set
## Service Management
```bash
# Start the service
sudo systemctl start tabby-web
# Enable automatic startup
sudo systemctl enable tabby-web
# Check service status
sudo systemctl status tabby-web
# View logs
sudo journalctl -u tabby-web -f
# Run management commands
sudo -u tabby-web tabby-web-manage migrate
sudo -u tabby-web tabby-web-manage add_version 1.0.156-nightly.2
```
## Security
The service runs with extensive security hardening:
- Dedicated user and group
- Restricted filesystem access
- No new privileges
- Protected system directories
- Private temporary directory
- Memory execution protection
- Namespace restrictions
## Database Setup
### PostgreSQL
If using PostgreSQL, ensure the database and user exist:
```sql
CREATE USER tabby WITH PASSWORD 'your-password';
CREATE DATABASE tabby OWNER tabby;
```
### SQLite
SQLite databases are created automatically in the data directory.
## Troubleshooting
1. **Service fails to start**: Check logs with `journalctl -u tabby-web`
2. **Database connection issues**: Verify database URL and credentials
3. **Permission errors**: Ensure data directory has correct ownership
4. **Port conflicts**: Check if another service is using the configured port
## Integration with Reverse Proxy
Example Nginx configuration:
```nginx
server {
listen 80;
server_name tabby.example.com;
location / {
proxy_pass http://localhost:9000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@@ -0,0 +1,121 @@
{
config,
lib,
pkgs,
namespace,
...
}:
with lib;
let
cfg = config.${namespace}.services.tabby-web;
# Build environment variables from configuration
environmentVars = {
DATABASE_URL = cfg.databaseUrl;
APP_DIST_STORAGE = cfg.appDistStorage;
PORT = toString cfg.port;
}
// optionalAttrs (cfg.socialAuth.github.key != null) {
SOCIAL_AUTH_GITHUB_KEY = cfg.socialAuth.github.key;
}
// optionalAttrs (cfg.socialAuth.github.secret != null) {
SOCIAL_AUTH_GITHUB_SECRET = cfg.socialAuth.github.secret;
}
// optionalAttrs (cfg.socialAuth.gitlab.key != null) {
SOCIAL_AUTH_GITLAB_KEY = cfg.socialAuth.gitlab.key;
}
// optionalAttrs (cfg.socialAuth.gitlab.secret != null) {
SOCIAL_AUTH_GITLAB_SECRET = cfg.socialAuth.gitlab.secret;
}
// optionalAttrs (cfg.socialAuth.microsoftGraph.key != null) {
SOCIAL_AUTH_MICROSOFT_GRAPH_KEY = cfg.socialAuth.microsoftGraph.key;
}
// optionalAttrs (cfg.socialAuth.microsoftGraph.secret != null) {
SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET = cfg.socialAuth.microsoftGraph.secret;
}
// optionalAttrs (cfg.socialAuth.googleOauth2.key != null) {
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = cfg.socialAuth.googleOauth2.key;
}
// optionalAttrs (cfg.socialAuth.googleOauth2.secret != null) {
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = cfg.socialAuth.googleOauth2.secret;
}
// cfg.extraEnvironment;
in
{
imports = [ ./options.nix ];
config = mkIf cfg.enable {
# Create user and group
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
home = cfg.dataDir;
createHome = true;
description = "Tabby Web service user";
};
users.groups.${cfg.group} = { };
# Ensure data directory exists with correct permissions
systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
"d '${cfg.dataDir}/dist' 0750 ${cfg.user} ${cfg.group} - -"
];
# Create the systemd service
systemd.services.tabby-web = {
description = "Tabby Web Terminal Application Server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ] ++ optional (hasPrefix "postgresql://" cfg.databaseUrl) "postgresql.service";
environment = environmentVars;
serviceConfig = {
Type = "exec";
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.dataDir;
# Use the tabby-web package from our custom packages
ExecStart = "${pkgs.${namespace}.tabby-web}/bin/tabby-web --workers ${toString cfg.workers} --timeout ${toString cfg.timeout}";
# Run database migrations before starting the service
ExecStartPre = "${pkgs.${namespace}.tabby-web}/bin/tabby-web-manage migrate";
# Security settings
NoNewPrivileges = true;
ProtectSystem = "strict";
ProtectHome = true;
ReadWritePaths = [ cfg.dataDir ];
PrivateTmp = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictSUIDSGID = true;
RestrictRealtime = true;
RestrictNamespaces = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
# Restart policy
Restart = "always";
RestartSec = "10s";
# Resource limits
LimitNOFILE = "65536";
};
# Ensure the service starts after database if using PostgreSQL
requisite = mkIf (hasPrefix "postgresql://" cfg.databaseUrl) [ "postgresql.service" ];
};
# Open firewall if requested
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
# Add the tabby-web package to system packages
environment.systemPackages = [ pkgs.${namespace}.tabby-web ];
};
}

View File

@@ -0,0 +1,45 @@
# Example configuration for Tabby Web service
# Add this to your NixOS configuration to enable tabby-web
{
# Basic configuration - SQLite database, local storage
mjallen.services.tabby-web = {
enable = true;
port = 9000;
openFirewall = true;
};
# Advanced configuration example (commented out)
/*
mjallen.services.tabby-web = {
enable = true;
port = 8080;
openFirewall = true;
# Use PostgreSQL database
databaseUrl = "postgresql://tabby:password@localhost:5432/tabby";
# Use S3 for app distribution storage
appDistStorage = "s3://my-bucket/tabby-dist";
# Configure GitHub OAuth
socialAuth.github = {
key = "your-github-oauth-key";
secret = "your-github-oauth-secret";
};
# Performance tuning
workers = 8;
timeout = 300;
# Custom data directory
dataDir = "/srv/tabby-web";
# Additional environment variables
extraEnvironment = {
DEBUG = "0";
LOG_LEVEL = "info";
};
};
*/
}

View File

@@ -0,0 +1,127 @@
{ lib, namespace, ... }:
with lib;
{
options.${namespace}.services.tabby-web = {
enable = mkEnableOption "Tabby Web terminal application server";
port = mkOption {
type = types.port;
default = 9000;
description = "Port for tabby-web server";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Whether to open firewall for tabby-web";
};
user = mkOption {
type = types.str;
default = "tabby-web";
description = "User to run tabby-web as";
};
group = mkOption {
type = types.str;
default = "tabby-web";
description = "Group to run tabby-web as";
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/tabby-web";
description = "Directory to store tabby-web data";
};
databaseUrl = mkOption {
type = types.str;
default = "sqlite:///var/lib/tabby-web/tabby.db";
description = "Database connection URL";
example = "postgresql://user:password@localhost:5432/tabby";
};
appDistStorage = mkOption {
type = types.str;
default = "file:///var/lib/tabby-web/dist";
description = "Storage URL for app distributions";
example = "s3://my-bucket/tabby-dist";
};
socialAuth = {
github = {
key = mkOption {
type = types.nullOr types.str;
default = null;
description = "GitHub OAuth key";
};
secret = mkOption {
type = types.nullOr types.str;
default = null;
description = "GitHub OAuth secret";
};
};
gitlab = {
key = mkOption {
type = types.nullOr types.str;
default = null;
description = "GitLab OAuth key";
};
secret = mkOption {
type = types.nullOr types.str;
default = null;
description = "GitLab OAuth secret";
};
};
microsoftGraph = {
key = mkOption {
type = types.nullOr types.str;
default = null;
description = "Microsoft Graph OAuth key";
};
secret = mkOption {
type = types.nullOr types.str;
default = null;
description = "Microsoft Graph OAuth secret";
};
};
googleOauth2 = {
key = mkOption {
type = types.nullOr types.str;
default = null;
description = "Google OAuth2 key";
};
secret = mkOption {
type = types.nullOr types.str;
default = null;
description = "Google OAuth2 secret";
};
};
};
extraEnvironment = mkOption {
type = types.attrsOf types.str;
default = { };
description = "Extra environment variables for tabby-web";
example = {
DEBUG = "1";
LOG_LEVEL = "info";
};
};
workers = mkOption {
type = types.ints.positive;
default = 4;
description = "Number of gunicorn worker processes";
};
timeout = mkOption {
type = types.ints.positive;
default = 120;
description = "Worker timeout in seconds";
};
};
}

View File

@@ -0,0 +1,186 @@
{
lib,
stdenv,
fetchFromGitHub,
nodejs,
yarn,
python3,
poetry,
makeWrapper,
fetchYarnDeps,
fixup-yarn-lock,
...
}:
stdenv.mkDerivation rec {
pname = "tabby-web";
version = "0.0.1";
src = fetchFromGitHub {
owner = "Eugeny";
repo = "tabby-web";
rev = "16847cea93f730814c1855241d8ebdea20b1ff6e";
sha256 = "sha256-FaVJdizSQq600awY9HAwMNv6vpcjLVAVqdWnn+sYAxk=";
};
# Fetch yarn dependencies separately for reproducibility
yarnDeps = fetchYarnDeps {
yarnLock = "${src}/frontend/yarn.lock";
hash = "sha256-NInsyKgp2+ppHJZLFn3qKW08rvSSIShhh2JbR91WgOk=";
};
nativeBuildInputs = [
nodejs
yarn
python3
poetry
makeWrapper
fixup-yarn-lock
];
buildInputs = [
python3
];
propagatedBuildInputs = with python3.pkgs; [
gunicorn
django
];
configurePhase = ''
runHook preConfigure
# Set up yarn
export HOME=$TMPDIR
cd frontend
# Fix up yarn.lock and set up offline cache
fixup-yarn-lock yarn.lock
yarn config --offline set yarn-offline-mirror ${yarnDeps}
yarn install --offline --frozen-lockfile --ignore-platform --ignore-scripts --no-progress --non-interactive
patchShebangs node_modules/
cd ..
# Set up poetry
export POETRY_CACHE_DIR=$TMPDIR/poetry-cache
export POETRY_VENV_IN_PROJECT=1
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
echo "Building frontend..."
cd frontend
yarn run build
cd ..
echo "Backend is ready (dependencies will be handled by Nix)"
runHook postBuild
'';
installPhase = ''
runHook preInstall
# Create output directories
mkdir -p $out/lib/tabby-web
mkdir -p $out/bin
mkdir -p $out/share/tabby-web
# Install backend
cp -r backend/* $out/lib/tabby-web/
# Install frontend build output
if [ -d frontend/dist ]; then
cp -r frontend/dist/* $out/share/tabby-web/
elif [ -d frontend/build ]; then
cp -r frontend/build/* $out/share/tabby-web/
fi
# Create main executable wrapper
makeWrapper ${python3.withPackages (ps: with ps; [ gunicorn django ])}/bin/python $out/bin/tabby-web \
--add-flags "-m gunicorn tabby_web.wsgi:application" \
--set PYTHONPATH "$out/lib/tabby-web" \
--set DJANGO_SETTINGS_MODULE "tabby_web.settings" \
--set STATIC_ROOT "$out/share/tabby-web" \
--run "cd $out/lib/tabby-web" \
--run 'export DATABASE_URL="''${DATABASE_URL:-sqlite:///tmp/tabby-web.db}"' \
--run 'export APP_DIST_STORAGE="''${APP_DIST_STORAGE:-file:///tmp/tabby-web-dist}"' \
--run 'export PORT="''${PORT:-9000}"' \
--add-flags '--bind "0.0.0.0:$PORT"' \
--add-flags "--workers 4" \
--add-flags "--timeout 120"
# Create Django management wrapper
makeWrapper ${python3.withPackages (ps: with ps; [ django ])}/bin/python $out/bin/tabby-web-manage \
--add-flags "manage.py" \
--set PYTHONPATH "$out/lib/tabby-web" \
--set DJANGO_SETTINGS_MODULE "tabby_web.settings" \
--set STATIC_ROOT "$out/share/tabby-web" \
--run "cd $out/lib/tabby-web" \
--run 'export DATABASE_URL="''${DATABASE_URL:-sqlite:///tmp/tabby-web.db}"' \
--run 'export APP_DIST_STORAGE="''${APP_DIST_STORAGE:-file:///tmp/tabby-web-dist}"'
# Create a help script
cat > $out/bin/tabby-web-help << 'HELP_EOF'
#!/bin/bash
cat << 'HELP'
Tabby Web - Terminal application server
Usage:
tabby-web Start the server
tabby-web-manage <command> Run Django management commands
tabby-web-help Show this help
Environment Variables:
DATABASE_URL Database connection URL
Examples: sqlite:///path/to/db.sqlite
postgresql://user:pass@host:5432/dbname
APP_DIST_STORAGE Storage URL for app distributions
Examples: file:///path/to/storage
s3://bucket-name/path
gcs://bucket-name/path
PORT Server port (default: 9000)
Social Authentication (optional):
SOCIAL_AUTH_GITHUB_KEY GitHub OAuth key
SOCIAL_AUTH_GITHUB_SECRET GitHub OAuth secret
SOCIAL_AUTH_GITLAB_KEY GitLab OAuth key
SOCIAL_AUTH_GITLAB_SECRET GitLab OAuth secret
SOCIAL_AUTH_MICROSOFT_GRAPH_KEY Microsoft Graph OAuth key
SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET Microsoft Graph OAuth secret
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY Google OAuth2 key
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET Google OAuth2 secret
Examples:
# Development with defaults
tabby-web
# Production with PostgreSQL
DATABASE_URL="postgresql://user:pass@localhost:5432/tabby" tabby-web
# Run migrations
tabby-web-manage migrate
# Add app version
tabby-web-manage add_version 1.0.156-nightly.2
HELP
HELP_EOF
chmod +x $out/bin/tabby-web-help
runHook postInstall
'';
meta = with lib; {
description = "Web-based terminal application";
homepage = "https://github.com/Eugeny/tabby-web";
license = licenses.mit;
maintainers = [ ];
platforms = platforms.linux ++ platforms.darwin;
};
}

View File

@@ -151,6 +151,12 @@
htpasswdFile = "/media/nas/main/backup/restic/.htpasswd"; htpasswdFile = "/media/nas/main/backup/restic/.htpasswd";
extraFlags = [ "--no-auth" ]; extraFlags = [ "--no-auth" ];
}; };
tabby-web = {
enable = true;
port = 9000;
openFirewall = true;
};
}; };
}; };
} }