Skip to main content

Node

Struct Node 

Source
pub struct Node { /* private fields */ }
Expand description

A running node daemon serving repos under a root directory.

Implementations§

Source§

impl Node

Source

pub fn bind(root: &Path, port: u16) -> Result<Self>

Binds to 127.0.0.1:port (0 = ephemeral) over root, with no authentication — localhost/dev only.

§Errors

Returns an error when the socket cannot be bound or root cannot be created.

Source

pub fn bind_with_auth( root: &Path, port: u16, auth: Option<AuthTable>, ) -> Result<Self>

Binds like Node::bind; when auth is Some, every request must carry valid basic-auth credentials from the table or it is answered with 401 before touching git.

§Errors

Same failure modes as Node::bind.

Source

pub fn bind_full( root: &Path, addr: &str, port: u16, auth: Option<AuthTable>, tls: Option<(Vec<u8>, Vec<u8>)>, ) -> Result<Self>

Full-control bind: address, port, auth, and optional TLS (PEM certificate chain + PEM private key).

A non-loopback addr is refused without TLS — plaintext basic auth must never cross a real network (standing privacy rule: nothing leaves loopback without an explicit, protected choice).

§Errors

Returns an error when the socket cannot be bound, root cannot be created, the TLS material is invalid, or a non-loopback bind is requested without TLS.

Source

pub fn enable_api_body_limit(&mut self, bytes: NonZeroU64)

Sets the absolute body ceiling for every /api/... route.

Source

pub fn disable_browser_writes(&mut self)

Removes browser mutation controls and their preparation endpoint.

It withholds authorship, not credentials (D73). A browser under this flag renders no control that would put an operation in the op log – no verdict, no comment, no /api/prepare – because that is the launch gate D39 shipped behind and the thing an operator switches on when they are ready for it.

It does not withhold signing in, enrolling the passkey that signs in, asking for access (D72), or the operator’s console. None of those reaches the log: the accounts store is a node-owned file and revocation is deletion (D36). Withholding them was the same flag doing two jobs, and the second job made the node’s own manifest untrue – passkeys=enabled beside a posture that would not serve the file the ceremony is written in.

Source

pub fn serve_single_repository(&mut self, repo: &str) -> Result<()>

Presents one repository as this node’s entire browser surface.

/ becomes that repository instead of the index, and the browser answers for no other repository. This is what a node serving a project’s own domain wants: a reader arriving at git.example.com came for that project, and an index naming every other repository the host holds is both noise and a disclosure.

Presentation only. It changes no grant: git access stays the ACL’s answer, and a repository hidden here is still clonable by whoever could clone it before.

§Errors

Refuses a name this node could not hold, checked here rather than at the call site: the router trusts this value against the disk, so the grammar has to be enforced where it is stored.

Source

pub fn enable_ready_min_free_bytes(&mut self, bytes: u64)

Sets the free-space floor below which /readyz refuses traffic.

Source

pub fn enable_request_log( &mut self, path: PathBuf, max_bytes: u64, ) -> Result<()>

Records every served request to path (D33), rotating it at max_bytes — see limits::RequestLog for the line format, the rotation rule, and what is deliberately never written.

§Errors

Returns the failure to open the file. Fatal by design: an operator who asked for a record and did not get one should learn that at startup rather than from its absence during an incident.

Source

pub fn enable_rate_limit( &mut self, api_per_minute: Option<NonZeroU32>, git_per_minute: Option<NonZeroU32>, )

Limits each authenticated user to the given requests per minute per class (D33). None leaves that class unlimited.

Never applied to the loopback hook callback, to a holder of a D29 @node grant, or on a node without authentication — see Node::serve_forever for why each of those would be worse than the flood it prevents.

Source

pub fn enable_quotas( &mut self, push_bytes: Option<NonZeroU64>, workspaces: Option<NonZeroU32>, )

Sets the per-user quotas (D37): the largest git request body one user may send, and the most workspaces one user may hold at once. None leaves that ceiling off.

Exempt exactly where the D33 rate limiter is exempt, and for the same reason — see Node::serve_forever. A quota that can lock an operator out of their own node is the failure this must not cause.

