No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-17 12:24:24 +03:00
lib litellm: create empty environment by default 2026-09-17 12:24:24 +03:00
modules litellm: create empty environment by default 2026-09-17 12:24:24 +03:00
AGENTS.md mi6: initial reusable flake 2026-09-12 11:12:48 +03:00
COPYING legal: add agpl license text 2026-09-13 11:12:22 +03:00
flake.lock fix: update maki and forward mux environment 2026-09-12 21:03:13 +03:00
flake.nix legal: add agpl license text 2026-09-13 11:12:22 +03:00
guest.nix legal: add agpl license text 2026-09-13 11:12:22 +03:00
package.nix litellm: create empty environment by default 2026-09-17 12:24:24 +03:00
README.md litellm: create empty environment by default 2026-09-17 12:24:24 +03:00
result feat: add shell completions and manpage 2026-09-13 11:12:22 +03:00

mi6

Warning

This repository is slop-coded: most of its implementation and documentation were generated by an AI coding agent and have not received a thorough independent human security review. Treat it as experimental, inspect the code and generated NixOS configuration, and do not rely on it as a hardened sandbox for hostile workloads or secrets.

mi6 runs Maki and other development commands in reusable, per-project Incus containers. Each container has its own network namespace on incusbr0, with IPv4 and IPv6 NAT, a persistent home directory, the current project mounted at /workspace, and access to the host Nix store and daemon.

Provider credentials remain on the host. A per-user LiteLLM service holds upstream API keys and ChatGPT subscription OAuth state, listens on a Unix socket under $XDG_RUNTIME_DIR, and is exposed to each container as http://127.0.0.1:4000. Both Maki and LiteLLM run through proxychains4.

Requirements

  • NixOS with flakes enabled
  • A fixed UID for the host user
  • /etc/proxychains.conf
  • Host SOCKS and HTTP proxy listeners on 127.0.0.1:51823 and 127.0.0.1:8124 by default
  • A localnet rule in the proxychains configuration that bypasses loopback

The NixOS module enables Incus and adds the configured user to incus-admin.

NixOS setup

Add this flake as an input and import its NixOS module. For a local checkout:

{
  inputs.mi6.url = "path:/path/to/mi6";

  outputs = { nixpkgs, mi6, ... }: {
    nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        mi6.nixosModules.default
        ({ config, ... }: {
          networking.nftables.enable = true;

          users.users.alice = {
            isNormalUser = true;
            uid = 1000;
          };

          programs.mi6 = {
            enable = true;
            hostUser = "alice";
          };
        })
      ];
    };
  };
}

Apply the configuration:

sudo nixos-rebuild switch --flake .#my-host

Log out and back in after the first activation so the new incus-admin group membership is applied. The NixOS module only configures Incus and system-level user access.

Optional Home Manager setup

Import the Home Manager module to install mi6 persistently:

{
  imports = [ inputs.mi6.homeManagerModules.default ];
  programs.mi6.enable = true;
}

Home Manager is optional. nix run . -- run maki is self-contained and starts LiteLLM as a transient systemd user service at runtime.

LiteLLM configuration

The per-user service reads upstream provider variables from:

~/.config/mi6/litellm/environment

The launcher creates this file empty with mode 0600 if it does not exist. An empty file is sufficient when only a ChatGPT subscription is used. mi6 run starts a transient user service named mi6-litellm-transient and waits for its socket and runtime key before launching the container command.

For API-key providers, add shell-compatible assignments. Do not use spaces around =:

cat >~/.config/mi6/litellm/environment <<'EOF'
ANTHROPIC_API_KEY=sk-ant-...
EOF
chmod 600 ~/.config/mi6/litellm/environment

Restart and inspect the transient user service with:

systemctl --user restart mi6-litellm-transient
systemctl --user status mi6-litellm-transient
journalctl --user -u mi6-litellm-transient -f

The service generates these runtime files on every start:

$XDG_RUNTIME_DIR/mi6-litellm/gateway.sock
$XDG_RUNTIME_DIR/mi6-litellm/master-key

