Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,59 @@ Use `thirdshift help` for the command reference, or read [Run behavior](docs/gui

The operating system schedules Architect, Pickup and Security runs. See [scheduling](docs/guide.md#on-a-schedule), [Weeding](docs/guide.md#weeding) and [Fencing](docs/guide.md#fencing).

## Console on a server

Set the machine's address, fixed localhost port and sign-in allowlist in `~/.thirdshift/config.toml`:

```toml
[console]
address = "https://console.example.com" # a DNS name, with or without https://; no port or path
port = 8711
allowed_emails = ["you@example.com"]
```

Point that DNS name at the machine, then run these commands as the owner:

```sh
thirdshift console password --email you@example.com
thirdshift console install > install-console.sh
# Read the script, then install it:
sudo sh install-console.sh
```

`console install` only prints a script. On Linux it prefers a running Caddy or Apache service; otherwise it checks for `caddy`, `apache2ctl` or `httpd` on PATH. The script writes `thirdshift-console.service`, running the installed binary as you with your HOME and automatic restarts. Only the script needs root. It enables the service, then restarts the proxy.

For Caddy it writes `/etc/caddy/sites/thirdshift-console.caddy`, imports the sites folder from `/etc/caddy/Caddyfile` if needed, and disables proxy buffering with `flush_interval -1`. Caddy handles HTTPS. It **restarts**, rather than reloads, Caddy to preserve HTTP/3 on Android. For Apache it writes the port-80 site under `/etc/apache2/sites-available` or `/etc/httpd/conf.d`, preserves Host, forwards the request protocol and disables gzip on the Console's event streams. Install certbot with its Apache plugin first; the script runs `certbot --apache -d <name>`. Debian's module/site helpers are included. On an `httpd` machine, enable `proxy`, `proxy_http`, `headers`, `ssl` and `setenvif` and permit proxy connections in the host's SELinux policy first.

With neither proxy, the script installs a localhost-only service with `--no-sign-in` and prints an SSH tunnel command. No address or accounts are needed for that mode. Run the tunnel on your computer and open the printed `http://localhost:<port>` address. On other operating systems, run `thirdshift console --no-sign-in` directly and use the same tunnel; only the Linux installer assumes systemd.

### nginx

For nginx, use the generated service but remove `--no-sign-in` from its `ExecStart` before installing it. Configure the allowlist and password above. Obtain an HTTPS certificate for your DNS name, then add a site like this, adjusting the name, certificate paths and port:

```nginx
server {
listen 443 ssl;
server_name console.example.com;
ssl_certificate /etc/letsencrypt/live/console.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/console.example.com/privkey.pem;

location / {
proxy_pass http://127.0.0.1:8711;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
gzip off;
proxy_read_timeout 1h;
}
}
```

Run `sudo nginx -t`, then `sudo systemctl restart nginx` on a systemd host. The Console still binds only to 127.0.0.1; nginx terminates HTTPS and passes live updates without buffering.

## Documentation

- [Configuration](docs/guide.md#user-config), [Harness, Model and Effort](docs/guide.md#harness-model-and-effort), and [email notifications](docs/guide.md#email).
Expand Down
4 changes: 4 additions & 0 deletions console/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ stdout. To require sign-in, set the allowlist in `~/.thirdshift/config.toml`:

```toml
[console]
address = "https://console.example.com" # used by console install
port = 8711
allowed_emails = ["you@example.com"]
other_consoles = ["https://console.example.test"]
theme = "focus-auto" # also focus-light or focus-dark
```

For a persistent service behind Caddy, Apache or nginx, see the root README's
[server installation instructions](../README.md#console-on-a-server).

Run `thirdshift console password --email you@example.com` in a terminal to set
the password, then `thirdshift console`. Password entry is hidden and asks
twice. With exactly one allowed address, `--email` can be omitted. An empty
Expand Down
1 change: 1 addition & 0 deletions docs/code-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Start with the issue's terms in `CONTEXT.md`, then use this index to find their
| Command naming, Session selection, show, logs | `src/logs/reading/selection.rs` — resolution, captured outer owner and child-refusal target, Session inventory/selection and bounded latest events; `src/logs/status/inspect.rs` — formatting and log streaming | `src/logs/reading/tests.rs`, `tests/show_logs.rs` |
| Console Logs page, conversation, log stream, exact log inventory | `src/logs/reading/catalog.rs` — current-layout inventory shared with `logs`; `src/console/logs.rs` — pages and byte-offset SSE; `src/console/conversation.rs` — Claude/Codex rendering | `tests/console.rs`, `tests/fixtures/console-*.jsonl`, `console/tests/logs.test.cjs` |
| Console account, Console log, sign-in | `src/console/accounts.rs` — durable accounts, sessions, slowdown and account events; `src/console/auth.rs` — HTTP authorization and CSRF; `src/console/account_commands.rs` — CLI; `src/terminal.rs` — shared hidden terminal input | `src/console/accounts/tests.rs`, `tests/console/accounts.rs` |
| Console installation, proxy site, system service | `src/console/install.rs` — printed Linux installation script; `src/config/console.rs` — address and port | `tests/console_install.rs` |
| Stop | `src/logs/reading.rs` — `retained_work` reload by captured record; `src/logs/reading/selection.rs` — `Selection::stop_target`; `src/logs/stop.rs` — request, signaling and waiting; `src/claim.rs` — Stop endings | `src/logs/reading/tests.rs`, `tests/stop.rs`; inline Claim and Stop tests |
| User config, Setup | `src/config.rs`; `src/setup.rs` | `tests/user_config.rs`, `tests/setup.rs` |
| Architecture review, Architect run | `src/architect.rs` | `tests/architect_run.rs` |
Expand Down
4 changes: 3 additions & 1 deletion src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum Command {
Console {
no_sign_in: bool,
},
ConsoleInstall,
ConsolePassword(Option<String>),
ConsoleSignOut {
email: Option<String>,
Expand Down Expand Up @@ -282,6 +283,7 @@ pub fn parse(args: &[String]) -> Result<Command> {
return match &args[1..] {
[] => Ok(Command::Console { no_sign_in: false }),
[flag] if flag == "--no-sign-in" => Ok(Command::Console { no_sign_in: true }),
[action] if action == "install" => Ok(Command::ConsoleInstall),
[action] if action == "password" => Ok(Command::ConsolePassword(None)),
[action, flag, email]
if action == "password" && flag == "--email" && !email.starts_with('-') =>
Expand All @@ -293,7 +295,7 @@ pub fn parse(args: &[String]) -> Result<Command> {
Ok(Command::ConsoleLog { json: true })
}
_ => bail!(
"usage: thirdshift console [--no-sign-in] | password [--email <address>] | sign-out [--email <address>] [--json] | log [--json]"
"usage: thirdshift console [--no-sign-in] | install | password [--email <address>] | sign-out [--email <address>] [--json] | log [--json]"
),
};
}
Expand Down
2 changes: 2 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,7 @@ days = 7 # days to keep ended problems and Resolved; default 7
silent_minutes = 10 # minutes without growth in either log before a Run is Silent; default 10

[console]
address = "" # HTTPS address for console install, e.g. https://console.example.com; default blank
port = 8711 # localhost port for the Console; 0 chooses a free port; default 8711
allowed_emails = [] # addresses that may sign in; empty requires --no-sign-in; default []
other_consoles = [] # http or https addresses linked in the header, never fetched; default []
Expand Down Expand Up @@ -973,6 +974,7 @@ mod tests {
"logs.waiting_line_minutes",
"inbox.days",
"inbox.silent_minutes",
"console.address",
"console.port",
"console.allowed_emails",
"console.other_consoles",
Expand Down
29 changes: 29 additions & 0 deletions src/config/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use serde::Deserialize;
#[derive(Debug, PartialEq, Eq, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub struct ConsoleSettings {
pub address: String,
pub port: u16,
pub allowed_emails: Vec<String>,
pub other_consoles: Vec<String>,
Expand All @@ -14,6 +15,7 @@ pub struct ConsoleSettings {
impl Default for ConsoleSettings {
fn default() -> Self {
Self {
address: String::new(),
port: 8711,
allowed_emails: Vec::new(),
other_consoles: Vec::new(),
Expand All @@ -23,7 +25,34 @@ impl Default for ConsoleSettings {
}

impl ConsoleSettings {
/// The HTTPS site's DNS name, accepted with or without `https://`.
pub fn hostname(&self) -> Result<&str> {
let host = self
.address
.strip_prefix("https://")
.unwrap_or(&self.address);
let host = host.strip_suffix('/').unwrap_or(host);
ensure!(
!host.is_empty()
&& host.len() <= 253
&& host.split('.').all(|label| {
!label.is_empty()
&& label.len() <= 63
&& !label.starts_with('-')
&& !label.ends_with('-')
&& label
.bytes()
.all(|b| b.is_ascii_alphanumeric() || b == b'-')
}),
"console.address must be an HTTPS DNS name, such as https://console.example.com, without a port or path"
);
Ok(host)
}

pub(super) fn validate(&self) -> Result<()> {
if !self.address.is_empty() {
self.hostname()?;
}
for email in &self.allowed_emails {
ensure!(
email.contains('@') && !email.chars().any(char::is_whitespace),
Expand Down
2 changes: 2 additions & 0 deletions src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ mod accounts;
mod auth;
mod conversation;
pub use account_commands::{password, print_log, sign_out};
mod install;
pub use install::install;
mod live;
mod logs;
mod peek;
Expand Down
Loading
Loading