If you have stumbled upon this project and you're just looking for an easy way to play the game again, without having to host a server by yourself, you can do so by joinining this public server. Otherwise, if you would rather prefer to host your own server, continue reading the guide.
A full-featured server for the game: "The Simpsons: Tapped Out".
Get your old Springfield back up and running again with this super customizable game server.
Among all its features, it includes support for:
-
multiple accounts,
-
usernames and profile pictures,
-
adding and visiting friend neighbors,
-
editing money and donut currencies at your will,
-
importing and exporting town files,
-
tracking connected devices,
-
a beautiful user dashboard for managing your accounts,
-
versatile configuration to set up the server, with the option to run a single or multiple instances in parallel, all connected to the same database and storage (e.g., S3 bucket)
-
and a lot of other cool things.
- 📋 Requirements
- ⚡ Usage
- 💪 Advanced usage
- 📧 Sending emails with a custom email service
- 🗃️ Picking another database
- ⬆️ Updating the server
- 🩺 Run tests
- ⚙️ Environment variables
The server can be set up in a plethora of ways according to your preferences, but it relies on some external services to work.
Some services are essential, while others are optional and can extend the server functionality.
The required services, which must be available in any configuration, are:
-
a web server to act as a reverse proxy to the game server and to serve the static and DLC files, e.g., nginx
-
and a database service.
The optional services, which extend the server functionality, are:
-
Docker (highly recommended)
-
Redis for caching (recommended),
-
any other kind of storage service listed in django-storages, just in case you prefer to use another type of storage rather than local storage (e.g., S3 bucket),
-
an email service to deliver emails with authentication codes. This is completely optional as you can also request permission to use TSTO API.
Other independent services are not covered in this guide, like Fail2ban for rate limiting with nginx and whatnot.
The easiest and recommended way to get the server running is through the usage of Docker containers. If you do not want to use Docker, you will need to install each dependency listed
in the file environment.yaml with your favorite Python package manager: pip, conda, etc.
To make this guide easier to follow we will focus on Docker Compose. Let's start with the simplest possible configuration which just includes the server itself. Create the following compose file somewhere in your file system. If necessary adjust the ports field.
compose.yaml
services:
springfield-server:
image: ghcr.io/al1sant0s/springfield:latest
ports:
- "8000:8000"
env_file:
- .env
volumes:
- ./towns/:/app/towns/:z
- ./database.db:/app/database.db:z
- /data/static/:$STATIC_ROOT:zWith this configuration the server will use a SQLite file as your database. It only requires that you provide a web server, e.g., nginx, to act as a reverse proxy and serve the DLC and static files for the dashboard.
A simple nginx configuration for a local server, which listens on port 8080, may be specified like so:
server {
listen 8080;
server_name localhost;
client_max_body_size 5M;
location /static/ {
root /data;
}
location /dlc/ {
root /data;
}
location / {
proxy_pass http://localhost:8000;
}
}
This configuration specifies that static files are served at /data/static/ and DLC served at /data/dlc/ in the file system. By default the server listens on port 8000, so we redirect the other requests to that port. Obviously this is just an example of configuration for the proxy server. You will need to make one according to your own circumstances. For example, if your server and proxy are running on different machines, you shouldn't use localhost for the proxy_pass entry.
Finally you need to create an .env file at the same directory where you have the compose.yaml file. With the following minimal settings:
.env
# Server settings
DEBUG=false
DOMAIN=192.168.1.115
PORT=8080
PROTOCOL=http
SECRET_KEY='insert-your-secret-key-here'
STATIC_LOCATION=static/
STATIC_ROOT=/app/static/
TOWNS_ROOT=./towns/
A few things to consider.
- Pick a good SECRET_KEY.
- Remember to change the DOMAIN, PORT, PROTOCOL and STATIC_LOCATION with your own values to reflect your nginx settings. If you use a real domain, you must set PORT to either 80 (http) or 443 (https) along with the corresponding PROTOCOL.
- STATIC_ROOT is where the static files from the server will be stored in. Change it if necessary.
- TOWNS_ROOT is where towns will be stored in. Change it if necessary.
For a full detailed list of the environment variables, jump to the environment variables section.
With nginx running and your compose and .env file ready, start your server running the following command in a terminal at the same location as your compose.yaml file.
docker compose up -dTo check if your server is running, navigate to the address http://localhost:8080 or whatever address your
nginx instance is running on. If you get a "Hello, World!" page, then your server is running, but it is not ready for usage yet.
There are still two remaining steps that need to be done.
First, you must run the migrations against your database. Run the following command for that.
docker compose exec springfield-server python manage.py migrateSecond, you must collect the static files into STATIC_ROOT. Since STATIC_ROOT lives within the container and we need its contents to be available to the host machine (where nginx is running), we provided a bind mount in the compose.yaml file that links STATIC_ROOT with the static files location from nginx.
docker compose exec springfield-server python manage.py collectstaticAdditionally, you should create an admin account for you. This isn't exactly required but it is recommended in case you need to manage the server directly with Django admin dashboard. Run the following command and answer the questions it prompts to you.
docker compose exec springfield-server python manage.py createsuperuserAfter that, check the admin dashboard at http://localhost:8080/admin/.
The normal user dashboard is located at http://localhost:8080/dashboard/.
Now your server is ready to be used. Congratulations!
The previous configurations work, but since the server is so flexible, you can do a lot more with it. To demonstrate this, in this advanced section, we will explore some optional external services to use with the server. Mainly we will:
-
pick another database engine, PostgreSQL in this case,
-
set up Redis for caching,
-
configure the TSTO API for delivering code emails,
-
use a self-hosted garage S3 bucket to illustrate how to use other types of storages.
Any external service can be installed in a variety of ways. To keep this guide the most simplest possible, we will stick with Docker Compose to Install these additional services. Be aware that some of these services (like garage for example) require additional configuration that cannot be covered in this guide. You should definitely check their documentation too.
With that said, let's update our compose file like so.
compose.yaml
services:
springfield-server:
image: ghcr.io/al1sant0s/springfield:latest
ports:
- "8000:8000"
env_file:
- .env
command: [
"gunicorn",
"springfield.wsgi",
"--capture-output",
"--access-logfile", "-",
"--error-logfile", "-",
"--bind", "0.0.0.0:8000",
"--worker-class", "gthread",
"--workers", "6",
"--threads", "8",
"--preload"
]
depends_on:
db:
condition: service_healthy
garage:
condition: service_healthy
redis:
condition: service_healthy
db:
image: postgres:latest
environment:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- db-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
garage:
image: dxflrs/garage:v2.2.0
ports:
- "3900:3900"
- "3901:3901"
- "3902:3902"
- "3903:3903"
volumes:
- /etc/garage.toml:/etc/garage.toml:ro,z
- /var/lib/garage/meta:/var/lib/garage/meta:z
- /var/lib/garage/data:/var/lib/garage/data:z
healthcheck:
test: ["CMD", "/garage", "status"]
interval: 15s
timeout: 10s
retries: 3
start_period: 10s
webui:
image: khairul169/garage-webui:latest
container_name: garage-webui
volumes:
- /etc/garage.toml:/etc/garage.toml:ro,z
ports:
- 3909:3909
environment:
API_BASE_URL: "http://garage:3903"
S3_ENDPOINT_URL: "http://garage:3900"
redis:
image: redis:alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
volumes:
db-data:
redis-data:
For these new services to run, we need to expand our .env file. Remember, you must update each environment variable with your own values.
.env
# Server settings
AUTH_CODE_MINUTES=30
CACHE_DEFAULT_BACKEND=django_redis.cache.RedisCache
CACHE_DEFAULT_LOCATION=redis://redis:6379/1
CACHEOPS_REDIS_URL=$CACHE_DEFAULT_LOCATION
CACHE_SECONDS=43200
DEBUG=false
DOMAIN=192.168.1.115
LOGIN_ATTEMPTS=10
LOGIN_FAIL_COOLOFF_TIME=10
PORT=8080
PROTOCOL=http
SECRET_KEY='insert-your-secret-key-here'
STATIC_LOCATION=static/
STATIC_ROOT=static/
TOWNS_ROOT=./
# TSTO API configuration
TSTO_API_KEY='insert-your-api-key-if-you-have-one'
TSTO_API_TEAM_NAME=MyTeamNameHere
# PostgreSQL configuration
POSTGRES_DB=springfield
POSTGRES_USER=springfield
POSTGRES_PASSWORD=springfield
DATABASE_DEFAULT=postgres://springfield:springfield@db:5432/springfield
# Garage configuration
AWS_ACCESS_KEY_ID=ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY=SECRET_ACCESS_KEY
AWS_DEFAULT_REGION=garage
AWS_ENDPOINT_URL=http://garage:3900
STORAGE_DEFAULT=s3://?bucket_name=tsto-bucket
STORAGE_STATICFILES=s3+static://?bucket_name=static-bucket&url_protocol=$PROTOCOL:&custom_domain=$DOMAIN:$PORT&location=$STATIC_LOCATION
This .env file is way longer than the first one we saw before, so lets take it easy.
In the first part of the file we are defining some new defaults for the lifetime of the authentication codes (AUTH_CODE_MINUTES), the cache duration (CACHE_SECONDS), the maximum number of failed login attempts (LOGIN_ATTEMPTS) and the time in minutes the user will be locked out of their account once they exceed this limit within the period (LOGIN_FAIL_COOLOFF_TIME).
We are specifying that our cache backend is powered by Redis (CACHE_DEFAULT_BACKEND) and pointing to its location (CACHE_DEFAULT_LOCATION). The variable CACHEOPS_REDIS_URL is also important here; it signals to our server that we want to enable an additional service for caching (actually it would be called an app in Django context), which depends on Redis. It's called django-cacheops and its main purpose is to support automatic or manual queryset caching.
Also we are now saying that our static files will be situated at static/ (STATIC_ROOT). This directory is actually relative to
the S3 bucket we will use to store the static files.
Moving on to the second part, we have our TSTO API configuration. If you have obtained access to the TSTO API, then you can insert your settings here. The TSTO API settings will be used for authentication when users request a code for login.
The third part is our database configuration for PostgreSQL. We are setting an user, their password and database; all with the same value 'springfield'. The variable DATABASE_DEFAULT defines the server database backend configuration.
The last part is our S3 service configuration. The first four variables are for establishing a connection with it.
STORAGE_DEFAULT defines the backend for the default storage as well as the name of our bucket (tsto-bucket in this case).
Analogously, there is STORAGE_STATICFILES which defines the storage backend for static files. We provide extra options to it: the custom_domain and location, so the server may construct the appropriate static URL. These extra options are described in the specific page for S3 storage from django-storages. For the static files we are using another bucket rather than tsto-bucket called static-bucket. This bucket is different since it's exposed as a public website. This is done so the user web browser can request the static files from the bucket. Otherwise, only the game server would have access to the static files. To reflect our new static configuration, we have also updated our nginx settings.
server {
listen 8080;
server_name localhost;
client_max_body_size 5M;
location /static/ {
proxy_pass http://localhost:3902;
proxy_set_header Host static-bucket.web.garage.localhost;
}
location /dlc/ {
root /data;
}
location / {
proxy_pass http://localhost:8000;
}
}
Now that everything is done, run the commands to start and set the server up. Note that this time we need to run the makemigrations command since we have defined a new location for the static files.
docker compose up -d
docker compose exec springfield-server python manage.py makemigrations
docker compose exec springfield-server python manage.py migrate
docker compose exec springfield-server python manage.py collectstatic
docker compose exec springfield-server python manage.py createsuperuserTo confirm your server works correctly, run the testing routines.
If you are unable to use the TSTO API service for sending emails for you, you can use another service for that. Remove the TSTO_API variables from your .env file and set two new variables: SENDER_EMAIL and EMAIL_BACKEND.
SENDER_EMAIL is used to specify whose email address sends emails to users. EMAIL_BACKEND contains the configuration for the server to connect with your email service. Check django-service-urls documentation for details on how to specify that.
Here is an example to illustrate, using a "fake" Gmail account to send emails. Note that at the moment of writing this, in order to send emails with Gmail, you need to set up an app password. This may change in the foreseeable future. Check the details as a precaution.
.env
SENDER_EMAIL=myaddress@gmail.com
EMAIL_BACKEND=smtp+tls://myaddress%40gmail.com:abcd%20efgh%20ijkl%20mnop@smtp.gmail.com:587The default email message comes from a template file located at /proxy/templates/templated_email/auth_code.email. If you wish to replace the email message with one of your own, you can bind your email template file with the server's. To do this, include the following line in the volumes section from the springfield-server service in your compose file.
.compose.yaml
volumes:
- /path/to/auth_code.email:/app/proxy/templates/templated_email/auth_code.email:zThe server uses django-templated-email for sending emails. It supports both plain text and HTML for structuring the email message. The template receives a context that includes three variables: username, code and auth_code_minutes, which you may use in your custom email message.
Be aware that you can also use both the TSTO API service and an email custom service as a fallback in case the API is not available. Just set up both of them in your .env file. The server will prioritize the TSTO API for authentication and use only the custom email service when the API becomes unavailable.
Django offers support for multiple database engines. If you plan to run a server only for you and a few acquaintances, you may stick with the light SQLite database. However, if you plan to have multiple people playing in your server, I highly recommend picking PostgreSQL as your database. If you decide to pick another database other than PostgreSQL or SQLite, you may need to install additional dependencies in your container so the server can talk with the specified database.
In order to update the server, you must run the following sequence of commands.
docker compose down
docker compose up -d --pull always
docker compose exec springfield-server python manage.py makemigrations
docker compose exec springfield-server python manage.py migrateThis will shut down the server instance (if it is running at the time), recreate the containers with the latest images available, and perform new migrations (if necessary).
If you are running multiple servers connected to the same database. You should run
docker compose downon all of them first. Then you should perform the previous four commands in one server. After doing this, you may update and restart the rest of the servers by runningdocker compose up -d --pull alwayson each one.
Always run tests whenever you start your server.
docker compose exec springfield-server python manage.py testHere is a list of all available environment variables, which you can tweak in your .env file to adjust the server. Variables between square brackets [] are optional. Variables without square brackets [] are required, and if they are not specified, the server will not work.
Variables which specifies the backends for: cache, database, storages, etc; use django-service-urls to specify multiple values with a single string. Consult the documentation to understand how to specify such values.
-
[AUTH_CODE_MINUTES]: authentication code lifetime in minutes. Default: 30 minutes.
-
[CACHEOPS_REDIS_URL]: enables django-cacheops. Only set this variable in case you are using Redis as your cache backend. Usually it should be set with the same value as CACHE_DEFAULT_LOCATION.
-
[CACHE_DEFAULT_BACKEND]: a string specified in the format described by django-service-urls which determines the cache backend. Default: django.core.cache.backends.locmem.LocMemCache.
-
[CACHE_DEFAULT_LOCATION]: a string specified in the format described by django-service-urls which determines the cache service location. Default: unique-snowflake.
-
[CACHE_SECONDS]: duration of cached values in seconds. Default: 3600 seconds.
-
[DATABASE_DEFAULT]: a string specified in the format described by django-service-urls which determines the database backend. Default: a SQLite file called database.db which is created in the working directory.
-
DEBUG: boolean variable which determines if server runs in debug mode. This value must be set to false when the server is in a production environment.
-
DOMAIN: reverse proxy domain or ip address which redirects to the server.
-
[EMAIL_BACKEND]: a string specified in the format described by django-service-urls which determines the email backend. Default: printing emails to console. Note that this is only used if SENDER_EMAIL is defined and TSTO_API variables are not.
-
[LOGIN_ATTEMPTS]: maximum number of failed login attempts permitted before temporarily blocking user access. Default: 0 attempts.
-
[LOGIN_FAIL_COOLOFF_TIME]: Duration (in minutes) that a user remains locked out after exceeding LOGIN_ATTEMPTS failed attempts. Default: 30 minutes.
-
PORT: reverse proxy port.
-
PROTOCOL: reverse proxy protocol.
-
SECRET_KEY: the server secret key.
-
[SENDER_EMAIL]: email address where emails are dispatched from.
-
[STATIC_LOCATION]: static endpoint. Default:
static/. -
STATIC_ROOT: directory or path where static files will be stored.
-
[STORAGE_DEFAULT]: a string specified in the format described by django-service-urls which determines the default storage backend. Default: django.core.files.storage.filesystem.FileSystemStorage.
-
[STORAGE_STATICFILES]: a string specified in the format described by django-service-urls which determines the static storage backend. Default: django.contrib.staticfiles.storage.StaticFilesStorage.
-
[TIME_ZONE]: a string representing the time zone for this installation. See the list of time zones.
-
[TOWNS_ROOT]: directory or path where town files will be stored. Default:
towns/. -
[TSTO_API_KEY]: self-explanatory. No default value.
-
[TSTO_API_TEAM_NAME]: self-explanatory. No default value.
👤 Alisson Santos
- Github: @al1sant0s
Contributions, issues and feature requests are welcome!
Feel free to check issues page.
Give a ⭐️ if this project helped you!
This README was generated with ❤️ by readme-md-generator