← Back to Modules

dbbasic-accounts Specification

Version: 1.0 Status: Specification Author: DBBasic Project Date: October 2025

Links: - PyPI: https://pypi.org/project/dbbasic-accounts/ - GitHub: https://github.com/askrobots/dbbasic-accounts - Specification: http://dbbasic.com/accounts-spec


Philosophy

"The web forked from Unix. Let's merge it back."

The critical insight: Unix already solved user management, authentication, and permissions. The web abandoned it and reinvented everything poorly. What if we just... used Unix?

Design Principles

  1. Mirror Unix: /etc/passwd, /etc/shadow, /etc/group - don't reinvent
  2. Stay Compatible: Unix commands should work
  3. Web-Friendly: Add convenience methods for web developers
  4. Docker-Native: Each app is a Unix system with users
  5. No Fork: Don't diverge from Unix, extend it

The Fork: How We Got Here

1970s-1990s: Unix User Management Works

# /etc/passwd
john:x:1000:100:John Doe:/home/john:/bin/bash

# /etc/shadow (passwords)
john:$6$salt$hash:19000:0:99999:7:::

# /etc/group
editors:x:101:john,jane
admins:x:102:john

Every Unix app used this: - Email (sendmail) → mail to john@hostname - FTP → login with passwd credentials - SSH → same credentials - CGI scripts → same credentials - Forums, wikis → same credentials

One user database for everything.

Late 1990s: Web Apps Fork Away

The problem: Shared hosting

Server has 1000 websites
Each site needs different users
Can't use /etc/passwd (would conflict)

The "solution": Every app invents its own user system

WordPress → wp_users table
phpBB → phpbb_users table
Drupal → drupal_users table
Custom apps → users table (everyone invents their own)

The loss: - No standard user database - Can't share users across apps - Every app reinvents authentication - No Unix tools work (can't use passwd, groups, etc.)

2000s-2010s: The Reinvention Era

Everyone builds their own: - Django: User model, auth framework - Rails: Devise, Authlogic, etc. - Laravel: Built-in auth - Express: Passport.js (100+ strategies)

Each slightly different, none Unix-compatible.

2025: Docker Changes Everything

Docker = isolated Unix system per app

Each container has its own:
- /etc/passwd
- /etc/shadow
- /etc/group

No conflicts anymore! Each web app can have its own /etc/passwd without conflicting.

We can return to Unix.


The Vision: Unix + Web

What This Looks Like

Your web app IS a Unix system:

myapp/ (Docker container or directory)
├── etc/
│   ├── passwd.tsv      # Users (like /etc/passwd)
│   ├── shadow.tsv      # Passwords (like /etc/shadow)
│   └── group.tsv       # Groups/roles (like /etc/group)
├── home/               # User directories
│   ├── john/           # User john's files
│   │   ├── profile.jpg
│   │   └── uploads/
│   └── jane/
├── var/
│   ├── mail/           # Email spool (user inboxes)
│   └── log/            # Logs
└── app.py              # Web app

Now Unix tools work:

# List users
cat etc/passwd.tsv

# Change password
dbpasswd passwd john

# Check groups
dbpasswd groups john

# Send email to user
echo "Hello" | mail john

# View user's files
ls home/john/

# Check user's mailbox
cat var/mail/john

Your web app, Unix commands, and Docker all use the same user database.


How This Works with Docker

Traditional Approach (Current Web Apps)

FROM python:3.11
COPY app.py /app/
# Users stored in MySQL/PostgreSQL/custom database
# No Unix integration

Problems: - Web app users ≠ Unix users - Can't use Unix tools - Everything custom

dbbasic Approach (Unix + Web)

FROM python:3.11

# Web app creates its own user database
COPY app.py /app/
COPY etc/ /app/etc/

# Now Unix tools work with web users
RUN dbpasswd useradd john --fullname "John Doe <john@example.com>"
RUN dbpasswd groupadd editors
RUN dbpasswd usermod john --add-group editors

CMD ["python", "app.py"]

Each container = isolated Unix system with its own users.

No conflicts. No shared /etc/passwd. Each app independent.