Source

pub fn enable_platform(&mut self, platform: Platform)

Enables the platform API (/api/submit, /api/view) backed by platform. Call before Node::serve_forever.

Source

pub fn enable_queue(&mut self, config: QueueConfig)

Enables POST /api/queue/run (D5, D68).

Without it the endpoint answers 501. There is deliberately no timer: a node that spends CI on its own schedule surprises whoever pays for it, and a round only a clock can start is one no test can reach without waiting on wall-clock time. An operator’s cron, a hook, or a person decides the cadence.

Source

pub fn platform(&self) -> Option<&Platform>

The platform this node serves, or None if the platform API was never enabled.

Source

pub fn reconcile_refs(&self) -> RefReconciliation

Brings the bare repos back into agreement with the view before the node serves anything. See Platform::reconcile_git_refs for what it repairs and what it refuses to.

Separate from Node::enable_platform and from Node::serve_forever so it is called deliberately: it writes git refs and can append compensating ops, which is not something a constructor should do behind a caller’s back.

Source

pub fn watch_keys_file(&mut self, path: PathBuf)

Watches the trusted-keys file and regenerates <root>/.choir/allowed_signers whenever its mtime moves, so registering a signing key is “append a line” — the same mechanism the platform registry already uses for submission keys, which until now diverged from push-certificate verification and left the two lists out of step.

Source

pub fn watch_acl_file(&mut self, path: PathBuf) -> Result<(), String>

Enforces per-repository authorization (D29) from path, reloaded whenever its mtime moves — so granting access is “append a line”, the same discipline as the trusted-keys file.

§Errors

Returns a message when the file cannot be read or does not parse. This is fatal by design: there is no previous table to fall back to at startup, and an empty table under a fail-closed ACL locks out everyone including the operator.

Source

pub fn behind_tls_proxy(&mut self)

Declares that a TLS-terminating proxy sits in front of this node, so absolute URLs it builds are written https and the cookies it sets carry Secure.

Declared by the operator rather than read from X-Forwarded-Proto, because the node cannot tell a header its proxy set from one a client sent: trusting it would mean any caller that can reach the node decides how its invite links are spelled. The proxy this repository ships already sets that header rather than appending to it, for the same reason X-Forwarded-For is cleared there (D59), and a declaration needs no such care.

The defect this exists for: an invite is a bearer credential carried in a URL, and behind the proxy the node was writing that URL with http. The recipient’s first request would carry the credential in cleartext and only then be redirected.

Source

pub fn enable_passkeys(&mut self)

Turns on passkeys: WebAuthn enrolment and the browser write path that verifies assertions against enrolled keys (D39, D71).

Separate from Node::enable_accounts even though both need the accounts store, because a node can reasonably offer self-service credentials without offering browser signing, and the private beta says in its manifest that it does exactly that. Without this switch that sentence could not be true: the enrolment routes and platform’s assertion check are both reachable the moment the store exists.

Source

pub fn enable_accounts( &mut self, path: PathBuf, keys_out: Option<SshKeysOut>, actor_keys: Option<PathBuf>, ) -> Result<(), String>

Turns on account and token self-service (D36) from the store at path, optionally generating the authorized_keys D31’s forced commands live in.

§Errors

Refuses without --auth-file and without --acl-file, for the reason D29 and D33 refuse the same combinations: a credential issued on a node that authenticates nobody is not a credential, and one issued on a node with no ACL is a credential to every repository, which is the thing being issued against. Also returns the store’s own load failures. actor_keys names the trusted-keys file a redemption may bind one actor key into (--invite-binds-keys). None keeps the pre-existing behaviour, in which an actor key reaches the node only by an operator editing that file.

Source

pub fn port(&self) -> u16

Port the daemon is listening on.

Source

pub fn write_ssh_handoff(&self, path: &Path) -> Result<()>

Writes the handoff file the SSH shim reads (D31): where this daemon is listening, and the loopback secret its git hooks authenticate with.

Both are per-process values — an ephemeral port, a secret minted at startup — so a forced command written once cannot carry them. The secret is not returned to the caller, only written, at 0600.

