| Comment: | vastly improve the setup process |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
d228cd7fcb7c10b982f8c2129c506440 |
| User & Date: | lexi on 2020-12-28 23:42:22 |
| Other Links: | manifest | tags |
|
2020-12-29
| ||
| 00:57 | add privilege control verbs check-in: a64461061f user: lexi tags: trunk | |
|
2020-12-28
| ||
| 23:42 | vastly improve the setup process check-in: d228cd7fcb user: lexi tags: trunk | |
|
2020-12-27
| ||
| 04:08 | look ma, im tweetin check-in: 8f954221a1 user: lexi tags: trunk | |
Modified backend/pgsql.t from [2c2a215381] to [0fdc39456b].
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 .. 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 .. 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 .. 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 ... 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 ... 190 191 192 193 194 195 196 197 198 199 200 201 202 203 ... 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 ... 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 ... 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 ... 530 531 532 533 534 535 536 537 538 539 540 541 542 543 ... 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 ... 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 ... 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 |
params = {rawstring}, sql = [[
select value from parsav_config
where key = $1::text limit 1
]];
};
conf_set = {
params = {rawstring,rawstring}, sql = [[
insert into parsav_config (key, value)
values ($1::text, $2::text)
on conflict (key) do update set value = $2::text
]];
};
conf_reset = {
params = {rawstring}, sql = [[
delete from parsav_config where
key = $1::text
]];
};
actor_fetch_uid = {
params = {uint64}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors as a
left join parsav_servers as s
on a.origin = s.id
................................................................................
where a.id = $1::bigint
]];
};
actor_fetch_xid = {
params = {pstring}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
coalesce(s.domain,
(select value from parsav_config
where key='domain' limit 1)) as domain
................................................................................
rawstring, uint16, uint32
};
sql = [[
insert into parsav_actors (
nym,handle,
origin,knownsince,
bio,avataruri,key,
title,rank,quota
) values ($1::text, $2::text,
case when $3::bigint = 0 then null
else $3::bigint end,
to_timestamp($4::bigint),
$5::bigint, $6::bigint, $7::bytea,
$8::text, $9::smallint, $10::integer
) returning id
................................................................................
order by blacklist desc limit 1
]];
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key,
extract(epoch from knownsince)::bigint,
handle ||'@'||
(select value from parsav_config
where key='domain' limit 1) as xid
from parsav_actors where origin is null
]];
};
actor_enum = {
params = {}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors a
left join parsav_servers s on s.id = a.origin
]];
};
................................................................................
(select count(*) from mts where kind = 'trust') > 0
]]; -- cheat
};
actor_session_fetch = {
params = {uint64, lib.store.inet}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
au.restrict,
array['post' ] <@ au.restrict as can_post,
array['edit' ] <@ au.restrict as can_edit,
................................................................................
};
actor_powers_fetch = {
params = {uint64}, sql = [[
select key, allow from parsav_rights where actor = $1::bigint
]]
};
post_create = {
params = {uint64, rawstring, rawstring, rawstring}, sql = [[
insert into parsav_posts (
author, subject, acl, body,
posted, discovered,
circles, mentions
................................................................................
return buf
end
end;
}
local con = symbol(&lib.pq.PGconn)
local prep = {}
local sqlsquash = function(s) return s:gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') end
for k,q in pairs(queries) do
local qt = sqlsquash(q.sql)
local stmt = 'parsavpg_' .. k
prep[#prep + 1] = quote
var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
defer lib.pq.PQclear(res)
if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
if res == nil then
lib.bail('grievous error occurred preparing ',k,' statement')
end
lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
end
lib.dbg('prepared PGSQL statement ',k)
end
local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
local dumpers = {}
for i, ty in ipairs(q.params) do
args[i] = symbol(ty)
ft[i] = `1
if ty == rawstring then
................................................................................
dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
fixers[#fixers + 1] = quote
[args[i]] = lib.math.netswap(ty, [args[i]])
end
end
end
terra q.exec(src: &lib.store.source, [args])
var params = arrayof([&int8], [casts])
var params_sz = arrayof(int, [counters])
var params_ft = arrayof(int, [ft])
[fixers]
--[dumpers]
var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
[#args], params, params_sz, params_ft, 1)
if res == nil then
lib.bail(['grievous error occurred executing '..k..' against database'])
elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
lib.bail(['PGSQL database procedure '..k..' failed\n'],
lib.pq.PQresultErrorMessage(res))
end
var ct = lib.pq.PQntuples(res)
if ct == 0 then
lib.pq.PQclear(res)
................................................................................
return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
var a: lib.mem.ptr(lib.store.actor)
var av: rawstring, avlen: intptr
var nym: rawstring, nymlen: intptr
var bio: rawstring, biolen: intptr
if r:null(row,5) then avlen = 0 av = nil else
av = r:string(row,5)
avlen = r:len(row,5)+1
end
if r:null(row,1) then nymlen = 0 nym = nil else
nym = r:string(row,1)
nymlen = r:len(row,1)+1
end
if r:null(row,4) then biolen = 0 bio = nil else
bio = r:string(row,4)
biolen = r:len(row,4)+1
end
a = [ lib.str.encapsulate(lib.store.actor, {
nym = {`nym, `nymlen};
bio = {`bio, `biolen};
avatar = {`av,`avlen};
handle = {`r:string(row, 2); `r:len(row,2) + 1};
xid = {`r:string(row, 10); `r:len(row,10) + 1};
}) ]
a.ptr.id = r:int(uint64, row, 0);
a.ptr.rights = lib.store.rights_default();
a.ptr.rights.rank = r:int(uint16, row, 6);
a.ptr.rights.quota = r:int(uint32, row, 7);
a.ptr.knownsince = r:int(int64,row, 9);
if r:null(row,8) then
a.ptr.key.ct = 0 a.ptr.key.ptr = nil
else
a.ptr.key = r:bin(row,8)
end
if r:null(row,3) then a.ptr.origin = 0
else a.ptr.origin = r:int(uint64,row,3) end
................................................................................
lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
var [out]
[vdrs]
lib.dbg(['could not find password hash'])
end
end
local b = `lib.store.backend {
id = "pgsql";
open = [terra(src: &lib.store.source): &opaque
lib.report('connecting to postgres database: ', src.string.ptr)
var [con] = lib.pq.PQconnectdb(src.string.ptr)
if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
lib.warn('postgres backend connection failed')
................................................................................
defer lib.pq.PQclear(res)
if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
lib.warn('failed to secure postgres connection')
lib.pq.PQfinish(con)
return nil
end
[prep]
return con
end];
close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
conf_get = [terra(src: &lib.store.source, key: rawstring)
var r = queries.conf_get.exec(src, key)
if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
defer r:free()
return r:String(0,0)
end
................................................................................
var a = row_to_actor(&r, 0)
a.ptr.source = src
var au = [lib.stat(lib.store.auth)] { ok = true }
au.val.aid = aid
au.val.uid = a.ptr.id
if not r:null(0,12) then -- restricted?
au.val.privs:clear()
(au.val.privs.post << r:bool(0,13))
(au.val.privs.edit << r:bool(0,14))
(au.val.privs.acct << r:bool(0,15))
(au.val.privs.upload << r:bool(0,16))
(au.val.privs.censor << r:bool(0,17))
(au.val.privs.admin << r:bool(0,18))
else au.val.privs:fill() end
return au, a
end
::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
[lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
return powers
end];
actor_create = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): uint64
var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.title, ac.rights.rank, ac.rights.quota)
if r.sz == 0 then lib.bail('failed to create actor!') end
return r:int(uint64,0,0)
end];
actor_auth_register_uid = nil; -- not necessary for view-based auth
}
return b
|
| | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > | > > | > > > | > > > > > > | | > > > < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 .. 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 .. 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 .. 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 ... 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 ... 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 ... 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 ... 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 ... 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 ... 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 ... 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 ... 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 ... 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 |
params = {rawstring}, sql = [[
select value from parsav_config
where key = $1::text limit 1
]];
};
conf_set = {
params = {rawstring,rawstring}, cmd=true, sql = [[
insert into parsav_config (key, value)
values ($1::text, $2::text)
on conflict (key) do update set value = $2::text
]];
};
conf_reset = {
params = {rawstring}, cmd=true, sql = [[
delete from parsav_config where
key = $1::text
]];
};
actor_fetch_uid = {
params = {uint64}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors as a
left join parsav_servers as s
on a.origin = s.id
................................................................................
where a.id = $1::bigint
]];
};
actor_fetch_xid = {
params = {pstring}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
coalesce(s.domain,
(select value from parsav_config
where key='domain' limit 1)) as domain
................................................................................
rawstring, uint16, uint32
};
sql = [[
insert into parsav_actors (
nym,handle,
origin,knownsince,
bio,avataruri,key,
epithet,rank,quota
) values ($1::text, $2::text,
case when $3::bigint = 0 then null
else $3::bigint end,
to_timestamp($4::bigint),
$5::bigint, $6::bigint, $7::bytea,
$8::text, $9::smallint, $10::integer
) returning id
................................................................................
order by blacklist desc limit 1
]];
};
actor_enum_local = {
params = {}, sql = [[
select id, nym, handle, origin, bio,
null::text, rank, quota, key, epithet,
extract(epoch from knownsince)::bigint,
handle ||'@'||
(select value from parsav_config
where key='domain' limit 1) as xid
from parsav_actors where origin is null
]];
};
actor_enum = {
params = {}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid
from parsav_actors a
left join parsav_servers s on s.id = a.origin
]];
};
................................................................................
(select count(*) from mts where kind = 'trust') > 0
]]; -- cheat
};
actor_session_fetch = {
params = {uint64, lib.store.inet}, sql = [[
select a.id, a.nym, a.handle, a.origin, a.bio,
a.avataruri, a.rank, a.quota, a.key, a.epithet,
extract(epoch from a.knownsince)::bigint,
coalesce(a.handle || '@' || s.domain,
'@' || a.handle) as xid,
au.restrict,
array['post' ] <@ au.restrict as can_post,
array['edit' ] <@ au.restrict as can_edit,
................................................................................
};
actor_powers_fetch = {
params = {uint64}, sql = [[
select key, allow from parsav_rights where actor = $1::bigint
]]
};
actor_power_insert = {
params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
insert into parsav_rights (actor, key, allow) values (
$1::bigint, $2::text, ($3::smallint)::integer::bool
)
]]
};
auth_create_pw = {
params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[
insert into parsav_auth (uid, name, kind, cred) values (
$1::bigint,
(select handle from parsav_actors where id = $1::bigint),
'pw-sha256', $2::bytea
)
]]
};
post_create = {
params = {uint64, rawstring, rawstring, rawstring}, sql = [[
insert into parsav_posts (
author, subject, acl, body,
posted, discovered,
circles, mentions
................................................................................
return buf
end
end;
}
local con = symbol(&lib.pq.PGconn)
local prep = {}
local function sqlsquash(s) return s
:gsub('%%include (.-)%%',function(f)
return sqlsquash(lib.util.ingest('backend/schema/' .. f))
end) -- include dependencies
:gsub('%-%-.-\n','') -- remove disruptive line comments
:gsub('%-%-.-$','') -- remove unnecessary terminal comments
:gsub('%s+',' ') -- remove whitespace
:gsub('^%s*(.-)%s*$','%1') -- chomp
end
for k,q in pairs(queries) do
local qt = sqlsquash(q.sql)
local stmt = 'parsavpg_' .. k
terra q.prep([con])
var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
defer lib.pq.PQclear(res)
if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
if res == nil then
lib.bail('grievous error occurred preparing ',k,' statement')
end
lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
end
lib.dbg('prepared PGSQL statement ',k)
end
prep[#prep + 1] = quote q.prep([con]) end
local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
local dumpers = {}
for i, ty in ipairs(q.params) do
args[i] = symbol(ty)
ft[i] = `1
if ty == rawstring then
................................................................................
dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
fixers[#fixers + 1] = quote
[args[i]] = lib.math.netswap(ty, [args[i]])
end
end
end
local okconst = lib.pq.PGRES_TUPLES_OK
if q.cmd then okconst = lib.pq.PGRES_COMMAND_OK end
terra q.exec(src: &lib.store.source, [args])
var params = arrayof([&int8], [casts])
var params_sz = arrayof(int, [counters])
var params_ft = arrayof(int, [ft])
[fixers]
--[dumpers]
var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
[#args], params, params_sz, params_ft, 1)
if res == nil then
lib.bail(['grievous error occurred executing '..k..' against database'])
elseif lib.pq.PQresultStatus(res) ~= okconst then
lib.bail(['PGSQL database procedure '..k..' failed\n'],
lib.pq.PQresultErrorMessage(res))
end
var ct = lib.pq.PQntuples(res)
if ct == 0 then
lib.pq.PQclear(res)
................................................................................
return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
var a: lib.mem.ptr(lib.store.actor)
var av: rawstring, avlen: intptr
var nym: rawstring, nymlen: intptr
var bio: rawstring, biolen: intptr
var epi: rawstring, epilen: intptr
if r:null(row,5) then avlen = 0 av = nil else
av = r:string(row,5)
avlen = r:len(row,5)+1
end
if r:null(row,1) then nymlen = 0 nym = nil else
nym = r:string(row,1)
nymlen = r:len(row,1)+1
end
if r:null(row,4) then biolen = 0 bio = nil else
bio = r:string(row,4)
biolen = r:len(row,4)+1
end
if r:null(row,9) then epilen = 0 epi = nil else
epi = r:string(row,9)
epilen = r:len(row,9)+1
end
a = [ lib.str.encapsulate(lib.store.actor, {
nym = {`nym, `nymlen};
bio = {`bio, `biolen};
epithet = {`epi, `epilen};
avatar = {`av,`avlen};
handle = {`r:string(row, 2); `r:len(row,2) + 1};
xid = {`r:string(row, 11); `r:len(row,11) + 1};
}) ]
a.ptr.id = r:int(uint64, row, 0);
a.ptr.rights = lib.store.rights_default();
a.ptr.rights.rank = r:int(uint16, row, 6);
a.ptr.rights.quota = r:int(uint32, row, 7);
a.ptr.knownsince = r:int(int64,row, 10);
if r:null(row,8) then
a.ptr.key.ct = 0 a.ptr.key.ptr = nil
else
a.ptr.key = r:bin(row,8)
end
if r:null(row,3) then a.ptr.origin = 0
else a.ptr.origin = r:int(uint64,row,3) end
................................................................................
lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
var [out]
[vdrs]
lib.dbg(['could not find password hash'])
end
end
local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))
local b = `lib.store.backend {
id = "pgsql";
open = [terra(src: &lib.store.source): &opaque
lib.report('connecting to postgres database: ', src.string.ptr)
var [con] = lib.pq.PQconnectdb(src.string.ptr)
if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
lib.warn('postgres backend connection failed')
................................................................................
defer lib.pq.PQclear(res)
if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
lib.warn('failed to secure postgres connection')
lib.pq.PQfinish(con)
return nil
end
return con
end];
close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
var [con] = [&lib.pq.PGconn](src.handle)
if mode == lib.store.prepmode.full then [prep]
elseif mode == lib.store.prepmode.conf or
mode == lib.store.prepmode.admin then
queries.conf_get.prep(con)
queries.conf_set.prep(con)
queries.conf_reset.prep(con)
if mode == lib.store.prepmode.admin then
end
else lib.bail('unsupported connection preparation mode') end
end];
dbsetup = [terra(src: &lib.store.source)
var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), schema)
if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
lib.report('successfully instantiated schema in database')
return true
else
lib.warn('backend pgsql - failed to initialize database: \n', lib.pq.PQresultErrorMessage(res))
return false
end
end];
obliterate_everything = [terra(src: &lib.store.source)
var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), obliterator)
if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
lib.report('successfully wiped out everything parsav-related in database')
return true
else
lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
return false
end
end];
conf_get = [terra(src: &lib.store.source, key: rawstring)
var r = queries.conf_get.exec(src, key)
if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
defer r:free()
return r:String(0,0)
end
................................................................................
var a = row_to_actor(&r, 0)
a.ptr.source = src
var au = [lib.stat(lib.store.auth)] { ok = true }
au.val.aid = aid
au.val.uid = a.ptr.id
if not r:null(0,13) then -- restricted?
au.val.privs:clear()
(au.val.privs.post << r:bool(0,14))
(au.val.privs.edit << r:bool(0,15))
(au.val.privs.acct << r:bool(0,16))
(au.val.privs.upload << r:bool(0,17))
(au.val.privs.censor << r:bool(0,18))
(au.val.privs.admin << r:bool(0,19))
else au.val.privs:fill() end
return au, a
end
::fail:: return [lib.stat (lib.store.auth) ] { ok = false },
[lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
................................................................................
return powers
end];
actor_create = [terra(
src: &lib.store.source,
ac: &lib.store.actor
): uint64
var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
if r.sz == 0 then lib.bail('failed to create actor!') end
var uid = r:int(uint64,0,0)
-- check against default rights, insert records for wherever powers differ
lib.dbg('created new actor, establishing powers')
var pdef = lib.store.rights_default().powers
var map = array([privmap])
for i=0, [map.type.N] do
var d = pdef and map[i].priv
var u = ac.rights.powers and map[i].priv
if d:sz() > 0 and u:sz() == 0 then
lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
queries.actor_power_insert.exec(src, uid, map[i].name, 0)
elseif d:sz() == 0 and u:sz() > 0 then
lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
queries.actor_power_insert.exec(src, uid, map[i].name, 1)
end
end
lib.dbg('powers established')
return uid
end];
auth_create_pw = [terra(
src: &lib.store.source,
uid: uint64,
reset: bool,
pw: lib.mem.ptr(int8)
): {}
-- TODO impl reset support
var hash: uint8[lib.crypt.algsz.sha256]
if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
[&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then
lib.bail('cannot hash password')
end
queries.auth_create_pw.exec(src, uid, [lib.mem.ptr(uint8)] {ptr = &hash[0], ct = [hash.type.N]})
end];
actor_auth_register_uid = nil; -- not necessary for view-based auth
}
return b
|
Added backend/schema/pgsql-auth.sql version [1170b3857b].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
-- in managed-auth configurations, parsav_auth is a table which is directly -- controlled by the parsav daemon and utilities themselves. in unmanaged -- configuration, you will need to create your own view with the same fields -- as this table create table parsav_auth ( aid bigint primary key default (1+random()*(2^63-1))::bigint, -- the AID is the value that links a session to its credentials, -- so the aid needs to be stable over time. if you don't have a -- convenient field to rely on in your own datasets, the best -- approach is to use digest(str,'sha256') from the pgcrypto -- extension to create a value that depends on the values of -- kind, cred, and a unique user ID from your own dataset (NOT -- uid, as the UID associated with a session will change when -- a user logs in for the first time). uid bigint, -- the UID links a credential set to an actor in the parsav -- database. if it is equal to 0 (but not null) a new actor -- will be created and associated with the authentication -- records bearing its name when that user first logs in name text, -- this is the handle of the actor that will be created when -- a user first logs in with this as the username and one of -- its associated credentials. the field is otherwise unused. kind text not null, -- see parsav.md cred bytea, restrict text[], -- per-credential restrictions can be levelled, for instance -- to prevent a certain API key from being used to post tweets -- as that user, while allowing it to be used to collect data. -- if restrict is null, no restrictions will be applied. -- otherwise, it should be an array of privileges that will be -- permitted when authenticated via this credential. netmask cidr, -- if not null, the credential will only be valid when logging -- in from an IP address contained by this netmask. blacklist bool not null default false, -- if the credential matches, access will be denied, even if -- non-blacklisted credentials match. most useful with -- uid = null, kind = trust, cidr = (untrusted IP range) valperiod timestamp default now(), -- cookies bearing timestamps earlier than this point in time -- will be considered invalid and will not grant access unique(name,kind,cred) ); |
Added backend/schema/pgsql-drop.sql version [e1fb43be2e].
> > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
-- destroy absolutely everything drop table if exists parsav_config cascade; drop table if exists parsav_servers cascade; drop table if exists parsav_actors cascade; drop table if exists parsav_rights cascade; drop table if exists parsav_posts cascade; drop table if exists parsav_conversations cascade; drop table if exists parsav_rels cascade; drop table if exists parsav_acts cascade; drop table if exists parsav_log cascade; drop table if exists parsav_attach cascade; drop table if exists parsav_circles cascade; drop table if exists parsav_rooms cascade; drop table if exists parsav_room_members cascade; drop table if exists parsav_invites cascade; drop table if exists parsav_interventions cascade; drop table if exists parsav_auth cascade; |
Modified backend/schema/pgsql.sql from [097969b0cb] to [0ef43163b5].
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
..
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
\prompt 'domain name: ' domain \prompt 'instance name: ' inst \prompt 'bind to socket: ' bind \qecho 'how locked down should this server be? public = anyone can see public timeline and tweets, private = anyone can see tweets with a link but login required for everything else, lockdown = login required for all activities, isolate = like lockdown but with federation protocols completely disabled' \prompt 'security mode: ' secmode \qecho 'should user self-registration be allowed? yes or no' \prompt 'registration: ' regpol \qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in and identified as the server owner.' \prompt 'master actor: ' admin \qecho 'you will need to create an authentication view named parsav_auth mapping your user database to something parsav can understand; see auth.sql for an example.' begin; drop table if exists parsav_config; create table if not exists parsav_config ( key text primary key, value text ); insert into parsav_config (key,value) values ('bind',:'bind'), ('domain',:'domain'), ('instance-name',:'inst'), ('policy-security',:'secmode'), ('policy-self-register',:'regpol'), ('master',:'admin'), ('server-secret', encode( digest(int8send((2^63 * (random()*2 - 1))::bigint), 'sha512'), 'base64')); -- note that valid ids should always > 0, as 0 is reserved for null -- on the client side, vastly simplifying code drop table if exists parsav_servers cascade; create table parsav_servers ( id bigint primary key default (1+random()*(2^63-1))::bigint, domain text not null, key bytea, knownsince timestamp, parsav boolean -- whether to use parsav protocol extensions ); drop table if exists parsav_actors cascade; create table parsav_actors ( id bigint primary key default (1+random()*(2^63-1))::bigint, nym text, handle text not null, -- nym [@handle@origin] origin bigint references parsav_servers(id) on delete cascade, -- null origin = local actor knownsince timestamp, bio text, avataruri text, -- null if local rank smallint not null default 0, quota integer not null default 1000, key bytea, -- private if localactor; public if remote title text, unique (handle,origin) ); drop table if exists parsav_rights cascade; create table parsav_rights ( key text, actor bigint references parsav_actors(id) on delete cascade, allow boolean not null, scope bigint, -- for future expansion primary key (key,actor) ); insert into parsav_actors (handle,rank,quota) values (:'admin',1,0); insert into parsav_rights (actor,key,allow) select (select id from parsav_actors where handle=:'admin'), a.column1, a.column2 from (values ('purge',true), ('config',true), ('censor',true), ('suspend',true), ('cred',true), ('elevate',true), ('demote',true), ('rebrand',true) ) as a; drop table if exists parsav_posts cascade; create table parsav_posts ( id bigint primary key default (1+random()*(2^63-1))::bigint, author bigint references parsav_actors(id) on delete cascade, subject text, acl text not null default 'all', -- just store the script raw 🤷 body text, ................................................................................ convoheaduri text -- only used for tracking foreign conversations and tying them to post heads; -- local conversations are tracked directly and mapped to URIs based on the -- head's ID. null if native tweet or not the first tweet in convo ); drop table if exists parsav_conversations cascade; drop table if exists parsav_rels cascade; create table parsav_rels ( relator bigint references parsav_actors(id) on delete cascade, -- e.g. follower relatee bigint references parsav_actors(id) on delete cascade, -- e.g. followed kind smallint, -- e.g. follow, block, mute primary key (relator, relatee, kind) ); drop table if exists parsav_acts cascade; create table parsav_acts ( id bigint primary key default (1+random()*(2^63-1))::bigint, kind text not null, -- like, react, so on time timestamp not null default now(), actor bigint references parsav_actors(id) on delete cascade, subject bigint -- may be post or act, depending on kind ); drop table if exists parsav_log cascade; create table parsav_log ( -- accesses are tracked for security & sending delete acts id bigint primary key default (1+random()*(2^63-1))::bigint, time timestamp not null default now(), actor bigint references parsav_actors(id) on delete cascade, post bigint not null ); drop table if exists parsav_attach cascade; create table parsav_attach ( id bigint primary key default (1+random()*(2^63-1))::bigint, birth timestamp not null default now(), content bytea not null, mime text, -- null if unknown, will be reported as x-octet-stream description text, parent bigint -- post id, or userid for avatars ); drop table if exists parsav_circles cascade; create table parsav_circles ( id bigint primary key default (1+random()*(2^63-1))::bigint, owner bigint not null references parsav_actors(id), name text not null, members bigint[] not null default array[]::bigint[], unique (owner,name) ); drop table if exists parsav_rooms cascade; create table parsav_rooms ( id bigint primary key default (1+random()*(2^63-1))::bigint, origin bigint references parsav_servers(id), name text not null, description text not null, policy smallint not null ); drop table if exists parsav_room_members cascade; create table parsav_room_members ( room bigint references parsav_rooms(id), member bigint references parsav_actors(id), rank smallint not null default 0, admin boolean not null default false, -- non-admins with rank can only moderate + invite title text, -- admin-granted title like reddit flair vouchedby bigint references parsav_actors(id) ); drop table if exists parsav_invites cascade; create table parsav_invites ( id bigint primary key default (1+random()*(2^63-1))::bigint, -- when a user is created from an invite, the invite is deleted and the invite -- ID becomes the user ID. privileges granted on the invite ID during the invite -- process are thus inherited by the user issuer bigint references parsav_actors(id), handle text, -- admin can lock invite to specific handle rank smallint not null default 0, quota integer not null default 1000 ); drop table if exists parsav_interventions cascade; create table parsav_interventions ( id bigint primary key default (1+random()*(2^63-1))::bigint, issuer bigint references parsav_actors(id) not null, scope bigint, -- can be null or room for local actions nature smallint not null, -- silence, suspend, disemvowel, etc victim bigint not null, -- could potentially target group as well expire timestamp -- auto-expires if set ); end; |
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
>
|
|
|
|
|
|
<
<
<
<
<
|
>
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
>
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
..
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
create table parsav_config ( key text primary key, value text ); insert into parsav_config (key,value) values ('schema-version','1'), ('credential-store','managed'); -- ('bind',:'bind'), -- ('domain',:'domain'), -- ('instance-name',:'inst'), -- ('policy-security',:'secmode'), -- ('policy-self-register',:'regpol'), -- ('master',:'admin'), -- note that valid ids should always > 0, as 0 is reserved for null -- on the client side, vastly simplifying code create table parsav_servers ( id bigint primary key default (1+random()*(2^63-1))::bigint, domain text not null, key bytea, knownsince timestamp, parsav boolean -- whether to use parsav protocol extensions ); create table parsav_actors ( id bigint primary key default (1+random()*(2^63-1))::bigint, nym text, handle text not null, -- nym [@handle@origin] origin bigint references parsav_servers(id) on delete cascade, -- null origin = local actor knownsince timestamp, bio text, avataruri text, -- null if local rank smallint not null default 0, quota integer not null default 1000, key bytea, -- private if localactor; public if remote epithet text, authtime timestamp not null default now(), -- cookies earlier than this timepoint will not be accepted unique (handle,origin) ); create table parsav_rights ( key text, actor bigint references parsav_actors(id) on delete cascade, allow boolean not null, scope bigint, -- for future expansion primary key (key,actor) ); create table parsav_posts ( id bigint primary key default (1+random()*(2^63-1))::bigint, author bigint references parsav_actors(id) on delete cascade, subject text, acl text not null default 'all', -- just store the script raw 🤷 body text, ................................................................................ convoheaduri text -- only used for tracking foreign conversations and tying them to post heads; -- local conversations are tracked directly and mapped to URIs based on the -- head's ID. null if native tweet or not the first tweet in convo ); create table parsav_rels ( relator bigint references parsav_actors(id) on delete cascade, -- e.g. follower relatee bigint references parsav_actors(id) on delete cascade, -- e.g. followed kind smallint, -- e.g. follow, block, mute primary key (relator, relatee, kind) ); create table parsav_acts ( id bigint primary key default (1+random()*(2^63-1))::bigint, kind text not null, -- like, react, so on time timestamp not null default now(), actor bigint references parsav_actors(id) on delete cascade, subject bigint -- may be post or act, depending on kind ); create table parsav_log ( -- accesses are tracked for security & sending delete acts id bigint primary key default (1+random()*(2^63-1))::bigint, time timestamp not null default now(), actor bigint references parsav_actors(id) on delete cascade, post bigint not null ); create table parsav_attach ( id bigint primary key default (1+random()*(2^63-1))::bigint, birth timestamp not null default now(), content bytea not null, mime text, -- null if unknown, will be reported as x-octet-stream description text, parent bigint -- post id, or userid for avatars ); create table parsav_circles ( id bigint primary key default (1+random()*(2^63-1))::bigint, owner bigint not null references parsav_actors(id), name text not null, members bigint[] not null default array[]::bigint[], unique (owner,name) ); create table parsav_rooms ( id bigint primary key default (1+random()*(2^63-1))::bigint, origin bigint references parsav_servers(id), name text not null, description text not null, policy smallint not null ); create table parsav_room_members ( room bigint references parsav_rooms(id), member bigint references parsav_actors(id), rank smallint not null default 0, admin boolean not null default false, -- non-admins with rank can only moderate + invite title text, -- admin-granted title like reddit flair vouchedby bigint references parsav_actors(id) ); create table parsav_invites ( id bigint primary key default (1+random()*(2^63-1))::bigint, -- when a user is created from an invite, the invite is deleted and the invite -- ID becomes the user ID. privileges granted on the invite ID during the invite -- process are thus inherited by the user issuer bigint references parsav_actors(id), handle text, -- admin can lock invite to specific handle rank smallint not null default 0, quota integer not null default 1000 ); create table parsav_interventions ( id bigint primary key default (1+random()*(2^63-1))::bigint, issuer bigint references parsav_actors(id) not null, scope bigint, -- can be null or room for local actions nature smallint not null, -- silence, suspend, disemvowel, etc victim bigint not null, -- could potentially target group as well expire timestamp -- auto-expires if set ); -- create a temporary managed auth table; we can delete this later -- if it ends up being replaced with a view %include pgsql-auth.sql% |
Modified cmdparse.t from [50677a3c0c] to [bfedd61eec].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 .. 49 50 51 52 53 54 55 56 57 58 59 60 61 62 .. 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
-- vim: ft=terra
return function(tbl)
local options = terralib.types.newstruct('options') do
local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end
local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n'
options.entries = {
{field = 'arglist', type = lib.mem.ptr(rawstring)}
}
local shortcases, longcases, init, verifiers = {}, {}, {}, {}
local self = symbol(&options)
local arg = symbol(rawstring)
local idx = symbol(uint)
local argv = symbol(&rawstring)
local argc = symbol(int)
local optstack = symbol(intptr)
local skip = label()
local sanitize = function(s) return s:gsub('_','-') end
for o,desc in pairs(tbl) do
local consume = desc[3] or 0
options.entries[#options.entries + 1] = {
field = o, type = (consume > 0) and &rawstring or bool
}
helpstr = helpstr .. string.format(' -%s --%s: %s\n',
desc[1], sanitize(o), desc[2])
end
for o,desc in pairs(tbl) do
local flag = desc[1]
local consume = desc[3] or 0
init[#init + 1] = quote [self].[o] = [(consume > 0 and `nil) or false] end
local ch if consume > 0 then
ch = quote
[self].[o] = argv+(idx+1+optstack)
optstack = optstack + consume
end
verifiers[#verifiers+1] = quote
var terminus = argv + argc
if [self].[o] ~= nil and [self].[o] >= terminus then
lib.bail(['missing argument for command line option ' .. sanitize(o)])
end
end
else ch = quote
[self].[o] = true
end end
shortcases[#shortcases + 1] = quote
case [int8]([string.byte(flag)]) then [ch] end
end
longcases[#longcases + 1] = quote
................................................................................
end
end
terra options:free() self.arglist:free() end
options.methods.parse = terra([self], [argc], [argv])
[init]
var parseopts = true
var [optstack] = 0
self.arglist = lib.mem.heapa(rawstring, argc)
var finalargc = 0
for [idx]=1,argc do
var [arg] = argv[idx]
if optstack > 0 then optstack = optstack - 1 goto [skip] end
if arg[0] == @'-' and parseopts then
if arg[1] == @'-' then -- long option
................................................................................
switch arg[j] do [shortcases] end
j = j + 1
end
end
else
self.arglist.ptr[finalargc] = arg
finalargc = finalargc + 1
end
::[skip]::
end
[verifiers]
if finalargc == 0 then self.arglist:free()
else self.arglist:resize(finalargc) end
end
options.helptxt = helpstr
end
return options
end
|
| > > | > | > | > | > | > > > > > > > > > > | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 .. 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 .. 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
-- vim: ft=terra return function(tbl,opts) opts = opts or {} local options = terralib.types.newstruct('options') do local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end local flagstr = '[-' .. flags .. ']' local helpstr = '\n' options.entries = { {field = 'arglist', type = lib.mem.ptr(rawstring)} } local shortcases, longcases, init, verifiers = {}, {}, {}, {} local self = symbol(&options) local arg = symbol(rawstring) local idx = symbol(uint) local argv = symbol(&rawstring) local argc = symbol(int) local optstack = symbol(intptr) local subcmd = symbol(intptr) local skip = label() local sanitize = function(s) return s:gsub('_','-') end for o,desc in pairs(tbl) do local consume = desc.consume or 0 local incr = desc.inc or 0 options.entries[#options.entries + 1] = { field = o, type = (consume > 0) and &rawstring or (incr > 0) and uint or bool } helpstr = helpstr .. string.format(' -%s --%s: %s\n', desc[1], sanitize(o), desc[2]) end for o,desc in pairs(tbl) do local flag = desc[1] local consume = desc.consume or 0 local incr = desc.inc or 0 init[#init + 1] = quote [self].[o] = [ (consume > 0 and `nil) or (incr > 0 and `0 ) or false ] end local ch if consume > 0 then ch = quote [self].[o] = argv+(idx+1+optstack) optstack = optstack + consume end verifiers[#verifiers+1] = quote var terminus = argv + argc if [self].[o] ~= nil and [self].[o] >= terminus then lib.bail(['missing argument for command line option ' .. sanitize(o)]) end end elseif incr > 0 then ch = quote [self].[o] = [self].[o] + incr end else ch = quote [self].[o] = true end end shortcases[#shortcases + 1] = quote case [int8]([string.byte(flag)]) then [ch] end end longcases[#longcases + 1] = quote ................................................................................ end end terra options:free() self.arglist:free() end options.methods.parse = terra([self], [argc], [argv]) [init] var parseopts = true var [optstack] = 0 var [subcmd] = [ opts.subcmd or 0 ] self.arglist = lib.mem.heapa(rawstring, argc) var finalargc = 0 for [idx]=1,argc do var [arg] = argv[idx] if optstack > 0 then optstack = optstack - 1 goto [skip] end if arg[0] == @'-' and parseopts then if arg[1] == @'-' then -- long option ................................................................................ switch arg[j] do [shortcases] end j = j + 1 end end else self.arglist.ptr[finalargc] = arg finalargc = finalargc + 1 if subcmd > 0 then subcmd = subcmd - 1 if subcmd == 0 then parseopts = false end end end ::[skip]:: end [verifiers] if finalargc == 0 then self.arglist:free() else self.arglist:resize(finalargc) end end options.helptxt = { opts = helpstr, flags = flagstr } end return options end |
Modified common.lua from [e762fc8997] to [c72e0a0971].
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
local kt = {}
for k,v in pairs(ary) do kt[#kt+1] = k end
return kt
end;
ingest = function(f)
local h = io.open(f, 'r')
if h == nil then return nil end
local txt = f:read('*a') f:close()
return chomp(txt)
end;
parseargs = function(a)
local raw = false
local opts, args = {}, {}
for i,v in ipairs(a) do
if v == '--' then
|
| |
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
local kt = {}
for k,v in pairs(ary) do kt[#kt+1] = k end
return kt
end;
ingest = function(f)
local h = io.open(f, 'r')
if h == nil then return nil end
local txt = h:read('*a') h:close()
return chomp(txt)
end;
parseargs = function(a)
local raw = false
local opts, args = {}, {}
for i,v in ipairs(a) do
if v == '--' then
|
Modified config.lua from [29834379ec] to [0c09bec0e6].
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
..
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
tgthf = u.tobool(default('parsav_arch_armhf',true));
doc = {
online = u.tobool(default('parsav_online_documentation',true));
offline = u.tobool(default('parsav_offline_documentation',true));
};
outform = default('parsav_emit_type', 'o');
endian = default('parsav_arch_endian', 'little');
build = {
id = u.rndstr(6);
release = u.ingest('release');
when = os.date();
};
feat = {};
backends = defaultlist('parsav_backends', 'pgsql');
braingeniousmode = false;
embeds = {
{'style.css', 'text/css'};
{'default-avatar.webp', 'image/webp'};
{'padlock.webp', 'image/webp'};
{'warn.webp', 'image/webp'};
................................................................................
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
if u.ping '_FOSSIL_' then default_os = 'windows' end
conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
conf.build.checkout = (u.exec { 'fossil', 'sql',
[[select value from localdb.vvar where name = 'checkout-hash']]
}):gsub("^'(.*)'$", '%1')
end
conf.os = default('parsav_host_os', default_os);
conf.tgtos = default('parsav_target_os', default_os);
conf.posix = posixes[conf.os]
conf.exe = u.tobool(default('parsav_link',not conf.tgttrip)); -- turn off for partial builds
conf.build.origin = coalesce(
os.getenv('parsav_builder'),
string.format('%s@%s', coalesce (
os.getenv('USER'),
u.exec{'whoami'}
), u.exec{'hostname'}) -- whoami and hostname are present on both windows & unix
)
|
>
>
|
|
|
>
>
|
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
..
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
tgthf = u.tobool(default('parsav_arch_armhf',true));
doc = {
online = u.tobool(default('parsav_online_documentation',true));
offline = u.tobool(default('parsav_offline_documentation',true));
};
outform = default('parsav_emit_type', 'o');
endian = default('parsav_arch_endian', 'little');
prefix = default('parsav_install_prefix', './');
build = {
id = u.rndstr(6);
release = u.ingest('release');
when = os.date();
};
feat = {};
debug = u.tobool(default('parsav_enable_debug',true));
backends = defaultlist('parsav_backends', 'pgsql');
braingeniousmode = false;
embeds = {
{'style.css', 'text/css'};
{'default-avatar.webp', 'image/webp'};
{'padlock.webp', 'image/webp'};
{'warn.webp', 'image/webp'};
................................................................................
if u.ping '.fslckout' or u.ping '_FOSSIL_' then
if u.ping '_FOSSIL_' then default_os = 'windows' end
conf.build.branch = u.exec { 'fossil', 'branch', 'current' }
conf.build.checkout = (u.exec { 'fossil', 'sql',
[[select value from localdb.vvar where name = 'checkout-hash']]
}):gsub("^'(.*)'$", '%1')
end
conf.os = default('parsav_host_os', default_os)
conf.tgtos = default('parsav_target_os', default_os)
conf.posix = posixes[conf.os]
conf.exe = u.tobool(default('parsav_link',not conf.tgttrip)) -- turn off for partial builds
conf.prefix_conf = default('parsav_install_prefix_cfg', conf.prefix)
conf.prefix_static = default('parsav_install_prefix_static', nil)
conf.build.origin = coalesce(
os.getenv('parsav_builder'),
string.format('%s@%s', coalesce (
os.getenv('USER'),
u.exec{'whoami'}
), u.exec{'hostname'}) -- whoami and hostname are present on both windows & unix
)
|
Modified crypt.t from [bf3957f4f4] to [9b6529621c].
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = m.algsz.sha384};
sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = m.algsz.sha224};
-- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!!
};
local callbacks = {}
if config.feat.randomizer == 'kern' then
local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
return rnd(dest, sz, 0)
end
elseif config.feat.randomizer == 'devfs' then
terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
var gen = lib.io.open("/dev/urandom",0)
lib.io.read(gen, dest, sz)
lib.io.close(gen)
return sz
end
elseif config.feat.randomizer == 'libc' then
local rnd = terralib.externfunction('rand', {} -> int);
local srnd = terralib.externfunction('srand', uint -> int);
local time = terralib.includec 'time.h'
lib.init[#lib.init + 1] = quote srnd(time.time(nil)) end
print '(warn) using libc soft-rand function for cryptographic purposes, this is very bad!'
terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr): int
for i=0,sz do dest[i] = [uint8](rnd()) end
return sz
end
end
terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool
if pub then
return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
else
return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
end
|
| | | > > > > > > > > > > > > |
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
sha384 = `hashalg {id = lib.md.MBEDTLS_MD_SHA384; bytes = m.algsz.sha384};
sha224 = `hashalg {id = lib.md.MBEDTLS_MD_SHA224; bytes = m.algsz.sha224};
-- md5 = {id = lib.md.MBEDTLS_MD_MD5};-- !!!
};
local callbacks = {}
if config.feat.randomizer == 'kern' then
local rnd = terralib.externfunction('getrandom', {&opaque, intptr, uint} -> ptrdiff);
terra m.spray(dest: &uint8, sz: intptr): int
return rnd(dest, sz, 0)
end
elseif config.feat.randomizer == 'devfs' then
terra m.spray(dest: &uint8, sz: intptr): int
var gen = lib.io.open("/dev/urandom",0)
lib.io.read(gen, dest, sz)
lib.io.close(gen)
return sz
end
elseif config.feat.randomizer == 'libc' then
local rnd = terralib.externfunction('rand', {} -> int);
local srnd = terralib.externfunction('srand', uint -> int);
local time = terralib.includec 'time.h'
lib.init[#lib.init + 1] = quote srnd(time.time(nil)) end
print '(warn) using libc soft-rand function for cryptographic purposes, this is very bad!'
terra m.spray(dest: &uint8, sz: intptr): int
for i=0,sz do dest[i] = [uint8](rnd()) end
return sz
end
end
m.random = macro(function(typ, from, to)
local ty = typ:astype()
return quote
var v: ty
m.spray([&uint8](&v), sizeof(ty))
v = v % (to - from) + from -- only works with unsigned!!
in v end
end)
terra callbacks.randomize(ctx: &opaque, dest: &uint8, sz: intptr)
return m.spray(dest,sz) end
terra m.pem(pub: bool, key: &ctx, buf: &uint8): bool
if pub then
return lib.pk.mbedtls_pk_write_pubkey_pem(key, buf, const.maxpemsz) == 0
else
return lib.pk.mbedtls_pk_write_key_pem(key, buf, const.maxpemsz) == 0
end
|
Added html.t version [ee4d50abb4].
> > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
-- vim: ft=terra local m={} local pstr = lib.mem.ptr(int8) terra m.sanitize(txt: pstr, quo: bool) var a: lib.str.acc a:init(txt.ct*1.3) for i=0,txt.ct do if txt(i) == @'<' then a:lpush('<') elseif txt(i) == @'>' then a:lpush('>') elseif txt(i) == @'&' then a:lpush('&') elseif quo and txt(i) == @'"' then a:lpush('"') else a:push(&txt(i),1) end end return a:finalize() end return m |
Modified makefile from [3210eb684d] to [8946539e56].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
dl = git dbg-flags = $(if $(dbg),-g) images = $(addsuffix .webp, $(basename $(wildcard static/*.svg))) styles = $(addsuffix .css, $(basename $(wildcard static/*.scss))) parsav: parsav.t config.lua pkgdata.lua $(images) $(styles) terra $(dbg-flags) $< parsav.o: parsav.t config.lua pkgdata.lua $(images) $(styles) env parsav_link=no terra $(dbg-flags) $< parsav.ll: parsav.t config.lua pkgdata.lua $(images) $(styles) env parsav_emit_type=ll parsav_link=no terra $(dbg-flags) $< parsav.s: parsav.ll llc --march=$(target) $< static/%.webp: static/%.png cwebp -q 90 $< -o $@ static/%.png: static/%.svg inkscape -f $< -C -d 180 -e $@ static/%.css: static/%.scss |
| | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
dl = git dbg-flags = $(if $(dbg),-g) images = $(addsuffix .webp, $(basename $(wildcard static/*.svg))) styles = $(addsuffix .css, $(basename $(wildcard static/*.scss))) parsav parsavd: parsav.t config.lua pkgdata.lua $(images) $(styles) terra $(dbg-flags) $< parsav.o parsavd.o: parsav.t config.lua pkgdata.lua $(images) $(styles) env parsav_link=no terra $(dbg-flags) $< parsav.ll parsavd.ll: parsav.t config.lua pkgdata.lua $(images) $(styles) env parsav_emit_type=ll parsav_link=no terra $(dbg-flags) $< parsav.s parsavd.ss: parsav.ll llc --march=$(target) $< static/%.webp: static/%.png cwebp -q 90 $< -o $@ static/%.png: static/%.svg inkscape -f $< -C -d 180 -e $@ static/%.css: static/%.scss |
Added mgtool.t version [293667feb7].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
-- vim: ft=terra -- provides the functionality of the `parsav` utility that controls `parsavd` local pstr = lib.mem.ptr(int8) local ctloptions = lib.cmdparse({ version = {'V', 'display information about the binary build and exit'}; verbose = {'v', 'increase logging verbosity', inc=1}; quiet = {'q', 'do not print to standard out'}; help = {'h', 'display this list'}; backend_file = {'B', 'init from specified backend file', consume=1}; backend = {'b', 'operate on only the selected backend'}; instance = {'i', 'specify the instance to control by name', consume=1}; all = {'A', 'affect all running instances'}; }, { subcmd = 1 }) local pbasic = lib.cmdparse { help = {'h', 'display this list'} } local subcmds = { } local ctlcmds = { { 'start', 'start a new instance of the server' }; { 'stop', 'stop a running instance' }; { 'attach', 'capture log output from a running instance' }; { 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' }; { 'db vacuum', 'delete old remote content from the database' }; { 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' }; { 'db excise <artifact>', 'extracts an attachment artifact from the database and prints it to standard out' }; { 'db obliterate', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' }; { 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' }; { 'mkroot <handle>', 'establish a new root user with the given handle' }; { 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' }; { 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' }; { 'user <handle> auth purge-credentials [<type>]', 'delete all credentials that would allow this user to log in (where possible)' }; { 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' }; { 'user <handle> emasculate', 'strip all administrative powers from a user' }; { 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'}; { 'actor <xid> purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth purge-credentials\27[m to prevent a user from accessing the instance)' }; { 'actor <xid> create', 'instantiate a new actor' }; { 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' }; { 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' }; { 'conf get <setting>', 'report the value of a server setting' }; { 'conf reset <setting>', 'reset a server setting to its default value' }; { 'conf refresh', 'instruct an instance to refresh its configuration cache' }; { 'conf chsec', 'reset the server secret, invalidating all authentication cookies' }; { 'serv dl', 'initiate an update cycle over foreign actors' }; { 'tl', 'print the current local timeline to standard out' }; { 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' }; } local ctlcmdhelp = 'commands:\n' for _, v in ipairs(ctlcmds) do ctlcmdhelp = ctlcmdhelp .. string.format ( ' \27[1m%s\27[m: %s\n', v[1]:gsub('(<%w+>)','\27[36m%1\27[;1m'), v[2] ) end local struct idelegate { all: bool src: &lib.store.source srv: &lib.srv.overlord } idelegate.metamethods.__methodmissing = macro(function(meth, self, ...) local expr = {...} local rt for _,f in pairs(lib.store.backend.entries) do local fn = f.field or f[1] local ft = f.type or f[2] if fn == meth then rt = ft.type.returntype break end end return quote var r: rt if self.all then r=self.srv:[meth]([expr]) elseif self.src ~= nil then r=self.src:[meth]([expr]) else lib.bail('no data source specified') end in r end end) local terra gensec(sdest: rawstring) var dest = [&uint8](sdest) lib.crypt.spray(dest,64) for i=0,64 do dest[i] = dest[i] % (0x7e - 0x20) + 0x20 end dest[64] = 0 end local terra entry_mgtool(argc: int, argv: &rawstring): int if argc < 1 then lib.bail('bad invocation!') end lib.noise_init(2) [lib.init] var srv: lib.srv.overlord var dlg = idelegate { srv = &srv, src = nil } var mode: ctloptions mode:parse(argc,argv) defer mode:free() if mode.version then version() return 0 end if mode.help then [ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, ctlcmdhelp) ] return 0 end var cnf: rawstring if mode.backend_file ~= nil then cnf = @mode.backend_file else cnf = lib.proc.getenv('parsav_backend_file') end if cnf == nil then cnf = "backend.conf" end if mode.all then dlg.all = true else -- iterate through and pick the right backend end if mode.arglist.ct == 0 then lib.bail('no command') return 1 end if lib.str.cmp(mode.arglist(0),'attach') == 0 then elseif lib.str.cmp(mode.arglist(0),'start') == 0 then elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then else if lib.str.cmp(mode.arglist(0),'db') == 0 then var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0)) if dbmode.help then [ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts) ] return 1 end if dbmode.arglist.ct < 1 then goto cmderr end srv:setup(cnf) if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then lib.report('initializing new database structure for domain ', dbmode.arglist(1)) dlg:dbsetup() srv:conprep(lib.store.prepmode.conf) dlg:conf_set('instance-name', dbmode.arglist(1)) do var sec: int8[65] gensec(&sec[0]) dlg:conf_set('server-secret', &sec[0]) end lib.report('database setup complete; use mkroot to create an administrative user') elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then var confirmstrs = array( 'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa' ) var cfmstr: int8[64] cfmstr[0] = 0 var tdx = lib.osclock.time(nil) / 60 for i=0,3 do if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end lib.str.cat(&cfmstr[0], confirmstrs[(tdx + 49*i) % [confirmstrs.type.N]]) end if dbmode.arglist.ct == 1 then lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0]) elseif dbmode.arglist.ct == 2 then if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then lib.warn('completely obliterating all data!') dlg:obliterate_everything() else lib.bail('you passed an incorrect confirmation string; pass ', &cfmstr[0], ' if you really want to destroy everything') end else goto cmderr end else goto cmderr end elseif lib.str.cmp(mode.arglist(0),'be') == 0 then srv:setup(cnf) elseif lib.str.cmp(mode.arglist(0),'conf') == 0 then srv:setup(cnf) srv:conprep(lib.store.prepmode.conf) var cfmode: lib.cmdparse { help = {'h','display this list'}; no_notify = {'n', "don't instruct the server to refresh its configuration cache after making changes; useful for \"transactional\" configuration changes."}; } cfmode:parse(mode.arglist.ct, &mode.arglist(0)) if cfmode.help then [ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts) ] return 1 end if cfmode.arglist.ct < 1 then goto cmderr end if cfmode.arglist.ct == 1 then if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then var sec: int8[65] gensec(&sec[0]) dlg:conf_set('server-secret', &sec[0]) lib.report('server secret reset') -- FIXME notify server to reload its config elseif lib.str.cmp(cfmode.arglist(0),'refresh') == 0 then -- TODO notify server to reload config else goto cmderr end elseif cfmode.arglist.ct == 3 and lib.str.cmp(cfmode.arglist(0),'set') == 0 then dlg:conf_set(cfmode.arglist(1),cfmode.arglist(2)) lib.report('parameter set') else goto cmderr end else srv:setup(cnf) srv:conprep(lib.store.prepmode.full) if lib.str.cmp(mode.arglist(0),'mkroot') == 0 then var cfmode: pbasic cfmode:parse(mode.arglist.ct, &mode.arglist(0)) if cfmode.help then [ lib.emit(false, 1, 'usage: ', `argv[0], ' mkroot ', cfmode.type.helptxt.flags, ' <handle>', cfmode.type.helptxt.opts) ] return 1 end if cfmode.arglist.ct == 1 then var am = dlg:conf_get('credential-store') var mg: bool if (not am) or am:cmp(lib.str.plit 'managed') then mg = true elseif am:cmp(lib.str.plit 'unmanaged') then lib.warn('credential store is unmanaged; you will need to create credentials for the new root user manually!') mg = false else lib.bail('unknown credential store mode "',{am.ptr,am.ct},'"; should be either "managed" or "unmanaged"') end var kbuf: uint8[lib.crypt.const.maxdersz] var root = lib.store.actor.mk(&kbuf[0]) root.handle = cfmode.arglist(0) var epithets = array( 'root', 'god', 'regional jehovah', 'titan king', 'king of olympus', 'cyberpharaoh', 'electric ellimist', "rampaging c'tan", 'deathless tweetlord', 'postmaster', 'faerie queene', 'lord of the posts', 'ruthless cybercrat', 'general secretary', 'commissar', 'kwisatz haderach' -- feel free to add more ) root.epithet = epithets[lib.crypt.random(intptr,0,[epithets.type.N])] root.rights.powers:fill() -- grant omnipotence root.rights.rank = 1 var ruid = dlg:actor_create(&root) dlg:conf_set('master',root.handle) lib.report('created new administrator') if mg then lib.dbg('generating temporary password') var tmppw: uint8[33] lib.crypt.spray(&tmppw[0],32) tmppw[32] = 0 for i=0,32 do tmppw[i] = tmppw[i] % (10 + 26*2) if tmppw[i] >= 36 then tmppw[i] = tmppw[i] + (0x61 - 36) elseif tmppw[i] >= 10 then tmppw[i] = tmppw[i] + (0x41 - 10) else tmppw[i] = tmppw[i] + 0x30 end end lib.dbg('assigning temporary password') dlg:auth_create_pw(ruid, false, pstr { ptr = [rawstring](&tmppw[0]), ct = 32 }) lib.report('temporary root pw: ', {[rawstring](&tmppw[0]), 32}) end else goto cmderr end elseif lib.str.cmp(mode.arglist(0),'user') == 0 then elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then elseif lib.str.cmp(mode.arglist(0),'tl') == 0 then elseif lib.str.cmp(mode.arglist(0),'serv') == 0 then else goto cmderr end end end do return 0 end ::cmderr:: lib.bail('invalid command') return 2 end return entry_mgtool |
Modified parsav.md from [ae23203c4b] to [4b27db126a].
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 .. 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 .. 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
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. ## configuring 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. eventually, we'll add a command-line tool `parsav-cfg` to enable easy modification of the configuration store from the command line; for now, you'll need to modify the database by hand or use the online administration menu. the schema.sql file contains commands to prompt for various important values like the name of your administrative user. 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 master pgsql host=localhost dbname=parsav tweets pgsql host=420.69.dread.cloud dbname=content the form the configuration string takes depends on the specific backend. ### postgresql backend currently, postgres needs to be configured manually before parsav can make use of it to store data. the first step is to create a database for parsav's use. once you've done that, you need to create the database schema with the command `$ psql (-h $host) -d $database -f schema.sql`. you'll be prompted for some crucial settings to install in the configuration store, such as the name of the relation you want to use for authentication (we'll call it `parsav_auth` from here on out). parsav separates the storage of user credentials from the storage of other user data, in order to facilitate centralized user accounting. you don't need to take advantage of this feature, and if you don't want to, you can just create a `parsav_auth` table and have done. however, `parsav_auth` can also be a view, collecting a list of authorized users and their various credentials from whatever source you please. `parsav_auth` has the following schema: create table parsav_auth ( aid bigint primary key, uid bigint, newname text, ................................................................................ restrict text[], netmask cidr, blacklist bool ) `aid` is a unique value identifying the authentication method. it must be deterministic -- values based on time of creation or a hash of `uid`+`kind`+`cred` are ideal. `uid` is the identifier of the user the row specifies credentials for. `kind` is a string indicating the credential type, and `cred` is the content of that credential.for the meaning of these fields and use of this structure, see **authentication** below. ## authentication in the most basic case, an authentication record would be something like `{uid = 123, kind = "pw-sha512", cred = "\x12bf90…a10e"::bytea}`. but `parsav` is not restricted to username-password authentication, and in addition to various hashing styles, it also will support more esoteric forms of authentcation. any individual user can have as many auth rows as she likes. there is also a `restrict` field, which is normally null, but can be specified in order to restrict a particular credential to certain operations, such as posting tweets or updating a bio. `blacklist` indicates that any attempt to authenticate that matches this row will be denied, regardless of whether it matches other rows. if `netmask` is present, this authentication will only succeed if it comes from the specified IP mask. `uid` can also be `0` (not null, which matches any user!), indicating that there is not yet a record in `parsav_actors` for this account. if this is the case, `name` must contain the handle of the account to be created when someone attempts to log in with this credential. whether `name` is used in the authentication process depends on whether the authentication method accepts a username. all rows with the same `uid` *must* have the same `name`. below is a full list of authentication types we intend/hope to one day support. contributors should consider this a to-do list. a checked box indicates the scheme has been implemented. * ☑ pw-sha{512,384,256,224}: an ordinary password, hashed with the appropriate algorithm * ☐ pw-{sha1,md5,clear} (insecure, must be manually enabled at compile time with the config variable `parsav_let_me_be_a_dumbass="i know what i'm doing"`) * ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 * ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server * ☐ pw-extern-cyrus: try to authenticate against saslauthd ................................................................................ * ☐ api-digest-sha{…}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. * ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key * ☐ tls-cert-fp: a fingerprint of a client certificate * ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user * ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☑ trust: authentication always succeeds. only use in combination with netmask!!! ## license parsav is released under the terms of the EUPL v1.2. copies of this license are included in the repository. dependencies are produced ## future direction parsav needs more storage backends, as it currently supports only postgres. some possibilities, in order of priority, are: * plain text/filesystem storage * lmdb |
| > > > > < > < > > > < | > > > > > > > > | | | > > > > |
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 .. 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 ... 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
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. ## configuring 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. you can directly modify the store from the command line with the `parsav conf` command; see `parsav conf -h` for more information. 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 master pgsql host=localhost dbname=parsav tweets pgsql host=420.69.dread.cloud dbname=content the form the configuration string takes depends on the specific backend. 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. 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) ### postgresql backend a database will need to be created for `parsav`'s use before `parsav db init` will work. this can be accomplished with a command like `$ createdb parsav`. you'll also of course need to set up some way for `parsavd` to authenticate itself to `postgres`. peer auth is the most secure option, and this is what you should use if postgres and `parsavd` are running on the same box. specify the database name to the backend the usual way, with a clause like `dbname=parsav` in your connection string. the postgresql backend has some extra features that enable it to be integrated with existing authentication databases you may have. when you initialize the database, a table `parsav_auth` will be created to hold the credentials of the instance users and the authentication mode will be set to "managed", which will enable parsav's built-in credential administration tools. if you would prefer to use your own source of credentials, you'll need to set parsav to "unmanaged" mode with the command `parsav be pgsql setup-auth unmanaged`. this command will reconfigure `parsav` and remove the `parsav_auth` table, making room for you to create a view with the same name. if you want to go back to managed mode at any time, just run `parsav be psql setup-auth managed`; just be aware that this will delete your auth view! `parsav_auth` has the following schema: create table parsav_auth ( aid bigint primary key, uid bigint, newname text, ................................................................................ restrict text[], netmask cidr, blacklist bool ) `aid` is a unique value identifying the authentication method. it must be deterministic -- values based on time of creation or a hash of `uid`+`kind`+`cred` are ideal. `uid` is the identifier of the user the row specifies credentials for. `kind` is a string indicating the credential type, and `cred` is the content of that credential.for the meaning of these fields and use of this structure, see **authentication** below. in the most basic case, an authentication record would be something like `{uid = 123, kind = "pw-sha512", cred = "\x12bf90…a10e"::bytea}`. but `parsav` is not restricted to username-password authentication, and in addition to various hashing styles, it also will support more esoteric forms of authentcation. any individual user can have as many auth rows as she likes. there is also a `restrict` field, which is normally null, but can be specified in order to restrict a particular credential to certain operations, such as posting tweets or updating a bio. `blacklist` indicates that any attempt to authenticate that matches this row will be denied, regardless of whether it matches other rows. if `netmask` is present, this authentication will only succeed if it comes from the specified IP mask. `uid` can also be `0` (emphatically *not* null, which causes the rule to match any user!), indicating that there is not yet a record in `parsav_actors` for this account. if this is the case, `name` must contain the handle of the account to be created when someone attempts to log in with this credential. whether `name` is used in the authentication process depends on whether the authentication method accepts a username. all rows with the same `uid` *must* have the same `name`. ## invoking the build process generates two binaries, `parsav` and `parsavd`. `parsav` is a driver tool that can be used to set up and start a `parsav` instance, as well as administer it from the command line. it accesses databases directly and uses the same backend configuration file as parsav, but can also send IPC messages directly to running `parsavd` instances. as a convenience, the `parsav start` command can be used to start and daemonize a `parsav` instance. additionally, the `-l` option to `parsav start` can be used to redirect `parsavd`'s logging output to a file; without `-l`, logging output will be discarded and can be viewed only by connecting to the running instance with `parsav attach`. `parsav start` passes its arguments on to `parsavd`; you can use this to pass options by separating `parsav`'s arguments from `parsavd`'s with `--`. if you launch an instance with `parsav start -- -i chungus`, you can then stop that instance with `parsav -i chungus stop`. `parsav stop` can be used on its own if only one `parsavd` instance is running; otherwise, `parsav -a stop` will cleanly terminate all running instances. you generally should not invoke `parsavd` directly except for debugging purposes, or in the context of an init daemon (particularly systemd). if you launch `parsavd` directly it will not fork to the background. ## authentication below is a full list of authentication types we intend/hope to one day support. contributors should consider this a to-do list. a checked box indicates the scheme has been implemented. * ☑ pw-sha{512,384,256,224}: an ordinary password, hashed with the appropriate algorithm * ☐ pw-{sha1,md5,clear} (insecure, must be manually enabled at compile time with the config variable `parsav_let_me_be_a_dumbass="i know what i'm doing"`) * ☐ pw-pbkdf2-hmac-sha{…}: a password hashed with the Password-Based Key Derivation Function 2 instead of plain SHA2 * ☐ pw-extern-ldap: try to authenticate by binding against an LDAP server * ☐ pw-extern-cyrus: try to authenticate against saslauthd ................................................................................ * ☐ api-digest-sha{…}: a value that can be hashed with the current epoch to derive a temporary access key without logging in. these are used for API calls, sent in the header `X-API-Key`. * ☐ otp-time-sha1: a TOTP PSK: the first two bytes represent the step, the third byte the OTP length, and the remaining ten bytes the secret key * ☐ tls-cert-fp: a fingerprint of a client certificate * ☐ tls-cert-ca: a value of the form `fp/key=value` where a client certificate with the property `key=value` (e.g. `uid=cyberlord19`) signed by a certificate authority matching the given fingerprint `fp` can authenticate the user * ☐ challenge-rsa-sha256: an RSA public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc-sha256: a Curve25519 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☐ challenge-ecc448-sha256: a Curve448 public key. the user is presented with a challenge and must sign it with the corresponding private key using SHA256. * ☑ trust: authentication always succeeds (or fails, if blacklisted). only use in combination with netmask!!! ## legal parsav is released under the terms of the EUPL v1.2. copies of this license are included in the repository. by contributing any intellectual property to this project, you reassign ownership and all attendant rights over that intellectual property to the current maintainer. this is to ensure that the project can be relicensed without difficulty in the unlikely event that it is necessary. ## code of conduct when hacking on `parsav`, it is absolutely mandatory to wear a wizard hat and burgundy silk summoning cloak. this code of conduct is enforced capriciously by the Fair Folk, and violations are punishable by dancing hex. ## future direction parsav needs more storage backends, as it currently supports only postgres. some possibilities, in order of priority, are: * plain text/filesystem storage * lmdb |
Modified parsav.t from [1c10e6f8f8] to [d1470e4b10].
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 ... 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 ... 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 ... 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
local path = {}
for m in l:gmatch('([^:]+)') do path[#path+1]=m end
local tgt = lib
for i=1,#path-1 do
if tgt[path[i]] == nil then tgt[path[i]] = {} end
tgt = tgt[path[i]]
end
tgt[path[#path]] = terralib.loadfile(l:gsub(':','/') .. '.t')()
end
end;
loadlib = function(name,hdr)
local p = config.pkg[name]
-- for _,v in pairs(p.dylibs) do
-- terralib.linklibrary(p.libdir .. '/' .. v)
-- end
................................................................................
else -- print time since last msg
var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
end
end
local defrep = function(level,n,code)
return macro(function(...)
local fn = (...).filename
local ln = tostring((...).linenumber)
local dbgtag = string.format('\27[35m · \27[34m%s:\27[1m%s\27[m\n', fn,ln)
local q = lib.emit(level < 3 and true or dbgtag, 2, noise_header(code,n), ...)
return quote if noise >= level then timehdr(); [q] end end
end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
................................................................................
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')
lib.load {
'mem', 'math', 'str', 'file', 'crypt';
'http', 'session', 'tpl', 'store';
'smackdown'; -- md-alike parser
}
local be = {}
for _, b in pairs(config.backends) do
be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
................................................................................
local t = lib.tpl.mk { body = v, id = 'view/'..k }
data.view[k] = t
end
lib.load {
'srv';
'render:nav';
'render:login';
'render:profile';
'render:compose';
'render:tweet';
'render:userpage';
'render:timeline';
'render:docpage';
'route';
}
do
local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
terra version() lib.io.send(1, p, [#p]) end
end
terra noise_init()
starttime = lib.osclock.time(nil)
lastnoisetime = 0
var n = lib.proc.getenv('parsav_noise')
if n ~= nil then
if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
noise = n[0] - 0x30
return
end
end
noise = 1
end
local options = lib.cmdparse {
version = {'V', 'display information about the binary build and exit'};
quiet = {'q', 'do not print to standard out'};
help = {'h', 'display this list'};
backend_file = {'b', 'init from specified backend file', 1};
static_dir = {'S', 'directory with overrides for static content', 1};
builtin_data = {'B', 'do not load static content overrides at runtime under any circumstances'};
}
local static_setup = quote end
local mapin = quote end
local odir = symbol(rawstring)
local pathbuf = symbol(lib.str.acc)
................................................................................
[static_setup]
if mode.builtin_data then return end
var [odir] = lib.proc.getenv('parsav_override_dir')
if mode.static_dir ~= nil then
odir=@mode.static_dir
end
if odir == nil then return end
var [pathbuf] defer pathbuf:free()
pathbuf:compose(odir,'/')
[mapin]
end
terra entry(argc: int, argv: &rawstring): int
if argc < 1 then lib.bail('bad invocation!') end
noise_init()
[lib.init]
-- shut mongoose the fuck up
lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
var srv: lib.srv.overlord
do var mode: options
mode:parse(argc,argv) defer mode:free()
static_init(&mode)
if mode.version then version() return 0 end
if mode.help then
lib.io.send(1, [options.helptxt], [#options.helptxt])
return 0
end
var cnf: rawstring
if mode.backend_file ~= nil
then cnf = @mode.backend_file
else cnf = lib.proc.getenv('parsav_backend_file')
end
if cnf == nil then cnf = "backend.conf" end
srv:start(cnf)
end
lib.report('listening for requests')
while true do
srv:poll()
end
srv:shutdown()
return 0
end
local bflag = function(long,short)
if short and util.has(buildopts, short) then return true end
if long and util.has(buildopts, long) then return true end
return false
end
if bflag('dump-config','C') then
print(util.dump(config))
os.exit(0)
end
local holler = print
local out = config.exe and 'parsav' or ('parsav.' .. config.outform)
local linkargs = {}
if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end
if config.posix then
linkargs[#linkargs+1] = '-pthread'
end
for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
holler('linking with args',util.dump(linkargs))
terralib.saveobj(out, {
main = entry
},
linkargs,
config.tgttrip and terralib.newtarget {
Triple = config.tgttrip;
CPU = config.tgtcpu;
FloatABIHard = config.tgthf;
} or nil)
|
| > > > > > | | > > > > > > | | > > | | | > | > > > > | | | | | > > | > > > > > > > > | < < < > | | < < < < < |
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 ... 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 ... 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 ... 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
local path = {}
for m in l:gmatch('([^:]+)') do path[#path+1]=m end
local tgt = lib
for i=1,#path-1 do
if tgt[path[i]] == nil then tgt[path[i]] = {} end
tgt = tgt[path[i]]
end
tgt[path[#path]:gsub('-','_')] = terralib.loadfile(l:gsub(':','/') .. '.t')()
end
end;
loadlib = function(name,hdr)
local p = config.pkg[name]
-- for _,v in pairs(p.dylibs) do
-- terralib.linklibrary(p.libdir .. '/' .. v)
-- end
................................................................................
else -- print time since last msg
var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
end
end
local defrep = function(level,n,code)
if level >= 3 and config.debug == false then
return macro(function(...) return {} end)
end
return macro(function(...)
local fn = (...).filename
local ln = tostring((...).linenumber)
local dbgtag = string.format('\27[35m · \27[34m%s:\27[1m%s\27[m\n', fn,ln)
local q = lib.emit(level < 3 and true or dbgtag, 2, noise_header(code,n), ...)
return quote
--lib.io.fmt(['attempting to emit at ' .. fn..':'..ln.. '\n'])
if noise >= level then timehdr(); [q] end end
end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
................................................................................
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')
lib.load {
'mem', 'math', 'str', 'file', 'crypt';
'http', 'html', 'session', 'tpl', 'store';
'smackdown'; -- md-alike parser
}
local be = {}
for _, b in pairs(config.backends) do
be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
................................................................................
local t = lib.tpl.mk { body = v, id = 'view/'..k }
data.view[k] = t
end
lib.load {
'srv';
'render:nav';
'render:nym';
'render:login';
'render:profile';
'render:compose';
'render:tweet';
'render:userpage';
'render:timeline';
'render:docpage';
'render:conf:profile';
'render:conf';
'route';
}
do
local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
terra version() lib.io.send(1, p, [#p]) end
end
terra lib.noise_init(default_level: uint)
starttime = lib.osclock.time(nil)
lastnoisetime = 0
var n = lib.proc.getenv('parsav_noise')
if n ~= nil then
if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
noise = n[0] - 0x30
return
end
end
noise = default_level
end
lib.load{'mgtool'}
local options = lib.cmdparse {
version = {'V', 'display information about the binary build and exit'};
verbose = {'v', 'increase logging verbosity', inc=1};
quiet = {'q', 'do not print to standard out'};
help = {'h', 'display this list'};
backend_file = {'B', 'init from specified backend file', consume=1};
static_dir = {'S', 'directory with overrides for static content', consume=1};
builtin_data = {'D', 'do not load static content overrides at runtime under any circumstances'};
instance = {'i', 'set an instance name to make it easier to control multiple daemons', consume = 1};
}
local static_setup = quote end
local mapin = quote end
local odir = symbol(rawstring)
local pathbuf = symbol(lib.str.acc)
................................................................................
[static_setup]
if mode.builtin_data then return end
var [odir] = lib.proc.getenv('parsav_override_dir')
if mode.static_dir ~= nil then
odir=@mode.static_dir
end
if odir == nil then [
config.prefix_static and quote
odir = [config.prefix_static]
end or quote return end
] end
var [pathbuf] defer pathbuf:free()
pathbuf:compose(odir,'/')
[mapin]
end
local terra entry_daemon(argc: int, argv: &rawstring): int
if argc < 1 then lib.bail('bad invocation!') end
lib.noise_init(1)
[lib.init]
-- shut mongoose the fuck up
lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
var srv: lib.srv.overlord
do var mode: options
mode:parse(argc,argv) defer mode:free()
static_init(&mode)
if mode.version then version() return 0 end
if mode.help then
[ lib.emit(true, 1, 'usage: ',`argv[0],' ', options.helptxt.flags, ' [<args>…]', options.helptxt.opts) ]
return 0
end
var cnf: rawstring
if mode.backend_file ~= nil
then cnf = @mode.backend_file
else cnf = lib.proc.getenv('parsav_backend_file')
end
if cnf == nil then cnf = [config.prefix_conf .. "backend.conf"] end
srv:setup(cnf)
srv:start(lib.trn(mode.instance ~= nil, @mode.instance, nil))
end
lib.report('listening for requests')
while true do
srv:poll()
end
srv:shutdown()
return 0
end
local bflag = function(long,short)
if short and util.has(buildopts, short) then return true end
if long and util.has(buildopts, long) then return true end
return false
end
if bflag('dump-config','C') then
print(util.dump(config))
os.exit(0)
end
local holler = print
local suffix = config.exe and '' or ('.'..config.outform)
local out = 'parsavd' .. suffix
local linkargs = {}
local target = config.tgttrip and terralib.newtarget {
Triple = config.tgttrip;
CPU = config.tgtcpu;
FloatABIHard = config.tgthf;
} or nil
if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end
for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
local linkargs_d = linkargs -- controller is not multithreaded
if config.posix then
linkargs_d[#linkargs_d+1] = '-pthread'
end
holler('linking with args',util.dump(linkargs))
terralib.saveobj('parsavd'..suffix, { main = entry_daemon }, linkargs_d, target)
terralib.saveobj('parsav' ..suffix, { main = lib.mgtool }, linkargs, target)
|
Modified render/compose.t from [0685338a7e] to [cb3a66bab9].
4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var target, tgtlen = co:getv('to')
var form: data.view.compose
if edit == nil then
form = data.view.compose {
content = lib.coalesce(target, '');
acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting?
handle = co.who.handle;
}
end
var cotxt = form:tostr() defer cotxt:free()
var doc = data.view.docskel {
instance = co.srv.cfg.instance;
title = lib.str.plit 'compose';
|
> |
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var target, tgtlen = co:getv('to')
var form: data.view.compose
if edit == nil then
form = data.view.compose {
content = lib.coalesce(target, '');
acl = lib.trn(target == nil, 'all', 'mentioned'); -- TODO default acl setting?
handle = co.who.handle;
circles = ''; -- TODO: list user's circles, rooms, and saved aclexps
}
end
var cotxt = form:tostr() defer cotxt:free()
var doc = data.view.docskel {
instance = co.srv.cfg.instance;
title = lib.str.plit 'compose';
|
Added render/conf.t version [6e08f785f6].
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
-- vim: ft=terra local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local mappings = { {url = 'profile', title = 'account profile', render = 'profile'}; {url = 'avi', title = 'avatar', render = 'avatar'}; {url = 'sec', title = 'security', render = 'sec'}; {url = 'rel', title = 'relationships', render = 'rel'}; {url = 'qnt', title = 'quarantine', render = 'quarantine'}; {url = 'acl', title = 'access control shortcuts', render = 'acl'}; {url = 'rooms', title = 'chatrooms', render = 'rooms'}; {url = 'circles', title = 'circles', render = 'circles'}; {url = 'srv', title = 'server settings', render = 'srv'}; {url = 'brand', title = 'instance branding', render = 'rebrand'}; {url = 'censor', title = 'censorship & badthink suppression', render = 'rebrand'}; {url = 'users', title = 'user accounting', render = 'users'}; } local path = symbol(lib.mem.ptr(pref)) local co = symbol(&lib.srv.convo) local panel = symbol(pstr) local invoker = quote co:complain(404,'not found','no such control panel is available in this version of parsav') end for i, m in ipairs(mappings) do if lib.render.conf[m.render] then invoker = quote if path(1):cmp(lib.str.lit([m.url])) then var body = [lib.render.conf[m.render]] (co, path) var a: lib.str.acc a:init(body.ct+48) a:lpush(['<h1>' .. m.title .. '</h1>']):ppush(body) panel = a:finalize() body:free() else [invoker] end end end end local terra render_conf([co], [path]) var menu: lib.str.acc menu:init(64):lpush('<hr>') defer menu:free() -- build menu do var p = co.who.rights.powers if p.config() then menu:lpush '<a href="/conf/srv">server settings</a>' end if p.rebrand() then menu:lpush '<a href="/conf/brand">instance branding</a>' end if p.censor() then menu:lpush '<a href="/conf/censor">badthink alerts</a>' end if p:affect_users() then menu:lpush '<a href="/conf/users">users</a>' end end -- select the appropriate panel var [panel] = pstr { ptr = ''; ct = 0 } if path.ct >= 2 then [invoker] end -- avoid the hr if we didn't add any elements var mptr = pstr { ptr = menu.buf, ct = menu.sz } if menu.sz <= 4 then mptr.ct = 0 end -- 🙄 var pg = data.view.conf { menu = mptr; panel = panel; } var pgt = pg:tostr() defer pgt:free() co:stdpage([lib.srv.convo.page] { title = 'configure'; body = pgt; class = lib.str.plit 'conf'; }) if panel.ct ~= 0 then panel:free() end end return render_conf |
Added render/conf/profile.t version [248ab207d4].
> > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
-- vim: ft=terra local pstr = lib.mem.ptr(int8) local pref = lib.mem.ref(int8) local terra cs(s: rawstring) return pstr { ptr = s, ct = lib.str.sz(s) } end local terra render_conf_profile(co: &lib.srv.convo, path: lib.mem.ptr(pref)): pstr var c = data.view.conf_profile { handle = cs(co.who.handle); nym = cs(lib.coalesce(co.who.nym,'')); bio = cs(lib.coalesce(co.who.bio,'')); } return c:tostr() end return render_conf_profile |
Modified render/login.t from [671b92715b] to [dd5c50c3e9].
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
handle = user.handle; name = lib.coalesce(user.nym, user.handle); } if creds.pw() then ch.challenge = P'enter the password associated with your account' ch.label = P'password' ch.method = P'pw' elseif creds.otp() then ch.challenge = P'enter a valid one-time password for your account' ch.label = P'OTP code' ch.method = P'otp' elseif creds.challenge() then ch.challenge = P'sign the challenge token: <code>...</code>' ch.label = P'digest' ch.method = P'challenge' else co:complain(500,'login failure','unknown login method') return end doc.body = ch:tostr() else |
> > > |
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
handle = user.handle; name = lib.coalesce(user.nym, user.handle); } if creds.pw() then ch.challenge = P'enter the password associated with your account' ch.label = P'password' ch.method = P'pw' ch.auto = P'current-password'; elseif creds.otp() then ch.challenge = P'enter a valid one-time password for your account' ch.label = P'OTP code' ch.method = P'otp' ch.auto = P'one-time-code'; elseif creds.challenge() then ch.challenge = P'sign the challenge token: <code>...</code>' ch.label = P'digest' ch.method = P'challenge' ch.auto = P'one-time-code'; else co:complain(500,'login failure','unknown login method') return end doc.body = ch:tostr() else |
Added render/nym.t version [89e574dd98].
> > > > > > > > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
-- vim: ft=terra local pstr = lib.mem.ptr(int8) local terra render_nym(who: &lib.store.actor, scope: uint64) var n: lib.str.acc n:init(128) if who.nym ~= nil and who.nym[0] ~= 0 then n:compose('<span class="nym">',who.nym,'</span> [<span class="handle">', who.xid,'</span>]') else n:compose('<span class="handle">',who.xid,'</span>') end if who.epithet ~= nil then n:lpush(' <span class="epithet">'):push(who.epithet,0):lpush('</span>') end -- TODO: if scope == chat room then lookup titles in room member db return n:finalize() end return render_nym |
Modified render/profile.t from [efe49adad0] to [03b39adc21].
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
..
47
48
49
50
51
52
53
54
55
56
57
|
var strfbuf: int8[28*4] var stats = co.srv:actor_stats(actor.id) var sn_posts = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ])) var sn_follows = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1)) var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1)) var sn_mutuals = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1)) var profile = data.view.profile { nym = cs(lib.coalesce(actor.nym, actor.handle)); bio = cs(lib.coalesce(actor.bio, "<em>tall, dark, and mysterious</em>")); xid = cs(actor.xid); avatar = lib.trn(actor.origin == 0, pstr{ptr=avistr.buf,ct=avistr.sz}, cs(lib.coalesce(actor.avatar, '/s/default-avatar.webp'))); nposts = sn_posts, nfollows = sn_follows; nfollowers = sn_followers, nmutuals = sn_mutuals; tweetday = cs(timestr); ................................................................................ auxbtn = auxp; } var ret = profile:tostr() if actor.origin == 0 then avistr:free() end if not (co.aid ~= 0 and co.who.id == actor.id) then auxp:free() end return ret end return render_profile |
<
>
>
>
>
>
<
<
>
>
>
|
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
..
51
52
53
54
55
56
57
58
59
60
61
62
|
var strfbuf: int8[28*4] var stats = co.srv:actor_stats(actor.id) var sn_posts = cs(lib.math.decstr_friendly(stats.posts, &strfbuf[ [strfbuf.type.N - 1] ])) var sn_follows = cs(lib.math.decstr_friendly(stats.follows, sn_posts.ptr - 1)) var sn_followers = cs(lib.math.decstr_friendly(stats.followers, sn_follows.ptr - 1)) var sn_mutuals = cs(lib.math.decstr_friendly(stats.mutuals, sn_followers.ptr - 1)) var bio = lib.str.plit "<em>tall, dark, and mysterious</em>" if actor.bio ~= nil then bio = lib.html.sanitize(cs(actor.bio), false) end var fullname = lib.render.nym(actor,0) defer fullname:free() var profile = data.view.profile { nym = fullname; bio = bio; xid = cs(actor.xid); avatar = lib.trn(actor.origin == 0, pstr{ptr=avistr.buf,ct=avistr.sz}, cs(lib.coalesce(actor.avatar, '/s/default-avatar.webp'))); nposts = sn_posts, nfollows = sn_follows; nfollowers = sn_followers, nmutuals = sn_mutuals; tweetday = cs(timestr); ................................................................................ auxbtn = auxp; } var ret = profile:tostr() if actor.origin == 0 then avistr:free() end if not (co.aid ~= 0 and co.who.id == actor.id) then auxp:free() end if actor.bio ~= nil then bio:free() end return ret end return render_profile |
Modified render/tweet.t from [00c7b6fd89] to [ac0f8e680f].
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0])
var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free()
var idbuf: int8[lib.math.shorthand.maxlen]
var idlen = lib.math.shorthand.gen(p.id, idbuf)
var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
var tpl = data.view.tweet {
text = bhtml;
subject = cs(lib.coalesce(p.subject,''));
nym = cs(lib.coalesce(author.nym, author.handle));
xid = cs(author.xid);
when = cs(×tr[0]);
avatar = cs(lib.trn(author.origin == 0, avistr.buf,
lib.coalesce(author.avatar, '/s/default-avatar.webp')));
acctlink = cs(author.xid);
permalink = permalink:finalize();
}
defer tpl.permalink:free()
if acc ~= nil then tpl:append(acc) return [lib.mem.ptr(int8)]{ptr=nil,ct=0} end
var txt = tpl:tostr()
return txt
end
return render_tweet
|
| | < |
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
var timestr: int8[26] lib.osclock.ctime_r(&p.posted, ×tr[0])
var bhtml = lib.smackdown.html([lib.mem.ptr(int8)] {ptr=p.body,ct=0}) defer bhtml:free()
var idbuf: int8[lib.math.shorthand.maxlen]
var idlen = lib.math.shorthand.gen(p.id, idbuf)
var permalink: lib.str.acc permalink:compose('/post/',{idbuf,idlen})
var fullname = lib.render.nym(author,0) defer fullname:free()
var tpl = data.view.tweet {
text = bhtml;
subject = cs(lib.coalesce(p.subject,''));
nym = fullname;
when = cs(×tr[0]);
avatar = cs(lib.trn(author.origin == 0, avistr.buf,
lib.coalesce(author.avatar, '/s/default-avatar.webp')));
acctlink = cs(author.xid);
permalink = permalink:finalize();
}
defer tpl.permalink:free()
if acc ~= nil then tpl:append(acc) return [lib.mem.ptr(int8)]{ptr=nil,ct=0} end
var txt = tpl:tostr()
return txt
end
return render_tweet
|
Modified route.t from [d6af263481] to [4fbd6ed0a5].
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 ... 169 170 171 172 173 174 175 176 177 178 179 180 181 182 ... 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
var fakeactor: lib.store.actor
if act.ptr == nil then
-- the user is known to us but has not yet claimed an
-- account on the server. create a template for the
-- account that will be created once they log in
fakeact = true
fakeactor = lib.store.actor {
id = 0, handle = usn, nym = usn;
origin = 0, bio = nil;
key = [lib.mem.ptr(uint8)] {ptr=nil, ct=0}
}
act.ct = 1
act.ptr = &fakeactor
act.ptr.rights = lib.store.rights_default()
end
if am == nil then
-- pick an auth method
................................................................................
lib.render.docpage(co,path(1))
elseif path.ct == 1 then
lib.render.docpage(co, rstring.null())
else
co:complain(404, 'no such documentation', 'invalid documentation URL')
end
end
do local branches = quote end
local filename, flen = symbol(&int8), symbol(intptr)
local page = symbol(lib.http.page)
local send = label()
local storage = data.stmap
for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
if path.ptr[0]:cmp(lib.str.lit('user')) then
http.actor_profile_uid(co, path, meth)
elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
http.timeline(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('doc')) then
if meth ~= method.get and meth ~= method.head then goto wrongmeth end
http.documentation(co, path)
else goto notfound end
return
end
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
end
|
| | > > > > > > > > > |
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 ... 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 ... 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
var fakeactor: lib.store.actor
if act.ptr == nil then
-- the user is known to us but has not yet claimed an
-- account on the server. create a template for the
-- account that will be created once they log in
fakeact = true
fakeactor = lib.store.actor {
id = 0, handle = usn, nym = nil;
origin = 0, bio = nil;
key = [lib.mem.ptr(uint8)] {ptr=nil, ct=0};
epithet = nil;
}
act.ct = 1
act.ptr = &fakeactor
act.ptr.rights = lib.store.rights_default()
end
if am == nil then
-- pick an auth method
................................................................................
lib.render.docpage(co,path(1))
elseif path.ct == 1 then
lib.render.docpage(co, rstring.null())
else
co:complain(404, 'no such documentation', 'invalid documentation URL')
end
end
terra http.configure(co: &lib.srv.convo, path: hpath)
lib.render.conf(co,path)
end
do local branches = quote end
local filename, flen = symbol(&int8), symbol(intptr)
local page = symbol(lib.http.page)
local send = label()
local storage = data.stmap
for i,e in ipairs(config.embeds) do local id,mime = e[1],e[2]
................................................................................
if path.ptr[0]:cmp(lib.str.lit('user')) then
http.actor_profile_uid(co, path, meth)
elseif path.ptr[0]:cmp(lib.str.lit('tl')) then
http.timeline(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('doc')) then
if meth ~= method.get and meth ~= method.head then goto wrongmeth end
http.documentation(co, path)
elseif path.ptr[0]:cmp(lib.str.lit('conf')) then
if co.aid == 0 then goto unauth end
http.configure(co,path)
else goto notfound end
return
end
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
::notfound:: co:complain(404, 'not found', 'no such resource available') do return end
::unauth:: co:complain(401, 'unauthorized', 'this content is not available at your clearance level') do return end
end
|
Modified smackdown.t from [896438e021] to [c51eb8a6b2].
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
...
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr) var sl = scanline(l,max,n,nc) if sl == nil then return nil else sl = sl + nc end if sl >= l+max or isws(@sl) then return sl-nc end return nil end terra m.html(md: pstr) if md.ct == 0 then md.ct = lib.str.sz(md.ptr) end var styled: lib.str.acc styled:init(md.ct) do var i = 0 while i < md.ct do var wordstart = (i == 0 or isws(md.ptr[i-1])) var wordend = (i == md.ct - 1 or isws(md.ptr[i+1])) var here = md.ptr + i ................................................................................ goto skip end end ::fallback::styled:push(here,1) -- :/ i = i + 1 ::skip::end end -- we make two passes: the first detects and transforms inline elements, -- the second carries out block-level organization var html: lib.str.acc html:init(styled.sz) var s = state { segt = segt.none; |
|
|
>
>
>
>
|
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
...
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
|
local terra scanline_wordend(l: rawstring, max: intptr, n: rawstring, nc: intptr) var sl = scanline(l,max,n,nc) if sl == nil then return nil else sl = sl + nc end if sl >= l+max or isws(@sl) then return sl-nc end return nil end terra m.html(input: pstr) if input.ct == 0 then input.ct = lib.str.sz(input.ptr) end var md = lib.html.sanitize(input,false) var styled: lib.str.acc styled:init(md.ct) do var i = 0 while i < md.ct do var wordstart = (i == 0 or isws(md.ptr[i-1])) var wordend = (i == md.ct - 1 or isws(md.ptr[i+1])) var here = md.ptr + i ................................................................................ goto skip end end ::fallback::styled:push(here,1) -- :/ i = i + 1 ::skip::end end md:free() -- we make two passes: the first detects and transforms inline elements, -- the second carries out block-level organization var html: lib.str.acc html:init(styled.sz) var s = state { segt = segt.none; |
Modified srv.t from [8808dbd7a5] to [7234d58b59].
11 12 13 14 15 16 17 18 19 20 21 22 23 24 ... 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 ... 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 |
pol_reg: bool
}
local struct srv {
sources: lib.mem.ptr(lib.store.source)
webmgr: lib.net.mg_mgr
webcon: &lib.net.mg_connection
cfg: cfgcache
}
terra cfgcache:free() -- :/
self.secret:free()
self.instance:free()
end
................................................................................
if self.sources(i).backend ~= nil and
self.sources(i).backend.actor_auth_pw ~= nil then
var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw)
if aid ~= 0 then
if uid == 0 then
lib.dbg('new user just logged in, creating account entry')
var kbuf: uint8[lib.crypt.const.maxdersz]
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,&kbuf[0])
var na = lib.store.actor {
id = 0; nym = nil; handle = newhnd.ptr;
origin = 0; bio = nil; avatar = nil;
knownsince = lib.osclock.time(nil);
rights = lib.store.rights_default();
title = nil, key = [lib.mem.ptr(uint8)] {
ptr = &kbuf[0], ct = privsz
};
}
var newuid: uint64
if self.sources(i).backend.actor_create ~= nil then
newuid = self.sources(i):actor_create(&na)
else newuid = self:actor_create(&na) end
if self.sources(i).backend.actor_auth_register_uid ~= nil then
self.sources(i):actor_auth_register_uid(aid,newuid)
................................................................................
--9twh8y94i5c1qqr7hxu20fyd
terra cfgcache.methods.load :: {&cfgcache} -> {}
terra cfgcache:init(o: &srv)
self.overlord = o
self:load()
end
srv.methods.start = terra(self: &srv, befile: rawstring)
cfg(self, befile)
var success = false
if self.sources.ct == 0 then lib.bail('no data sources specified') end
for i=0,self.sources.ct do var src = self.sources.ptr + i
lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
src.handle = src.backend.open(src)
if src.handle ~= nil then success = true end
end
if not success then
lib.bail('could not connect to any data sources!')
end
self.cfg:init(self)
var dbbind = self:conf_get('bind')
var envbind = lib.proc.getenv('parsav_bind')
var bind: rawstring
if envbind ~= nil then
bind = envbind
elseif dbbind.ptr ~= nil then
bind = dbbind.ptr
else bind = '[::]:10917' end
lib.report('binding to ', bind)
lib.net.mg_mgr_init(&self.webmgr)
self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self)
if dbbind.ptr ~= nil then dbbind:free() end
end
srv.methods.poll = terra(self: &srv)
lib.net.mg_mgr_poll(&self.webmgr,1000)
end
srv.methods.shutdown = terra(self: &srv)
lib.net.mg_mgr_free(&self.webmgr)
for i=0,self.sources.ct do var src = self.sources.ptr + i
lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
src:close()
end
self.sources:free()
end
|
> < < | < < < < < < < < | > > > < > > > > > > > > > > | | | |
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ... 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 ... 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 |
pol_reg: bool
}
local struct srv {
sources: lib.mem.ptr(lib.store.source)
webmgr: lib.net.mg_mgr
webcon: &lib.net.mg_connection
cfg: cfgcache
id: rawstring
}
terra cfgcache:free() -- :/
self.secret:free()
self.instance:free()
end
................................................................................
if self.sources(i).backend ~= nil and
self.sources(i).backend.actor_auth_pw ~= nil then
var aid,uid,newhnd = self.sources(i):actor_auth_pw(ip,user,pw)
if aid ~= 0 then
if uid == 0 then
lib.dbg('new user just logged in, creating account entry')
var kbuf: uint8[lib.crypt.const.maxdersz]
var na = lib.store.actor.mk(&kbuf[0])
var newuid: uint64
if self.sources(i).backend.actor_create ~= nil then
newuid = self.sources(i):actor_create(&na)
else newuid = self:actor_create(&na) end
if self.sources(i).backend.actor_auth_register_uid ~= nil then
self.sources(i):actor_auth_register_uid(aid,newuid)
................................................................................
--9twh8y94i5c1qqr7hxu20fyd
terra cfgcache.methods.load :: {&cfgcache} -> {}
terra cfgcache:init(o: &srv)
self.overlord = o
self:load()
end
terra srv:setup(befile: rawstring)
cfg(self, befile)
var success = false
if self.sources.ct == 0 then lib.bail('no data sources specified') end
for i=0,self.sources.ct do var src = self.sources.ptr + i
lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
src.handle = src.backend.open(src)
if src.handle ~= nil then success = true end
end
if not success then
lib.bail('could not connect to any data sources!')
end
end
terra srv:start(iname: rawstring)
self:conprep(lib.store.prepmode.full)
self.cfg:init(self)
var dbbind = self:conf_get('bind')
if iname == nil then iname = lib.proc.getenv('parsav_instance') end
if iname == nil then
self.id = self.cfg.instance.ptr;
-- let this leak -- it'll be needed for the lifetime of the process anyway
else self.id = iname end
if iname ~= nil then
lib.report('parsav instance "',iname,'" starting')
end
var envbind = lib.proc.getenv('parsav_bind')
var bind: rawstring
if envbind ~= nil then
bind = envbind
elseif dbbind.ptr ~= nil then
bind = dbbind.ptr
else bind = '[::1]:10917' end
lib.report('binding to ', bind)
lib.net.mg_mgr_init(&self.webmgr)
self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self)
if dbbind.ptr ~= nil then dbbind:free() end
end
terra srv:poll()
lib.net.mg_mgr_poll(&self.webmgr,1000)
end
terra srv:shutdown()
lib.net.mg_mgr_free(&self.webmgr)
for i=0,self.sources.ct do var src = self.sources.ptr + i
lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
src:close()
end
self.sources:free()
end
|
Modified static/style.scss from [b0a8082b0c] to [1f479ddac7].
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 .. 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 .. 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 ... 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 ... 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 ... 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 ... 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 ... 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 ... 457 458 459 460 461 462 463 464 465 466 467 468 469 470 ... 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 |
color: tone(25%);
font-size: 14pt;
margin: 0;
padding: 0;
}
a[href] {
color: tone(10%);
text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.5);
&:hover {
color: white;
text-shadow: 0 0 15px tone(20%);
text-decoration-color: adjust-color($color, $lightness: 10%, $alpha: -0.1);
}
}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
&:hover::after {
color: black;
................................................................................
background-color: white;
}
&::after {
content: "↗";
display: inline-block;
color: black;
margin-left: 4pt;
background-color: adjust-color($color, $lightness: 10%);
padding: 0 4px;
text-shadow: none;
padding-right: 5px;
vertical-align: baseline;
font-size: 80%;
}
}
................................................................................
%content {
width: 8in;
margin: auto;
}
%glow {
box-shadow: 0 0 20px adjust-color($color, $alpha: -0.8);
}
%button {
@extend %sans;
font-size: 14pt;
padding: 0.1in 0.2in;
border: 1px solid black;
color: adjust-color($color, $lightness: 25%);
text-shadow: 1px 1px black;
text-decoration: none;
text-align: center;
background: linear-gradient(to bottom,
adjust-color($color, $lightness: -45%),
adjust-color($color, $lightness: -50%) 15%,
adjust-color($color, $lightness: -50%) 75%,
adjust-color($color, $lightness: -55%)
);
&:hover, &:focus {
@extend %glow;
outline: none;
color: adjust-color($color, $lightness: -55%);
text-shadow: none;
background: linear-gradient(to bottom,
adjust-color($color, $lightness: -25%),
adjust-color($color, $lightness: -30%) 15%,
adjust-color($color, $lightness: -30%) 75%,
adjust-color($color, $lightness: -35%)
);
}
&:active {
color: black;
padding-bottom: calc(0.1in - 2px);
padding-top: calc(0.1in + 2px);
background: linear-gradient(to top,
adjust-color($color, $lightness: -25%),
adjust-color($color, $lightness: -30%) 15%,
adjust-color($color, $lightness: -30%) 75%,
adjust-color($color, $lightness: -35%)
);
}
}
button { @extend %button;
&:first-of-type {
@extend %button;
color: white;
box-shadow: inset 0 1px adjust-color($color, $lightness: -25%),
inset 0 -1px adjust-color($color, $lightness: -50%);
background: linear-gradient(to bottom,
adjust-color($color, $lightness: -35%),
adjust-color($color, $lightness: -40%) 15%,
adjust-color($color, $lightness: -40%) 75%,
adjust-color($color, $lightness: -45%)
);
&:hover, &:focus {
box-shadow: inset 0 1px adjust-color($color, $lightness: -15%),
inset 0 -1px adjust-color($color, $lightness: -40%);
}
&:active {
box-shadow: inset 0 1px adjust-color($color, $lightness: -50%),
inset 0 -1px adjust-color($color, $lightness: -25%);
background: linear-gradient(to top,
adjust-color($color, $lightness: -30%),
adjust-color($color, $lightness: -35%) 15%,
adjust-color($color, $lightness: -35%) 75%,
adjust-color($color, $lightness: -40%)
);
}
}
&:hover { font-weight: bold; }
}
$grad-ui-focus: linear-gradient(to bottom,
adjust-color($color, $lightness: -50%),
adjust-color($color, $lightness: -35%)
);
input[type='text'], input[type='password'], textarea {
@extend %serif;
padding: 0.08in 0.1in;
border: 1px solid black;
background: linear-gradient(to bottom,
adjust-color($color, $lightness: -55%),
adjust-color($color, $lightness: -40%)
);
font-size: 16pt;
color: adjust-color($color, $lightness: 25%);
box-shadow: inset 0 0 20px -3px adjust-color($color, $lightness: -55%);
&:focus {
color: white;
border-image: linear-gradient(to bottom,
adjust-color($color, $lightness: -10%),
adjust-color($color, $lightness: -30%)
) 1 / 1px;
background: $grad-ui-focus;
outline: none;
@extend %glow;
}
}
@mixin glass {
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
background-color: adjust-color($color, $lightness: -53%, $alpha: -0.7);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
background-color: adjust-color($color, $lightness: -53%, $alpha: -0.1);
}
}
h1 { margin-top: 0 }
header {
position: fixed;
................................................................................
position: relative;
min-height: calc(100vh - 1.1in);
margin-top: 0;
margin-bottom: 0;
padding: 0 0.4in;
padding-top: 1.1in;
padding-bottom: 0.1in;
background-color: adjust-color($color, $lightness: -45%, $alpha: 0.4);
border: {
left: 1px solid black;
right: 1px solid black;
}
}
div.profile {
................................................................................
grid-column: 1 / 2;
grid-row: 1 / 3;
border: 1px solid black;
}
> .id {
grid-column: 2 / 3;
grid-row: 1 / 2;
color: adjust-color($color, $lightness: 25%, $alpha: -0.4);
> .nym {
font-weight: bold;
color: adjust-color($color, $lightness: 25%);
}
> .xid {
color: adjust-color($color, $lightness: 20%, $alpha: -0.1);
font-size: 80%;
vertical-align: text-top;
}
}
> .bio {
grid-column: 2 / 3;
grid-row: 2 / 3;
................................................................................
display: block;
height: 0.3in;
width: 1px;
border-left: 1px solid rgba(0,0,0,0.6);
}
}
}
%box {
margin: auto;
border: 1px solid adjust-color($color, $lightness: -55%);
border-bottom: 3px solid black;
box-shadow: 0 0 1px black;
border-image: linear-gradient(to bottom,
adjust-color($color, $lightness: -40%),
adjust-color($color, $lightness: -52%) 10%,
adjust-color($color, $lightness: -55%) 90%,
adjust-color($color, $lightness: -60%)
) 1 / 1px;
background: linear-gradient(to bottom,
adjust-color($color, $lightness: -58%),
adjust-color($color, $lightness: -55%) 10%,
adjust-color($color, $lightness: -50%) 80%,
adjust-color($color, $lightness: -45%)
);
// outline: 1px solid black;
}
body.error .message {
@extend %box;
width: 4in;
................................................................................
> .msg {
text-align: center;
padding: 0.3in;
}
> .msg:first-child { padding-top: 0; }
> .user {
width: min-content; margin: auto;
background: adjust-color($color, $lightness: -20%, $alpha: -0.3);
border: 1px solid black;
color: adjust-color($color, $lightness: -50%);
padding: 0.1in;
> img { width: 1in; height: 1in; border: 1px solid black; }
> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
}
>form {
display: grid;
grid-template-columns: 1fr 1fr;
................................................................................
@extend %box;
display: grid;
grid-template-columns: 1.1in 2fr min-content 1fr;
grid-template-rows: 1fr min-content;
grid-gap: 2px;
padding: 0.1in;
> img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;}
> textarea { grid-column: 2/5; grid-row: 1/2; height: 3in;}
> input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }
> button { grid-column: 4/5; grid-row: 2/3; }
a.help[href] { margin-right: 0.05in }
}
a.help[href] {
display: block;
................................................................................
background: linear-gradient(to right, tone(-55%), transparent);
}
>.content {
grid-column: 2/4; grid-row: 1/2;
padding: 0.2in;
@extend %serif;
font-size: 110%;
}
> a[href].permalink {
display: block;
grid-column: 3/4; grid-row: 2/3;
font-size: 80%;
text-align: right;
padding: 0.1in;
................................................................................
a[href].rawlink {
@extend %teletype;
}
body.doc main {
@extend %serif;
li { margin-top: 0.05in; }
li:first-child { margin-top: 0; }
h1, h2, h3, h4, h5, h6 {
background: linear-gradient(to right, tone(-50%), transparent);
margin-left: -0.4in;
padding-left: 0.2in;
text-shadow: 0 2px 0 black;
}
}
|
| | | | | > < | | | > | | | | | | | | | | | | | | | | | | | | | | | | | | < < < < > | | < < < | | | | | | > > > > > > > > > > > > > > > > > | | | | | | | | | | | > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > |
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 .. 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 .. 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 ... 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 ... 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 ... 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 ... 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 ... 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 ... 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 ... 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 |
color: tone(25%);
font-size: 14pt;
margin: 0;
padding: 0;
}
a[href] {
color: tone(10%);
text-decoration-color: tone(10%,-0.5);
&:hover {
color: white;
text-shadow: 0 0 15px tone(20%);
text-decoration-color: tone(10%,-0.1);
}
}
a[href^="//"],
a[href^="http://"],
a[href^="https://"] { // external link
&:hover::after {
color: black;
................................................................................
background-color: white;
}
&::after {
content: "↗";
display: inline-block;
color: black;
margin-left: 4pt;
background-color: tone(10%);
padding: 0 4px;
text-shadow: none;
padding-right: 5px;
vertical-align: baseline;
font-size: 80%;
}
}
................................................................................
%content {
width: 8in;
margin: auto;
}
%glow {
box-shadow: 0 0 20px tone(0%,-0.8);
}
%button {
@extend %sans;
font-size: 14pt;
padding: 0.1in 0.2in;
border: 1px solid black;
color: tone(25%);
text-shadow: 1px 1px black;
text-decoration: none;
text-align: center;
cursor: default;
background: linear-gradient(to bottom,
tone(-47%),
tone(-50%) 15%,
tone(-50%) 75%,
tone(-53%)
);
&:hover, &:focus {
@extend %glow;
outline: none;
color: tone(-55%);
text-shadow: none;
background: linear-gradient(to bottom,
tone(-27%),
tone(-30%) 15%,
tone(-30%) 75%,
tone(-35%)
);
}
&:active {
color: black;
padding-bottom: calc(0.1in - 2px);
padding-top: calc(0.1in + 2px);
background: linear-gradient(to top,
tone(-25%),
tone(-30%) 15%,
tone(-30%) 75%,
tone(-35%)
);
}
}
button { @extend %button;
&:first-of-type {
@extend %button;
color: white;
box-shadow: inset 0 1px tone(-25%),
inset 0 -1px tone(-50%);
background: linear-gradient(to bottom,
tone(-35%),
tone(-40%) 15%,
tone(-40%) 75%,
tone(-45%)
);
&:hover, &:focus {
box-shadow: inset 0 1px tone(-15%),
inset 0 -1px tone(-40%);
}
&:active {
box-shadow: inset 0 1px tone(-50%),
inset 0 -1px tone(-25%);
background: linear-gradient(to top,
tone(-30%),
tone(-35%) 15%,
tone(-35%) 75%,
tone(-40%)
);
}
}
&:hover { font-weight: bold; }
}
$grad-ui-focus: linear-gradient(to bottom,
tone(-50%),
tone(-35%)
);
input[type='text'], input[type='password'], textarea {
@extend %serif;
padding: 0.08in 0.1in;
border: 1px solid black;
background: linear-gradient(to bottom, tone(-55%), tone(-40%));
font-size: 16pt;
color: tone(25%);
box-shadow: inset 0 0 20px -3px tone(-55%);
&:focus {
color: white;
border-image: linear-gradient(to bottom, tone(-10%), tone(-30%)) 1 / 1px;
background: $grad-ui-focus;
outline: none;
@extend %glow;
}
}
@mixin glass {
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
background-color: tone(-53%, -0.7);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
background-color: tone(-53%, -0.1);
}
}
h1 { margin-top: 0 }
header {
position: fixed;
................................................................................
position: relative;
min-height: calc(100vh - 1.1in);
margin-top: 0;
margin-bottom: 0;
padding: 0 0.4in;
padding-top: 1.1in;
padding-bottom: 0.1in;
background-color: tone(-45%,-0.3);
border: {
left: 1px solid black;
right: 1px solid black;
}
}
div.profile {
................................................................................
grid-column: 1 / 2;
grid-row: 1 / 3;
border: 1px solid black;
}
> .id {
grid-column: 2 / 3;
grid-row: 1 / 2;
color: tone(25%,-0.4);
> .nym {
font-weight: bold;
color: tone(25%);
}
> .xid {
color: tone(20%,-0.1);
font-size: 80%;
vertical-align: text-top;
}
}
> .bio {
grid-column: 2 / 3;
grid-row: 2 / 3;
................................................................................
display: block;
height: 0.3in;
width: 1px;
border-left: 1px solid rgba(0,0,0,0.6);
}
}
}
.epithet {
display: inline-block;
background: tone(20%);
color: tone(-45%);
text-shadow: 0 0 3px tone(-30%, -0.4);
border-radius: 3px;
padding: 6px;
padding-top: 2px;
padding-bottom: 4px;
font-size: 80%;
vertical-align: top;
font-weight: 300;
letter-spacing: 0.5px;
margin: 0 5pt;
// transform: scale(80%) translateX(-10pt); // cheating!
}
%box {
margin: auto;
border: 1px solid tone(-55%);
border-bottom: 3px solid black;
box-shadow: 0 0 1px black;
border-image: linear-gradient(to bottom,
tone(-40%),
tone(-52%) 10%,
tone(-55%) 90%,
tone(-60%)
) 1 / 1px;
background: linear-gradient(to bottom,
tone(-58%),
tone(-55%) 10%,
tone(-50%) 80%,
tone(-45%)
);
// outline: 1px solid black;
}
body.error .message {
@extend %box;
width: 4in;
................................................................................
> .msg {
text-align: center;
padding: 0.3in;
}
> .msg:first-child { padding-top: 0; }
> .user {
width: min-content; margin: auto;
background: tone(-20%,-0.3);
border: 1px solid black;
color: tone(-50%);
padding: 0.1in;
> img { width: 1in; height: 1in; border: 1px solid black; }
> .name { @extend %serif; text-align: center; font-size: 130%; font-weight: bold; margin-top: 0.08in; }
}
>form {
display: grid;
grid-template-columns: 1fr 1fr;
................................................................................
@extend %box;
display: grid;
grid-template-columns: 1.1in 2fr min-content 1fr;
grid-template-rows: 1fr min-content;
grid-gap: 2px;
padding: 0.1in;
> img { grid-column: 1/2; grid-row: 1/3; width: 1in; height: 1in;}
> textarea {
grid-column: 2/5; grid-row: 1/2; height: 3in;
resize: vertical;
margin-bottom: 0.08in;
}
> input[name="acl"] { grid-column: 2/3; grid-row: 2/3; }
> button { grid-column: 4/5; grid-row: 2/3; }
a.help[href] { margin-right: 0.05in }
}
a.help[href] {
display: block;
................................................................................
background: linear-gradient(to right, tone(-55%), transparent);
}
>.content {
grid-column: 2/4; grid-row: 1/2;
padding: 0.2in;
@extend %serif;
font-size: 110%;
text-align: justify;
}
> a[href].permalink {
display: block;
grid-column: 3/4; grid-row: 2/3;
font-size: 80%;
text-align: right;
padding: 0.1in;
................................................................................
a[href].rawlink {
@extend %teletype;
}
body.doc main {
@extend %serif;
text-align: justify;
li { margin-top: 0.05in; }
li:first-child { margin-top: 0; }
h1, h2, h3, h4, h5, h6 {
background: linear-gradient(to right, tone(-50%), transparent);
margin-left: -0.4in;
padding-left: 0.2in;
text-shadow: 0 2px 0 black;
}
}
body.conf main {
display: grid;
grid-template-columns: 2in 1fr;
grid-template-rows: max-content 1fr;
> .menu {
margin-left: -0.25in;
grid-column: 1/2; grid-row: 1/2;
background: linear-gradient(to bottom, tone(-45%),tone(-55%));
border: 1px solid black;
padding: 0.1in;
> a[href] {
@extend %button;
display: block;
text-align: left;
}
> a[href] + a[href] {
border-top: none;
}
hr {
border: none;
}
}
> .panel {
grid-column: 2/3; grid-row: 1/3;
padding-left: 0.15in;
> h1 {
padding-bottom: 0.1in;
margin-bottom: 0.1in;
margin-left: -0.15in;
padding-left: 0.15in;
padding-top: 0.12in;
background: linear-gradient(to right, tone(-50%), tone(-50%,-0.7));
border: 1px solid tone(-55%);
border-left: none;
text-shadow: 1px 1px 0 black;
}
}
}
|
Modified store.t from [4959208545] to [71684bc451].
4 5 6 7 8 9 10 11 12 13 14 15 16 17 .. 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 .. 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 ... 178 179 180 181 182 183 184 185 186 187 188 189 190 191 ... 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
scope = lib.enum {
'public', 'private', 'local';
'personal', 'direct', 'circle';
};
notiftype = lib.enum {
'mention', 'like', 'rt', 'react'
};
relation = lib.enum {
'follow', 'mute', 'block'
};
credset = lib.set {
'pw', 'otp', 'challenge', 'trust'
};
privset = lib.set {
................................................................................
powerset = lib.set {
-- user powers -- default on
'login', 'visible', 'post', 'shout',
'propagate', 'upload', 'acct', 'edit';
-- admin powers -- default off
'purge', 'config', 'censor', 'suspend',
'cred', 'elevate', 'demote', 'rebrand' -- modify site's brand identity
}
}
terra m.powerset:affect_users()
return self.purge() or self.censor() or self.suspend() or
self.elevate() or self.demote() or self.rebrand() or
self.cred()
end
local str = rawstring
local pstr = lib.mem.ptr(int8)
struct m.source
................................................................................
struct m.actor {
id: uint64
nym: str
handle: str
origin: uint64
bio: str
title: str
avatar: str
knownsince: m.timepoint
rights: m.rights
key: lib.mem.ptr(uint8)
-- ephemera
xid: str
source: &m.source
}
struct m.actor_stats {
posts: intptr
follows: intptr
followers: intptr
mutuals: intptr
}
................................................................................
blacklist: bool
}
-- backends only handle content on the local server
struct m.backend { id: rawstring
open: &m.source -> &opaque
close: &m.source -> {}
conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
conf_set: {&m.source, rawstring, rawstring} -> {}
conf_reset: {&m.source, rawstring} -> {}
actor_save: {&m.source, &m.actor} -> bool
actor_create: {&m.source, &m.actor} -> uint64
................................................................................
-- notifies the backend module of the UID that has been assigned for
-- an authentication ID
-- aid: uint64
-- uid: uint64
actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
actor_conf_int: cnf(intptr, lib.stat(intptr))
post_save: {&m.source, &m.post} -> {}
post_create: {&m.source, &m.post} -> uint64
post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post)
|
> | > > > > | < | > > > > > > > > > > > > > > > > > > > > > > |
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .. 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 .. 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 ... 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 ... 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
scope = lib.enum {
'public', 'private', 'local';
'personal', 'direct', 'circle';
};
notiftype = lib.enum {
'mention', 'like', 'rt', 'react'
};
relation = lib.enum {
'follow', 'mute', 'block'
};
credset = lib.set {
'pw', 'otp', 'challenge', 'trust'
};
privset = lib.set {
................................................................................
powerset = lib.set {
-- user powers -- default on
'login', 'visible', 'post', 'shout',
'propagate', 'upload', 'acct', 'edit';
-- admin powers -- default off
'purge', 'config', 'censor', 'suspend',
'cred', 'elevate', 'demote', 'rebrand', -- modify site's brand identity
'herald' -- grant serverwide epithets
};
prepmode = lib.enum {
'full','conf','admin'
}
}
terra m.powerset:affect_users()
return self.purge() or self.censor() or self.suspend() or
self.elevate() or self.demote() or self.cred()
end
local str = rawstring
local pstr = lib.mem.ptr(int8)
struct m.source
................................................................................
struct m.actor {
id: uint64
nym: str
handle: str
origin: uint64
bio: str
epithet: str
avatar: str
knownsince: m.timepoint
rights: m.rights
key: lib.mem.ptr(uint8)
-- ephemera
xid: str
source: &m.source
}
terra m.actor.methods.mk(kbuf: &uint8)
var newkp = lib.crypt.genkp()
var privsz = lib.crypt.der(false,&newkp,kbuf)
return m.actor {
id = 0; nym = nil; handle = nil;
origin = 0; bio = nil; avatar = nil;
knownsince = lib.osclock.time(nil);
rights = m.rights_default();
epithet = nil, key = [lib.mem.ptr(uint8)] {
ptr = &kbuf[0], ct = privsz
};
}
end
struct m.actor_stats {
posts: intptr
follows: intptr
followers: intptr
mutuals: intptr
}
................................................................................
blacklist: bool
}
-- backends only handle content on the local server
struct m.backend { id: rawstring
open: &m.source -> &opaque
close: &m.source -> {}
dbsetup: &m.source -> bool -- creates the schema needed to call conprep (called only once per database e.g. with `parsav db init`)
conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database
conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
conf_set: {&m.source, rawstring, rawstring} -> {}
conf_reset: {&m.source, rawstring} -> {}
actor_save: {&m.source, &m.actor} -> bool
actor_create: {&m.source, &m.actor} -> uint64
................................................................................
-- notifies the backend module of the UID that has been assigned for
-- an authentication ID
-- aid: uint64
-- uid: uint64
actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
actor_conf_int: cnf(intptr, lib.stat(intptr))
auth_create_pw: {&m.source, uint64, bool, lib.mem.ptr(int8)} -> {}
-- uid: uint64
-- reset: bool (delete other passwords?)
-- pw: pstring
post_save: {&m.source, &m.post} -> {}
post_create: {&m.source, &m.post} -> uint64
post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post)
|
Modified str.t from [5af1afba76] to [f2457b558f].
6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
local m = {
sz = terralib.externfunction('strlen', rawstring -> intptr);
cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int);
cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
dup = terralib.externfunction('strdup',rawstring -> rawstring);
ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
fmt = terralib.externfunction('asprintf',
terralib.types.funcpointer({&rawstring,rawstring},{int},true));
bfmt = terralib.externfunction('sprintf',
terralib.types.funcpointer({rawstring,rawstring},{int},true));
span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
|
> > |
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
local m = {
sz = terralib.externfunction('strlen', rawstring -> intptr);
cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int);
ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int);
cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring);
ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring);
cat = terralib.externfunction('strcat',{rawstring, rawstring} -> rawstring);
ncat = terralib.externfunction('strncat',{rawstring, rawstring, intptr} -> rawstring);
dup = terralib.externfunction('strdup',rawstring -> rawstring);
ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring);
fmt = terralib.externfunction('asprintf',
terralib.types.funcpointer({&rawstring,rawstring},{int},true));
bfmt = terralib.externfunction('sprintf',
terralib.types.funcpointer({rawstring,rawstring},{int},true));
span = terralib.externfunction('strspn',{rawstring, rawstring} -> rawstring);
|
Modified tpl.t from [ba911a8ebc] to [682e534236].
1 2 3 4 5 6 7 8 9 10 11 12 13 .. 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 .. 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 .. 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
-- vim: ft=terra
-- string template generator:
-- returns a function that fills out a template
-- with the strings given
local util = lib.util
local m = {}
function m.mk(tplspec)
local str
if type(tplspec) == 'string'
then str = tplspec tplspec = {}
else str = tplspec.body
end
................................................................................
str = str:gsub('%s+[\n$]','')
str = str:gsub('\n','')
str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed
str = str:gsub(tplchar .. '%?([-%w]+)', function(file)
if not docs[file] then docs[file] = data.doc[file] end
return string.format('<a href="#help-%s" class="help">?</a>', file)
end)
for start, key, stop in string.gmatch(str,'()'..tplchar..'(%w+)()') do
if string.sub(str,start-1,start-1) ~= '\\' then
segs[#segs+1] = string.sub(str,last,start-1)
fields[#segs] = key
last = stop
end
end
segs[#segs+1] = string.sub(str,last)
for i, s in ipairs(segs) do
segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o)
................................................................................
local runningtally = symbol(intptr)
local tallyup = {quote
var [runningtally] = 1 + constlen
end}
local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or ''))
local symself = symbol(&rec)
do local kfac = {}
for afterseg,key in pairs(fields) do
if not kfac[key] then
rec.entries[#rec.entries + 1] = {
field = key;
type = lib.mem.ptr(int8);
}
end
kfac[key] = (kfac[key] or 0) + 1
end
for key, fac in pairs(kfac) do
tallyup[#tallyup + 1] = quote
[runningtally] = [runningtally] + ([symself].[key].ct)*fac
end
end
end
local copiers = {}
local senders = {}
local appenders = {}
................................................................................
local cpypos = symbol(&opaque)
local accumulator = symbol(&lib.str.acc)
local destcon = symbol(&lib.net.mg_connection)
for idx, seg in ipairs(segs) do
copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end
senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end
appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end
if fields[idx] then
--local fsz = `lib.str.sz(symself.[fields[idx]])
local fval = `symself.[fields[idx]].ptr
local fsz = `symself.[fields[idx]].ct
copiers[#copiers+1] = quote
[cpypos] = lib.mem.cpy([cpypos], [&opaque]([fval]), [fsz])
end
senders[#senders+1] = quote
lib.net.mg_send([destcon], [fval], [fsz])
end
appenders[#appenders+1] = quote
[accumulator]:push([fval], [fsz])
end
end
end
local tid = tplspec.id or '<anonymous>'
rec.methods.tostr = terra([symself])
lib.dbg(['compiling template ' .. tid])
[tallyup]
var [symtxt] = lib.mem.heapa(int8, [runningtally])
var [cpypos] = [&opaque](symtxt.ptr)
[copiers]
@[&int8](cpypos) = 0
return symtxt
end
rec.methods.append = terra([symself], [accumulator])
lib.dbg(['appending template ' .. tid])
[tallyup]
accumulator:cue([runningtally])
[appenders]
|
> | | > | | | | > > > | | | | > > > > > > > > > > > > > > > > > > > > > > > | | > | | > > | | > | > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .. 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 .. 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 .. 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
-- vim: ft=terra -- string template generator: -- returns a function that fills out a template -- with the strings given local util = lib.util local pstr = lib.mem.ptr(int8) local m = {} function m.mk(tplspec) local str if type(tplspec) == 'string' then str = tplspec tplspec = {} else str = tplspec.body end ................................................................................ str = str:gsub('%s+[\n$]','') str = str:gsub('\n','') str = str:gsub('</a><a ','</a> <a ') -- keep nav links from getting smooshed str = str:gsub(tplchar .. '%?([-%w]+)', function(file) if not docs[file] then docs[file] = data.doc[file] end return string.format('<a href="#help-%s" class="help">?</a>', file) end) for start, mode, key, stop in string.gmatch(str,'()'..tplchar..'([:!]?)(%w+)()') do if string.sub(str,start-1,start-1) ~= '\\' then segs[#segs+1] = string.sub(str,last,start-1) fields[#segs] = { key = key, mode = (mode ~= '' and mode or nil) } last = stop end end segs[#segs+1] = string.sub(str,last) for i, s in ipairs(segs) do segs[i] = string.gsub(s, '\\'..tplchar, tplchar_o) ................................................................................ local runningtally = symbol(intptr) local tallyup = {quote var [runningtally] = 1 + constlen end} local rec = terralib.types.newstruct(string.format('template<%s>',tplspec.id or '')) local symself = symbol(&rec) do local kfac = {} local sanmode = {} for afterseg,fld in ipairs(fields) do if not kfac[fld.key] then rec.entries[#rec.entries + 1] = { field = fld.key; type = lib.mem.ptr(int8); } end kfac[fld.key] = (kfac[fld.key] or 0) + 1 sanmode[fld.key] = fld.mode == ':' and 6 or fld.mode == '!' and 5 or 1 end for key, fac in pairs(kfac) do local sanfac = sanmode[key] tallyup[#tallyup + 1] = quote [runningtally] = [runningtally] + ([symself].[key].ct)*fac*sanfac end end end local copiers = {} local senders = {} local appenders = {} ................................................................................ local cpypos = symbol(&opaque) local accumulator = symbol(&lib.str.acc) local destcon = symbol(&lib.net.mg_connection) for idx, seg in ipairs(segs) do copiers[#copiers+1] = quote [cpypos] = lib.mem.cpy([cpypos], [&opaque]([seg]), [#seg]) end senders[#senders+1] = quote lib.net.mg_send([destcon], [seg], [#seg]) end appenders[#appenders+1] = quote [accumulator]:push([seg], [#seg]) end if fields[idx] and fields[idx].mode then local f = fields[idx] local fp = `symself.[f.key] copiers[#copiers+1] = quote if fp.ct > 0 then var san = lib.html.sanitize(fp, [f.mode == ':']) [cpypos] = lib.mem.cpy([cpypos], [&opaque](san.ptr), san.ct) --san:free() end end senders[#senders+1] = quote if fp.ct > 0 then var san = lib.html.sanitize(fp, [f.mode == ':']) lib.net.mg_send([destcon], san.ptr, san.ct) --san:free() end end appenders[#appenders+1] = quote if fp.ct > 0 then var san = lib.html.sanitize(fp, [f.mode == ':']) [accumulator]:ppush(san) --san:free() end end elseif fields[idx] then local f = fields[idx] local fp = `symself.[f.key] copiers[#copiers+1] = quote if fp.ct > 0 then [cpypos] = lib.mem.cpy([cpypos], [&opaque](fp.ptr), fp.ct) end end senders[#senders+1] = quote if fp.ct > 0 then lib.net.mg_send([destcon], fp.ptr, fp.ct) end end appenders[#appenders+1] = quote if fp.ct > 0 then [accumulator]:ppush(fp) end end end end local tid = tplspec.id or '<anonymous>' rec.methods.tostr = terra([symself]) lib.dbg(['compiling template ' .. tid]) [tallyup] var [symtxt] = lib.mem.heapa(int8, [runningtally]) var [cpypos] = [&opaque](symtxt.ptr) [copiers] @[&int8](cpypos) = 0 symtxt.ct = [&int8](cpypos) - symtxt.ptr return symtxt end rec.methods.append = terra([symself], [accumulator]) lib.dbg(['appending template ' .. tid]) [tallyup] accumulator:cue([runningtally]) [appenders] |
Modified view/compose.tpl from [bb642e2999] to [5ccb8d92d6].
1 2 3 4 5 6 |
<form class="compose" method="post"> <img src="/avi/@handle"> <textarea autofocus name="post" placeholder="it was a dark and stormy night…">@content</textarea> <input required type="text" name="acl" class="acl" value="@acl"> @?acl <button type="submit">commit</button> </form> |
| | > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<form class="compose" method="post"> <img src="/avi/@handle"> <textarea autofocus name="post" placeholder="it was a dark and stormy night…">@!content</textarea> <input required autocomplete="on" type="text" name="acl" class="acl" value="@acl" list="scopes" placeholder="access control"> @?acl <button type="submit">commit</button> </form> <datalist id="scopes"> <option>all</option> <option>mentioned</option> <option>local</option> <option>mutual</option> <option>followers</option> <option>followed</option> <option>groupies</option> <option>staff</option> <option>admin</option> @circles </datalist> |
Added view/conf-profile.tpl version [746111dd26].
> > > > > > |
1 2 3 4 5 6 |
<form method="post"> <label>handle <div class="txtbox">@!handle</div></label> <label>display name <input type="text" name="nym" value="@:nym"></label> <label>bio <textarea name="bio">@!bio</textarea></label> <input type="submit" value="commit"> </form> |
Added view/conf-sec.tpl version [7ba95a81c5].
> > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 |
<form method="post"> <p>if you are concerned that your account may have been compromised, you can terminate all other login sessions by invalidating their session cookies. note that this will not have any effect on API tokens; these must be revoked separately!</p> <label> sessions valid from <div class="txtbox">@lastreset</div> </label> <button type="submit" name="act" value="invalidate"> invalidate other sessions </button> </form> |
Added view/conf.tpl version [bd130ad9d3].
> > > > > > > > > > > > > > > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<div class="menu"> <a href="/conf/profile">profile</a> <a href="/conf/avi">avatar</a> <a href="/conf/sec">security</a> <a href="/conf/rel">relationships</a> <a href="/conf/qnt">quarantine</a> <a href="/conf/acl">ACL shortcuts</a> <a href="/conf/rooms">chatrooms</a> <a href="/conf/circles">circles</a> @menu </div> <div class="panel"> @panel </div> |
Modified view/load.lua from [1503c625ad] to [f63ef60595].
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
-- create templates from when we return to terra
local path = ...
local sources = {
'docskel';
'tweet';
'profile';
'compose';
'login-username';
'login-challenge';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
txt = txt:gsub('([^\\])!%b[]', '%1')
|
> > > > |
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
-- create templates from when we return to terra
local path = ...
local sources = {
'docskel';
'tweet';
'profile';
'compose';
'login-username';
'login-challenge';
'conf';
'conf-profile';
}
local ingest = function(filename)
local hnd = io.open(path..'/'..filename)
local txt = hnd:read('*a')
io.close(hnd)
txt = txt:gsub('([^\\])!%b[]', '%1')
|
Modified view/login-challenge.tpl from [c8511de2b7] to [84fccbb367].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="login"> <div class="user"> <img src="/avi/@handle"> <div class="name">@name</div> </div> <div class="msg">@challenge</div> <form action="/login" method="post"> <label for="response">@label</label> <input type="hidden" name="user" value="@handle"> <input type="password" name="response" id="response" autofocus required> <button type="submit" name="authmethod" value="@method">authenticate</button> <a href="/login">cancel</a> </form> </div> |
| | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<div class="login"> <div class="user"> <img src="/avi/@handle"> <div class="name">@!name</div> </div> <div class="msg">@challenge</div> <form action="/login" method="post"> <label for="response">@label</label> <input type="hidden" name="user" value="@:handle"> <input type="password" autocomplete="@auto" name="response" id="response" autofocus required> <button type="submit" name="authmethod" value="@method">authenticate</button> <a href="/login">cancel</a> </form> </div> |
Modified view/login-username.tpl from [4dc628d5ef] to [8c165f8ae9].
1 2 3 4 5 6 7 8 |
<div class="login"> <div class="msg">@loginmsg</div> <form action="/login" method="post"> <label for="user">local handle</label> <input type="text" name="user" id="user" autofocus required> <button type="submit">log on</button> </form> </div> |
| |
1 2 3 4 5 6 7 8 |
<div class="login">
<div class="msg">@loginmsg</div>
<form action="/login" method="post">
<label for="user">local handle</label>
<input type="text" name="user" id="user" autocomplete="username" autofocus required>
<button type="submit">log on</button>
</form>
</div>
|
Modified view/profile.tpl from [6c61b2a3c1] to [d21ccabbe8].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<div class="profile">
<div class="banner">
<img class="avatar" src="@avatar">
<div class="id"><span class="nym">@nym</span> [<span class="xid">@xid</span>]</div>
<div class="bio">
@bio
</div>
</div>
<table class="stats">
<tr><th>posts</th> <td>@nposts</td></tr>
<tr><th>following</th> <td>@nfollows</td></tr>
<tr><th>followers</th> <td>@nfollowers</td></tr>
<tr><th>mutuals</th> <td>@nmutuals</td></tr>
<tr><th>@timephrase</th> <td>@tweetday</td></tr>
</table>
<div class="menu">
<a href="/@xid">posts</a>
<a href="/@xid/media">media</a>
<a href="/@xid/social">associates</a>
<hr>
@auxbtn
</div>
</div>
|
| | | | | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<div class="profile"> <div class="banner"> <img class="avatar" src="@:avatar"> <div class="id">@nym</div> <div class="bio"> @bio </div> </div> <table class="stats"> <tr><th>posts</th> <td>@nposts</td></tr> <tr><th>following</th> <td>@nfollows</td></tr> <tr><th>followers</th> <td>@nfollowers</td></tr> <tr><th>mutuals</th> <td>@nmutuals</td></tr> <tr><th>@timephrase</th> <td>@tweetday</td></tr> </table> <div class="menu"> <a href="/@:xid">posts</a> <a href="/@:xid/media">media</a> <a href="/@:xid/social">associates</a> <hr> @auxbtn </div> </div> |
Modified view/tweet.tpl from [43ed0b36e9] to [806c88c01c].
1 2 3 4 5 6 7 8 9 10 11 |
<div class="post"> <div class="avatar"><img src="@avatar"></div> <a class="username" href="/@acctlink"> <span class="nym">@nym</span> [<span class="handle">@xid</span>] </a> <div class="content"> <div class="subject">@subject</div> <div class="text">@text</div> </div> <a class="permalink" href="@permalink">@when</a> </div> |
| | < < | |
1 2 3 4 5 6 7 8 9 |
<div class="post"> <div class="avatar"><img src="@:avatar"></div> <a class="username" href="/@:acctlink">@nym</a> <div class="content"> <div class="subject">@!subject</div> <div class="text">@text</div> </div> <a class="permalink" href="@permalink">@when</a> </div> |