The Missing Pieces

What Unix Had

  1. User database - /etc/passwd ✅ (we have this)
  2. Password management - /etc/shadow ✅ (we have this)
  3. Groups - /etc/group ✅ (we have this)
  4. Email - sendmail, local mail ❌ (missing)
  5. Home directories - /home/user ❌ (missing)
  6. Mail spools - /var/mail/user ❌ (missing)

What We Need to Add

For a complete "Unix web system":

# User creates account (web registration)
accounts.register('john@example.com', 'secret', name='John Doe')

# This creates:
# 1. etc/passwd.tsv entry
# 2. etc/shadow.tsv entry
# 3. home/john/ directory (user's files)
# 4. var/mail/john (user's inbox)

# Now user can:
# - Login to web app
# - Upload files to home/john/uploads/
# - Receive mail at var/mail/john
# - Use Unix tools on their data

Everything integrated.


API Specification

Dual API Design

Unix layer (foundation):

from dbbasic_accounts import PasswdDB

passwd = PasswdDB('./etc')
user = passwd.useradd('john', password='secret', fullname='John Doe <john@example.com>')
passwd.usermod('john', add_groups=['editors'])
authenticated = passwd.authenticate('john', 'secret')

Web layer (convenience):

from dbbasic_accounts import Accounts

accounts = Accounts('./etc', domain='example.com')
user = accounts.register('john@example.com', 'secret', name='John Doe')
accounts.add_role('john@example.com', 'editor')
authenticated = accounts.login('john@example.com', 'secret')

Both use the same underlying files.

Core Functions

register(email, password, name='')

Purpose: Register new user (web-friendly)

Parameters: - email (str): Email address (becomes username@domain) - password (str): Plain text password (will be hashed) - name (str): Display name

Returns: - User object

Behavior: 1. Extract username from email (john@example.comjohn) 2. Create entry in etc/passwd.tsv 3. Hash password and store in etc/shadow.tsv 4. Add to 'users' group in etc/group.tsv 5. Create home/{username}/ directory 6. Create var/mail/{username} mailbox 7. Return User object

Example:

user = accounts.register('john@example.com', 'secret123', name='John Doe')
# Creates:
# - etc/passwd.tsv: john, 1000, 100, "John Doe <john@example.com>", /home/john, /bin/bash
# - etc/shadow.tsv: john, $argon2id$..., 2025-10-09
# - etc/group.tsv: users group with john as member
# - home/john/ directory
# - var/mail/john file

login(email, password)

Purpose: Authenticate user (web-friendly)

Parameters: - email (str): Email address - password (str): Plain text password

Returns: - User object if valid, None otherwise

Example:

user = accounts.login('john@example.com', 'secret123')
if user:
    session['user_id'] = user.uid
    session['username'] = user.username

get_user(user_id=None, email=None, username=None)

Purpose: Get user by ID, email, or username

Example:

user = accounts.get_user(user_id=1000)
user = accounts.get_user(email='john@example.com')
user = accounts.get_user(username='john')

Unix Commands (Power Users)

useradd(username, password, fullname, groups)

Standard Unix useradd.

passwd(username, new_password)

Standard Unix passwd.

usermod(username, add_groups, remove_groups)

Standard Unix usermod.

groups(username)

Standard Unix groups command.


File Structure

etc/ directory (Unix-style)

etc/
├── passwd.tsv      # User database (like /etc/passwd)
├── shadow.tsv      # Password hashes (like /etc/shadow, chmod 600)
└── group.tsv       # Groups/roles (like /etc/group)

passwd.tsv:

username    uid gid fullname    homedir shell   created
john    1000    100 John Doe <john@example.com> /home/john  /bin/bash   2025-10-09T10:30:00
jane    1001    100 Jane Smith <jane@example.com>   /home/jane  /bin/bash   2025-10-09T11:00:00

shadow.tsv:

username    password_hash   last_changed    min_age max_age warn_age    inactive
john    $argon2id$v=19$m=65536,t=3,p=4$...  2025-10-09T10:30:00 0   90  7   14
jane    $argon2id$v=19$m=65536,t=3,p=4$...  2025-10-09T11:00:00 0   90  7   14

