A2RULE(8)
๐ A2RULE(8) ๐
okay so bestie, this is a2rule โ a manual page from section 8, the System Management Commands crew, and it's literally the tool that keeps the ARPA2 Policy Rule database from descending into chaos. grab a snack, we're going in ๐
โจ NAME โจ
a2rule โ Manage the ARPA2 Policy Rule database ๐
๐ธ SYNOPSIS ๐ธ
like, here's every single way you can call her. all of them. no skipping ๐
a2rule group add|set [ dbsetup ]
member a2group+member@a2domain
identity remote@domain [marks marks]
a2rule group del|get [ dbsetup ]
member a2group+member@a2domain
a2rule pseudo set [ dbsetup ]
loginid remote@domain
alterego a2user@a2domain rights rights
a2rule pseudo del|get [ dbsetup ]
loginid remote@domain
alterego a2user@a2domain
a2rule comm add|set [ dbsetup ]
local a2user@a2domain remote remote@domain
( list list | rights rights )
[alias|endalias alias] [signflags number]
[newuserid newuserid] [newalias newalias] [actorid actorid]
a2rule comm del|get [ dbsetup ]
local a2user@a2domain remote remote@domain
( list list | rights rights )
[alias|endalias alias] [signflags number]
a2rule docu add|set [ dbsetup ]
domain a2domain ( collection colluuid
| volume volume [path path] )
remote remote@domain rights rights [actorid actorid]
a2rule docu del|get [ dbsetup ]
domain a2domain ( collection colluuid
| volume volume [path path] )
remote remote@domain
a2rule lets add|set [ dbsetup ]
domain a2domain [currency currency [balance balid]]
user user@domain rights rights
a2rule lets del|get [ dbsetup ]
domain a2domain [currency currency [balance balid]]
user user@domain
a2rule rules add|set [ dbsetup ] servicekey hexkey|base85key
[type rules_type] name rules_name
[selector a2selector] rule policy_rule
a2rule rules get|del [ dbsetup ] servicekey hexkey|base85key
[type rules_type] name rules_name
[selector a2selector] [rule policy_rule]
๐ DESCRIPTION ๐
so a2rule is the command you use to make changes in the ARPA2 Policy Rule database. that's the whole vibe. ๐ซถ a central database like this is what keeps Access Control and Group Management changes coordinated with each other, and it's what lets you automate updates to those settings โ so admins can absolutely just use a2rule for manual changes to the central Policy Rule database too. best of both worlds, honestly.
now here's the caveat that lives rent-free in my head ๐ญ โ a customary assumption for Access Control logic is that any identities passed into it are authenticated. applications that forego such precaution may be lied to so they select permissive Access Rules. and worse, fr fr: there's a serious risk that such applications become an oracle to be probed for Access Rights. like, not a cute look. ๐
the Policy Rule database stores things that may be done by certain remotes, during particular usage patterns. for Access Control, it concentrates on who may do what to which resource. resources can be stuff like users to connect to (that's Communication Access) or documents to see or edit (that's Document Access). and there are also Policy Rules for Groups, which let you set up and iterate their members and the mappings from each to their delivery address. iconic, honestly.
Policy Rules are designed for fast automated processing, and even though they follow a somewhat readable ASCII format, writing it by hand would be straight-up error-prone โ especially because each use of rules adds its own quirks. that's literally the whole reason a2rule exists: to write well-formed rules into the database, and to spare you the anxiety of having to debug this format. she's your emotional support CLI ๐
๐ DATABASE SETUP ๐
the dbsetup general parameters let you change the database access from the default. listed below, bestie. normally, the database is found in a set location and used without protective secret. ๐ฆ
dbtrunk numโ provides the trunk number, which identifies the origin of a key-value mapping. applications ignore trunks and simply take the first that comes along, but for bulk management it can be used to indicate an origin of the database rules โ and that may be handy when the need arises to erase or restart the database input from a certain source without hampering any others. the default trunk number0is advised for manual operations. ๐dbprefix #wordโ provides a non-standard prefix word todelandgetoperations. the standard prefix fora2ruleis#a2xs. these prefix words are of no meaning during rule processing, but they usually help to select among tools from which rules originate. rules inserted by other tools thana2rulecan be seen and even cleaned up, but they should not be (over)written, because that might damage assumptions made in these other tools. use those other tools to produce fresh rule values if so desired, under the consistency observations of those tools. ๐ซถdbdomkey hexkey|base85keyโ provides the Domain Key from which domain admins may access settings for their domain without seeing those in others. this is a lesser grant than thedbsecretprovides. (you cannot provide a Domain Key as well as a Service Key.) the key may be represented in hexadecimal (BASE16) or in theBASE85notation defined by ARPA2 Common. ๐
also note there are a few environment variables that control the database setup โ they're right below and they're kind of a big deal ๐
๐ฑ Environment variables ๐ฑ
ARPA2_RULES_DIRโ sets the directory that serves as the environment for the ARPA2 Policy Rules database. when not set, the tool uses a built-in default like/var/lib/arpa2/rules. this variable is not a property ofa2rulebut of the ARPA2 Common library, so it works consistently across applications using the library. in other words: setting this environment variable can switch all uses of ARPA2 Policy Rules to another database. โจARPA2_DOMAINKEY_domainโ holds the Domain Key in hexadecimal or base85 form. this may be setup in the environment for a domain administrator. the inclusion of the domain helps to support multiple domains in a single administrator account. an example variable would beARPA2_DOMAINKEY_example_orgfor domainexample.org. the domain name is mapped to make it suitable for an environment variable: letters are mapped to lowercase, letters and digits pass, dots turn to underscores and dashes turn toXcharacters. anything else is ignored, including UTF-8 specialties. this is not expected to be problematic in practice. the administrator account should be protected from dumping those environment variables; online services should not set them and at most configure a Service Key. ๐งARPA2_SERVICEKEYโ holds the Service Key in hexadecimal or base85 form. this may be setup in an environment that runs a particular service, be it a large daemon or a partial program on its behalf. it is used in therulesetsubcommand. this variable may change or disappear in future versions, because its value varies with Access Domain and Access Type, but neither have to be provided and therefore a single variable name is used โ which reduces the practical use of this variable. ๐
๐ฎ Key derivation ๐ฎ
while running a2rule, the key derivation process is shown, which should help to give an idea what the parameters do at the various levels. the levels of interest are:
- Domain Key โ hashed from the
dbsecretand the domain name. this can be used by domain admins, while the generaldbsecretremains the property of their provider. ๐ - Service Key โ hashed from the Domain Key and the Access Type, which separates access types
comm,docu,pseudoandgroupwith completely different keys. this can be used by service providers, who install it in a (virtual) service for a given domain. โญ - Selector Key โ hashed from the Service Key, Access Name and Remote Selector, this is used to look for a rule set in the database. when the Remote Selector is subjected to iteration as described below, a few Selector Keys will be derived and looked up. the fact that
a2ruleshows precisely one Service Key is because it works on individual rule sets, without iteration. services would normally iterate to find the most concrete rule set that is defined. ๐ฅ
future extensions with encrypted database values may protect Rule content, which may be useful when data records are sent from an Identity Provider to a Service Provider, where the latter gets to data only on a need-to-know basis โ so only for an actual request by a user. for now though, the database scheme assumes that the data does not travel between data processing industries. ๐
๐ฆ ARPA2 PSEUDONYMS ๐ฆ
Pseudonyms allow a user to switch from their login identity to an alter ego. an alter ego is a form of Actor Identity, along with aliases and group members. ๐
it's possible for local or remote login users to switch to a pseudonymic identity in the supporting ARPA2 domain. when no @a2domain is provided, then the a2domain supporting the pseudonym will be added. ๐ธ
rights for pseudonyms are simple; %T permits the following remote@domain to switch to the pseudonym, and %A permits seeing and changing the login identities permitted to use the pseudonym. easy. ๐
the Access Name used is the pseudonym identity, with a fully qualified domain name and no + symbol before the @ symbol, all in lower case. the Selector โ which may indeed be a Remote Selector โ holds the proven identity that the user wants to switch away from, also a fully qualified name, and this value takes the form as it was presented by the (possibly remote) party. this may involve uppercase characters in the user name, and symbols such as + are not banned, to avoid placing an interpretation on remote user names. no cap. ๐
๐ธ ARPA2 GROUPS ๐ธ
Okay bestie, so groups are a domain facility that collects internal and external identities ๐ Those identities are usually remote@domain addresses, and they can get used across a whole bunch of protocols โ ARPA2 Groups are not specific to any one protocol, they just map onto stuff that already exists, like email lists, chat rooms and conference calls ๐ Connecting groups to whatever your app thinks "collective use" means? That's on the applications. Not our problem, we just provide the slay infrastructure โจ
Every identity involved in a group gets assigned an ARPA2 Actor Identity that looks like a2group+member@domain, where the a2group@domain part is the ARPA2 Actor Identity for the entire group, and every member alias references one group member ๐ซถ The underlying identity of the member does not need to be shown in order to address some or all members, which means when you're communicating with a group you don't have to surrender your address โ no cap, the privacy is real ๐
Members only need to know one another by their ARPA2 Actor Identity annex ARPA2 Group Member Identity ๐ but like, how far that privacy actually gets shielded is eventually an implementer's choice, and maybe also depends on the protocols. There's also support for non-member access to ARPA2 Groups, by mapping them to a dynamic member address ๐
Groups consist of a rule set, with a key that's influenced by the a2domain name, the Access Type for ARPA2 Groups, and the a2group serving as the Access Name ๐ You can create a separate rule for each ARPA2 Group Member with a2rule operations add, del, set and get โ which end up being set updating operations, iykyk. The value in the set looks like this:
#a2xs %marks ^member@remote@domain
where #a2xs marks this tool and remote@domain marks the (generally considered remote) identity โญ To access group facilities, users authenticate under that latter form, but implementations of ARPA2 Groups should aim to store and log mostly the a2group+member@domain form โ the Actor Identity which, in the context of ARPA2 Groups, is called the ARPA2 Group Member Identity ๐ฆ
This provides privacy but โ and here's the tea โ as the administrator sees fit, the rules can be queried to retrieve an abuser's remote identity ๐ So like, private, but not anonymous-anonymous, bestie.
The programming interface to ARPA2 Groups is mostly through the group_iterate() operation, which delivers callbacks for any member that matches selection criteria ๐ Those criteria may be part of recipient addresses, but there may also be applications that want to hard-wire them. It's generally assumed that the sender or requesting ARPA2 Identity is already a member identity, which is usually achieved through Access Control, as described below ๐
๐ ARPA2 COMMUNICATION ACCESS ๐
The whole purpose of ARPA2 Communication Access is to grant recipients maximum control over who may communicate with them, regardless of the protocol being used ๐ฅ Which means the same access control applies to email, chat and telephony. One ruleset to rule them all, periodt ๐
The remote is assumed to provide an address โ considered the remote address โ of the form remote@domain, in addition to the destined address, which will be parsed as:
a2userid[+aliases][+signature+]@a2domain
The parts +aliases and +signature+ are optional, as indicated by those rectangular brackets ๐ Alternatively, when the destination address starts with a plus, it's considered a service under ARPA2 Identity management, and parsed as:
+a2service[+svcargs][+signature+]@a2domain
Normally, the +a2service is processed like a2userid and svcargs like aliases โจ
Communication Access iterates over the remote address from concrete to abstract ๐ It first removes the user identity, and then removes one label from the domain name at a time, for instance:
john+cook@example.com
@example.com
@.com
@.
The last form is the most abstract, and it matches every remote address ๐ The strings produced this way are known as ARPA2 Selectors, and they form a pattern to match ARPA2 Identities. One might think of an ARPA2 Selector as the set of all ARPA2 Identities that find it during iteration towards the general form ๐ The ARPA2 Selector @. is always last, so its set would hold all Remote Identities.
Access Rules are stored in the database under a key that's determined by the Access Type for Communication Access, the a2domain name, possibly a database key, and the selector (which gets iteratively generalised as indicated above) ๐ธ The Access Name is the a2userid or the +a2service. Note that the aliases, svcargs and signature parts are not part of the lookup key โ ๏ธ The result is a key that is highly specific, yet only calls for a few key lookups in the LMDB database to decide on Communication Access. This schedule scales well to large operations, because key lookup in a database is a very efficient operation ๐ The section Data Structures and Constants for Rules and RuleDB in the documentation (derived from <arpa2/rules_db.h>) gives the calculations made to optimise the design choices for large-scale operations without interfering with modest situations.
Communication Access stores a rule set under the keys, but empty sets do not exist ๐ This means a key/value lookup only succeeds when a non-empty ruleset is found, which is then processed without regard for any more abstract keys ๐ซถ So the most concrete form is leading. And since this is a rule set, it's possible to modify the rules stored there โ done with the a2rule operations add, del and get, which end up being set management operations โจ A rule for Communication Access can be as simple as:
#a2xs %rights
or as feature-packed as:
#a2xs =aaliases =ssigflags =nnewuserid =oovraliases =gactor %rights
The values for rights overlap with list, namely %W for white listing, %C for grey listing, %V for black listing and %K for honeypot condemnation ๐ฅ The rule format does not represent the list form, so it's encoded as %rights instead.
The following keyword-value pairs locate an Access Rule in the database:
aliasalias โ adds=aaliasto the rule to require that these match the beginning of the aliases or svcargs in the local ARPA2 Identity ๐ This is used to configure separate Communication Access rules for aliases. Longer aliases than the matched alias are permitted, as long as they start with+.endaliasalias โ adds=aalias@to the rule. This variation does not match longer aliases in the local ARPA2 Identity than this option's alias ๐ The special form=a@indicates that the local ARPA2 Identity must not hold any aliases, which is requested with anendalias ""option.signflagsnumber โ adds=snumberto the rule to require the signature to be present in the local ARPA2 Identity and have no zero bits overlapping one bits in the mask given as a number in this option โญ Signature flags exist to demand such things as an expiration day, the remote domain and the remote user. The values of the bits are derived by thea2id_parse()operation on the local destination address, and turned into a numeric value. This value is setup as sigflags here, in decimal form. Note the difference in the format from the destination address, which uses base32 notation ๐ The signature itself is not validated, as that's normally done by the application through a call toa2id_verify()โ but the question whether a signature is present and what its constraints on the context of use are is decided with this attribute.
The following keyword-value pairs serve to modify the lookup in case of white listing:
newuseridnewuserid โ adds=nnewuseridto the rule to indicate that the local ARPA2 Identity will be changed from the originala2useridto this newuserid ๐ฆ Any aliases in the original address are removed, but any signature is kept.newaliasoveralias โ adds=ooveraliasto the rule to indicate that the local ARPA2 Identity will be set to these aliases ๐ธ Any aliases in the original address are removed, but any signature is kept, and this option does not modify thea2useridor+serviceeither.actoridactorid โ adds=gactoridto the rule, to be extended with@a2domainto form an Actor Identity that is returned to the access-requesting application ๐ Unlike the=nand=oattributes, this one does not alter the local ARPA2 Identity โ it provides an alter ego that can be used alongside it, or possibly instead of it, as the application sees fit. Actor Identities are prominently used for Group Members, which is why the attribute name=gstuck ๐
To process Communication Access in applications, they call access_comm() โจ This takes a remote sender and a local destination address. The local address may be altered by the call. In addition, there's an output parameter for an Actor Identity that overtakes from the remote sender for local classification purposes ๐ Since this is the result of Communication Access, it's possible to have a different Actor Identity for different access patterns โ notably, when addressing an ARPA2 Group there's an option of assigning (remote) members with their group member Actor Identity ๐
๐ ARPA2 DOCUMENT ACCESS ๐
Okay so like, the whole point of Access Control is stopping users from getting at documents or resources they are not supposed to touch. That's the general idea behind Document Access. And here's the cute part: local users get treated as if they're remote anyway, with facilities to set up rules for the local a2domain that are more permissive than for other domains. Local besties get the VIP lane, basically. ๐
The things that may be accessed are named by a path. These paths can point to directories or files on an operator-defined volume. Volumes might have names like "home" or maybe even "john@home". Cute, right?
Aside from those operator-defined volumes, there's an implicit document naming structure called Reservoir. It's a two-level structure local to a domain โ a directory represented as a UUID serving as a Resource Collection (or directory), plus a second UUID to zoom in on a specific Resource (or file). The metadata for Reservoir lives in LDAP so it's easily searchable with descriptive attributes. Searchable metadata is such a slay. ๐ฅ
A third concept in Reservoir is a Resource Index โ a mapping from names to Resource Collections, much like a symbolic link in a file system. Indexes usually have a default Collection. Every domain can have an Index, and every user under a domain can have an Index too; these constructs define a default Collection to domains and their users, plus a mapping from names to alternate Collections. Names are a human thing; for Reservoir they always map to a single Collection UUID. Nesting is just as meaningless to the computer, because all Collection UUIDs sit side by side. Paths of name lookups in Indexes may look like they traverse to certain depths, but they translate to side-way jumping through the single level of Collection UUIDs. It's giving "walking through a hallway where all the doors are actually right next to each other." ๐ชโจ
Policy Rules for Document Access are incredibly simple. The basic form is:
#a2xs %rights
and the only feature currently defined is:
#a2xs =gactor %rights
The following keyword-value pairs are used to locate a Rule in the database:
domaina2domainโ specifies thata2domainserves as the Access Domain. It's the scope under which both Reservoir and the volume form reside. ๐collectioncolluuidโ represents a collection in the domain's Reservoir in the reproducible form of a UUID in lowercase text form. Any paths, domains or users into the Reservoir are transformed to theircolluuidvalue. Document Access in Reservoir is always and only at the Resource Collection level, and normally applied only to the last element of a traversed path, so the form is always/colluuid/with no extra characters added. Don't add anything, bestie. ๐คvolumevolumeโ an operator-selected name for a volume. There cannot be/characters in a volume name, but the@character can be safely used. It has no formal meaning but may be a pleasant form to represent user-specific (parts of) volumes. ๐pathpathโ a path on the volume. Any leading/characters are removed. An empty path adds an empty string after the//volume/or/colluuid/strings. Non-empty paths ending in/should be used to indicate a directory, while any other ending character should indicate a file.remoteremote@domainโ specifies the Remote Selector that should match with the (local or) remote user who wants to gain access to the described resource or document. ๐ซถ
The following options are used to influence the output from Document Access:
actoridactoridโ adds=gactorid@a2domainto the rule to indicate that access should be granted under that Actor Identity. This may be used to indicate a document owner by an ARPA2 Group, which is accessible to Group Members who use their member alias under thea2domain. This would influence visibility to others โ including such things as document audit logs โ and thus aid the privacy of the Group Members. ๐คซrightsrightsโ adds%rightsto the rule to indicate the rights for (local or) remote users matching this Policy Rule. ๐
๐ฐ ARPA2 LETS ACCESS ๐ฐ
The ARPA2 Local Exchange Trading System grants rights to users and administrators โ for things like creating currencies and opening a new balance balid with them. Currencies are designed to operate under the principles of Local Exchange Trading Systems, which offer abundant currency by allowing negative balances and community judgement over reasonable use of this facility. As a result of this, the currency must remain local to a community, and that misuse of the abundancy can be evaluated by all community members as part of their trading. LETSystems have no systemic inflation, but individual community members may erode their own purchasing power when their balance is excessively negative. ๐ฌ
LETSystem currency is only used to communicate between supply and demand, and ARPA2 LETS therefore allows booking funds to whitelisted parties under Communication Access. This does not imply that all communication peers have access to the currencies that make this happen; currencies may be tied into a smaller community by way of the Access Rights of the currency itself. Keep the circle tight, fr. ๐ซ
Access Names in an ARPA2 LETS take one of the following forms:
+lets@a2domainโ for the LETSystem undera2domainas a whole, ๐+lets+currency@a2domainโ for the namedcurrencyinside the LETSystem undera2domain, or ๐ต+lets+currency+balid@a2domainโ for a balancebalid(usually a number) under the namedcurrencyin the LETSystem undera2domain. ๐งพ
Note that currencies are scoped under the a2domain. For example, ARPA2 LETSystems usually install a currency named XES ("stones") as a complete separate currency in each supported a2domain.
The user@domain is parsed as a Remote Selector. Remote users may be given access to a local currency, but this is not a trivial choice; it must be balanced against the risk of abuse of community benefits built around the ARPA2 LETS. โ๏ธ
The Access Rights to a currency are assigned as letters in the rights, matching the general Rules letters. Other flags may be set, and their interpretation would depend on the LETSystem software.
%CforACCESS_CREATEโ On a currency, this confers the right to create a new balance. On the whole LETSystem, this confers the right to create a new currency. ๐ฑ%DforACCESS_DELETEโ On a balance, this confers the right to delete that balance if it is zeroed. On a currency, this confers the right to delete any zeroed balance. On the whole LETSystem, this confers the right to delete any whole currency, without requiring all-zero balances. ๐๏ธ%OforACCESS_OWNERโ On a balance, this indicates responsibility for keeping it functioning, and closing it off properly; it confers the right to attach user identities to an account, and to detach them. On a currency, this indicates ownership; the combination%DOAconfers the right to remove the entire currency, without requiring all-zero balances. ๐%KforACCESS_KNOWโ On a balance, this confers the right to see the transactions in which it participated as a producer or consumer. This includes information about hot transactions. On a currency, this confers the right to see the balances, including information about hot money. ๐%RforACCESS_READโ On a currency, this confers the right to see user identities attached to its balances. Without this right, user identities will be concealed and balances are named in their general+lets+currency+balance@a2domainform. ๐%WforACCESS_WRITEโ On a balance, this confers the right to move currency out of it. In addition, Communication Access to the recipient must be whitelisted by the recipient. On a currency, this confers the right to move funds between its balances. This could be used to extract fees (which is not advised), to distribute an expense (which may be agreeable if an action was taken on the community's behalf), or to zero a problematic balance (and compensate it with another) so it may be deleted. ๐ธ%AforACCESS_ADMINโ On a currency, this confers the right to apply the combined Access Rights in individual user accounts. This means that an administrator can obtain an overview, manage accounts on behalf of users, and even override users. When it is easy to create new currencies, as is customary under ARPA2 LETS, then any such power is subject to democratic control. ๐ณ๏ธ
A common setting on a new currency would be %ACDWRKO for the creator and %DKRWO on any newly created balances. In addition, creation %C may be granted on any currency with a Remote Selector in user that could welcome anyone @. or just the domain users @a2domain or a specific group a2group@a2domain. ๐
๐ ARPA2 POLICY RULES ๐
The foregoing sections detailed applications of the general framework for ARPA2 Policy Rules. It is possible to perform each of the foregoing operations at an abstract level by interacting with the ruleset directly. This means that the specifics of the application logic are no longer concealed, but there are also more possibilities โ albeit constrained to a level where a Service Key must be presented. ๐
The following arguments can be provided on the commandline for interaction with Policy Rules:
servicekeyhexkey|base85keyโ provides the Service Key available to the service for which the request is being made. When not present, it will be loaded from an environment variableARPA2_SERVICEKEY. The key may be represented in hexadecimal (BASE16) or in theBASE85notation defined by ARPA2 Common. ๐๏ธnamerules_nameโ provides the Rules Name, which is an UTF-8 string with a grammar specific to the Rules Type. This grammar is only checked when thetypeargument is present. ๐rulepolicy_ruleโ provides a specific Policy Rule, used in set operationsadd,del,setorget. When not provided indelorget, the operation will be applied to all rules in the ruleset. ๐typerules_typeโ may optionally be provided to improve the interaction. It can set up a grammar to check the name, thereby avoiding notational variations that could make services in different implementations incompatible. ๐งฉ
๐ COPYRIGHT ๐
Copyright ยฉ 2021-2024 Rick van Rein, ARPA2.net.
okay so the legal bits, bestie, and honestly they're kind of wholesome?? Let me break it down ๐ซถ
ARPA2 gets its bag from InternetWide.org, which in turn receives funding from various sources โ iconique funding chain, no cap ๐
Part of this work was funded through a grant of SIDNfonds, a public interest fund geared towards a better Internet for all โ and it's paid by administration fees for .nl domain names ๐ท so like, every Dutch domain is lowkey sponsoring a better internet, we love to see it โจ
Other parts were funded by NLnet, a public interest fund geared towwards open source excellence, and by the NGI Pointer fund of the European Union ๐ช๐บ open source excellence and EU money?? the way that this page is stacked with generous funders, honestly ate ๐
๐ SEE ALSO ๐
Bestie, if you wanna go deeper (and you know you do ๐):
- ARPA2 Common is documented on
http://common.arpa2.net/โ the docs, the lore, the whole vibe check ๐ - and its code is on
https://gitlab.com/arpa2/arpa2common/โ go read the source, it's giving main character energy ๐
and that's the whole page, bestie!! ๐ this one came from ARPA2.net and it's dated June 2021 โ yes, A2RULE(8), section 8 of the manual, the iconic system administration era ๐ญ you're literally a sysadmin now, go off and configure those rules ๐โจ bye bestie, ily, drink some water ๐ซถ๐ธ
๐ฆ where this page comes from
- nixos 26.05: arpa2common 2.6.4
- nixpkgs unstable: arpa2common 2.6.4
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms