{"id":13581607,"url":"https://github.com/owenthereal/upterm","last_synced_at":"2026-01-17T01:43:26.314Z","repository":{"id":40291336,"uuid":"218876111","full_name":"owenthereal/upterm","owner":"owenthereal","description":"Instant Terminal Sharing","archived":false,"fork":false,"pushed_at":"2026-01-14T05:13:05.000Z","size":175514,"stargazers_count":1089,"open_issues_count":50,"forks_count":72,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-01-14T19:34:29.197Z","etag":null,"topics":["golang","sharing","ssh","terminal","tools","upterm"],"latest_commit_sha":null,"homepage":"https://upterm.dev","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/owenthereal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"owenthereal","open_collective":"upterm"}},"created_at":"2019-10-31T23:09:26.000Z","updated_at":"2026-01-14T13:58:04.000Z","dependencies_parsed_at":"2023-02-16T10:16:08.746Z","dependency_job_id":"5d83f1c1-0aca-4b90-9604-6bc6e795da18","html_url":"https://github.com/owenthereal/upterm","commit_stats":{"total_commits":527,"total_committers":15,"mean_commits":35.13333333333333,"dds":0.555977229601518,"last_synced_commit":"f5ecabf47125e3edcf8bea0984f4661f27ba0009"},"previous_names":["jingweno/upterm"],"tags_count":74,"template":false,"template_full_name":null,"purl":"pkg:github/owenthereal/upterm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owenthereal%2Fupterm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owenthereal%2Fupterm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owenthereal%2Fupterm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owenthereal%2Fupterm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/owenthereal","download_url":"https://codeload.github.com/owenthereal/upterm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/owenthereal%2Fupterm/sbom","scorecard":{"id":715696,"data":{"date":"2025-08-11","repo":{"name":"github.com/owenthereal/upterm","commit":"a17e7b4b127abe1907ba6764a07e02b892aef809"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.5,"checks":[{"name":"Code-Review","score":6,"reason":"Found 11/16 approved changesets -- score normalized to 6","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"19 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql-analysis.yml:28","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql-analysis.yml:29","Warn: no topLevel permission defined: .github/workflows/build-and-release.yaml:1","Warn: topLevel 'contents' permission set to 'write': .github/workflows/build.yaml:8","Warn: topLevel 'packages' permission set to 'write': .github/workflows/build.yaml:9","Warn: no topLevel permission defined: .github/workflows/codeql-analysis.yml:1","Warn: topLevel 'contents' permission set to 'write': .github/workflows/release.yaml:7","Warn: topLevel 'packages' permission set to 'write': .github/workflows/release.yaml:8","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.15.2 not signed: https://api.github.com/repos/owenthereal/upterm/releases/236146616","Warn: release artifact v0.15.1 not signed: https://api.github.com/repos/owenthereal/upterm/releases/236144864","Warn: release artifact v0.15.0 not signed: https://api.github.com/repos/owenthereal/upterm/releases/235496793","Warn: release artifact v0.14.3 not signed: https://api.github.com/repos/owenthereal/upterm/releases/156635309","Warn: release artifact v0.14.2 not signed: https://api.github.com/repos/owenthereal/upterm/releases/156622001","Warn: release artifact v0.15.2 does not have provenance: https://api.github.com/repos/owenthereal/upterm/releases/236146616","Warn: release artifact v0.15.1 does not have provenance: https://api.github.com/repos/owenthereal/upterm/releases/236144864","Warn: release artifact v0.15.0 does not have provenance: https://api.github.com/repos/owenthereal/upterm/releases/235496793","Warn: release artifact v0.14.3 does not have provenance: https://api.github.com/repos/owenthereal/upterm/releases/156635309","Warn: release artifact v0.14.2 does not have provenance: https://api.github.com/repos/owenthereal/upterm/releases/156622001"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:35: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-and-release.yaml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build-and-release.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:72: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:74: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:31: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:33: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:57: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yaml:59: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/build.yaml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:41: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:45: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:56: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/codeql-analysis.yml:70: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/codeql-analysis.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yaml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/release.yaml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yaml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/owenthereal/upterm/release.yaml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile.uptermd:4","Warn: containerImage not pinned by hash: Dockerfile.uptermd:17","Warn: containerImage not pinned by hash: Dockerfile.uptermd:26","Warn: containerImage not pinned by hash: Dockerfile.uptermd:31","Warn: containerImage not pinned by hash: Dockerfile.uptermd:36","Info:   0 out of  15 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   6 third-party GitHubAction dependencies pinned","Info:   0 out of   5 containerImage dependencies pinned","Info:   1 out of   1 goCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":9,"reason":"SAST tool detected but not run on all commits","details":["Info: SAST configuration detected: CodeQL","Warn: 27 commits out of 29 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-22T09:35:21.945Z","repository_id":40291336,"created_at":"2025-08-22T09:35:21.945Z","updated_at":"2025-08-22T09:35:21.945Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28491630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T00:50:05.742Z","status":"ssl_error","status_checked_at":"2026-01-17T00:43:11.982Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","sharing","ssh","terminal","tools","upterm"],"created_at":"2024-08-01T15:02:07.581Z","updated_at":"2026-01-17T01:43:26.305Z","avatar_url":"https://github.com/owenthereal.png","language":"Go","funding_links":["https://github.com/sponsors/owenthereal","https://opencollective.com/upterm"],"categories":["Go","CLI","Utilities","公用事业公司"],"sub_categories":["Utility/Miscellaneous","实用程序/Miscellaneous"],"readme":"# Upterm\n\n[Upterm](https://github.com/owenthereal/upterm) is an open-source tool enabling developers to share terminal sessions securely over the web. It’s perfect for remote pair programming, accessing computers behind NATs/firewalls, remote debugging, and more.\n\nThis is a [blog post](https://owenou.com/upterm) to describe Upterm in depth.\n\n## :movie_camera: Quick Demo\n\n[![demo](https://raw.githubusercontent.com/owenthereal/upterm/gh-pages/demo.gif)](https://asciinema.org/a/efeKPxxzKi3pkyu9LWs1yqdbB)\n\n## :rocket: Getting Started\n\n## Installation\n\n### Mac\n\n```console\nbrew install --cask owenthereal/upterm/upterm\n```\n\n#### Migrating from Formula to Cask\n\nIf you previously installed upterm using the Homebrew formula (without `--cask`), you'll need to migrate to the Cask version:\n\n```console\n# Uninstall the old formula version\nbrew uninstall upterm\n\n# Install the new Cask version\nbrew install --cask owenthereal/upterm/upterm\n```\n\n**Note:** Running `brew upgrade` with the old formula installed will fail with an error. Follow the migration steps above to resolve this.\n\n### Windows\n\n```powershell\nscoop bucket add upterm https://github.com/owenthereal/scoop-upterm\nscoop install upterm\n```\n\n### Standalone\n\n`upterm` can be easily installed as an executable. Download the latest [compiled binaries](https://github.com/owenthereal/upterm/releases) and put it in your executable path.\n\n### From source\n\n```console\ngit clone git@github.com:owenthereal/upterm.git\ncd upterm\ngo install ./cmd/upterm/...\n```\n\n## :wrench: Basic Usage\n\n1. Host starts a terminal session:\n\n   ```console\n   upterm host\n   ```\n\n1. Host retrieves and shares the SSH connection string:\n\n   ```console\n   upterm session current\n   ```\n\n1. Client connects using the shared string:\n\n   ```console\n   ssh TOKEN@uptermd.upterm.dev\n   ```\n\n## :blue_book: Quick Reference\n\nDive into more commands and advanced usage in the [documentation](docs/upterm.md).\nBelow are some notable highlights:\n\n### Command Execution\n\nHost a session with any desired command:\n\n```console\nupterm host -- docker run --rm -ti ubuntu bash\n```\n\n### Access Control\n\nHost a session with specified client public key(s) authorized to connect:\n\n```console\nupterm host --authorized-keys PATH_TO_PUBLIC_KEY\n```\n\nAuthorize specified GitHub, GitLab, SourceHut, Codeberg users with their corresponding public keys:\n\n```console\nupterm host --github-user username\nupterm host --gitlab-user username\nupterm host --srht-user username\nupterm host --codeberg-user username\n```\n\n### Force command\n\nHost a session initiating `tmux new -t pair-programming`, while ensuring clients join with `tmux attach -t pair-programming`.\nThis mirrors functionality provided by tmate:\n\n```console\nupterm host --force-command 'tmux attach -t pair-programming' -- tmux new -t pair-programming\n```\n\n### File Transfer (SFTP/SCP)\n\nClients can transfer files using standard `scp` or `sftp` commands. The connection details are shown when running `upterm session current`:\n\n```console\n# Download a file from host\nscp -P PORT USER@HOST:/path/to/file.txt ./local/\n\n# Upload a file to host\nscp -P PORT ./local/file.txt USER@HOST:/path/to/destination/\n```\n\n**Security model:**\n\n- File transfers have the same access as the terminal session (clients can already access any file via the shell)\n- Without `--accept`, each file operation prompts the host for approval via a dialog\n- Use `--read-only` to restrict SFTP to downloads only (no uploads, deletes, or modifications)\n- Use `--no-sftp` to disable file transfers entirely\n\n### WebSocket Connection\n\nIn scenarios where your host restricts ssh transport, establish a connection to `uptermd.upterm.dev` (or your self-hosted server) via WebSocket:\n\n```console\nupterm host --server wss://uptermd.upterm.dev -- bash\n```\n\nClients can connect to the host session via WebSocket as well:\n\n```console\nssh -o ProxyCommand='upterm proxy wss://TOKEN@uptermd.upterm.dev' TOKEN@uptermd.upterm.dev:443\n```\n\n### Debug GitHub Actions\n\n`upterm` can be integrated with GitHub Actions to enable real-time SSH debugging, allowing you to interact directly with the runner system during workflow execution. This is achieved through [action-upterm](https://github.com/owenthereal/action-upterm), which sets up an `upterm` session within your CI pipeline.\n\nTo get started, include `action-upterm` in your GitHub Actions workflow as follows:\n\n```yaml\nname: CI\non: [push]\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v2\n    - name: Setup upterm session\n      uses: owenthereal/action-upterm@v1\n```\n\nThis setup allows you to SSH into the workflow runner whenever you need to troubleshoot or inspect the execution environment. Find the SSH connection string in the `Checks` tab of your Pull Request or in the workflow logs.\n\nFor comprehensive details on configuring and using this integration, visit the [action-upterm GitHub repo](https://github.com/owenthereal/action-upterm).\n\n## :bulb: Tips\n\n### Resolving Tmux Session Display Issue\n\n**Issue**: The command `upterm session current` does not display the current session when used within Tmux.\n\n**Cause**: This occurs because `upterm session current` requires the `UPTERM_ADMIN_SOCKET` environment variable, which is set in the specified command. Tmux, however, does not carry over environment variables not on its default list to any Tmux session unless instructed to do so ([Reference](http://man.openbsd.org/i386/tmux.1#GLOBAL_AND_SESSION_ENVIRONMENT)).\n\n**Solution**: To rectify this, add the following line to your `~/.tmux.conf`:\n\n```conf\nset-option -ga update-environment \" UPTERM_ADMIN_SOCKET\"\n```\n\n### Identifying Upterm Session\n\n**Issue**: It might be unclear whether your shell command is running in an upterm session, especially with common shell commands like `bash` or `zsh`.\n\n**Solution**: Use `upterm session current -o go-template` to customize your shell prompt with session info. Add to your `~/.bashrc` or `~/.zshrc`:\n\n```bash\n# Show 🆙 emoji and connected client count when in upterm session\nexport PS1='$(upterm session current -o go-template=\"🆙 {{.ClientCount}} \" 2\u003e/dev/null)'\"$PS1\"\n```\n\n**Template variables available** (Go templates use PascalCase field names):\n\n- `{{.SessionID}}` - Session ID\n- `{{.ClientCount}}` - Number of connected clients\n- `{{.Host}}` - Server host\n- `{{.Command}}` - Command being shared\n- `{{.ForceCommand}}` - Force command (if set)\n\n\u003e **Note**: JSON output (`-o json`) uses camelCase keys (e.g., `sessionId`, `clientCount`).\n\u003e\n\u003e **Tip**: The same template mechanism can be used for terminal titles or other integrations.\n\n**Alternative** (simpler, without client count):\n\n```bash\nexport PS1=\"$([[ ! -z \"${UPTERM_ADMIN_SOCKET}\"  ]] \u0026\u0026 echo -e '\\xF0\\x9F\\x86\\x99 ')$PS1\"\n```\n\n## :gear: How it works\n\nUpterm starts an SSH server (a.k.a. `sshd`) in the host machine and sets up a reverse SSH tunnel to a [Upterm server](https://github.com/owenthereal/upterm/tree/master/cmd/uptermd) (a.k.a. `uptermd`).\nClients connect to a terminal session over the public internet via `uptermd` using `ssh` or `ssh` over WebSocket.\n\n![upterm flowchart](https://raw.githubusercontent.com/owenthereal/upterm/gh-pages/upterm-flowchart.svg?sanitize=true)\n\n## :hammer_and_wrench: Deployment\n\n### Kubernetes\n\nYou can deploy uptermd to a Kubernetes cluster. Install it with [helm](https://helm.sh):\n\n```console\nhelm repo add upterm https://upterm.dev\nhelm repo update\nhelm install uptermd upterm/uptermd\n```\n\n### Fly.io\n\nThe cheapest way to deploy a worry-free [Upterm server](https://github.com/owenthereal/upterm/tree/master/cmd/uptermd) (a.k.a. `uptermd`) is to use [Fly.io](https://fly.io).\nFly offers a generous free tier and excellent global performance. The official uptermd community server is hosted on Fly.\n\n1. Install the Fly CLI and authenticate:\n\n   ```console\n   curl -L https://fly.io/install.sh | sh\n   flyctl auth login\n   ```\n\n1. Copy and customize the [`fly.example.toml`](./fly.example.toml) file to `fly.toml` for your deployment configuration.\n1. Deploy your uptermd server:\n\n  ```console\n  flyctl deploy\n  ```\n\nYour uptermd server will be available at `your-app-name.fly.dev`. You can connect using either SSH or WebSocket protocols.\n\n### Heroku\n\nYou can deploy an [Upterm server](https://github.com/owenthereal/upterm/tree/master/cmd/uptermd) (a.k.a. `uptermd`) to [Heroku](https://heroku.com).\nNote that Heroku discontinued their free tier in November 2022, so this option now requires paid plans.\n\nYou can deploy with one click of the following button:\n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)\n\nYou can also automate the deployment with [Heroku Terraform](https://devcenter.heroku.com/articles/using-terraform-with-heroku).\nThe Heroku Terraform scripts are in the [terraform/heroku folder](./terraform/heroku).\nA [util script](./bin/heroku-install) is provided for your convenience to automate everything:\n\n```console\ngit clone https://github.com/owenthereal/upterm\ncd upterm\n```\n\nProvision uptermd in Heroku Common Runtime. Follow instructions.\n\n```console\nbin/heroku-install\n```\n\nProvision uptermd in Heroku Private Spaces. Follow instructions.\n\n```console\nTF_VAR_heroku_region=REGION TF_VAR_heroku_space=SPACE_NAME TF_VAR_heroku_team=TEAM_NAME bin/heroku-install\n```\n\nYou **must** use WebSocket as the protocol for a Heroku-deployed Uptermd server because the platform only support HTTP/HTTPS routing.\nThis is how you host a session and join a session:\n\nUse the Heroku-deployed Uptermd server via WebSocket\n\n```console\nupterm host --server wss://YOUR_HEROKU_APP_URL -- YOUR_COMMAND\n```\n\nA client connects to the host session via WebSocket\n\n```console\nssh -o ProxyCommand='upterm proxy wss://TOKEN@YOUR_HEROKU_APP_URL' TOKEN@YOUR_HEROKU_APP_URL:443\n```\n\n### Digital Ocean\n\nThere is an util script that makes provisioning [Digital Ocean Kubernetes](https://www.digitalocean.com/products/kubernetes) and an Upterm server easier:\n\n```bash\nTF_VAR_do_token=$DO_PAT \\\nTF_VAR_uptermd_host=uptermd.upterm.dev \\\nTF_VAR_uptermd_acme_email=YOUR_EMAIL \\\nTF_VAR_uptermd_helm_repo=http://localhost:8080 \\\nTF_VAR_uptermd_host_keys_dir=PATH_TO_HOST_KEYS \\\nbin/do-install\n```\n\n### Systemd\n\nA hardened systemd service is provided in `systemd/uptermd.service`. You can use it to easily run a\nsecured `uptermd` on your machine:\n\n```console\ncp systemd/uptermd.service /etc/systemd/system/uptermd.service\nsystemctl daemon-reload\nsystemctl start uptermd\n```\n\n### Traefik\n\nBelow is an example `docker-compose` configuration for deploying `uptermd` behind [Traefik](https://doc.traefik.io/traefik/), including support for both SSH and WebSocket connections:\n\n```yaml\nservices:\n  upterm:\n    build: \n        context: https://github.com/owenthereal/upterm.git\n        dockerfile: Dockerfile.uptermd\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.docker.network=web\"\n      # SSH over TCP (port 2222)\n      - \"traefik.tcp.services.uptermd.loadbalancer.server.port=2222\"\n      - \"traefik.tcp.services.uptermd.loadbalancer.proxyProtocol.version=2\" # required for real IP forwarding over TCP\n      - \"traefik.tcp.routers.uptermd.service=uptermd\"\n      - \"traefik.tcp.routers.uptermd.rule=HostSNI(`*`)\"\n      - \"traefik.tcp.routers.uptermd.entrypoints=uptermd\"\n      # WebSocket over HTTPS (port 8443)\n      - \"traefik.http.services.uptermd-wss.loadbalancer.server.port=8443\"\n      - \"traefik.http.routers.uptermd-wss.service=uptermd-wss\"\n      - \"traefik.http.routers.uptermd-wss.rule=Host(`upterm.example.com`)\" # edit as needed\n      - \"traefik.http.routers.uptermd-wss.entrypoints=websecure\"\n      - \"traefik.http.routers.uptermd-wss.tls.certresolver=\u003cyour cert resolver here\u003e\"\n\n    command:\n      - --ssh-addr=0.0.0.0:2222\n      - --ws-addr=0.0.0.0:8443\n      - --ssh-proxy-protocol\n\n    networks:\n      - web\n\nnetworks:\n  web:\n    external: true\n```\n\n**Important notes:**\n\n- **Proxy Protocol:**\n  The `--ssh-proxy-protocol` flag (or `UPTERMD_SSH_PROXY_PROTOCOL=true` environment variable) tells `uptermd` to expect the [PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) header on incoming SSH connections. This is essential when using Traefik (or other TCP proxies like HAProxy or AWS ELB) to preserve the real client IP address.\n  **If you enable `--ssh-proxy-protocol`, all incoming SSH connections must come through a proxy that supports and is configured to use the PROXY protocol. Direct SSH connections will fail, as `uptermd` will expect the protocol header.**\n\n- **Entrypoints:**\n  Make sure to configure the appropriate [Traefik entrypoints](https://doc.traefik.io/traefik/routing/entrypoints/). This example uses two: one for SSH (`uptermd` on port `2222`) and one for WebSocket/HTTPS (`websecure` on port `443`).\n\n- **WebSocket:**\n  The WebSocket service allows clients to connect to `uptermd` over HTTPS, which is useful in restrictive network environments.\n\n- **Certificates:**\n  Replace `\u003cyour cert resolver here\u003e` with your actual Traefik certificate resolver for TLS.\n\nFor more details on Traefik TCP and HTTP routing, see the [Traefik documentation](https://doc.traefik.io/traefik/routing/overview/).\n\n## :chart_with_upwards_trend: Monitoring\n\n`uptermd` exposes Prometheus metrics at the `/metrics` endpoint when configured with `--metric-addr` (or `UPTERMD_METRIC_ADDR` environment variable).\n\nAvailable metrics:\n\n- `routing_connections_count` (Counter) - Total number of SSH connections accepted\n- `routing_active_connections_count` (Gauge) - Current number of active SSH connections\n- `routing_connection_duration_seconds` (Histogram) - Connection duration in seconds\n- `routing_errors_count` (Counter) - Total number of connection errors\n- `routing_connection_timeout_count` (Counter) - Number of connections that timed out during establishment\n\n## :balance_scale: Comparison with Prior Arts\n\nUpterm stands as a modern alternative to [Tmate](https://github.com/tmate-io/tmate).\n\nTmate originates as a fork from an older iteration of Tmux, extending terminal sharing capabilities atop Tmux 2.x. However, Tmate has no plans to align with the latest Tmux updates, compelling Tmate \u0026 Tmux users to manage two separate configurations. For instance, the necessity to [bind identical keys twice, conditionally](https://github.com/tmate-io/tmate/issues/108).\n\nOn the flip side, Upterm is architected from the ground up to be an independent solution, not a fork. It embodies the idea of connecting the input \u0026 output of any shell command between a host and its clients, transcending beyond merely `tmux`. This paves the way for securely sharing terminal sessions utilizing containers.\n\nWritten in Go, Upterm is more hack-friendly compared to Tmate, which is crafted in C, akin to Tmux. The seamless compilation of Upterm CLI and server (`uptermd`) into a single binary facilitates swift [deployment of your pairing server](#hammer_and_wrench-deployment) across any cloud environment, devoid of dependencies.\n\n## License\n\n[Apache 2.0](https://github.com/owenthereal/upterm/blob/master/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowenthereal%2Fupterm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fowenthereal%2Fupterm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fowenthereal%2Fupterm/lists"}