group.tsv:

groupname   gid members
users   100
editors 101 john,jane
admins  102 john

home/ directory (User files)

home/
├── john/
│   ├── profile.jpg
│   ├── uploads/
│   │   ├── photo1.jpg
│   │   └── document.pdf
│   └── settings.json
└── jane/
    └── avatar.png

Each user has their own directory (like Unix /home)

var/mail/ directory (User mailboxes)

var/mail/
├── john        # Mailbox for john (mbox format)
└── jane        # Mailbox for jane

Internal messaging, notifications, etc.


Integration with Other Modules

dbbasic-sessions Integration

from dbbasic_accounts import Accounts
from dbbasic_sessions import create_session, get_session

accounts = Accounts('./etc')

@app.route('/login', methods=['POST'])
def login():
    user = accounts.login(
        request.form['email'],
        request.form['password']
    )

    if user:
        # Create session (signed cookie)
        token = create_session(user.uid)
        response = redirect('/dashboard')
        response.set_cookie('session', token, httponly=True)
        return response

    return 'Invalid credentials', 401

@app.route('/dashboard')
def dashboard():
    user_id = get_session(request.cookies.get('session'))
    if not user_id:
        return redirect('/login')

    user = accounts.get_user(user_id=user_id)
    return render('dashboard.html', user=user)

dbbasic-email Integration

from dbbasic_accounts import Accounts
from dbbasic_email import send_email

accounts = Accounts('./etc')

# Send to user
user = accounts.get_user(email='john@example.com')
send_email(
    to=user.email,  # Extracted from fullname
    subject='Welcome!',
    body='Thanks for registering'
)

# Or use Unix mail (internal messaging)
accounts.mail('john', 'You have a new message')
# → Appends to var/mail/john

Home Directory Integration

from dbbasic_accounts import Accounts
from dbbasic_upload import save_upload

accounts = Accounts('./etc')
user = accounts.get_user(user_id=current_user_id)

# Save upload to user's home directory
save_upload(
    file=request.files['photo'],
    path=f"{user.homedir}/uploads/profile.jpg"
)
# → Saves to home/john/uploads/profile.jpg

The Unix Web Vision

Traditional Web App

Web App
├── MySQL database (users table)
├── Redis (sessions)
├── S3 (file uploads)
├── SendGrid (email)
└── Custom auth code

Everything separate, nothing integrated.

Unix Web App (dbbasic)

