The server itself: repo now mirrors the machine it runs on, plus install.sh

The repository carried two different servers side by side - the early
`justrp` prototype with its Python auth API, and a snapshot of the real
one under `server-code/`. Only the second one is a server anyone should
start from, so the prototype is gone and the real one moved to the root.

Taken from the live box, so the UI is the finished version (the tokens,
the county-records sheets and the variable fonts landed after the last
snapshot was pushed):

  resources/[rp]/   rp_db, rp_core, rp_session, rp_loading, rp_ui,
                    rp_selftest, rp_dbtest
  bin/              supervise.sh (keep-alive + console FIFO), rcon, init script
  etc/schema.sql    accounts, characters, transactions, inventory, vehicles
  assets/fonts/     the bundled subsets
  docs/SERVER.md    how it is put together, resource by resource

install.sh turns a clean Ubuntu/Debian box into this server in one
command: recommended FXServer build, MariaDB with the schema, resources,
server.cfg + a generated database password, boot entry (systemd or
init.d), then it waits for the Cfx registration. --name/--port/--db-name
let a second server live on the same machine. Tested end to end on a
spare install root: build 25770 fetched, schema applied, boot entry
written, FXServer started and stopped exactly where a wrong licence key
should stop it.

No secrets travel with it: the licence key and the database password live
in data/secrets.cfg on the machine, and .gitignore now names it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Claude Opus 5
2026-08-12 23:23:33 +00:00
co-authored by Claude Opus 5
parent 80c1d75d2f
commit 71856b15e9
66 changed files with 2030 additions and 3814 deletions
+87
View File
@@ -0,0 +1,87 @@
-- ---------------------------------------------------------------------------
-- DB - Lua front end for rp_db.
--
-- Loaded *into* the consuming resource with:
-- server_scripts { '@rp_db/lib/db.lua', 'server/whatever.lua' }
--
-- so that Citizen.Await suspends the caller's own coroutine instead of trying
-- to yield across a cross-resource export call.
--
-- Every function comes in two flavours:
-- DB.Query(sql, params) -> rows, err (blocking, inside a thread)
-- DB.QueryAsync(sql, params, cb) -> cb(rows, err) (callback, anywhere)
--
-- Values are always passed as `?` parameters. Never build SQL by concatenation.
-- Use DB.NULL where you need a literal SQL NULL (Lua nil cannot survive a table).
-- ---------------------------------------------------------------------------
DB = {}
DB.NULL = 'RP_NULL'
local backend = exports.rp_db
local function async(method)
return function(sql, params, cb)
backend[method](backend, sql, params or {}, cb)
end
end
local function blocking(method)
return function(sql, params)
local p = promise.new()
backend[method](backend, sql, params or {}, function(result, err)
-- wrapped: a promise resolved with nil would never wake the coroutine
p:resolve({ result = result, err = err })
end)
local r = Citizen.Await(p)
if r.err then return nil, r.err end
return r.result
end
end
--- Returns all rows as an array of tables (empty table when nothing matched).
DB.Query = blocking('query')
--- Returns the first row as a table, or nil.
DB.Single = blocking('single')
--- Returns the first column of the first row, or nil.
DB.Scalar = blocking('scalar')
--- Returns the AUTO_INCREMENT id produced by an INSERT.
DB.Insert = blocking('insert')
--- Returns the number of rows changed by an UPDATE/DELETE.
DB.Update = blocking('update')
DB.QueryAsync = async('query')
DB.SingleAsync = async('single')
DB.ScalarAsync = async('scalar')
DB.InsertAsync = async('insert')
DB.UpdateAsync = async('update')
--- All statements commit together or none of them do.
--- statements: { { query = 'UPDATE ..', values = { 1, 2 } }, ... }
function DB.Transaction(statements)
local p = promise.new()
backend:transaction(statements, function(ok, err)
p:resolve({ ok = ok, err = err })
end)
local r = Citizen.Await(p)
if not r.ok then return false, r.err end
return true
end
function DB.TransactionAsync(statements, cb)
backend:transaction(statements, cb)
end
--- Blocks until the connection pool has at least one live connection.
--- Returns false if the database never came up within `timeout` ms.
function DB.WaitReady(timeout)
local waited, step = 0, 100
timeout = timeout or 30000
while not backend:isReady() do
if waited >= timeout then return false end
Citizen.Wait(step)
waited = waited + step
end
return true
end