This guide will walk you through setting up ELF on your Ubuntu 24 server from scratch. Every step is explained in detail.
Before You Start: This guide assumes you're starting fresh. Follow each section in order from top to bottom.
First, let's understand what folders we're creating and what goes where:
/home/yourusername/elf-project/ ← Main project folder (you pick the name) │ ├── backend/ ← All Python/Django code goes here │ ├── config/ ← Django configuration files │ │ ├── __init__.py ← Empty file (Python needs this) │ │ ├── settings.py ← Main Django settings │ │ ├── urls.py ← URL routing │ │ └── wsgi.py ← Web server config │ │ │ ├── elf/ ← Main application code │ │ ├── __init__.py ← Empty file │ │ ├── models.py ← Database models │ │ │ │ │ ├── api/ ← REST API code │ │ │ ├── __init__.py │ │ │ ├── serializers.py ← Data conversion │ │ │ └── views.py ← API endpoints │ │ │ │ │ ├── services/ ← Business logic │ │ │ ├── __init__.py │ │ │ ├── rivendell_service.py ← Rivendell sync │ │ │ ├── export_service.py ← Log export │ │ │ └── scheduling_service.py ← Auto-scheduler │ │ │ │ │ ├── migrations/ ← Database migrations (auto-created) │ │ │ └── __init__.py │ │ │ │ │ └── management/ ← Custom commands │ │ ├── __init__.py │ │ └── commands/ │ │ └── __init__.py │ │ │ ├── venv/ ← Virtual environment (created by setup) │ ├── requirements.txt ← Python packages list │ ├── manage.py ← Django command-line tool │ ├── Dockerfile ← Docker config (optional) │ └── .env ← Your local settings (you create this) │ ├── frontend/ ← All React/JavaScript code goes here │ ├── src/ ← Source code │ │ ├── components/ ← Reusable UI components │ │ │ └── Layout.jsx ← Main layout │ │ │ │ │ ├── pages/ ← Page components │ │ │ ├── Dashboard.jsx │ │ │ ├── Stations.jsx │ │ │ ├── Advertisers.jsx │ │ │ ├── Campaigns.jsx │ │ │ ├── Schedules.jsx │ │ │ └── Login.jsx │ │ │ │ │ ├── services/ ← API communication │ │ │ └── api.js ← API client │ │ │ │ │ ├── App.jsx ← Main app component │ │ ├── main.jsx ← Entry point │ │ └── index.css ← Global styles │ │ │ ├── node_modules/ ← Node packages (created by npm) │ ├── index.html ← HTML entry point │ ├── package.json ← Node packages list │ ├── vite.config.js ← Build tool config │ ├── tailwind.config.js ← CSS framework config │ ├── postcss.config.js ← CSS processor config │ └── .env.local ← Your local settings (you create this) │ ├── docker-compose.yml ← Docker orchestration (optional) ├── README.md ← Project overview ├── SETUP_GUIDE.md ← This guide (in markdown) ├── DEVELOPMENT_GUIDE.md ← Developer docs ├── TODO.md ← Roadmap ├── quickstart.sh ← Quick setup script └── .gitignore ← Git ignore rules
<note tip>Important: Files ending in `.py` are Python, `.jsx` or `.js` are JavaScript, `.json` are configuration, `.md` are documentation.</note>
# Update package lists sudo apt update # Upgrade existing packages sudo apt upgrade -y
What this does: Makes sure your Ubuntu has the latest security updates and package lists.
# Install Python 3.11 and development tools sudo apt install -y python3.11 python3.11-venv python3-pip python3-dev # Verify Python is installed python3 --version # Should show: Python 3.11.x
What this does:
# Install PostgreSQL database server sudo apt install -y postgresql postgresql-contrib # Verify it's running sudo systemctl status postgresql # Should show "active (running)" # If not running, start it: sudo systemctl start postgresql sudo systemctl enable postgresql
What this does: Installs and starts the PostgreSQL database server where all your data will be stored.
# Install MySQL client libraries sudo apt install -y default-libmysqlclient-dev pkg-config # These are needed to connect to Rivendell's database
What this does: Installs tools needed to connect to Rivendell's MySQL database for syncing cart data.
# Add NodeSource repository for Node.js 18 curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - # Install Node.js and npm sudo apt install -y nodejs # Verify installation node --version # Should show: v18.x.x npm --version # Should show: 9.x.x or higher
What this does: Installs Node.js (JavaScript runtime) and npm (JavaScript package manager) needed for the frontend.
# Install Nginx sudo apt install -y nginx # Start Nginx sudo systemctl start nginx sudo systemctl enable nginx # Check status sudo systemctl status nginx # Should show "active (running)"
What this does: Installs Nginx web server that will serve your application in production.
# Install Git sudo apt install -y git # Verify git --version
What this does: Installs Git version control system (you may already have this).
# Go to your home directory cd ~ # Create main project folder mkdir elf-project # Go into it cd elf-project # Check where you are pwd # Should show: /home/yourusername/elf-project
What this does: Creates your main project folder. Everything will go inside this folder.
# Make sure you're in the project folder cd ~/elf-project # Create all backend folders at once mkdir -p backend/config mkdir -p backend/elf/api mkdir -p backend/elf/services mkdir -p backend/elf/migrations mkdir -p backend/elf/management/commands # Verify structure tree backend -L 3 # Or if tree isn't installed: find backend -type d
What this does: Creates all the nested folders needed for Django backend code.
<note>About nested folders:
</note>
# Still in ~/elf-project mkdir -p frontend/src/components mkdir -p frontend/src/pages mkdir -p frontend/src/services # Verify find frontend -type d
What this does: Creates folders for React frontend code.
Now we'll create each file one by one. I'll show you the command and the content for each file.
These are empty files that Python needs to recognize folders as packages.
# Create all __init__.py files touch backend/config/__init__.py touch backend/elf/__init__.py touch backend/elf/api/__init__.py touch backend/elf/services/__init__.py touch backend/elf/migrations/__init__.py touch backend/elf/management/__init__.py touch backend/elf/management/commands/__init__.py
What this does: Creates empty Python package marker files. These tell Python “this folder contains Python code”.
This file lists all Python packages needed.
cat > backend/requirements.txt << 'EOF' # Django Core Django==5.0.1 djangorestframework==3.14.0 django-cors-headers==4.3.1 django-filter==23.5 # Database psycopg2-binary==2.9.9 mysqlclient==2.2.1 # Authentication & Security djangorestframework-simplejwt==5.3.1 python-decouple==3.8 # Date/Time handling python-dateutil==2.8.2 pytz==2024.1 # API Documentation drf-spectacular==0.27.0 # Utilities python-dotenv==1.0.0 # Development django-debug-toolbar==4.2.0 ipython==8.20.0 # Testing pytest==7.4.4 pytest-django==4.7.0 factory-boy==3.3.0 # Production gunicorn==21.2.0 whitenoise==6.6.0 EOF
What this does: Creates a file that lists all the Python libraries this project needs. We'll install these later.
<note important>This is a long file. Copy it carefully.</note>
cat > backend/config/settings.py << 'SETTINGSEOF' """ Django settings for ELF project. """ from pathlib import Path from decouple import config import os # Build paths BASE_DIR = Path(__file__).resolve().parent.parent # Security SECRET_KEY = config('SECRET_KEY', default='django-insecure-change-this-in-production-12345') DEBUG = config('DEBUG', default=True, cast=bool) ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost,127.0.0.1').split(',') # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', # Third party 'rest_framework', 'corsheaders', 'django_filters', 'drf_spectacular', # Local apps 'elf', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'whitenoise.middleware.WhiteNoiseMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'config.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] WSGI_APPLICATION = 'config.wsgi.application' # Database DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': config('DB_NAME', default='elf_db'), 'USER': config('DB_USER', default='elf_user'), 'PASSWORD': config('DB_PASSWORD', default='elf_password'), 'HOST': config('DB_HOST', default='localhost'), 'PORT': config('DB_PORT', default='5432'), } } # Password validation AUTH_PASSWORD_VALIDATORS = [ {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'}, {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'}, {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'}, {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}, ] # Internationalization LANGUAGE_CODE = 'en-us' TIME_ZONE = config('TIME_ZONE', default='America/New_York') USE_I18N = True USE_TZ = True # Static files (CSS, JavaScript, Images) STATIC_URL = 'static/' STATIC_ROOT = BASE_DIR / 'staticfiles' STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' # Media files MEDIA_URL = 'media/' MEDIA_ROOT = BASE_DIR / 'media' # Default primary key field type DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' # CORS settings CORS_ALLOWED_ORIGINS = config( 'CORS_ALLOWED_ORIGINS', default='http://localhost:5173,http://127.0.0.1:5173' ).split(',') CORS_ALLOW_CREDENTIALS = True # REST Framework REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.SessionAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], 'DEFAULT_FILTER_BACKENDS': [ 'django_filters.rest_framework.DjangoFilterBackend', 'rest_framework.filters.SearchFilter', 'rest_framework.filters.OrderingFilter', ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 50, 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', } # API Documentation SPECTACULAR_SETTINGS = { 'TITLE': 'ELF - Elfish Log Factory API', 'DESCRIPTION': 'Commercial Traffic Scheduling System for Broadcast Radio', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, } # JWT Settings from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(hours=1), 'REFRESH_TOKEN_LIFETIME': timedelta(days=7), } # ELF-specific settings ELF_EXPORT_BASE_PATH = config('ELF_EXPORT_BASE_PATH', default='/var/elf/exports') ELF_DEFAULT_SPOT_LENGTH = 30 # seconds SETTINGSEOF
What this does: Creates Django's main configuration file. This controls how Django behaves.
I'll provide download links for the remaining backend files since they're quite long. You already have them from earlier, so you need to copy:
From the files I shared earlier, copy these into backend/config/:
Copy into backend/:
Copy into backend/elf/:
Copy into backend/elf/api/:
Copy into backend/elf/services/:
<note tip>Easier method: Download all the files I shared earlier and use `scp` or FileZilla to upload them to your server in the right locations.</note>
# Switch to postgres user sudo -u postgres psql # Now you're in PostgreSQL prompt (postgres=#) # Copy and paste these commands one by one:
In the PostgreSQL prompt:
-- Create database CREATE DATABASE elf_db; -- Create user with password CREATE USER elf_user WITH PASSWORD 'your_secure_password_here'; -- Set encoding ALTER ROLE elf_user SET client_encoding TO 'utf8'; -- Set transaction isolation ALTER ROLE elf_user SET default_transaction_isolation TO 'read committed'; -- Set timezone ALTER ROLE elf_user SET timezone TO 'America/New_York'; -- Grant all privileges GRANT ALL PRIVILEGES ON DATABASE elf_db TO elf_user; -- Grant schema privileges (PostgreSQL 15+) \c elf_db GRANT ALL ON SCHEMA public TO elf_user; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO elf_user; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO elf_user; -- Exit PostgreSQL \q
What this does:
<note important>Remember the password you set! You'll need it in the next step.</note>
cd ~/elf-project/backend # Create .env file nano .env
This opens a text editor. Type or paste this content:
# Django Settings SECRET_KEY=change-this-to-a-random-long-string DEBUG=True ALLOWED_HOSTS=localhost,127.0.0.1,your-server-ip # Database (use the password you set in Step 4.1) DB_NAME=elf_db DB_USER=elf_user DB_PASSWORD=your_secure_password_here DB_HOST=localhost DB_PORT=5432 # Timezone (adjust to your timezone) TIME_ZONE=America/New_York # CORS CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173 # ELF Settings ELF_EXPORT_BASE_PATH=/var/elf/exports ELF_DEFAULT_SPOT_LENGTH=30
To save and exit nano:
What this does: Creates your local configuration file with database credentials and settings.
<note important>Replace these values:
</note>
# Generate a Django secret key python3 -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())" # Copy the output and paste it as your SECRET_KEY in .env
cd ~/elf-project/backend # Create virtual environment python3 -m venv venv # You should now see a venv folder in backend/ ls -la
What this does: Creates an isolated Python environment. This keeps ELF's packages separate from your system Python.
# Activate it source venv/bin/activate # Your prompt should now show (venv) at the beginning # Example: (venv) user@server:~/elf-project/backend$
What this does: Activates the virtual environment. Any Python commands now use this environment.
<note important>You need to activate the venv every time you open a new terminal and want to work on the backend. Use: `source ~/elf-project/backend/venv/bin/activate`</note>
# Make sure pip is latest version pip install --upgrade pip
# Install all packages from requirements.txt pip install -r requirements.txt # This will take a few minutes # You'll see lots of "Collecting..." and "Installing..." messages
What this does: Installs all the Python libraries that ELF needs (Django, PostgreSQL driver, etc).
# Make sure you're in backend/ with venv activated cd ~/elf-project/backend source venv/bin/activate # Run migrations to create database tables python manage.py migrate # You should see: # Running migrations: # Applying contenttypes.0001_initial... OK # Applying auth.0001_initial... OK # ... (many more)
What this does: Creates all the database tables that Django and ELF need.
# Create admin user python manage.py createsuperuser # It will ask for: # Username: (pick a username) # Email: (your email) # Password: (pick a password) # Password (again): (repeat it)
What this does: Creates an admin account so you can log into Django's admin panel.
<note>Remember these credentials! You'll use them to log into http://localhost:9000/admin</note>
# Collect static files (CSS, JavaScript for admin) python manage.py collectstatic --noinput # You should see: # X static files copied to '/home/.../backend/staticfiles'
What this does: Copies all CSS, JavaScript, and images that Django admin needs into one folder.
# Start the Django development server python manage.py runserver 0.0.0.0:9000 # You should see: # Starting development server at http://0.0.0.0:9000/ # Quit the server with CONTROL-C.
What this does: Starts the backend server on port 9000.
Open your web browser and go to:
You should see:
<note tip>If you can't access it, check:
</note>
# In the terminal where server is running: # Press Ctrl + C # This stops the server
Now we need to create the frontend files. Similar to backend, you need to copy the files I provided earlier:
Copy into frontend/:
Copy into frontend/src/:
Copy into frontend/src/components/:
Copy into frontend/src/pages/:
Copy into frontend/src/services/:
cd ~/elf-project/frontend # Install all packages npm install # This will take several minutes # Creates node_modules/ folder with thousands of files
What this does: Installs all JavaScript libraries that React and the frontend need.
# Create .env.local file echo "VITE_API_URL=http://localhost:9000/api" > .env.local # Or if accessing from another computer: echo "VITE_API_URL=http://your-server-ip:9000/api" > .env.local
What this does: Tells the frontend where to find the backend API.
# Start Vite development server npm run dev # You should see: # VITE vX.X.X ready in XXX ms # ➜ Local: http://localhost:5173/ # ➜ Network: http://192.168.x.x:5173/
What this does: Starts the frontend development server on port 5173.
Open browser to: http://your-server-ip:5173
You should see the ELF dashboard!
ELF needs a place to save exported log files for Rivendell.
# Create export directory sudo mkdir -p /var/elf/exports # Give your user ownership sudo chown $USER:$USER /var/elf/exports # Set permissions chmod 755 /var/elf/exports
What this does: Creates the folder where daily log files will be exported.
To use ELF, you need BOTH servers running at the same time.
Terminal 1 - Backend:
cd ~/elf-project/backend source venv/bin/activate python manage.py runserver 0.0.0.0:9000
Terminal 2 - Frontend:
cd ~/elf-project/frontend npm run dev
# Install tmux if not installed sudo apt install tmux # Start tmux session tmux new -s elf # Start backend cd ~/elf-project/backend source venv/bin/activate python manage.py runserver 0.0.0.0:9000 # Split window (Ctrl+B then %) # In new pane, start frontend cd ~/elf-project/frontend npm run dev # To detach: Ctrl+B then D # To reattach: tmux attach -t elf
Morning Drive:
Midday:
Afternoon Drive:
If you have Rivendell running:
# In backend terminal (with venv activated) cd ~/elf-project/backend source venv/bin/activate # Sync all carts from Rivendell python manage.py sync_rivendell_carts --station 1 # Or sync just commercial group python manage.py sync_rivendell_carts --station 1 --group-name COMM
What this does: Imports your commercial cart inventory from Rivendell into ELF's database.
# Terminal 1 - Backend cd ~/elf-project/backend && source venv/bin/activate && python manage.py runserver 0.0.0.0:9000 # Terminal 2 - Frontend cd ~/elf-project/frontend && npm run dev
# Always activate venv first cd ~/elf-project/backend source venv/bin/activate # Create migrations after model changes python manage.py makemigrations # Apply migrations python manage.py migrate # Create superuser python manage.py createsuperuser # Run server python manage.py runserver 0.0.0.0:9000
Error: “No module named 'django'“
# Make sure venv is activated cd ~/elf-project/backend source venv/bin/activate # You should see (venv) in your prompt # Reinstall packages pip install -r requirements.txt
Error: “FATAL: password authentication failed”
# Check your .env file nano ~/elf-project/backend/.env # Make sure DB_PASSWORD matches what you set in PostgreSQL # Make sure DB_USER is "elf_user" # Make sure DB_NAME is "elf_db"
Error: “port 9000 already in use”
# Check what's using port 9000 sudo lsof -i :9000 # Stop the other service or change ELF to different port python manage.py runserver 0.0.0.0:9001
Error: “Cannot find module”
# Reinstall node modules cd ~/elf-project/frontend rm -rf node_modules package-lock.json npm install
Error: “EACCES: permission denied”
# Fix npm permissions sudo chown -R $USER ~/.npm sudo chown -R $USER ~/elf-project/frontend/node_modules
Check PostgreSQL is running:
sudo systemctl status postgresql # If not running: sudo systemctl start postgresql
Test database connection:
psql -U elf_user -d elf_db -h localhost # Enter password when prompted # If successful, you'll see: elf_db=> # Type \q to quit
Reset database (if needed):
# Drop and recreate database sudo -u postgres psql DROP DATABASE elf_db; CREATE DATABASE elf_db; GRANT ALL PRIVILEGES ON DATABASE elf_db TO elf_user; \q # Recreate tables cd ~/elf-project/backend source venv/bin/activate python manage.py migrate python manage.py createsuperuser
Check firewall:
# Allow ports 9000 and 5173 sudo ufw allow 9000 sudo ufw allow 5173 # Check firewall status sudo ufw status
Check servers are running:
# Check if processes are listening sudo netstat -tlnp | grep 9000 sudo netstat -tlnp | grep 5173
Now that ELF is installed:
If you get stuck:
Common mistakes:
You now have:
To use ELF daily:
Last updated: February 19, 2024 For: ELF v1.0.0-alpha