parsav  Check-in [8d35307a7f]

Overview
Comment:add memory pool impl, handle various little details, add beginnings of mimelib
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 8d35307a7f76cc6624c69b3f11b1203c747d45596358531cd93d699f4265c35a
User & Date: lexi on 2021-01-10 03:54:46
Other Links: manifest | tags
Context
2021-01-10
08:19
begin replacing inefficient memory management with a pool-based solution; fix memory leaks check-in: 7c8769bf96 user: lexi tags: trunk
03:54
add memory pool impl, handle various little details, add beginnings of mimelib check-in: 8d35307a7f user: lexi tags: trunk
2021-01-09
07:15
user mgmt and rt improvements check-in: 05af79b909 user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [175b022aff] to [794844a9c8].

     1      1   -- vim: ft=terra
     2      2   local pstring = lib.mem.ptr(int8)
     3      3   local binblob = lib.mem.ptr(uint8)
     4      4   local queries = {
            5  +	server_setup_self = {
            6  +		params = {rawstring,binblob,int64}, cmd = true, sql = [[
            7  +			insert into parsav_servers (id, domain, key, parsav, knownsince)
            8  +				values (0, $1::text, $2::bytea, true, $3::bigint)
            9  +		]];
           10  +	};
           11  +
           12  +	server_fetch_sid = {
           13  +		params = {uint64}, sql = [[
           14  +			select domain, key, knownsince, parsav from parsav_servers
           15  +				where id = $1::bigint
           16  +		]];
           17  +	};
           18  +
     5     19   	conf_get = {
     6     20   		params = {rawstring}, sql = [[
     7     21   			select value from parsav_config
     8     22   				where key = $1::text limit 1
     9     23   		]];
    10     24   	};
    11     25   
................................................................................
  1146   1160   	tx_enter = txdo, tx_complete = txdone;
  1147   1161   
  1148   1162   	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t): {}
  1149   1163   		var [con] = [&lib.pq.PGconn](src.handle)
  1150   1164   		if mode == lib.store.prepmode.full then [prep]
  1151   1165   		elseif mode == lib.store.prepmode.conf or
  1152   1166   		       mode == lib.store.prepmode.admin then 
         1167  +			queries.server_setup_self.prep(con)
  1153   1168   			queries.conf_get.prep(con)
  1154   1169   			queries.conf_set.prep(con)
  1155   1170   			queries.conf_reset.prep(con)
  1156   1171   			if mode == lib.store.prepmode.admin then 
         1172  +				queries.server_fetch_sid.prep(con)
         1173  +				queries.actor_fetch_uid.prep(con)
         1174  +				queries.actor_fetch_xid.prep(con)
         1175  +				queries.actor_enum.prep(con)
         1176  +				queries.actor_enum_local.prep(con)
         1177  +				queries.actor_stats.prep(con)
         1178  +				queries.actor_powers_fetch.prep(con)
         1179  +				queries.actor_save.prep(con)
         1180  +				queries.actor_create.prep(con)
         1181  +				queries.actor_purge_uid.prep(con)
  1157   1182   			end
  1158   1183   		else lib.bail('unsupported connection preparation mode') end
  1159   1184   	end];
         1185  +
         1186  +	server_setup_self = [terra(
         1187  +		src: &lib.store.source,
         1188  +		domain: rawstring,
         1189  +		key: binblob
         1190  +	): {}
         1191  +		queries.server_setup_self.exec(src,domain,key,lib.osclock.time(nil))
         1192  +	end];
  1160   1193   
  1161   1194   	dbsetup = [terra(src: &lib.store.source): bool
  1162   1195   		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), schema)
  1163   1196   		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
  1164   1197   			lib.report('successfully instantiated schema in database')
  1165   1198   			return true
  1166   1199   		else