Web App (Unix System)
├── etc/passwd.tsv          # Users
├── home/{user}/            # User files
├── var/mail/{user}         # User mail
├── data/*.tsv              # Application data
└── app.py                  # Web interface to Unix system

Everything integrated, Unix tools work.

What This Enables

Scenario: User Registration

Traditional:

# Insert into database
db.execute("INSERT INTO users ...")
# Send welcome email (external service)
sendgrid.send(email, "Welcome")
# Create S3 bucket for uploads
s3.create_bucket(f"user-{user_id}")

Unix/dbbasic:

# Register user (creates user, home dir, mailbox)
user = accounts.register('john@example.com', 'secret')

# Welcome email (local delivery)
mail('john', 'Welcome to the site!')
# → Appends to var/mail/john

# User uploads
save_file(f'home/{user.username}/uploads/file.jpg')
# → Saves to local filesystem

Everything unified through Unix.


The Missing Pieces

What Unix Gave Us (That We Lost)

1. Unified user database

# One database for all apps
getent passwd john

2. Local email

# Send mail to user
echo "Hello" | mail john
# Read mail
mail

3. User home directories

# Each user has space
ls /home/john

4. Standard tools

# Change password
passwd
# Check groups
groups
# Who's logged in
w, who

5. Permissions

# File ownership
chown john:editors file.txt
chmod 640 file.txt

What We Need to Restore

For dbbasic to be "Unix for web apps":

  1. User database - dbbasic-accounts (passwd.tsv, shadow.tsv, group.tsv)
  2. Local mail - dbbasic-mail (var/mail/* mailboxes)
  3. Home directories - Automatic creation on register
  4. Standard tools - dbpasswd, dbmail, etc.
  5. ⚠️ Permissions - File ownership (use filesystem)

Implementation

Core Structure

# dbbasic_accounts/__init__.py
from .passwd import PasswdDB, User
from .accounts import Accounts

__all__ = ['PasswdDB', 'User', 'Accounts']

PasswdDB Class (Unix Layer)

class PasswdDB:
    """
    Unix-style user management.

    Mirrors /etc/passwd, /etc/shadow, /etc/group
    """

    def __init__(self, etc_dir='./etc'):
        self.etc_dir = Path(etc_dir)
        self.passwd_path = self.etc_dir / 'passwd.tsv'
        self.shadow_path = self.etc_dir / 'shadow.tsv'
        self.group_path = self.etc_dir / 'group.tsv'

        self._init_files()
        self.ph = PasswordHasher()  # Argon2id

    def useradd(self, username, password, fullname='', homedir=None, shell='/bin/bash', groups=None):
        """Add user (like Unix useradd)"""
        uid = self._next_uid()
        gid = 100  # users group
        homedir = homedir or f'/home/{username}'

        # Add to passwd.tsv
        append(self.passwd_path, [username, uid, gid, fullname, homedir, shell, now()])

        # Hash password and add to shadow.tsv
        password_hash = self.ph.hash(password)
        append(self.shadow_path, [username, password_hash, now(), 0, 90, 7, 14])

        # Create home directory
        Path(f'.{homedir}').mkdir(parents=True, exist_ok=True)

        # Add to groups
        if groups:
            for group in groups:
                self.usermod(username, add_groups=[group])

        return User(username, uid, gid, fullname, homedir, shell, now())

    def authenticate(self, username, password):
        """Authenticate user"""
        # Get hash from shadow.tsv
        shadow = get(self.shadow_path, username=username)
        if not shadow:
            return None

        # Verify password
        try:
            self.ph.verify(shadow.password_hash, password)
            return self.getuser(username)
        except VerifyMismatchError:
            return None

    def passwd(self, username, new_password):
        """Change password (like Unix passwd)"""
        password_hash = self.ph.hash(new_password)
        update(self.shadow_path,
            where={'username': username},
            values={'password_hash': password_hash, 'last_changed': now()}
        )

    def groups(self, username):
        """Get user's groups (like Unix groups command)"""
        user = self.getuser(username)
        groups = []

        for group in get_all(self.group_path):
            # Primary group
            if group.gid == user.gid:
                groups.append(group.groupname)
            # Member of group
            elif username in group.members.split(','):
                groups.append(group.groupname)

        return groups

Accounts Class (Web Layer)

class Accounts:
    """
    Web-friendly wrapper around Unix passwd.

    Provides email-based authentication and role management.
    """

    def __init__(self, etc_dir='./etc', domain='example.com'):
        self.passwd = PasswdDB(etc_dir)
        self.domain = domain

    def register(self, email, password, name=''):
        """Register user (web-friendly)"""
        username = email.split('@')[0]
        fullname = f"{name} <{email}>" if name else email

        user = self.passwd.useradd(username, password, fullname=fullname)

        # Send welcome email to user's mailbox
        self.mail(username, 'Welcome!', 'Thanks for registering.')

        return user

    def login(self, email, password):
        """Authenticate by email"""
        username = email.split('@')[0]
        return self.passwd.authenticate(username, password)

    def get_user(self, user_id=None, email=None, username=None):
        """Get user by ID, email, or username"""
        if email:
            username = email.split('@')[0]
        if username:
            return self.passwd.getuser(username)
        if user_id:
            # Find by UID
            users = get_all(self.passwd.passwd_path)
            for u in users:
                if u.uid == user_id:
                    return u
        return None

    def has_role(self, email, role):
        """Check if user has role (groups alias)"""
        username = email.split('@')[0]
        return role in self.passwd.groups(username)

    def add_role(self, email, role):
        """Add role to user"""
        username = email.split('@')[0]
        self.passwd.usermod(username, add_groups=[role])

    def mail(self, username, subject, body):
        """Send mail to user's mailbox (Unix mail)"""
        mailbox = Path(f'./var/mail/{username}')
        mailbox.parent.mkdir(parents=True, exist_ok=True)

        # Append to mailbox (mbox format)
        with mailbox.open('a') as f:
            f.write(f"From system {datetime.now()}\n")
            f.write(f"Subject: {subject}\n")
            f.write(f"\n{body}\n\n")

    @property
    def email(self, username):
        """Get user's email from fullname field"""
        user = self.passwd.getuser(username)
        if not user:
            return None

        # Extract from "Name <email>" format
        if '<' in user.fullname and '>' in user.fullname:
            return user.fullname.split('<')[1].split('>')[0]

        # Fallback to username@domain
        return f"{username}@{self.domain}"

The Full Unix Web System

Directory Structure

myapp/
├── app.py              # Web application
├── etc/                # User database (Unix /etc)
│   ├── passwd.tsv
│   ├── shadow.tsv
│   └── group.tsv
├── home/               # User home directories (Unix /home)
│   ├── john/
│   │   ├── uploads/
│   │   └── profile.jpg
│   └── jane/
├── var/                # Variable data (Unix /var)
│   ├── mail/           # User mailboxes
│   │   ├── john
│   │   └── jane
│   └── log/            # Logs (from dbbasic-logs)
├── data/               # Application data (dbbasic-tsv)
│   ├── posts.tsv
│   └── comments.tsv
└── static/             # Web static files

This IS a Unix system - just for your web app.

Example: Complete Blog with Unix Integration

from dbbasic_web import app, route, render
from dbbasic_accounts import Accounts
from dbbasic_tsv import get, insert
from dbbasic_sessions import create_session, get_session

accounts = Accounts('./etc', domain='myblog.com')

@route('/register', methods=['POST'])
def register():
    user = accounts.register(
        email=request.form['email'],
        password=request.form['password'],
        name=request.form['name']
    )

    # User now has:
    # - Account in etc/passwd.tsv
    # - Password in etc/shadow.tsv
    # - Home directory in home/john/
    # - Mailbox in var/mail/john

    # Send welcome message to their mailbox
    accounts.mail(user.username,
        'Welcome!',
        'Thanks for registering. Your home directory is ready.'
    )

    return redirect('/login')

@route('/upload', methods=['POST'])
def upload():
    user_id = get_session(request.cookies.get('session'))
    user = accounts.get_user(user_id=user_id)

    # Save to user's home directory
    file = request.files['photo']
    file.save(f'{user.homedir}/uploads/{file.filename}')

    # Notify user via internal mail
    accounts.mail(user.username,
        'File uploaded',
        f'Uploaded {file.filename} to your home directory'
    )

    return 'Uploaded!'

@route('/admin')
def admin():
    user_id = get_session(request.cookies.get('session'))
    user = accounts.get_user(user_id=user_id)

    # Check Unix group membership
    if not accounts.has_role(user.email, 'admins'):
        return 'Forbidden', 403

    return render('admin.html')

Unix commands work:

# List users
cat etc/passwd.tsv

# Change user's password
dbpasswd passwd john

# Check who's an admin
grep admins etc/group.tsv

# View user's files
ls home/john/uploads/

# Read user's mail
cat var/mail/john

# Make someone an admin
dbpasswd usermod john --add-group admins

Why This Matters

The Unix Web System Benefits

1. Standard tools work

# No custom admin panel needed
cat etc/passwd.tsv | wc -l  # Count users
grep admin etc/group.tsv     # Find admins
ls home/*/uploads/ | wc -l   # Count uploaded files