The ACL file goes in too, when one is configured, so a forced command that forgot --acl-file still enforces what this daemon enforces rather than reaching every repository.

§Errors

Any I/O error creating or writing the file.

Source

pub fn create_repo(&self, name: &str) -> Result<()>

Creates a bare repo name (e.g. "owner/repo.git") with pushes enabled.

§Errors

Fails when the path exists or git init fails.

Source

pub fn adopt_repo(&self, name: &str) -> Result<()>

Brings an existing bare repo under this root back under the sequencer: the same hook and the same git config Node::create_repo installs.

This is what a restore needs, and without it a restore is silently unsound. Objects arrive as a bundle, which git clone --bare and git fetch both unpack into a repo with no pre-receive hook — and a repo with no hook is served normally while every push into it bypasses the sequencer entirely, landing refs that no op in the log ever records. The alternative order is worse: letting the node create the repos empty and unpacking afterwards means startup reconciliation sees a log naming commits git does not have, decides the log is unbackable, and appends retractions for every restored ref (see Platform::reconcile_git_refs).

It also re-points gpg.ssh.allowedSignersFile, which Node::create_repo wrote as an absolute path into the root that existed then. A restore onto a different path leaves that config naming a directory that may not exist, or worse, one that does and holds someone else’s keys.

Idempotent, and safe to run on every start. The one value it does not overwrite is receive.certNonceSeed: rotating it would refuse the signed pushes already in flight against the old seed.

§Errors

Fails when the path does not exist, is not a git repository, or a git config call fails.

Source

pub fn adopt_existing_repos(&self) -> Result<usize>

Adopts every bare repository already under this node’s root, whatever put it there, and returns how many.

Adoption is what installs the pre-receive hook, so until a repository has been adopted it is served with no hook and every push into it bypasses the sequencer, landing refs that no op in the log ever records — invariants 5 and 6 both, broken silently.

That used to be reachable in ordinary operation, because adoption only ever happened for repositories named in --create. A repository restored from a bundle, moved in, or created against a running node was listed nowhere and hooked never. Walking the root closes the gap at its source: the question “what is this node about to serve” is answered by the filesystem, which is the same thing portable::export already asks.

Idempotent, and meant to run on every start: the one value Node::adopt_repo does not overwrite is receive.certNonceSeed, so an ordinary restart costs a few git config calls and changes nothing.

§Errors

Fails when the root cannot be walked, or when a repository under it cannot be adopted. Both are fatal rather than skipped: serving an unadopted repository is the exact failure this prevents, so a node that cannot guarantee the hook must not start.

Source§

impl Node

Source

pub fn serve_forever(&self)

Serves requests until the process exits. Run on a dedicated thread.

§Rate limiting and who is exempt (D33)

The check runs on the request’s own thread, after authentication — it has to be after, because the bucket is per authenticated user, and it must not be on the accept loop, which exists to accept.

Three exemptions, each because applying the limit would be worse than the flood it prevents:

  1. The loopback hook callback. A push of N refs makes N /api/git-update calls; refusing the fourth one fails the push halfway and drives the retraction path for refs git will never create. It carries a node-minted secret over loopback, so it is not an untrusted caller in the first place.
  2. A holder of a D29 @node grant. That grant is already total authority over the node. Throttling the one actor who can repair it, during the incident the limiter is reporting, is the lockout this feature must not cause.
  3. Any node without --auth-file. There is no per-user identity to bucket by, and a single shared anon bucket is a self-inflicted denial of service rather than a limit.

The D37 quotas (Node::enable_quotas) take the same three, from the same computed value rather than a second copy of the rule: whether a request is metered is one question, and answering it twice is how the two answers start to differ.

Source

pub fn unblock(&self)

Handle for stopping the accept loop (used by tests).

Auto Trait Implementations§

§

impl !Freeze for Node

§

impl !RefUnwindSafe for Node

§

impl Send for Node

§

impl Sync for Node

§

impl Unpin for Node

§

impl UnsafeUnpin for Node

§

impl !UnwindSafe for Node

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.