Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions alembic/versions/dc4df9b41ca4_vip_rework_to_vip_lists.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"""vip rework to vip lists

Revision ID: dc4df9b41ca4
Revises: 89a3502370a0
Create Date: 2026-03-31 08:53:14.047059

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = 'dc4df9b41ca4'
down_revision = '89a3502370a0'
branch_labels = None
depends_on = None


def upgrade():

op.create_table(
"vip_list",
sa.Column("id", sa.Integer, nullable=False, primary_key=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column(
"sync",
sa.Enum("IGNORE_UNKNOWN", "REMOVE_UNKNOWN", name="viplistsyncmethod"),
nullable=False,
),
sa.Column("servers", sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint("id"),
)
op.create_table(
"vip_list_record",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("admin_name", sa.String(), nullable=False),
sa.Column("created_at", sa.TIMESTAMP(timezone=True), nullable=False),
sa.Column("active", sa.Boolean(), nullable=False),
sa.Column("description", sa.String(), nullable=True),
sa.Column("notes", sa.String(), nullable=True),
sa.Column("expires_at", sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column("player_id_id", sa.Integer(), nullable=False),
sa.Column("vip_list_id", sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(
["player_id_id"],
["steam_id_64.id"],
),
sa.ForeignKeyConstraint(["vip_list_id"], ["vip_list.id"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint(
"id", "player_id_id", "vip_list_id", name="unique_vip_player_id_vip_list"
),
)
op.create_index(
op.f("ix_vip_list_record_player_id_id"),
"vip_list_record",
["player_id_id"],
unique=False,
)
op.create_index(
op.f("ix_vip_list_record_vip_list_id"),
"vip_list_record",
["vip_list_id"],
unique=False,
)

# Create default vip list from player_vip table
# using ID 0 for the default list
# and ID 1 for the default Seed VIP list
# We can't prefill the Seed VIP list reliably because it requires a game
# server connection and this runs in the maintenance container; but future
# records by default will go to the Seed VIP list
op.execute(
"""INSERT INTO vip_list(id, name, sync, servers)
VALUES (0, 'Default', 'IGNORE_UNKNOWN', NULL)
ON CONFLICT DO NOTHING"""
)
op.execute(
"""INSERT INTO vip_list(name, sync, servers)
VALUES ('Seed VIP', 'IGNORE_UNKNOWN', NULL)
ON CONFLICT DO NOTHING"""
)

# Migrate old VIP records; this is not the same as VIP entries on the game server
# but whatever records were in player_vip
# We can't access their description; that is stored on the game server
# and this is run in the maintenance container with no connection to any server
op.execute(
"""INSERT INTO
vip_list_record (
admin_name,
created_at,
active,
description,
notes,
expires_at,
player_id_id,
vip_list_id
)
SELECT
'CRCON',
NOW (),
true,
NULL,
NULL,
CASE WHEN expiration > '2030-01-01' THEN NULL ELSE expiration END,
playersteamid_id,
0
FROM
(
SELECT
expiration,
playersteamid_id,
ROW_NUMBER() OVER (
PARTITION BY
playersteamid_id
ORDER BY
expiration DESC
) AS rn
FROM
player_vip
WHERE server_number IS NOT NULL
) sub
WHERE
sub.rn = 1
"""
)

op.drop_constraint("unique_player_server_vip", "player_vip", type_="unique")
op.drop_index(op.f("ix_player_vip_playersteamid_id"), table_name="player_vip")
op.drop_table("player_vip")


def downgrade():
op.create_table(
"player_vip",
sa.Column("id", sa.Integer, primary_key=True),
sa.Column("expiration", sa.TIMESTAMP(timezone=True), nullable=False),
sa.Column(
"playersteamid_id",
sa.Integer,
sa.ForeignKey("steam_id_64.id"),
nullable=False,
),
sa.Column("server_number", sa.Integer(), nullable=True),
)

op.create_unique_constraint(
"unique_player_server_vip", "player_vip", ["playersteamid_id", "server_number"]
)
op.create_index(
op.f("ix_player_vip_playersteamid_id"),
"player_vip",
["playersteamid_id"],
unique=False,
)
op.drop_table("vip_list_record")
op.drop_table("vip_list")
op.execute("DROP TYPE viplistsyncmethod")
2 changes: 2 additions & 0 deletions config/crontab
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ LOGGING_FILENAME=cron.log
5 * * * * /bin/bash /config/do_logrotate.sh
# This routine updates your database every day at 10:00, pull steam profiles older than 30 days
0 10 * * * /code/manage.py enrich_db_users
# This routine resynchs your VIP lists with the game server every 5 minutes
*/5 * * * * /code/manage.py inactivate_expired_vip_records
# Below is an example show how to set your map to hill 400 at 9 am every day, remove the # to enable
# 0 9 * * * /code/manage.py set_map hill400_warfare >> /config/cronout 2>&1
# Below is an example show how to set your welcome message to Hello (line break) toto... at 23h15 every day, you can use the same variables as in the UI
Expand Down
11 changes: 6 additions & 5 deletions config/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ environment=LOGGING_FILENAME=broadcasts_%(ENV_SERVER_NUMBER)s.log
startretries=100
startsecs=1

[program:expiring_vips]
command=/code/manage.py expiring_vips
environment=LOGGING_FILENAME=expiring_vips_%(ENV_SERVER_NUMBER)s.log
startretries=10
autorestart=true
[program:vip_lists]
command=/code/manage.py vip_lists
environment=LOGGING_FILENAME=vip_lists_%(ENV_SERVER_NUMBER)s.log
startretries=100
startsecs=10
autostart=true

[program:seed_vip]
command=/code/manage.py seed_vip
Expand Down
3 changes: 3 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ then
echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin') if not User.objects.filter(username='admin').first() else None" | python manage.py shell
fi
export LOGGING_FILENAME=api_$SERVER_NUMBER.log
# Synchronize the game server with our VIP lists in case they differ
# which could be after moving game servers; long downtime, etc.
python -m rcon.cli synchronize_vip_lists
daphne -b 0.0.0.0 -p 8001 rconweb.asgi:application &
# Successfully running gunicorn will create the pid file which is how Docker determines the container is healthy
gunicorn --preload --pid gunicorn.pid -w $NB_API_WORKERS -k gthread --threads $NB_API_THREADS -t 120 -b 0.0.0.0 rconweb.wsgi
Expand Down
Loading
Loading