2. Integration for free - User signs up → gets mailbox automatically - User signs up → gets home directory automatically - User signs up → can receive mail automatically

3. Debuggability

# Is user in admin group?
grep john etc/group.tsv

# What's their home directory?
grep john etc/passwd.tsv

# What mail do they have?
cat var/mail/john

4. Docker-native - Each container = isolated Unix system - No user conflicts - Standard Unix tools work inside container

5. Educational - Web developers learn Unix - Unix admins understand web apps - Same concepts, same tools


Migration from Web to Unix

From Django/Rails User Model

Old (Django):

from django.contrib.auth.models import User

user = User.objects.create_user('john', 'john@example.com', 'password')
user.groups.add(editors_group)

New (dbbasic):

from dbbasic_accounts import Accounts

accounts = Accounts('./etc')
user = accounts.register('john@example.com', 'password', name='John')
accounts.add_role('john@example.com', 'editors')

Data migration:

# Export from Django
for user in User.objects.all():
    accounts.passwd.useradd(
        username=user.username,
        password='reset-required',  # Force password reset
        fullname=f"{user.first_name} {user.last_name} <{user.email}>"
    )

    # Migrate groups → roles
    for group in user.groups.all():
        accounts.add_role(user.email, group.name)

From WordPress

Old (WordPress):