The master key is random, mode 0600, and not persisted. mi6 reads the current key when launching a command and passes it to the container. The generated LiteLLM YAML is installed at:

~/.config/mi6/litellm/config.yaml

ChatGPT OAuth state is stored separately at:

~/.local/state/mi6/litellm/auth.json

Do not copy a Codex or Maki auth.json into this location. OAuth refresh tokens rotate, so this file should have one writer and represent an independent login grant.

Authenticate a ChatGPT subscription

The default gpt-* LiteLLM route uses the ChatGPT subscription backend. Authentication uses OpenAI's device-code flow and is initiated by the first request.

First start the service and follow its journal in one terminal:

systemctl --user restart mi6-litellm-transient
journalctl --user -u mi6-litellm-transient -f

In another terminal, submit a request directly over the Unix socket:

curl --no-buffer \
  --unix-socket "$XDG_RUNTIME_DIR/mi6-litellm/gateway.sock" \
  http://localhost/v1/responses \
  -H "Authorization: Bearer $(cat "$XDG_RUNTIME_DIR/mi6-litellm/master-key")" \
  -H 'Content-Type: application/json' \
  -d '{"model":"gpt-5.6-sol","input":"Reply with OK","stream":true}'

LiteLLM prints a verification address and device code in the journal. Open the displayed address, sign in to the ChatGPT account that owns the subscription, and enter the code. Keep the request running while the device flow completes. LiteLLM then writes ~/.local/state/mi6/litellm/auth.json and refreshes the tokens on later requests.

After authentication, verify the gateway with the same command or launch Maki through mi6.

If authentication expires or is revoked:

systemctl --user stop mi6-litellm-transient
rm ~/.local/state/mi6/litellm/auth.json
nix run . -- run maki

Then repeat the device-code flow.

Usage

Run Maki in the container associated with the current project:

mi6 run maki

The nearest existing project container is reused. Otherwise, mi6 selects the nearest version-control root, falling back to the current directory.

Run another command in the same environment:

mi6 run bash
mi6 run nix develop
mi6 run cargo test

Leave the container running after the command exits:

mi6 run-sticky maki

Inspect or stop it:

mi6 status
mi6 stop

Print its persistent host-side home directory:

mi6 container-home

Delete the container while preserving its home:

mi6 delete

Delete both the container and its persistent home:

mi6 delete --include-home

Networking and proxying

Containers use an isolated incusbr0 interface with NAT; they do not share the host network namespace. The launcher adds explicit Incus proxy devices for host-local services:

container 127.0.0.1:51823 -> host SOCKS listener
container 127.0.0.1:8124  -> host HTTP proxy listener
container 127.0.0.1:4000  -> host per-user LiteLLM Unix socket

The guest receives upper- and lower-case HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY variables. Maki is additionally wrapped in proxychains4, so its own provider traffic and tool subprocesses use the configured real proxy. LiteLLM is also wrapped in proxychains4, which covers provider SDKs that ignore proxy environment variables.

The LiteLLM endpoint remains on guest loopback while the host side uses a per-user Unix socket. This avoids shared host TCP ports on multiuser systems.

Customizing LiteLLM routes

programs.mi6.litellm.settings is rendered as LiteLLM YAML. The defaults route:

gpt-*    -> chatgpt/gpt-*
claude-* -> anthropic/claude-*

For example, replace the model list with an additional OpenAI API route:

programs.mi6.litellm.settings.model_list = [
  {
    model_name = "gpt-*";
    litellm_params.model = "chatgpt/gpt-*";
  }
  {
    model_name = "openai-api/*";
    litellm_params = {
      model = "openai/*";
      api_key = "os.environ/OPENAI_API_KEY";
    };
  }
  {
    model_name = "claude-*";
    litellm_params = {
      model = "anthropic/claude-*";
      api_key = "os.environ/ANTHROPIC_API_KEY";
    };
  }
];

Add the corresponding keys to ~/.config/mi6/litellm/environment and restart mi6-litellm-transient. The generated default config.yaml is only installed when the file does not already exist, so it can also be edited directly for custom routes.

The LiteLLM service and container bridge are enabled by default. Upstream authentication remains in the host-side gateway.