Modified backend/schema/pgsql.sql from [29d1b18fbc] to [bc736c4c1d].

     1      1   create table parsav_config (
     2      2   	key   text primary key,
     3      3   	value text
     4      4   );
            5  +comment on table parsav_config is
            6  +'server-wide configuration variables. highly sensitive!';
     5      7   
     6      8   insert into parsav_config (key,value) values ('schema-version','1'),
     7      9   	('credential-store','managed');
     8     10   --	('bind',:'bind'),
     9     11   --	('domain',:'domain'),
    10     12   --	('instance-name',:'inst'),
    11     13   --	('policy-security',:'secmode'),
................................................................................
    17     19   create table parsav_servers (
    18     20   	id     bigint primary key default (1+random()*(2^63-1))::bigint,
    19     21   	domain text not null unique,
    20     22   	key    bytea,
    21     23   	knownsince bigint,
    22     24   	parsav boolean -- whether to use parsav protocol extensions
    23     25   );
           26  +comment on table parsav_servers is
           27  +'all servers known to the parsav instance. the local server (including its private key) is stored in row (id = 0)';
    24     28   
    25     29   create table parsav_actors (
    26     30   	id        bigint primary key default (1+random()*(2^63-1))::bigint,
    27     31   	nym       text,
    28     32   	handle    text not null, -- nym [@handle@origin] 
    29     33   	origin    bigint references parsav_servers(id)
    30     34   		on delete cascade, -- null origin = local actor
................................................................................
    37     41   	invites   integer not null default 0,
    38     42   	key       bytea, -- private if localactor; public if remote
    39     43   	epithet   text,
    40     44   	authtime  bigint not null, -- cookies earlier than this timepoint will not be accepted
    41     45   	
    42     46   	unique (handle,origin)
    43     47   );
           48  +comment on table parsav_actors is
           49  +'all users known to the instance across the fediverse; local users satisfy constraint (origin = 0)';
    44     50   
    45     51   create table parsav_rights (
    46     52   	key text,
    47     53   	actor bigint references parsav_actors(id)
    48     54   		on delete cascade,
    49     55   	allow boolean not null,
    50     56   	scope bigint, -- for future expansion
    51     57   
    52     58   	primary key (key,actor)
    53     59   );
    54     60   create index on parsav_rights (actor);
           61  +comment on table parsav_rights is
           62  +'a backward-compatible list of every non-default privilege or deprivilege granted to a local user';
    55     63   
    56     64   create table parsav_posts (
    57     65   	id         bigint primary key default (1+random()*(2^63-1))::bigint,
    58     66   	author     bigint references parsav_actors(id) on delete cascade,
    59     67   	subject    text,
    60     68   	acl        text not null default 'all', -- just store the script raw 🤷
    61     69   	body       text,
................................................................................
    81     89   		on delete cascade, -- e.g. follower
    82     90   	relatee bigint references parsav_actors(id)
    83     91   		on delete cascade, -- e.g. followed
    84     92   	kind    smallint, -- e.g. follow, block, mute
    85     93   
    86     94   	primary key (relator, relatee, kind)
    87     95   );
           96  +comment on table parsav_rels is
           97  +'all relationships, positive and negative, between local users and other users; kind is a version-specific integer mapping to a type-of-relationship enum in store.t';
    88     98   
    89     99   create table parsav_acts (
    90    100   	id      bigint primary key default (1+random()*(2^63-1))::bigint,
    91    101   	kind    text not null, -- like, rt, react, so on
    92    102   	time    bigint not null,
    93    103   	actor   bigint references parsav_actors(id) on delete cascade,
    94    104   	subject bigint, -- may be post or act, depending on kind
    95         -	body	text -- emoji, if react
          105  +	body	text -- emoji, if react; complaint, if report
    96    106   );
    97    107   create index on parsav_acts (subject);
    98    108   create index on parsav_acts (actor);
    99    109   create index on parsav_acts (time);
          110  +comment on table parsav_acts is
          111  +'every simple action taken on a tweet by an actor, including likes, rts, reacts, and reports';
   100    112   
   101    113   create table parsav_log (
   102    114   	-- accesses are tracked for security & sending delete acts
   103    115   	id    bigint primary key default (1+random()*(2^63-1))::bigint,
   104    116   	time  bigint not null,
   105    117   	actor bigint references parsav_actors(id)
   106    118   		on delete cascade,
   107    119   	post  bigint not null
   108    120   );
          121  +comment on table parsav_log is
          122  +'a log of accesses from foreign servers, tracking which will be sent update & delete events for each post';
   109    123   
   110    124   create table parsav_artifacts (
   111    125   	id          bigint primary key default (1+random()*(2^63-1))::bigint,
   112    126   	birth       bigint not null,
   113    127   	content     bytea, -- if null, this is a "ban record" preventing content matching the hash from being re-uploaded
   114    128   	hash		bytea unique not null, -- sha256 hash of content
   115    129   	-- it would be cool to use a computed column for this, but i don't want
   116    130   	-- to lock people into PG12 or drag in the pgcrypto extension just for this
   117         -	mime        text -- null if unknown, will be reported as x-octet-stream
          131  +	mime        text -- null if unknown, will be reported as octet-stream
   118    132   );
   119    133   create index on parsav_artifacts (mime);
          134  +comment on table parsav_artifacts is
          135  +'deduplicated media files uploaded by users';
   120    136   
   121    137   create table parsav_artifact_claims (
   122    138   	birth bigint not null,
   123    139   	uid bigint references parsav_actors(id) on delete cascade,
   124    140   	rid bigint references parsav_artifacts(id) on delete cascade,
   125    141   	description text,
   126    142   	folder text,
   127    143   
   128    144   	unique (uid,rid)
   129    145   );
   130    146   create index on parsav_artifact_claims (uid);
   131    147   create index on parsav_artifact_claims (uid,folder);
          148  +comment on table parsav_artifact_claims is
          149  +'a list of users who have an ownership interest in each artifact (effectively an index of GC roots)';
   132    150   
   133    151   create table parsav_circles (
   134    152   	id          bigint primary key default (1+random()*(2^63-1))::bigint,
   135    153   	owner       bigint not null references parsav_actors(id) on delete cascade,
   136    154   	name        text not null,
   137    155   	members     bigint[] not null default array[]::bigint[],
   138    156   
................................................................................
   143    161   create table parsav_rooms (
   144    162   	id          bigint primary key default (1+random()*(2^63-1))::bigint,
   145    163   	origin		bigint references parsav_servers(id) on delete cascade,
   146    164   	name		text not null,
   147    165   	description text not null,
   148    166   	policy      smallint not null
   149    167   );
          168  +comment on table parsav_rooms is
          169  +'an index of user-created chatrooms';
   150    170   
   151    171   create table parsav_room_members (
   152    172   	room   bigint not null references parsav_rooms(id) on delete cascade,
   153    173   	member bigint not null references parsav_actors(id) on delete cascade,
   154    174   	rank   smallint not null default 0,
   155    175   	admin  boolean not null default false, -- non-admins with rank can only moderate + invite
   156    176   	title  text, -- admin-granted title like reddit flair
................................................................................
   165    185   	-- ID becomes the user ID. privileges granted on the invite ID during the invite
   166    186   	-- process are thus inherited by the user
   167    187   	issuer bigint references parsav_actors(id) on delete set null,
   168    188   	handle text, -- admin can lock invite to specific handle
   169    189   	rank   smallint not null default 0,
   170    190   	quota  integer not null  default 1000
   171    191   );
          192  +comment on table parsav_invites is
          193  +'all active invitations and the level of authority they grant if accepted';
   172    194   
   173    195   create table parsav_sanctions (
   174    196   	id     bigint primary key default (1+random()*(2^63-1))::bigint,
   175    197   	issuer bigint references parsav_actors(id) on delete set null,
   176    198   	scope  bigint, -- can be null or room for local actions
   177    199   	nature smallint not null, -- silence, suspend, disemvowel, censor, noreply, etc
   178    200   	victim bigint not null, -- can be user, room, or post
................................................................................
   180    202   	review bigint,  -- brings up for review at given time if set
   181    203   	reason text, -- visible to victim if set
   182    204   	context text, -- admin-only note
   183    205   	appeal text -- null if no appeal lodged
   184    206   );
   185    207   create index on parsav_sanctions (victim,scope);
   186    208   create index on parsav_sanctions (issuer);
          209  +comment on table parsav_sanctions is
          210  +'administrative actions taken against particular users, posts, rooms, or other entities';
   187    211   
   188    212   create table parsav_actor_conf_strs (
   189    213   	uid bigint not null references parsav_actors(id) on delete cascade,
   190    214   	key text not null, value text not null, unique (uid,key)
   191    215   );
   192    216   create table parsav_actor_conf_ints (
   193    217   	uid bigint not null references parsav_actors(id) on delete cascade,
   194    218   	key text not null, value bigint not null, unique (uid,key)
   195    219   );
          220  +comment on table parsav_actor_conf_strs is 'per-user configuration settings (string properties)';
          221  +comment on table parsav_actor_conf_ints is 'per-user configuration settings (integer and enumeration properties)';
   196    222   
   197    223   -- create a temporary managed auth table; we can delete this later
   198    224   -- if it ends up being replaced with a view
   199    225   %include pgsql-auth.sql%

Modified mem.t from [a0c3213659] to [de24aef2de].

   173    173   	v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index??
   174    174   		if self.sz > idx then
   175    175   			return self.storage.ptr + idx
   176    176   		else lib.bail('vector overrun!') end
   177    177   	end
   178    178   	return v 
   179    179   end)
          180  +
          181  +struct m.pool {
          182  + -- implements growable memory pools. EVERY THREAD MUST HAVE ITS OWN
          183  +	storage: &opaque
          184  +	cursor: &opaque
          185  +	sz: intptr
          186  +}
          187  +
          188  +terra m.pool:cue(sz: intptr)
          189  +	if self.storage == nil then
          190  +		self.storage = m.heapa_raw(sz)
          191  +		self.cursor = self.storage
          192  +		self.sz = sz
          193  +	else
          194  +		if self.sz >= sz then return self end
          195  +		var ofs = [&uint8](self.cursor) - [&uint8](self.storage)
          196  +		self.storage = m.heapr_raw(self.storage, sz)
          197  +		self.cursor = [&opaque]([&uint8](self.storage) + ofs)
          198  +		self.sz = sz
          199  +	end
          200  +	return self
          201  +end
          202  +
          203  +terra m.pool:init(sz: intptr)
          204  +	self.storage = nil
          205  +	self:cue(sz)
          206  +	return self
          207  +end
          208  +
          209  +terra m.pool:free()
          210  +	m.heapf(self.storage)
          211  +	self.storage = nil
          212  +	self.cursor = nil
          213  +	self.sz = 0
          214  +end
          215  +
          216  +terra m.pool:clear()
          217  +	self.cursor = self.storage
          218  +	return self
          219  +end
          220  +
          221  +terra m.pool:alloc_bytes(sz: intptr): &opaque
          222  +	var space = self.sz - ([&uint8](self.cursor) - [&uint8](self.storage))
          223  +	if space < sz then self:cue(space + sz + 256) end
          224  +	var ptr = self.cursor
          225  +	self.cursor = [&opaque]([&uint8](self.cursor) + sz)
          226  +	return ptr
          227  +end
          228  +
          229  +m.pool.methods.alloc = macro(function(self,ty,sz)
          230  +	return `[ty](self:alloc_bytes(sizeof(ty) * sz))
          231  +end)
          232  +
          233  +terra m.pool:frame() -- stack-style linear mgmt
          234  +	return self.cursor
          235  +end
          236  +
          237  +terra m.pool:reset(frame: &opaque)
          238  +	self.cursor = frame
          239  +	return self
          240  +end
          241  +
   180    242   
   181    243   return m

Modified mgtool.t from [b1a646e421] to [b40be7a821].

   287    287   
   288    288   			srv:setup(cnf) 
   289    289   			if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
   290    290   				lib.report('initializing new database structure for domain ', dbmode.arglist(1))
   291    291   				dlg:tx_enter()
   292    292   				if dlg:dbsetup() then
   293    293   					srv:conprep(lib.store.prepmode.conf)
          294  +
          295  +					do var newkp = lib.crypt.genkp()
          296  +					 -- generate server privkey
          297  +						var kbuf: uint8[lib.crypt.const.maxdersz]
          298  +						var privsz = lib.crypt.der(false,&newkp, kbuf)
          299  +						dlg:server_setup_self(dbmode.arglist(1), [lib.mem.ptr(uint8)] {
          300  +							ptr = &kbuf[0], ct = privsz
          301  +						})
          302  +					end
          303  +
   294    304   					dlg:conf_set('instance-name', dbmode.arglist(1))
   295    305   					dlg:conf_set('domain', dbmode.arglist(1))
   296    306   					do var sec: int8[65] gensec(&sec[0])
   297         -						dlg:conf_set('server-secret', &sec[0])
   298    307   						dlg:conf_set('server-secret', &sec[0])
   299    308   					end
   300    309   					lib.report('database setup complete; use mkroot to create an administrative user')
   301    310   				else lib.bail('initialization process interrupted') end
   302    311   				dlg:tx_complete()
   303    312   			elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then
   304    313   				var cfmstr: int8[64] gen_cfstr(&cfmstr[0],0)

Added mime.t version [2e40a434e4].

            1  +local knowntypes = {
            2  +	['text/csrc'] = {
            3  +		ext = 'c', lang = 'c';
            4  +	};
            5  +	['text/html'] = {
            6  +		ext = 'html', lang = 'html';
            7  +		unsafe = true;
            8  +	};
            9  +	['text/markdown'] = {
           10  +		formatter = 'smackdown';
           11  +		ext = 'md', doc = true;
           12  +	};
           13  +}

Modified parsav.md from [52b33381db] to [1e18c01a8c].

     1      1   # parsav
     2      2   
     3      3   **parsav** is a lightweight social media server written in [terra](https://terralang.org), intended to integrate to some degree with the fediverse. it is named for the [Ranuir](http://Êž.cc/fic/spirals/ranuir) words *par* "speech, communication" and *sav* "unity, togetherness, solidarity".
     4      4   
     5      5   ## backends
     6      6   parsav is designed to be storage-agnostic, and can draw data from multiple backends at a time. backends can be enabled or disabled at compile time to avoid unnecessary dependencies.
     7      7   
     8         -* postgresql
            8  +* postgresql (backend `pgsql`)
     9      9   
    10     10   ## dependencies
    11     11   
    12     12   * runtime
    13     13     * mongoose
    14     14     * json-c
    15     15     * mbedtls
................................................................................
    21     21   
    22     22   additional preconfigure dependencies are necessary if you are building directly from trunk, rather than from a release tarball that includes certain build artifacts which need to be embedded in the binary:
    23     23   
    24     24   * inkscape, for rendering out some of the UI graphics that can't be represented with standard svg
    25     25   * cwebp (libwebp package), for transforming inkscape PNGs to webp
    26     26   * sassc, for compiling the SCSS stylesheet into its final CSS
    27     27   
    28         -all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR. 
           28  +all builds require terra, which, unfortunately, requires installing an older version of llvm, v9 at the latest (which i develop parsav under). with any luck, your distro will be clever enough to package terra and its dependencies properly (it's trivial on nix, tho you'll need to tweak the terra expression to select a more recent llvm package if you want v9; this isn't necessary to successfully build parsav however); Arch Linux is one of those distros which is not so clever, and whose (AUR) terra package is totally broken. due to these unfortunate circumstances, terra is distributed not just in source form, but also in the the form of LLVM IR and x86-64 assembly + object code. 
    29     29   
    30         -i've noticed that terra (at least with llvm9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form. as a workaround, i've tried generating LLVM IR (ostensibly for x86-64, though this is in reality an architecture-independent language), and then compiling that down to an object file with llc. it doesn't work. the generated binaries seem to run but they crash with bizarre errors and are impossible to debug, as llc refuses to include debug symbols. for these reasons, parsav will (almost certainly) not run on any architecture besides x86-64, at least until terra and/or llvm are fixed.
           30  +i've noticed that terra (at least with llvm 6 and 9) seems to get a bit cantankerous and trigger llvm to fail with bizarre errors when you try to cross-compile parsav from x86-64 to any other platform, even x86-32. i don't know if this problem exists on other architectures or in what form. as a workaround, i've tried generating LLVM IR (putatively for x86-64, though this is an ostensibly architecture-independent language), and then compiling that down to an object file with llc. it doesn't work. the generated binaries seem to run but they crash with bizarre errors and are impossible to debug, as llc refuses to include debug symbols. for these reasons, parsav will (almost certainly) not run on any architecture besides x86-64, at least until terra and/or llvm are fixed. there is a very small possibility however that compiling natively on an ARM or x86-32 host might succeed. if you can pull it off, please let me know and i'll update the docs.
    31     31   
    32     32   also note that, while parsav has a flag to build with ASAN, ASAN has proven unusable for most purposes as it routinely reports false positive buffer-heap-overflows. if you figure out how to defuckulate this, i will be overjoyed.
    33     33   
    34     34   ## building
    35     35   
    36     36   first, either install any missing dependencies as shared libraries, or build them as static libraries with the command `make dep.$LIBRARY`. as a shortcut, `make dep` will build all dependencies as static libraries. note that if the build system finds a static version of a library in the `lib/` folder, it will use that instead of any system library. note that these commands require GNU make (it may be installed as `gmake` on your system), although this is a fairly soft dependency -- if you really need to build it on BSD make, you can probably translate it with a minute or so of work; you'll just have to do some of the various gmake functions' work manually. this may be worthwhile if you're packaging for a BSD.
    37     37   
    38     38   postgresql-libs must be installed systemwide, as `parsav` does not currently provide for statically compiling and linking it
    39     39   
    40     40   if you use nixos and wish to build the pdf documentation, you're going to have to do a bit of extra work (but you're used to that, aren't you). for some incomprehensible reason, the groff package on nix is split up, seemingly randomly, with many crucial output devices relegated to the "perl" output of the package, which is not installed by default (and `nix-env -iA nixos.groff.perl` doesn't work either; i don't know why either). you'll have to instantiate and install the outputs directly by path, e.g. `nix-env -i /nix/store/*groff*/` to get everything you need into your profile. alas, the battle is not over: you also need to change the environment variables `GROFF_FONT_PATH` and `GROFF_TMAC_PATH` to point at the `font` and `tmac` subdirs of `~/.nix-profile/share/groff/$groff_version/`. once this is done, invoking `groff -Tpdf` will work as expected.
    41     41   
    42         -## configuring
           42  +unfortunately, the produced daemon binary is rather large, weighing in around 600K at the time of writing. you can reduce this significantly however by `strip`ping the binary, and reduce it further by compiling without debug functionality turned on (i.e. no debug symbols and no debug log level, both of which insert a large number of strings into the resulting object code).
           43  +
           44  +## configuration
    43     45   
    44     46   the `parsav` configuration is comprised of two components: the backends list and the config store. the backends list is a simple text file that tells `parsav` which data sources to draw from. the config store is a key-value store which contains the rest of the server's configuration, and is loaded from the backends. the configuration store can be spread across the backends; backends will be checked for configuration keys according to the order in which they are listed. changes to the configuration store affect parsav in real time; you only need to restart the server if you make a change to the backend list.
    45     47   
    46     48   you can directly modify the store from the command line with the `parsav conf` command; see `parsav conf -h` for more information.
    47     49   
    48     50   by default, parsav looks for a file called `backend.conf` in the current directory when it is launched. you can override this default with the `parsav_backend_file` environment or with the `-b`/`--backend-file` flag. `backend.conf` lists one backend per line, in the form `id type confstring`. for instance, if you had two postgresql databases, you might write a backend file like
    49     51   
    50     52       master   pgsql   host=localhost dbname=parsav
    51     53   	tweets   pgsql   host=420.69.dread.cloud dbname=content
    52     54   
    53         -the form the configuration string takes depends on the specific backend.
           55  +the form the configuration string takes depends on the specific backend. for postgres, it's just the standard postgres connection string, and supports all the usual properties, as it's passed directly to the client library unmodified.
    54     56   
    55     57   once you've set up a backend and confirmed parsav can connect succesfully to it, you can initialize the database with the command `parsav db init <domain>`, where `<domain>` is the name of the domain name you will be hosting `parsav` from. this will install all necessary structures and functions in the target and create all necessary files. it will not, however, create any users. you can create an initial administrative user with the `parsav mkroot <handle>` command, where `<handle>` is the handle you want to use on the server. this will also assign a temporary password for the user if possible. you should now be able to log in and administer the server.
    56     58   
    57     59   if something goes awry with your administrative account, don't fret! you can get your powers themselves back with the command `parsav user <handle> grant all`, and if you're having difficulties logging in, the command `parsav user <handle> auth pw reset` will give you a fresh password. if all else fails, you can always run `mkroot` again to create a new root account, and try to repair the damage from there.
    58     60   
    59     61   by default, parsav binds to [::1]:10917. if you want to change this (to run it on a different port, or make it directly accessible to other servers on the network), you can use the command `parsav conf set bind <address>`, where `address` is a binding specification like `0.0.0.0:80`. it is recommended, however, that `parsavd` be kept accessible only from localhost, and that connections be forwarded to it from nginx, haproxy, or a similar reverse proxy. (this can also be changed with the online configuration UI)
    60     62   
................................................................................
   127    129   
   128    130   * plain text/filesystem storage
   129    131   * lmdb
   130    132   * sqlite3
   131    133   * generic odbc
   132    134   * lua
   133    135   * ldap for auth (and maybe actors?)
   134         -* cdb (for static content, maybe?)
          136  +* cdb (for static content, maybe? does this make sense?)
   135    137   * mariadb/mysql
   136    138   * the various nosql horrors, e.g. redis, mongo, and so on
   137    139   
   138    140   parsav urgently needs an internationalization framework as well. right now everything is just hardcoded in english. yuck.
          141  +
          142  +parsav could be significantly improved by adjusting its memory management strategy. instead of allocating everything with lib.mem.heapa (which currently maps to malloc on all platforms), we should allocate a static buffer for the server overlord object which can simply be cleared and re-used for each http request, and enlarged with `realloc` when necessary. the entire region could be `mlock`ed for better performance, and it would no longer be necessary to track and free memory, as the entire buffer would simply be discarded after use (similar to PHP's original memory management strategy). this would remove possibly the largest source of latency in the codebase, as `parsav` is regrettably quite heavy on malloc, performing numerous allocations for each page rendered.

Modified render/nav.t from [50b4e7c2b2] to [0fd87c81ae].

     3      3   render_nav(co: &lib.srv.convo)
     4      4   	var t: lib.str.acc t:init(64)
     5      5   	if co.who ~= nil or co.srv.cfg.pol_sec == lib.srv.secmode.public then
     6      6   		t:lpush(' <a accesskey="t" href="/">timeline</a>')
     7      7   	end
     8      8   	if co.who ~= nil then
     9      9   		t:lpush(' <a accesskey="c" href="/compose">compose</a> <a accesskey="p" href="/'):push(co.who.xid,0)
    10         -		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>')
           10  +		t:lpush('">profile</a> <a accesskey="m" href="/media">media</a> <a accesskey="o" href="/conf">configure</a> <a accesskey="d" href="/doc">docs</a> <div class="ident">@')
           11  +		t:push(co.who.handle,0)
           12  +		t:lpush('</div> <a accesskey="g" href="/logout">log out</a> <a class="bell" accesskey="x" href="/notices">notices</a>')
    11     13   	else
    12     14   		t:lpush(' <a accesskey="d" href="/doc">docs</a> <a accesskey="g" href="/login">log in</a>')
    13     15   	end
    14     16   	return t:finalize()
    15     17   end
    16     18   return render_nav

Modified route.t from [a8702e1420] to [2b1fe64d41].

   360    360   			end)()]
   361    361   			privs:dump()
   362    362   			if privs:sz() > 0 then
   363    363   				lib.dbg('installing credential restrictions')
   364    364   				lib.io.fmt('on priv %llu\n',aid)
   365    365   				co.srv:auth_privs_set(aid, privs)
   366    366   			end
          367  +
          368  +			lib.dbg('setting netmask restrictions')
          369  +			var nm = co:pgetv('netmask')
   367    370   		end
   368    371   		co:reroute('?')
   369    372   		return
   370    373   	end
   371    374   	co:complain(400,'bad request','the operation you have requested is not meaningful in this context')
   372    375   end
   373    376   

Modified static/style.scss from [f9852f8724] to [ba9256aed1].

   210    210   		}
   211    211   		nav {
   212    212   			all: unset;
   213    213   			display: flex;
   214    214   			justify-content: flex-end;
   215    215   			align-items: center;
   216    216   			grid-column: 2/3; grid-row: 1/2;
          217  +			.ident {
          218  +				color: tone(-20%);
          219  +				margin-left: 0.2em;
          220  +				border-left: 1px solid tone(-40%);
          221  +				padding-left: 0.5em;
          222  +			}
   217    223   			> a[href] {
   218    224   				display: block;
   219    225   				padding: 0.25in 0.10in;
   220    226   				//padding: calc((25% - 1em)/2) 0.15in;
   221    227   				&, &::after { transition: 0.3s; }
   222    228   				text-shadow: 1px 1px 1px black;
   223    229   				&:hover{
................................................................................
   516    522   	margin: unset;
   517    523   	grid-template-columns: 1in 1fr max-content max-content;
   518    524   	grid-template-rows: min-content max-content;
   519    525   	margin-bottom: 0.1in;
   520    526   	transition: 0.2s ease-out;
   521    527   	>.avatar {
   522    528   		grid-column: 1/2; grid-row: 1/2;
   523         -		img { display: block; width: 1in; height: 1in; margin:0; }
   524    529   		background: linear-gradient(to bottom, tone(-53%), tone(-57%));
          530  +		img {
          531  +			display: block; width: 1in; height: 1in; margin:0;
          532  +			border-right: 1px solid tone(-65%);
          533  +		}
   525    534   	}
   526    535   	>a[href].username {
   527    536   		display: block;
   528    537   		grid-column: 1/3;
   529    538   		grid-row: 2/3;
   530    539   		text-align: left;
   531    540   		text-decoration: none;

Modified store.t from [0ebd27b207] to [adf1545306].

   353    353   
   354    354   	tx_enter: &m.source -> bool
   355    355   	tx_complete: &m.source -> bool
   356    356   	-- these two functions are special, in that they should be called
   357    357   	-- directly on a specific backend, rather than passed down to the
   358    358   	-- backends by the server; that is pathological behavior that will
   359    359   	-- not have the desired effect
          360  +
          361  +	server_setup_self: {&m.source, rawstring, lib.mem.ptr(uint8)} -> {}
   360    362   
   361    363   	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
   362    364   	conf_set: {&m.source, rawstring, rawstring} -> {}
   363    365   	conf_reset: {&m.source, rawstring} -> {}
   364    366   
   365    367   	actor_create: {&m.source, &m.actor} -> uint64
   366    368   	actor_save: {&m.source, &m.actor} -> {}