shirk v0.2.1
Shirk
A Crystal library for building SSH servers using libssh. Provides both low-level FFI bindings and a high-level callback-based API.
⚠️ Important Limitations
This library is designed for non-interactive SSH command execution, such as:
- Remote API endpoints
- Automated deployment commands
- CI/CD integrations
- Git-over-SSH servers
It is NOT suitable for:
- Interactive shells (bash, zsh, etc.)
- Full terminal emulation (PTY)
- Real-time streaming input/output
- Commands that require user interaction (vim, nano, less, etc.)
Stdin Handling
The high-level API (Shirk::Server) collects all stdin before calling your handler. This means:
- ✅
echo "data" | ssh host commandworks - all piped data is available inctx.stdin - ✅
ssh host commandwithout stdin works - handler is called after a short timeout with emptyctx.stdin - ❌ Interactive input doesn't work - there's no way to read/write incrementally
If you need more control over stdin handling, use the low-level API (see examples/ssh_server.cr) which provides raw callbacks for data as it arrives.
Requirements
- Crystal 1.0+
- libssh (install via your package manager)
Installation
Add to your shard.yml:
dependencies:
shirk:
github: ralsina/shirk
High-Level API
The Shirk::Server class provides a clean callback-based interface:
require "shirk"
server = Shirk::Server.new(
host: "0.0.0.0",
port: 2222,
host_key: "ssh_host_rsa_key"
# KEX algorithms default to include post-quantum support:
# ml-kem-768-sha256-x25519-sha256@libssh.org:sntrup761x25519-sha512@openssh.com:...
)
# Public key authentication - receives SHA256 fingerprint
server.on_auth_pubkey do |user, fingerprint|
puts "Key auth: #{user} with #{fingerprint}"
true # accept all keys
end
# Password authentication
server.on_auth_password do |user, password|
user == "admin" && password == "secret"
end
# Handle exec requests
server.on_exec do |ctx|
puts "Command: #{ctx.command}"
ctx.write("Hello, #{ctx.user}!\n")
ctx.write_stderr("No errors\n")
0 # exit code
end
server.run
ExecContext
The on_exec callback receives an ExecContext with:
ctx.command- the command stringctx.user- authenticated usernamectx.stdin- all stdin data from client (collected before handler runs)ctx.write(data)- write to stdoutctx.write_stderr(data)- write to stderr- Return value is the exit code
Features
- Post-quantum cryptography - supports ML-KEM-768x25519 and SNTRUP761x25519 key exchange algorithms by default
- Fork model - each connection runs in a child process for isolation
- Password auth via
on_auth_passwordcallback - Public key auth via
on_auth_pubkeycallback (receives SHA256 fingerprint) - Exec handling with proper stdout/stderr/exit status
- Shell support via
on_shellcallback (optional)
Low-Level API
For more control, use the FFI bindings directly. See examples/ssh_server.cr for a complete example that closely follows the libssh C API.
Examples
Simple Server
crystal build examples/simple_server.cr -o simple_server
./simple_server
Test with:
ssh -p 2222 admin@localhost # password: secret
# or with Python
python3 -c "import paramiko; c=paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy()); c.connect('localhost',2222,username='admin',password='secret'); print(c.exec_command('whoami')[1].read())"
Generate Host Key
ssh-keygen -t rsa -b 2048 -f ssh_host_rsa_key -N ""
Development
shards install
crystal spec
crystal build examples/simple_server.cr
Contributing
- Fork it (https://github.com/ralsina/shirk/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Contributors
- Roberto Alsina - creator and maintainer
shirk
- 0
- 0
- 0
- 1
- 0
- 15 days ago
- November 28, 2025
MIT License
Fri, 19 Dec 2025 11:07:47 GMT