SELECT * FROM wp_users WHERE user_login = 'john';
SELECT * FROM wp_usermeta WHERE user_id = 1 AND meta_key = 'wp_capabilities';

New (dbbasic):

user = accounts.login('john@example.com', 'password')
if accounts.has_role(user.email, 'administrator'):
    # Is admin

Data migration:

# Import WordPress users
import MySQLdb

db = MySQLdb.connect('localhost', 'root', 'password', 'wordpress')
cursor = db.cursor()
cursor.execute("SELECT user_login, user_email, display_name FROM wp_users")

for username, email, display_name in cursor.fetchall():
    accounts.passwd.useradd(
        username=username,
        password='reset-required',  # Force password reset
        fullname=f"{display_name} <{email}>"
    )

    # Map WordPress roles to groups
    # administrator → admins
    # editor → editors
    # etc.

Security Considerations

Password Hashing

Argon2id (winner of Password Hashing Competition):

from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=3,        # Number of iterations
    memory_cost=65536,  # 64 MB
    parallelism=4       # 4 threads
)

hash = ph.hash('password')
# $argon2id$v=19$m=65536,t=3,p=4$salt$hash

Why Argon2id: - Current best practice (2015+) - Resists GPU/ASIC attacks - Memory-hard (can't parallelize on GPUs) - Better than bcrypt, scrypt, PBKDF2

shadow.tsv Permissions

# Automatically set on creation
os.chmod('etc/shadow.tsv', 0o600)
# Owner read/write only (like /etc/shadow)

Only the app can read passwords.

Password Reset

def reset_password(email, token):
    """Reset password with token"""
    # Verify token (time-limited, signed)
    username = verify_reset_token(token)
    if not username:
        return False

    # Generate temporary password
    temp_password = secrets.token_hex(16)

    # Update password
    accounts.passwd.passwd(username, temp_password)

    # Send to email
    send_email(email, 'Password Reset', f'Temporary password: {temp_password}')

    return True

Command Line Tools

dbpasswd Command

#!/usr/bin/env python3
# dbbaswd - Unix-style user management for dbbasic apps

import sys
from dbbasic_accounts import PasswdDB

passwd = PasswdDB('./etc')

if sys.argv[1] == 'useradd':
    username = sys.argv[2]
    password = input('Password: ')
    user = passwd.useradd(username, password)
    print(f"Created user {username} (UID {user.uid})")

elif sys.argv[1] == 'passwd':
    username = sys.argv[2]
    new_password = input('New password: ')
    passwd.passwd(username, new_password)
    print(f"Password changed for {username}")

elif sys.argv[1] == 'groups':
    username = sys.argv[2]
    groups = passwd.groups(username)
    print(' '.join(groups))

elif sys.argv[1] == 'list':
    for user in passwd.list_users():
        print(f"{user.username}:{user.uid}:{user.gid}:{user.fullname}")

Now Unix commands work:

dbpasswd useradd john
dbpasswd passwd john
dbpasswd groups john
dbpasswd list

The Bigger Vision

Unix Web App = Real Unix System

Your web app running in Docker:

FROM python:3.11

# This is a Unix system
COPY etc/ /app/etc/
COPY home/ /app/home/
COPY var/ /app/var/

# Unix tools work
RUN cat /app/etc/passwd.tsv
RUN ls /app/home/*/

# Web app uses same user database
COPY app.py /app/
CMD ["python", "/app/app.py"]

Inside the container:

# SSH into running container
docker exec -it myapp bash

# Unix tools work with web users
cat etc/passwd.tsv
mail john < message.txt
ls home/john/uploads/
grep admin etc/group.tsv

Your web app IS a Unix system.

Multiple Apps, Shared Users (Future)

Later, you could even:

Docker Compose:
  - blog app (etc/passwd.tsv)
  - forum app (etc/passwd.tsv)
  - wiki app (etc/passwd.tsv)

  Shared volume: /shared/etc/

All apps use same user database!

Just like Unix in the 1990s - one user database, all apps use it.

But that's phase 2. For now, each app = isolated Unix system.


Comparison to Alternatives

Django User Model

Django:

from django.contrib.auth.models import User
from django.contrib.auth import authenticate

user = User.objects.create_user('john', 'john@example.com', 'password')
authenticated = authenticate(username='john', password='password')

Pros: - Familiar to Django developers - Integrated with Django

Cons: - Requires Django - Database required - Not Unix-compatible - Custom implementation

dbbasic-accounts

dbbasic:

from dbbasic_accounts import Accounts

accounts = Accounts('./etc')
user = accounts.register('john@example.com', 'password', name='John')
authenticated = accounts.login('john@example.com', 'password')

Pros: - Unix-compatible - TSV files (no database needed) - Standard tools work - Simple implementation

Cons: - New to web developers - Requires learning Unix concepts

The Trade-off

Django approach: Familiar to web developers, custom implementation

dbbasic approach: Unix-compatible, standard implementation

dbbasic bet: Teaching Unix concepts is better than inventing new ones.


Package Structure

dbbasic-accounts/
├── dbbasic_accounts/
│   ├── __init__.py         # Exports
│   ├── passwd.py           # PasswdDB (Unix layer)
│   ├── accounts.py         # Accounts (web layer)
│   └── cli.py              # CLI commands (dbpasswd)
├── tests/
│   ├── test_passwd.py
│   ├── test_accounts.py
│   ├── test_integration.py
│   └── test_security.py
├── bin/
│   └── dbpasswd            # CLI tool
├── setup.py
├── README.md
├── LICENSE
└── CHANGELOG.md

Success Criteria

This implementation is successful if:

  1. Unix-compatible: Uses /etc/passwd, /etc/shadow, /etc/group format
  2. Web-friendly: Email-based registration and login
  3. Dual API: Both Unix and web interfaces
  4. Tool-compatible: Standard Unix tools work (cat, grep, etc.)
  5. Secure: Argon2id password hashing
  6. Simple: < 500 lines of code
  7. Integrated: Works with home directories, mail, permissions
  8. Docker-native: Each container = isolated Unix system

The Philosophical Point

The web didn't need to fork from Unix.

We abandoned Unix because of shared hosting constraints. Multiple websites on one server couldn't share /etc/passwd.

Docker solves this. Each container has its own /etc/.

So we can return to Unix.

dbbasic-accounts is the first step in making web apps Unix systems again.


Next Steps

  1. Implement PasswdDB (Unix layer)
  2. Implement Accounts (web layer)
  3. Add home directory creation
  4. Add var/mail integration
  5. Create dbpasswd CLI tool
  6. Test with real web app
  7. Publish to PyPI

Goal: Make web apps Unix systems again.

Philosophy: Don't reinvent. Return to what worked.


References


Summary

dbbasic-accounts brings Unix user management to web apps:

What it is: - TSV-based /etc/passwd, /etc/shadow, /etc/group - Dual API (Unix + web-friendly) - Home directories, mailboxes, groups - Standard Unix tools work

Why it matters: - Web apps can be Unix systems again - Docker enables isolated user databases - No need to reinvent authentication - 50 years of battle-tested design

Use it when: - Building web apps with Docker - Want Unix tool compatibility - Value standard over custom - Building the future by honoring the past

This is how we merge the fork.

The web returns to Unix.


Next Steps: Implement, test, deploy, ship.

No custom user tables. No reinvented auth. No fork.

Just Unix, for web apps.