This commit is contained in:
2025-06-18 08:41:22 +08:00
parent 66831c2bf0
commit 74747f220f
83 changed files with 0 additions and 46439 deletions

View File

@@ -1,6 +0,0 @@
BasedOnStyle: Google
Language: Cpp
ColumnLimit: 120
IndentWidth: 2
TabWidth: 2
UseTab: Never

1
ttyd/.gitattributes vendored
View File

@@ -1 +0,0 @@
src/html.h linguist-generated

View File

@@ -1,2 +0,0 @@
github: tsl0922
patreon: tsl0922

View File

@@ -1,31 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment:**
- OS: [e.g. macOS 10.15.2]
- Browser: [e.g. Chrome 79.0.3945.130]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,10 +0,0 @@
---
name: Support Request
about: Support request or question
title: ''
labels: question
assignees: ''
---
Describe your problem or question here.

View File

@@ -1,7 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/html"
schedule:
interval: daily
open-pull-requests-limit: 10

View File

@@ -1,39 +0,0 @@
name: backend
on:
push:
paths:
- ".github/workflows/backend.yml"
- "CMakeLists.txt"
- "src/*"
- "scripts/*"
pull_request:
paths:
- ".github/workflows/backend.yml"
- "CMakeLists.txt"
- "src/*"
- "scripts/*"
workflow_call:
jobs:
cross:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
target: [i686, x86_64, arm, armhf, aarch64, mips, mipsel, mips64, mips64el, s390x, win32]
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake build-essential cmake curl file libtool
- name: Cross build (${{ matrix.target }})
env:
BUILD_TARGET: ${{ matrix.target }}
run: ./scripts/cross-build.sh
- uses: actions/upload-artifact@v4
with:
name: ttyd.${{ matrix.target }}
path: build/ttyd*

View File

@@ -1,71 +0,0 @@
name: docker
on:
push:
branches: main
tags: ["*"]
jobs:
build:
runs-on: ubuntu-22.04
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Install packages
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake build-essential cmake curl file libtool
- name: Cross build multi-arch binary
run: |
mkdir dist
for arch in amd64 armv7 arm64 s390x; do
env BUILD_TARGET=$arch ./scripts/cross-build.sh
[ "$arch" = "armv7" ] && arch="arm"
mkdir -p dist/$arch && cp build/ttyd dist/$arch/ttyd
done
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USER }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine docker tags
id: docker_tag
run: |
case $GITHUB_REF in
refs/tags/*)
TAG_NAME=${GITHUB_REF#refs/tags/}
echo "DOCKER_TAG=tsl0922/ttyd:${TAG_NAME}" >> $GITHUB_ENV
echo "ALPINE_TAG=tsl0922/ttyd:${TAG_NAME}-alpine" >> $GITHUB_ENV
;;
*)
echo "DOCKER_TAG=tsl0922/ttyd:latest" >> $GITHUB_ENV
echo "ALPINE_TAG=tsl0922/ttyd:alpine" >> $GITHUB_ENV
esac
- name: build/push docker image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/s390x
push: true
tags: |
${{ env.DOCKER_TAG }}
ghcr.io/${{ env.DOCKER_TAG }}
- name: build/push docker image (alpine)
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.alpine
platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/s390x
push: true
tags: |
${{ env.ALPINE_TAG }}
ghcr.io/${{ env.ALPINE_TAG }}

View File

@@ -1,28 +0,0 @@
name: frontend
on:
push:
paths:
- ".github/workflows/frontend.yml"
- "html/*"
pull_request:
paths:
- ".github/workflows/frontend.yml"
- "html/*"
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Run yarn install, check and build
run: |
corepack enable
corepack prepare yarn@stable --activate
yarn install
yarn run check
yarn run build
working-directory: html

View File

@@ -1,37 +0,0 @@
name: release
on:
push:
tags: ["*"]
jobs:
build:
uses: ./.github/workflows/backend.yml
publish:
needs: [build]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Check version bump
run: |
TAG=$(git describe --tags --match "[0-9]*.[0-9]*.[0-9]*" --abbrev=8)
VERSION=$(grep project CMakeLists.txt| awk '{print $3}')
if [ "$TAG" != "$VERSION" ]; then
echo "=== Version in CMakeLists.txt and git tag does not match!"
echo "=== Git Tag: $TAG, Version: $VERSION"
exit 1
fi
- uses: actions/download-artifact@v4
- run: |
mkdir build
for file in ttyd.*/*; do
target=$(echo $file | awk -F/ '{print $1}')
[[ $file == *.exe ]] && target="$target.exe"
mv $file build/$target
done
pushd build; sha256sum ttyd.* > SHA256SUMS; popd
- uses: ncipollo/release-action@v1
with:
artifacts: build/*
allowUpdates: true
draft: true

54
ttyd/.gitignore vendored
View File

@@ -1,54 +0,0 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
# Cmake files
CMakeCache.txt
CMakeFiles
CMakeScripts
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
build
# Clion files
.idea/
# VSCode files
.vscode/
# Project files
!init.d

View File

@@ -1,569 +0,0 @@
# This is the CMakeCache file.
# For build in directory: /home/init/桌面/ttyd
# It was generated by CMake: /usr/bin/cmake
# You can edit this file to change values found and used by cmake.
# If you do not want to change any of the values, simply exit the editor.
# If you do want to change a value, simply edit, save, and exit the editor.
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
//Path to a program.
CMAKE_ADDR2LINE:FILEPATH=/usr/bin/addr2line
//Path to a program.
CMAKE_AR:FILEPATH=/usr/bin/ar
//Choose the type of build, options are: None Debug Release RelWithDebInfo
// MinSizeRel ...
CMAKE_BUILD_TYPE:STRING=
//Enable/Disable color output during build.
CMAKE_COLOR_MAKEFILE:BOOL=ON
//C compiler
CMAKE_C_COMPILER:FILEPATH=/usr/bin/cc
//A wrapper around 'ar' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_C_COMPILER_AR:FILEPATH=/usr/bin/gcc-ar-13
//A wrapper around 'ranlib' adding the appropriate '--plugin' option
// for the GCC compiler
CMAKE_C_COMPILER_RANLIB:FILEPATH=/usr/bin/gcc-ranlib-13
//Flags used by the C compiler during all build types.
CMAKE_C_FLAGS:STRING=
//Flags used by the C compiler during DEBUG builds.
CMAKE_C_FLAGS_DEBUG:STRING=-g
//Flags used by the C compiler during MINSIZEREL builds.
CMAKE_C_FLAGS_MINSIZEREL:STRING=-Os -DNDEBUG
//Flags used by the C compiler during RELEASE builds.
CMAKE_C_FLAGS_RELEASE:STRING=-O3 -DNDEBUG
//Flags used by the C compiler during RELWITHDEBINFO builds.
CMAKE_C_FLAGS_RELWITHDEBINFO:STRING=-O2 -g -DNDEBUG
//Path to a program.
CMAKE_DLLTOOL:FILEPATH=CMAKE_DLLTOOL-NOTFOUND
//Flags used by the linker during all build types.
CMAKE_EXE_LINKER_FLAGS:STRING=
//Flags used by the linker during DEBUG builds.
CMAKE_EXE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during MINSIZEREL builds.
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during RELEASE builds.
CMAKE_EXE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during RELWITHDEBINFO builds.
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Enable/Disable output of compile commands during generation.
CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=
//Value Computed by CMake.
CMAKE_FIND_PACKAGE_REDIRECTS_DIR:STATIC=/home/init/桌面/ttyd/CMakeFiles/pkgRedirects
//User executables (bin)
CMAKE_INSTALL_BINDIR:PATH=bin
//Read-only architecture-independent data (DATAROOTDIR)
CMAKE_INSTALL_DATADIR:PATH=
//Read-only architecture-independent data root (share)
CMAKE_INSTALL_DATAROOTDIR:PATH=share
//Documentation root (DATAROOTDIR/doc/PROJECT_NAME)
CMAKE_INSTALL_DOCDIR:PATH=
//C header files (include)
CMAKE_INSTALL_INCLUDEDIR:PATH=include
//Info documentation (DATAROOTDIR/info)
CMAKE_INSTALL_INFODIR:PATH=
//Object code libraries (lib)
CMAKE_INSTALL_LIBDIR:PATH=lib
//Program executables (libexec)
CMAKE_INSTALL_LIBEXECDIR:PATH=libexec
//Locale-dependent data (DATAROOTDIR/locale)
CMAKE_INSTALL_LOCALEDIR:PATH=
//Modifiable single-machine data (var)
CMAKE_INSTALL_LOCALSTATEDIR:PATH=var
//Man documentation (DATAROOTDIR/man)
CMAKE_INSTALL_MANDIR:PATH=
//C header files for non-gcc (/usr/include)
CMAKE_INSTALL_OLDINCLUDEDIR:PATH=/usr/include
//Install path prefix, prepended onto install directories.
CMAKE_INSTALL_PREFIX:PATH=/usr/local
//Run-time variable data (LOCALSTATEDIR/run)
CMAKE_INSTALL_RUNSTATEDIR:PATH=
//System admin executables (sbin)
CMAKE_INSTALL_SBINDIR:PATH=sbin
//Modifiable architecture-independent data (com)
CMAKE_INSTALL_SHAREDSTATEDIR:PATH=com
//Read-only single-machine data (etc)
CMAKE_INSTALL_SYSCONFDIR:PATH=etc
//Path to a program.
CMAKE_LINKER:FILEPATH=/usr/bin/ld
//Path to a program.
CMAKE_MAKE_PROGRAM:FILEPATH=/usr/bin/gmake
//Flags used by the linker during the creation of modules during
// all build types.
CMAKE_MODULE_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of modules during
// DEBUG builds.
CMAKE_MODULE_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of modules during
// MINSIZEREL builds.
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of modules during
// RELEASE builds.
CMAKE_MODULE_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of modules during
// RELWITHDEBINFO builds.
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Path to a program.
CMAKE_NM:FILEPATH=/usr/bin/nm
//Path to a program.
CMAKE_OBJCOPY:FILEPATH=/usr/bin/objcopy
//Path to a program.
CMAKE_OBJDUMP:FILEPATH=/usr/bin/objdump
//Value Computed by CMake
CMAKE_PROJECT_DESCRIPTION:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_HOMEPAGE_URL:STATIC=
//Value Computed by CMake
CMAKE_PROJECT_NAME:STATIC=ttyd
//Value Computed by CMake
CMAKE_PROJECT_VERSION:STATIC=1.7.7
//Value Computed by CMake
CMAKE_PROJECT_VERSION_MAJOR:STATIC=1
//Value Computed by CMake
CMAKE_PROJECT_VERSION_MINOR:STATIC=7
//Value Computed by CMake
CMAKE_PROJECT_VERSION_PATCH:STATIC=7
//Value Computed by CMake
CMAKE_PROJECT_VERSION_TWEAK:STATIC=
//Path to a program.
CMAKE_RANLIB:FILEPATH=/usr/bin/ranlib
//Path to a program.
CMAKE_READELF:FILEPATH=/usr/bin/readelf
//Flags used by the linker during the creation of shared libraries
// during all build types.
CMAKE_SHARED_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of shared libraries
// during DEBUG builds.
CMAKE_SHARED_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of shared libraries
// during MINSIZEREL builds.
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELEASE builds.
CMAKE_SHARED_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of shared libraries
// during RELWITHDEBINFO builds.
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//If set, runtime paths are not added when installing shared libraries,
// but are added when building.
CMAKE_SKIP_INSTALL_RPATH:BOOL=NO
//If set, runtime paths are not added when using shared libraries.
CMAKE_SKIP_RPATH:BOOL=NO
//Flags used by the linker during the creation of static libraries
// during all build types.
CMAKE_STATIC_LINKER_FLAGS:STRING=
//Flags used by the linker during the creation of static libraries
// during DEBUG builds.
CMAKE_STATIC_LINKER_FLAGS_DEBUG:STRING=
//Flags used by the linker during the creation of static libraries
// during MINSIZEREL builds.
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL:STRING=
//Flags used by the linker during the creation of static libraries
// during RELEASE builds.
CMAKE_STATIC_LINKER_FLAGS_RELEASE:STRING=
//Flags used by the linker during the creation of static libraries
// during RELWITHDEBINFO builds.
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO:STRING=
//Path to a program.
CMAKE_STRIP:FILEPATH=/usr/bin/strip
//Path to a program.
CMAKE_TAPI:FILEPATH=CMAKE_TAPI-NOTFOUND
//If this value is on, makefiles will be generated without the
// .SILENT directive, and all commands will be echoed to the console
// during the make. This is useful for debugging only. With Visual
// Studio IDE projects all commands are done without /nologo.
CMAKE_VERBOSE_MAKEFILE:BOOL=FALSE
//Git command line client
GIT_EXECUTABLE:FILEPATH=/usr/bin/git
//Path to a file.
JSON-C_INCLUDE_DIR:PATH=/usr/include/json-c
//Path to a library.
JSON-C_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libjson-c.so
//Path to a library.
LIBUTIL:FILEPATH=/usr/lib/x86_64-linux-gnu/libutil.a
//Path to a file.
LIBUV_INCLUDE_DIR:PATH=/usr/include
//Path to a library.
LIBUV_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libuv.so
//The directory containing a CMake configuration file for Libwebsockets.
Libwebsockets_DIR:PATH=/usr/lib/x86_64-linux-gnu/cmake/libwebsockets
//Path to a library.
OPENSSL_CRYPTO_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libcrypto.so
//Path to a file.
OPENSSL_INCLUDE_DIR:PATH=/usr/include
//Path to a library.
OPENSSL_SSL_LIBRARY:FILEPATH=/usr/lib/x86_64-linux-gnu/libssl.so
//Arguments to supply to pkg-config
PKG_CONFIG_ARGN:STRING=
//pkg-config executable
PKG_CONFIG_EXECUTABLE:FILEPATH=/usr/bin/pkg-config
//Path to a file.
ZLIB_INCLUDE_DIR:PATH=/usr/include
//Path to a library.
ZLIB_LIBRARY_DEBUG:FILEPATH=ZLIB_LIBRARY_DEBUG-NOTFOUND
//Path to a library.
ZLIB_LIBRARY_RELEASE:FILEPATH=/usr/lib/x86_64-linux-gnu/libz.so
//Path to a library.
pkgcfg_lib__OPENSSL_crypto:FILEPATH=/usr/lib/x86_64-linux-gnu/libcrypto.so
//Path to a library.
pkgcfg_lib__OPENSSL_ssl:FILEPATH=/usr/lib/x86_64-linux-gnu/libssl.so
//Value Computed by CMake
ttyd_BINARY_DIR:STATIC=/home/init/桌面/ttyd
//Value Computed by CMake
ttyd_IS_TOP_LEVEL:STATIC=ON
//Value Computed by CMake
ttyd_SOURCE_DIR:STATIC=/home/init/桌面/ttyd
########################
# INTERNAL cache entries
########################
//ADVANCED property for variable: CMAKE_ADDR2LINE
CMAKE_ADDR2LINE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_AR
CMAKE_AR-ADVANCED:INTERNAL=1
//This is the directory where this CMakeCache.txt was created
CMAKE_CACHEFILE_DIR:INTERNAL=/home/init/桌面/ttyd
//Major version of cmake used to create the current loaded cache
CMAKE_CACHE_MAJOR_VERSION:INTERNAL=3
//Minor version of cmake used to create the current loaded cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=28
//Patch version of cmake used to create the current loaded cache
CMAKE_CACHE_PATCH_VERSION:INTERNAL=3
//ADVANCED property for variable: CMAKE_COLOR_MAKEFILE
CMAKE_COLOR_MAKEFILE-ADVANCED:INTERNAL=1
//Path to CMake executable.
CMAKE_COMMAND:INTERNAL=/usr/bin/cmake
//Path to cpack program executable.
CMAKE_CPACK_COMMAND:INTERNAL=/usr/bin/cpack
//Path to ctest program executable.
CMAKE_CTEST_COMMAND:INTERNAL=/usr/bin/ctest
//ADVANCED property for variable: CMAKE_C_COMPILER
CMAKE_C_COMPILER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_AR
CMAKE_C_COMPILER_AR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_COMPILER_RANLIB
CMAKE_C_COMPILER_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS
CMAKE_C_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_MINSIZEREL
CMAKE_C_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELEASE
CMAKE_C_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_C_FLAGS_RELWITHDEBINFO
CMAKE_C_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_DLLTOOL
CMAKE_DLLTOOL-ADVANCED:INTERNAL=1
//Executable file format
CMAKE_EXECUTABLE_FORMAT:INTERNAL=ELF
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS
CMAKE_EXE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_DEBUG
CMAKE_EXE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_MINSIZEREL
CMAKE_EXE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELEASE
CMAKE_EXE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_EXPORT_COMPILE_COMMANDS
CMAKE_EXPORT_COMPILE_COMMANDS-ADVANCED:INTERNAL=1
//Name of external makefile project generator.
CMAKE_EXTRA_GENERATOR:INTERNAL=
//Name of generator.
CMAKE_GENERATOR:INTERNAL=Unix Makefiles
//Generator instance identifier.
CMAKE_GENERATOR_INSTANCE:INTERNAL=
//Name of generator platform.
CMAKE_GENERATOR_PLATFORM:INTERNAL=
//Name of generator toolset.
CMAKE_GENERATOR_TOOLSET:INTERNAL=
//Source directory with the top level CMakeLists.txt file for this
// project
CMAKE_HOME_DIRECTORY:INTERNAL=/home/init/桌面/ttyd
//ADVANCED property for variable: CMAKE_INSTALL_BINDIR
CMAKE_INSTALL_BINDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_DATADIR
CMAKE_INSTALL_DATADIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_DATAROOTDIR
CMAKE_INSTALL_DATAROOTDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_DOCDIR
CMAKE_INSTALL_DOCDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_INCLUDEDIR
CMAKE_INSTALL_INCLUDEDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_INFODIR
CMAKE_INSTALL_INFODIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_LIBDIR
CMAKE_INSTALL_LIBDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_LIBEXECDIR
CMAKE_INSTALL_LIBEXECDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_LOCALEDIR
CMAKE_INSTALL_LOCALEDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_LOCALSTATEDIR
CMAKE_INSTALL_LOCALSTATEDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_MANDIR
CMAKE_INSTALL_MANDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_OLDINCLUDEDIR
CMAKE_INSTALL_OLDINCLUDEDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_RUNSTATEDIR
CMAKE_INSTALL_RUNSTATEDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_SBINDIR
CMAKE_INSTALL_SBINDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_SHAREDSTATEDIR
CMAKE_INSTALL_SHAREDSTATEDIR-ADVANCED:INTERNAL=1
//Install .so files without execute permission.
CMAKE_INSTALL_SO_NO_EXE:INTERNAL=1
//ADVANCED property for variable: CMAKE_INSTALL_SYSCONFDIR
CMAKE_INSTALL_SYSCONFDIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_LINKER
CMAKE_LINKER-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MAKE_PROGRAM
CMAKE_MAKE_PROGRAM-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS
CMAKE_MODULE_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_DEBUG
CMAKE_MODULE_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL
CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELEASE
CMAKE_MODULE_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_NM
CMAKE_NM-ADVANCED:INTERNAL=1
//number of local generators
CMAKE_NUMBER_OF_MAKEFILES:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJCOPY
CMAKE_OBJCOPY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_OBJDUMP
CMAKE_OBJDUMP-ADVANCED:INTERNAL=1
//Platform information initialized
CMAKE_PLATFORM_INFO_INITIALIZED:INTERNAL=1
//ADVANCED property for variable: CMAKE_RANLIB
CMAKE_RANLIB-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_READELF
CMAKE_READELF-ADVANCED:INTERNAL=1
//Path to CMake installation.
CMAKE_ROOT:INTERNAL=/usr/share/cmake-3.28
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS
CMAKE_SHARED_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_DEBUG
CMAKE_SHARED_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL
CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELEASE
CMAKE_SHARED_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_INSTALL_RPATH
CMAKE_SKIP_INSTALL_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_SKIP_RPATH
CMAKE_SKIP_RPATH-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS
CMAKE_STATIC_LINKER_FLAGS-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_DEBUG
CMAKE_STATIC_LINKER_FLAGS_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL
CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELEASE
CMAKE_STATIC_LINKER_FLAGS_RELEASE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO
CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_STRIP
CMAKE_STRIP-ADVANCED:INTERNAL=1
//ADVANCED property for variable: CMAKE_TAPI
CMAKE_TAPI-ADVANCED:INTERNAL=1
//uname command
CMAKE_UNAME:INTERNAL=/usr/bin/uname
//ADVANCED property for variable: CMAKE_VERBOSE_MAKEFILE
CMAKE_VERBOSE_MAKEFILE-ADVANCED:INTERNAL=1
//Details about finding Git
FIND_PACKAGE_MESSAGE_DETAILS_Git:INTERNAL=[/usr/bin/git][v2.43.0()]
//Details about finding JSON-C
FIND_PACKAGE_MESSAGE_DETAILS_JSON-C:INTERNAL=[/usr/lib/x86_64-linux-gnu/libjson-c.so][/usr/include/json-c][v()]
//Details about finding LIBUV
FIND_PACKAGE_MESSAGE_DETAILS_LIBUV:INTERNAL=[/usr/lib/x86_64-linux-gnu/libuv.so][/usr/include][v()]
//Details about finding OpenSSL
FIND_PACKAGE_MESSAGE_DETAILS_OpenSSL:INTERNAL=[/usr/lib/x86_64-linux-gnu/libcrypto.so][/usr/include][c ][v3.0.13()]
//Details about finding ZLIB
FIND_PACKAGE_MESSAGE_DETAILS_ZLIB:INTERNAL=[/usr/lib/x86_64-linux-gnu/libz.so][/usr/include][c ][v1.3()]
//ADVANCED property for variable: GIT_EXECUTABLE
GIT_EXECUTABLE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: JSON-C_INCLUDE_DIR
JSON-C_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: JSON-C_LIBRARY
JSON-C_LIBRARY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: LIBUV_INCLUDE_DIR
LIBUV_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: LIBUV_LIBRARY
LIBUV_LIBRARY-ADVANCED:INTERNAL=1
//Have symbol LWS_WITH_MBEDTLS
LWS_MBEDTLS_ENABLED:INTERNAL=
//Have symbol LWS_OPENSSL_SUPPORT
LWS_OPENSSL_ENABLED:INTERNAL=1
//Have symbol LWS_WITH_LIBUV
LWS_WITH_LIBUV:INTERNAL=1
//ADVANCED property for variable: OPENSSL_CRYPTO_LIBRARY
OPENSSL_CRYPTO_LIBRARY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: OPENSSL_INCLUDE_DIR
OPENSSL_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: OPENSSL_SSL_LIBRARY
OPENSSL_SSL_LIBRARY-ADVANCED:INTERNAL=1
//ADVANCED property for variable: PKG_CONFIG_ARGN
PKG_CONFIG_ARGN-ADVANCED:INTERNAL=1
//ADVANCED property for variable: PKG_CONFIG_EXECUTABLE
PKG_CONFIG_EXECUTABLE-ADVANCED:INTERNAL=1
//ADVANCED property for variable: ZLIB_INCLUDE_DIR
ZLIB_INCLUDE_DIR-ADVANCED:INTERNAL=1
//ADVANCED property for variable: ZLIB_LIBRARY_DEBUG
ZLIB_LIBRARY_DEBUG-ADVANCED:INTERNAL=1
//ADVANCED property for variable: ZLIB_LIBRARY_RELEASE
ZLIB_LIBRARY_RELEASE-ADVANCED:INTERNAL=1
//linker supports push/pop state
_CMAKE_LINKER_PUSHPOP_STATE_SUPPORTED:INTERNAL=TRUE
//CMAKE_INSTALL_PREFIX during last run
_GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX:INTERNAL=/usr/local
_OPENSSL_CFLAGS:INTERNAL=-I/usr/include
_OPENSSL_CFLAGS_I:INTERNAL=
_OPENSSL_CFLAGS_OTHER:INTERNAL=
_OPENSSL_FOUND:INTERNAL=1
_OPENSSL_INCLUDEDIR:INTERNAL=/usr/include
_OPENSSL_INCLUDE_DIRS:INTERNAL=/usr/include
_OPENSSL_LDFLAGS:INTERNAL=-L/usr/lib/x86_64-linux-gnu;-lssl;-lcrypto
_OPENSSL_LDFLAGS_OTHER:INTERNAL=
_OPENSSL_LIBDIR:INTERNAL=/usr/lib/x86_64-linux-gnu
_OPENSSL_LIBRARIES:INTERNAL=ssl;crypto
_OPENSSL_LIBRARY_DIRS:INTERNAL=/usr/lib/x86_64-linux-gnu
_OPENSSL_LIBS:INTERNAL=
_OPENSSL_LIBS_L:INTERNAL=
_OPENSSL_LIBS_OTHER:INTERNAL=
_OPENSSL_LIBS_PATHS:INTERNAL=
_OPENSSL_MODULE_NAME:INTERNAL=openssl
_OPENSSL_PREFIX:INTERNAL=/usr
_OPENSSL_STATIC_CFLAGS:INTERNAL=-I/usr/include
_OPENSSL_STATIC_CFLAGS_I:INTERNAL=
_OPENSSL_STATIC_CFLAGS_OTHER:INTERNAL=
_OPENSSL_STATIC_INCLUDE_DIRS:INTERNAL=/usr/include
_OPENSSL_STATIC_LDFLAGS:INTERNAL=-L/usr/lib/x86_64-linux-gnu;-lssl;-L/usr/lib/x86_64-linux-gnu;-ldl;-pthread;-lcrypto;-ldl;-pthread
_OPENSSL_STATIC_LDFLAGS_OTHER:INTERNAL=-pthread;-pthread
_OPENSSL_STATIC_LIBDIR:INTERNAL=
_OPENSSL_STATIC_LIBRARIES:INTERNAL=ssl;dl;crypto;dl
_OPENSSL_STATIC_LIBRARY_DIRS:INTERNAL=/usr/lib/x86_64-linux-gnu;/usr/lib/x86_64-linux-gnu
_OPENSSL_STATIC_LIBS:INTERNAL=
_OPENSSL_STATIC_LIBS_L:INTERNAL=
_OPENSSL_STATIC_LIBS_OTHER:INTERNAL=
_OPENSSL_STATIC_LIBS_PATHS:INTERNAL=
_OPENSSL_VERSION:INTERNAL=3.0.13
_OPENSSL_openssl_INCLUDEDIR:INTERNAL=
_OPENSSL_openssl_LIBDIR:INTERNAL=
_OPENSSL_openssl_PREFIX:INTERNAL=
_OPENSSL_openssl_VERSION:INTERNAL=
__pkg_config_arguments__OPENSSL:INTERNAL=QUIET;openssl
__pkg_config_checked__OPENSSL:INTERNAL=1
//ADVANCED property for variable: pkgcfg_lib__OPENSSL_crypto
pkgcfg_lib__OPENSSL_crypto-ADVANCED:INTERNAL=1
//ADVANCED property for variable: pkgcfg_lib__OPENSSL_ssl
pkgcfg_lib__OPENSSL_ssl-ADVANCED:INTERNAL=1
prefix_result:INTERNAL=/usr/lib/x86_64-linux-gnu

View File

@@ -1,16 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# Relative path conversion top directories.
set(CMAKE_RELATIVE_PATH_TOP_SOURCE "/home/init/桌面/ttyd")
set(CMAKE_RELATIVE_PATH_TOP_BINARY "/home/init/桌面/ttyd")
# Force unix paths in dependencies.
set(CMAKE_FORCE_UNIX_PATHS 1)
# The C and CXX include file regular expressions for this directory.
set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$")
set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$")
set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN})
set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN})

View File

@@ -1,62 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# The generator used is:
set(CMAKE_DEPENDS_GENERATOR "Unix Makefiles")
# The top level Makefile was generated from the following files:
set(CMAKE_MAKEFILE_DEPENDS
"CMakeCache.txt"
"CMakeFiles/3.28.3/CMakeCCompiler.cmake"
"CMakeFiles/3.28.3/CMakeSystem.cmake"
"CMakeLists.txt"
"cmake/GetGitVersion.cmake"
"/usr/lib/x86_64-linux-gnu/cmake/libwebsockets/LibwebsocketsTargets-none.cmake"
"/usr/lib/x86_64-linux-gnu/cmake/libwebsockets/LibwebsocketsTargets.cmake"
"/usr/lib/x86_64-linux-gnu/cmake/libwebsockets/LwsCheckRequirements.cmake"
"/usr/lib/x86_64-linux-gnu/cmake/libwebsockets/libwebsockets-config-version.cmake"
"/usr/lib/x86_64-linux-gnu/cmake/libwebsockets/libwebsockets-config.cmake"
"/usr/share/cmake-3.28/Modules/CMakeCInformation.cmake"
"/usr/share/cmake-3.28/Modules/CMakeCommonLanguageInclude.cmake"
"/usr/share/cmake-3.28/Modules/CMakeGenericSystem.cmake"
"/usr/share/cmake-3.28/Modules/CMakeInitializeConfigs.cmake"
"/usr/share/cmake-3.28/Modules/CMakeLanguageInformation.cmake"
"/usr/share/cmake-3.28/Modules/CMakeSystemSpecificInformation.cmake"
"/usr/share/cmake-3.28/Modules/CMakeSystemSpecificInitialize.cmake"
"/usr/share/cmake-3.28/Modules/CheckCSourceCompiles.cmake"
"/usr/share/cmake-3.28/Modules/CheckIncludeFile.cmake"
"/usr/share/cmake-3.28/Modules/CheckSymbolExists.cmake"
"/usr/share/cmake-3.28/Modules/Compiler/CMakeCommonCompilerMacros.cmake"
"/usr/share/cmake-3.28/Modules/Compiler/GNU-C.cmake"
"/usr/share/cmake-3.28/Modules/Compiler/GNU.cmake"
"/usr/share/cmake-3.28/Modules/FindGit.cmake"
"/usr/share/cmake-3.28/Modules/FindOpenSSL.cmake"
"/usr/share/cmake-3.28/Modules/FindPackageHandleStandardArgs.cmake"
"/usr/share/cmake-3.28/Modules/FindPackageMessage.cmake"
"/usr/share/cmake-3.28/Modules/FindPkgConfig.cmake"
"/usr/share/cmake-3.28/Modules/FindZLIB.cmake"
"/usr/share/cmake-3.28/Modules/GNUInstallDirs.cmake"
"/usr/share/cmake-3.28/Modules/Internal/CheckSourceCompiles.cmake"
"/usr/share/cmake-3.28/Modules/Platform/Linux-GNU-C.cmake"
"/usr/share/cmake-3.28/Modules/Platform/Linux-GNU.cmake"
"/usr/share/cmake-3.28/Modules/Platform/Linux-Initialize.cmake"
"/usr/share/cmake-3.28/Modules/Platform/Linux.cmake"
"/usr/share/cmake-3.28/Modules/Platform/UnixPaths.cmake"
"/usr/share/cmake-3.28/Modules/SelectLibraryConfigurations.cmake"
)
# The corresponding makefile is:
set(CMAKE_MAKEFILE_OUTPUTS
"Makefile"
"CMakeFiles/cmake.check_cache"
)
# Byproducts of CMake generate step:
set(CMAKE_MAKEFILE_PRODUCTS
"CMakeFiles/CMakeDirectoryInformation.cmake"
)
# Dependency information for all targets:
set(CMAKE_DEPEND_INFO_FILES
"CMakeFiles/ttyd.dir/DependInfo.cmake"
)

View File

@@ -1,112 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# Default target executed when no arguments are given to make.
default_target: all
.PHONY : default_target
#=============================================================================
# Special targets provided by cmake.
# Disable implicit rules so canonical targets will work.
.SUFFIXES:
# Disable VCS-based implicit rules.
% : %,v
# Disable VCS-based implicit rules.
% : RCS/%
# Disable VCS-based implicit rules.
% : RCS/%,v
# Disable VCS-based implicit rules.
% : SCCS/s.%
# Disable VCS-based implicit rules.
% : s.%
.SUFFIXES: .hpux_make_needs_suffix_list
# Command-line flag to silence nested $(MAKE).
$(VERBOSE)MAKESILENT = -s
#Suppress display of executed commands.
$(VERBOSE).SILENT:
# A target that is always out of date.
cmake_force:
.PHONY : cmake_force
#=============================================================================
# Set environment variables for the build.
# The shell in which to execute make rules.
SHELL = /bin/sh
# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake
# The command to remove a file.
RM = /usr/bin/cmake -E rm -f
# Escaping for special characters.
EQUALS = =
# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/init/桌面/ttyd
# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/init/桌面/ttyd
#=============================================================================
# Directory level rules for the build root directory
# The main recursive "all" target.
all: CMakeFiles/ttyd.dir/all
.PHONY : all
# The main recursive "preinstall" target.
preinstall:
.PHONY : preinstall
# The main recursive "clean" target.
clean: CMakeFiles/ttyd.dir/clean
.PHONY : clean
#=============================================================================
# Target rules for target CMakeFiles/ttyd.dir
# All Build rule for target.
CMakeFiles/ttyd.dir/all:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/depend
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/build
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=1,2,3,4,5,6 "Built target ttyd"
.PHONY : CMakeFiles/ttyd.dir/all
# Build rule for subdir invocation for target.
CMakeFiles/ttyd.dir/rule: cmake_check_build_system
$(CMAKE_COMMAND) -E cmake_progress_start /home/init/桌面/ttyd/CMakeFiles 6
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 CMakeFiles/ttyd.dir/all
$(CMAKE_COMMAND) -E cmake_progress_start /home/init/桌面/ttyd/CMakeFiles 0
.PHONY : CMakeFiles/ttyd.dir/rule
# Convenience name for target.
ttyd: CMakeFiles/ttyd.dir/rule
.PHONY : ttyd
# clean rule for target.
CMakeFiles/ttyd.dir/clean:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/clean
.PHONY : CMakeFiles/ttyd.dir/clean
#=============================================================================
# Special targets to cleanup operation of make.
# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
$(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
.PHONY : cmake_check_build_system

View File

@@ -1,7 +0,0 @@
/home/init/桌面/ttyd/CMakeFiles/ttyd.dir
/home/init/桌面/ttyd/CMakeFiles/edit_cache.dir
/home/init/桌面/ttyd/CMakeFiles/rebuild_cache.dir
/home/init/桌面/ttyd/CMakeFiles/list_install_components.dir
/home/init/桌面/ttyd/CMakeFiles/install.dir
/home/init/桌面/ttyd/CMakeFiles/install/local.dir
/home/init/桌面/ttyd/CMakeFiles/install/strip.dir

View File

@@ -1 +0,0 @@
# This file is generated by cmake for dependency checking of the CMakeCache.txt file

View File

@@ -1 +0,0 @@
6

View File

@@ -1,27 +0,0 @@
# Consider dependencies only in project.
set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF)
# The set of languages for which implicit dependencies are needed:
set(CMAKE_DEPENDS_LANGUAGES
)
# The set of dependency files which are needed:
set(CMAKE_DEPENDS_DEPENDENCY_FILES
"/home/init/桌面/ttyd/src/http.c" "CMakeFiles/ttyd.dir/src/http.c.o" "gcc" "CMakeFiles/ttyd.dir/src/http.c.o.d"
"/home/init/桌面/ttyd/src/protocol.c" "CMakeFiles/ttyd.dir/src/protocol.c.o" "gcc" "CMakeFiles/ttyd.dir/src/protocol.c.o.d"
"/home/init/桌面/ttyd/src/pty.c" "CMakeFiles/ttyd.dir/src/pty.c.o" "gcc" "CMakeFiles/ttyd.dir/src/pty.c.o.d"
"/home/init/桌面/ttyd/src/server.c" "CMakeFiles/ttyd.dir/src/server.c.o" "gcc" "CMakeFiles/ttyd.dir/src/server.c.o.d"
"/home/init/桌面/ttyd/src/utils.c" "CMakeFiles/ttyd.dir/src/utils.c.o" "gcc" "CMakeFiles/ttyd.dir/src/utils.c.o.d"
)
# Targets to which this target links which contain Fortran sources.
set(CMAKE_Fortran_TARGET_LINKED_INFO_FILES
)
# Targets to which this target links which contain Fortran sources.
set(CMAKE_Fortran_TARGET_FORWARD_LINKED_INFO_FILES
)
# Fortran module output directory.
set(CMAKE_Fortran_TARGET_MODULE_DIR "")

View File

@@ -1,183 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# Delete rule output on recipe failure.
.DELETE_ON_ERROR:
#=============================================================================
# Special targets provided by cmake.
# Disable implicit rules so canonical targets will work.
.SUFFIXES:
# Disable VCS-based implicit rules.
% : %,v
# Disable VCS-based implicit rules.
% : RCS/%
# Disable VCS-based implicit rules.
% : RCS/%,v
# Disable VCS-based implicit rules.
% : SCCS/s.%
# Disable VCS-based implicit rules.
% : s.%
.SUFFIXES: .hpux_make_needs_suffix_list
# Command-line flag to silence nested $(MAKE).
$(VERBOSE)MAKESILENT = -s
#Suppress display of executed commands.
$(VERBOSE).SILENT:
# A target that is always out of date.
cmake_force:
.PHONY : cmake_force
#=============================================================================
# Set environment variables for the build.
# The shell in which to execute make rules.
SHELL = /bin/sh
# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake
# The command to remove a file.
RM = /usr/bin/cmake -E rm -f
# Escaping for special characters.
EQUALS = =
# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/init/桌面/ttyd
# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/init/桌面/ttyd
# Include any dependencies generated for this target.
include CMakeFiles/ttyd.dir/depend.make
# Include any dependencies generated by the compiler for this target.
include CMakeFiles/ttyd.dir/compiler_depend.make
# Include the progress variables for this target.
include CMakeFiles/ttyd.dir/progress.make
# Include the compile flags for this target's objects.
include CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/utils.c.o: CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/utils.c.o: src/utils.c
CMakeFiles/ttyd.dir/src/utils.c.o: CMakeFiles/ttyd.dir/compiler_depend.ts
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building C object CMakeFiles/ttyd.dir/src/utils.c.o"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/ttyd.dir/src/utils.c.o -MF CMakeFiles/ttyd.dir/src/utils.c.o.d -o CMakeFiles/ttyd.dir/src/utils.c.o -c /home/init/桌面/ttyd/src/utils.c
CMakeFiles/ttyd.dir/src/utils.c.i: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/ttyd.dir/src/utils.c.i"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/init/桌面/ttyd/src/utils.c > CMakeFiles/ttyd.dir/src/utils.c.i
CMakeFiles/ttyd.dir/src/utils.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/ttyd.dir/src/utils.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/init/桌面/ttyd/src/utils.c -o CMakeFiles/ttyd.dir/src/utils.c.s
CMakeFiles/ttyd.dir/src/pty.c.o: CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/pty.c.o: src/pty.c
CMakeFiles/ttyd.dir/src/pty.c.o: CMakeFiles/ttyd.dir/compiler_depend.ts
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Building C object CMakeFiles/ttyd.dir/src/pty.c.o"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/ttyd.dir/src/pty.c.o -MF CMakeFiles/ttyd.dir/src/pty.c.o.d -o CMakeFiles/ttyd.dir/src/pty.c.o -c /home/init/桌面/ttyd/src/pty.c
CMakeFiles/ttyd.dir/src/pty.c.i: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/ttyd.dir/src/pty.c.i"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/init/桌面/ttyd/src/pty.c > CMakeFiles/ttyd.dir/src/pty.c.i
CMakeFiles/ttyd.dir/src/pty.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/ttyd.dir/src/pty.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/init/桌面/ttyd/src/pty.c -o CMakeFiles/ttyd.dir/src/pty.c.s
CMakeFiles/ttyd.dir/src/protocol.c.o: CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/protocol.c.o: src/protocol.c
CMakeFiles/ttyd.dir/src/protocol.c.o: CMakeFiles/ttyd.dir/compiler_depend.ts
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Building C object CMakeFiles/ttyd.dir/src/protocol.c.o"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/ttyd.dir/src/protocol.c.o -MF CMakeFiles/ttyd.dir/src/protocol.c.o.d -o CMakeFiles/ttyd.dir/src/protocol.c.o -c /home/init/桌面/ttyd/src/protocol.c
CMakeFiles/ttyd.dir/src/protocol.c.i: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/ttyd.dir/src/protocol.c.i"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/init/桌面/ttyd/src/protocol.c > CMakeFiles/ttyd.dir/src/protocol.c.i
CMakeFiles/ttyd.dir/src/protocol.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/ttyd.dir/src/protocol.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/init/桌面/ttyd/src/protocol.c -o CMakeFiles/ttyd.dir/src/protocol.c.s
CMakeFiles/ttyd.dir/src/http.c.o: CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/http.c.o: src/http.c
CMakeFiles/ttyd.dir/src/http.c.o: CMakeFiles/ttyd.dir/compiler_depend.ts
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_4) "Building C object CMakeFiles/ttyd.dir/src/http.c.o"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/ttyd.dir/src/http.c.o -MF CMakeFiles/ttyd.dir/src/http.c.o.d -o CMakeFiles/ttyd.dir/src/http.c.o -c /home/init/桌面/ttyd/src/http.c
CMakeFiles/ttyd.dir/src/http.c.i: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/ttyd.dir/src/http.c.i"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/init/桌面/ttyd/src/http.c > CMakeFiles/ttyd.dir/src/http.c.i
CMakeFiles/ttyd.dir/src/http.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/ttyd.dir/src/http.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/init/桌面/ttyd/src/http.c -o CMakeFiles/ttyd.dir/src/http.c.s
CMakeFiles/ttyd.dir/src/server.c.o: CMakeFiles/ttyd.dir/flags.make
CMakeFiles/ttyd.dir/src/server.c.o: src/server.c
CMakeFiles/ttyd.dir/src/server.c.o: CMakeFiles/ttyd.dir/compiler_depend.ts
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_5) "Building C object CMakeFiles/ttyd.dir/src/server.c.o"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -MD -MT CMakeFiles/ttyd.dir/src/server.c.o -MF CMakeFiles/ttyd.dir/src/server.c.o.d -o CMakeFiles/ttyd.dir/src/server.c.o -c /home/init/桌面/ttyd/src/server.c
CMakeFiles/ttyd.dir/src/server.c.i: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Preprocessing C source to CMakeFiles/ttyd.dir/src/server.c.i"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/init/桌面/ttyd/src/server.c > CMakeFiles/ttyd.dir/src/server.c.i
CMakeFiles/ttyd.dir/src/server.c.s: cmake_force
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green "Compiling C source to assembly CMakeFiles/ttyd.dir/src/server.c.s"
/usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/init/桌面/ttyd/src/server.c -o CMakeFiles/ttyd.dir/src/server.c.s
# Object files for target ttyd
ttyd_OBJECTS = \
"CMakeFiles/ttyd.dir/src/utils.c.o" \
"CMakeFiles/ttyd.dir/src/pty.c.o" \
"CMakeFiles/ttyd.dir/src/protocol.c.o" \
"CMakeFiles/ttyd.dir/src/http.c.o" \
"CMakeFiles/ttyd.dir/src/server.c.o"
# External object files for target ttyd
ttyd_EXTERNAL_OBJECTS =
ttyd: CMakeFiles/ttyd.dir/src/utils.c.o
ttyd: CMakeFiles/ttyd.dir/src/pty.c.o
ttyd: CMakeFiles/ttyd.dir/src/protocol.c.o
ttyd: CMakeFiles/ttyd.dir/src/http.c.o
ttyd: CMakeFiles/ttyd.dir/src/server.c.o
ttyd: CMakeFiles/ttyd.dir/build.make
ttyd: /usr/lib/x86_64-linux-gnu/libz.so
ttyd: /usr/lib/x86_64-linux-gnu/libwebsockets.a
ttyd: /usr/lib/x86_64-linux-gnu/libwebsockets.so.19
ttyd: /usr/lib/x86_64-linux-gnu/libjson-c.so
ttyd: /usr/lib/x86_64-linux-gnu/libuv.so
ttyd: /usr/lib/x86_64-linux-gnu/libssl.so
ttyd: /usr/lib/x86_64-linux-gnu/libcrypto.so
ttyd: /usr/lib/x86_64-linux-gnu/libcap.so
ttyd: /usr/lib/x86_64-linux-gnu/libz.so
ttyd: CMakeFiles/ttyd.dir/link.txt
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --green --bold --progress-dir=/home/init/桌面/ttyd/CMakeFiles --progress-num=$(CMAKE_PROGRESS_6) "Linking C executable ttyd"
$(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/ttyd.dir/link.txt --verbose=$(VERBOSE)
# Rule to build all files generated by this target.
CMakeFiles/ttyd.dir/build: ttyd
.PHONY : CMakeFiles/ttyd.dir/build
CMakeFiles/ttyd.dir/clean:
$(CMAKE_COMMAND) -P CMakeFiles/ttyd.dir/cmake_clean.cmake
.PHONY : CMakeFiles/ttyd.dir/clean
CMakeFiles/ttyd.dir/depend:
cd /home/init/桌面/ttyd && $(CMAKE_COMMAND) -E cmake_depends "Unix Makefiles" /home/init/桌面/ttyd /home/init/桌面/ttyd /home/init/桌面/ttyd /home/init/桌面/ttyd /home/init/桌面/ttyd/CMakeFiles/ttyd.dir/DependInfo.cmake "--color=$(COLOR)"
.PHONY : CMakeFiles/ttyd.dir/depend

View File

@@ -1,19 +0,0 @@
file(REMOVE_RECURSE
"CMakeFiles/ttyd.dir/src/http.c.o"
"CMakeFiles/ttyd.dir/src/http.c.o.d"
"CMakeFiles/ttyd.dir/src/protocol.c.o"
"CMakeFiles/ttyd.dir/src/protocol.c.o.d"
"CMakeFiles/ttyd.dir/src/pty.c.o"
"CMakeFiles/ttyd.dir/src/pty.c.o.d"
"CMakeFiles/ttyd.dir/src/server.c.o"
"CMakeFiles/ttyd.dir/src/server.c.o.d"
"CMakeFiles/ttyd.dir/src/utils.c.o"
"CMakeFiles/ttyd.dir/src/utils.c.o.d"
"ttyd"
"ttyd.pdb"
)
# Per-language clean rules from dependency scanning.
foreach(lang C)
include(CMakeFiles/ttyd.dir/cmake_clean_${lang}.cmake OPTIONAL)
endforeach()

View File

@@ -1,2 +0,0 @@
# Empty dependencies file for ttyd.
# This may be replaced when dependencies are built.

View File

@@ -1,10 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# compile C with /usr/bin/cc
C_DEFINES = -DTTYD_VERSION=\"1.7.7-05422dc\"
C_INCLUDES = -I/usr/include/json-c
C_FLAGS = -D_GNU_SOURCE -std=gnu99

View File

@@ -1 +0,0 @@
/usr/bin/cc -D_GNU_SOURCE CMakeFiles/ttyd.dir/src/utils.c.o CMakeFiles/ttyd.dir/src/pty.c.o CMakeFiles/ttyd.dir/src/protocol.c.o CMakeFiles/ttyd.dir/src/http.c.o CMakeFiles/ttyd.dir/src/server.c.o -o ttyd /usr/lib/x86_64-linux-gnu/libz.so /usr/lib/x86_64-linux-gnu/libwebsockets.a /usr/lib/x86_64-linux-gnu/libwebsockets.so.19 /usr/lib/x86_64-linux-gnu/libjson-c.so /usr/lib/x86_64-linux-gnu/libuv.so /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/x86_64-linux-gnu/libcrypto.so -lutil /usr/lib/x86_64-linux-gnu/libcap.so -ldl -lpthread /usr/lib/x86_64-linux-gnu/libz.so

View File

@@ -1,7 +0,0 @@
CMAKE_PROGRESS_1 = 1
CMAKE_PROGRESS_2 = 2
CMAKE_PROGRESS_3 = 3
CMAKE_PROGRESS_4 = 4
CMAKE_PROGRESS_5 = 5
CMAKE_PROGRESS_6 = 6

Binary file not shown.

View File

@@ -1,94 +0,0 @@
cmake_minimum_required(VERSION 3.12.0)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
project(ttyd VERSION 1.7.7 LANGUAGES C)
set(TTYD_VERSION "${PROJECT_VERSION}")
include(GetGitVersion)
get_git_version(GIT_VERSION SEM_VER)
get_git_head(GIT_COMMIT)
if("${SEM_VER}" VERSION_GREATER "${TTYD_VERSION}")
set(TTYD_VERSION "${SEM_VER}")
endif()
if(NOT "${GIT_COMMIT}" STREQUAL "")
set(TTYD_VERSION "${TTYD_VERSION}-${GIT_COMMIT}")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_GNU_SOURCE")
if(CMAKE_VERSION VERSION_LESS "3.1")
if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
endif()
else()
set(CMAKE_C_STANDARD 99)
endif()
set(SOURCE_FILES src/utils.c src/pty.c src/protocol.c src/http.c src/server.c)
include(FindPackageHandleStandardArgs)
find_path(LIBUV_INCLUDE_DIR NAMES uv.h)
find_library(LIBUV_LIBRARY NAMES uv libuv)
find_package_handle_standard_args(LIBUV REQUIRED_VARS LIBUV_LIBRARY LIBUV_INCLUDE_DIR)
mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY)
if(LIBUV_FOUND)
SET(LIBUV_INCLUDE_DIRS "${LIBUV_INCLUDE_DIR}")
SET(LIBUV_LIBRARIES "${LIBUV_LIBRARY}")
endif()
find_path(JSON-C_INCLUDE_DIR NAMES json.h PATH_SUFFIXES json-c)
find_library(JSON-C_LIBRARY NAMES json-c)
find_package_handle_standard_args(JSON-C REQUIRED_VARS JSON-C_LIBRARY JSON-C_INCLUDE_DIR)
mark_as_advanced(JSON-C_INCLUDE_DIR JSON-C_LIBRARY)
if(JSON-C_FOUND)
SET(JSON-C_INCLUDE_DIRS "${JSON-C_INCLUDE_DIR}")
SET(JSON-C_LIBRARIES "${JSON-C_LIBRARY}")
endif()
find_package(ZLIB REQUIRED)
find_package(Libwebsockets 3.2.0 REQUIRED)
set(INCLUDE_DIRS ${ZLIB_INCLUDE_DIR} ${LIBWEBSOCKETS_INCLUDE_DIRS} ${JSON-C_INCLUDE_DIRS} ${LIBUV_INCLUDE_DIRS})
set(LINK_LIBS ${ZLIB_LIBRARIES} ${LIBWEBSOCKETS_LIBRARIES} ${JSON-C_LIBRARIES} ${LIBUV_LIBRARIES})
set (CMAKE_REQUIRED_INCLUDES ${INCLUDE_DIRS})
include(CheckSymbolExists)
check_symbol_exists(LWS_WITH_LIBUV "lws_config.h" LWS_WITH_LIBUV)
check_symbol_exists(LWS_OPENSSL_SUPPORT "lws_config.h" LWS_OPENSSL_ENABLED)
check_symbol_exists(LWS_WITH_MBEDTLS "lws_config.h" LWS_MBEDTLS_ENABLED)
if(NOT LWS_WITH_LIBUV)
message(FATAL_ERROR "libwebsockets was not build with libuv support (-DLWS_WITH_LIBUV=ON)")
endif()
if(LWS_OPENSSL_ENABLED AND NOT LWS_MBEDTLS_ENABLED)
find_package(OpenSSL REQUIRED)
list(APPEND INCLUDE_DIRS ${OPENSSL_INCLUDE_DIR})
list(APPEND LINK_LIBS ${OPENSSL_LIBRARIES})
endif()
if(WIN32)
list(APPEND LINK_LIBS shell32 ws2_32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/app.rc.in ${CMAKE_CURRENT_BINARY_DIR}/app.rc @ONLY)
list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/app.rc)
else()
find_library(LIBUTIL NAMES util)
if(LIBUTIL)
list(APPEND LINK_LIBS util)
endif()
endif()
add_executable(${PROJECT_NAME} ${SOURCE_FILES})
target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
target_compile_definitions(${PROJECT_NAME} PUBLIC
TTYD_VERSION="${TTYD_VERSION}"
$<$<PLATFORM_ID:Windows>:_WIN32_WINNT=0xa00 WINVER=0xa00>
)
include(GNUInstallDirs)
install(TARGETS ${PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT prog)
install(FILES man/ttyd.1 DESTINATION "${CMAKE_INSTALL_MANDIR}/man1" COMPONENT doc)

View File

@@ -1,15 +0,0 @@
FROM ubuntu:20.04
ARG TARGETARCH
# Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
# Application
COPY ./dist/${TARGETARCH}/ttyd /usr/bin/ttyd
EXPOSE 7681
WORKDIR /root
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["ttyd", "-W", "bash"]

View File

@@ -1,15 +0,0 @@
FROM alpine
ARG TARGETARCH
# Dependencies
RUN apk add --no-cache bash tini
# Application
COPY ./dist/${TARGETARCH}/ttyd /usr/bin/ttyd
EXPOSE 7681
WORKDIR /root
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["ttyd", "-W", "bash"]

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2016-2025 Shuanglei Tao <tsl0922@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,338 +0,0 @@
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.28
# Default target executed when no arguments are given to make.
default_target: all
.PHONY : default_target
# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:
#=============================================================================
# Special targets provided by cmake.
# Disable implicit rules so canonical targets will work.
.SUFFIXES:
# Disable VCS-based implicit rules.
% : %,v
# Disable VCS-based implicit rules.
% : RCS/%
# Disable VCS-based implicit rules.
% : RCS/%,v
# Disable VCS-based implicit rules.
% : SCCS/s.%
# Disable VCS-based implicit rules.
% : s.%
.SUFFIXES: .hpux_make_needs_suffix_list
# Command-line flag to silence nested $(MAKE).
$(VERBOSE)MAKESILENT = -s
#Suppress display of executed commands.
$(VERBOSE).SILENT:
# A target that is always out of date.
cmake_force:
.PHONY : cmake_force
#=============================================================================
# Set environment variables for the build.
# The shell in which to execute make rules.
SHELL = /bin/sh
# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake
# The command to remove a file.
RM = /usr/bin/cmake -E rm -f
# Escaping for special characters.
EQUALS = =
# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /home/init/桌面/ttyd
# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /home/init/桌面/ttyd
#=============================================================================
# Targets provided globally by CMake.
# Special rule for the target edit_cache
edit_cache:
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "No interactive CMake dialog available..."
/usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
.PHONY : edit_cache
# Special rule for the target edit_cache
edit_cache/fast: edit_cache
.PHONY : edit_cache/fast
# Special rule for the target rebuild_cache
rebuild_cache:
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..."
/usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache
# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache
.PHONY : rebuild_cache/fast
# Special rule for the target list_install_components
list_install_components:
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Available install components are: \"doc\" \"prog\""
.PHONY : list_install_components
# Special rule for the target list_install_components
list_install_components/fast: list_install_components
.PHONY : list_install_components/fast
# Special rule for the target install
install: preinstall
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..."
/usr/bin/cmake -P cmake_install.cmake
.PHONY : install
# Special rule for the target install
install/fast: preinstall/fast
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Install the project..."
/usr/bin/cmake -P cmake_install.cmake
.PHONY : install/fast
# Special rule for the target install/local
install/local: preinstall
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..."
/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
.PHONY : install/local
# Special rule for the target install/local
install/local/fast: preinstall/fast
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing only the local directory..."
/usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake
.PHONY : install/local/fast
# Special rule for the target install/strip
install/strip: preinstall
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..."
/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
.PHONY : install/strip
# Special rule for the target install/strip
install/strip/fast: preinstall/fast
@$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Installing the project stripped..."
/usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake
.PHONY : install/strip/fast
# The main all target
all: cmake_check_build_system
$(CMAKE_COMMAND) -E cmake_progress_start /home/init/桌面/ttyd/CMakeFiles /home/init/桌面/ttyd//CMakeFiles/progress.marks
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all
$(CMAKE_COMMAND) -E cmake_progress_start /home/init/桌面/ttyd/CMakeFiles 0
.PHONY : all
# The main clean target
clean:
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean
.PHONY : clean
# The main clean target
clean/fast: clean
.PHONY : clean/fast
# Prepare targets for installation.
preinstall: all
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall
# Prepare targets for installation.
preinstall/fast:
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall/fast
# clear depends
depend:
$(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
.PHONY : depend
#=============================================================================
# Target rules for targets named ttyd
# Build rule for target.
ttyd: cmake_check_build_system
$(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 ttyd
.PHONY : ttyd
# fast build rule for target.
ttyd/fast:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/build
.PHONY : ttyd/fast
src/http.o: src/http.c.o
.PHONY : src/http.o
# target to build an object file
src/http.c.o:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/http.c.o
.PHONY : src/http.c.o
src/http.i: src/http.c.i
.PHONY : src/http.i
# target to preprocess a source file
src/http.c.i:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/http.c.i
.PHONY : src/http.c.i
src/http.s: src/http.c.s
.PHONY : src/http.s
# target to generate assembly for a file
src/http.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/http.c.s
.PHONY : src/http.c.s
src/protocol.o: src/protocol.c.o
.PHONY : src/protocol.o
# target to build an object file
src/protocol.c.o:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/protocol.c.o
.PHONY : src/protocol.c.o
src/protocol.i: src/protocol.c.i
.PHONY : src/protocol.i
# target to preprocess a source file
src/protocol.c.i:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/protocol.c.i
.PHONY : src/protocol.c.i
src/protocol.s: src/protocol.c.s
.PHONY : src/protocol.s
# target to generate assembly for a file
src/protocol.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/protocol.c.s
.PHONY : src/protocol.c.s
src/pty.o: src/pty.c.o
.PHONY : src/pty.o
# target to build an object file
src/pty.c.o:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/pty.c.o
.PHONY : src/pty.c.o
src/pty.i: src/pty.c.i
.PHONY : src/pty.i
# target to preprocess a source file
src/pty.c.i:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/pty.c.i
.PHONY : src/pty.c.i
src/pty.s: src/pty.c.s
.PHONY : src/pty.s
# target to generate assembly for a file
src/pty.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/pty.c.s
.PHONY : src/pty.c.s
src/server.o: src/server.c.o
.PHONY : src/server.o
# target to build an object file
src/server.c.o:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/server.c.o
.PHONY : src/server.c.o
src/server.i: src/server.c.i
.PHONY : src/server.i
# target to preprocess a source file
src/server.c.i:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/server.c.i
.PHONY : src/server.c.i
src/server.s: src/server.c.s
.PHONY : src/server.s
# target to generate assembly for a file
src/server.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/server.c.s
.PHONY : src/server.c.s
src/utils.o: src/utils.c.o
.PHONY : src/utils.o
# target to build an object file
src/utils.c.o:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/utils.c.o
.PHONY : src/utils.c.o
src/utils.i: src/utils.c.i
.PHONY : src/utils.i
# target to preprocess a source file
src/utils.c.i:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/utils.c.i
.PHONY : src/utils.c.i
src/utils.s: src/utils.c.s
.PHONY : src/utils.s
# target to generate assembly for a file
src/utils.c.s:
$(MAKE) $(MAKESILENT) -f CMakeFiles/ttyd.dir/build.make CMakeFiles/ttyd.dir/src/utils.c.s
.PHONY : src/utils.c.s
# Help Target
help:
@echo "The following are some of the valid targets for this Makefile:"
@echo "... all (the default if no target is provided)"
@echo "... clean"
@echo "... depend"
@echo "... edit_cache"
@echo "... install"
@echo "... install/local"
@echo "... install/strip"
@echo "... list_install_components"
@echo "... rebuild_cache"
@echo "... ttyd"
@echo "... src/http.o"
@echo "... src/http.i"
@echo "... src/http.s"
@echo "... src/protocol.o"
@echo "... src/protocol.i"
@echo "... src/protocol.s"
@echo "... src/pty.o"
@echo "... src/pty.i"
@echo "... src/pty.s"
@echo "... src/server.o"
@echo "... src/server.i"
@echo "... src/server.s"
@echo "... src/utils.o"
@echo "... src/utils.i"
@echo "... src/utils.s"
.PHONY : help
#=============================================================================
# Special targets to cleanup operation of make.
# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
$(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
.PHONY : cmake_check_build_system

View File

@@ -1,108 +0,0 @@
![backend](https://github.com/tsl0922/ttyd/workflows/backend/badge.svg)
![frontend](https://github.com/tsl0922/ttyd/workflows/frontend/badge.svg)
[![GitHub Releases](https://img.shields.io/github/downloads/tsl0922/ttyd/total)](https://github.com/tsl0922/ttyd/releases)
[![Docker Pulls](https://img.shields.io/docker/pulls/tsl0922/ttyd)](https://hub.docker.com/r/tsl0922/ttyd)
[![Packaging status](https://repology.org/badge/tiny-repos/ttyd.svg)](https://repology.org/project/ttyd/versions)
![GitHub](https://img.shields.io/github/license/tsl0922/ttyd)
# ttyd - Share your terminal over the web
ttyd is a simple command-line tool for sharing terminal over the web.
![screenshot](https://github.com/tsl0922/ttyd/raw/main/screenshot.gif)
# Features
- Built on top of [libuv](https://libuv.org) and [WebGL2](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API) for speed
- Fully-featured terminal with [CJK](https://en.wikipedia.org/wiki/CJK_characters) and IME support
- [ZMODEM](https://en.wikipedia.org/wiki/ZMODEM) ([lrzsz](https://ohse.de/uwe/software/lrzsz.html)) / [trzsz](https://trzsz.github.io) file transfer support
- [Sixel](https://en.wikipedia.org/wiki/Sixel) image output support ([img2sixel](https://saitoha.github.io/libsixel) / [lsix](https://github.com/hackerb9/lsix))
- SSL support based on [OpenSSL](https://www.openssl.org) / [Mbed TLS](https://github.com/Mbed-TLS/mbedtls)
- Run any custom command with options
- Basic authentication support and many other custom options
- Cross platform: macOS, Linux, FreeBSD/OpenBSD, [OpenWrt](https://openwrt.org), Windows
> ❤ Special thanks to [JetBrains](https://www.jetbrains.com/?from=ttyd) for sponsoring the opensource license to this project.
# Installation
## Install on macOS
- Install with [Homebrew](http://brew.sh): `brew install ttyd`
- Install with [MacPorts](https://www.macports.org): `sudo port install ttyd`
## Install on Linux
- Binary version (recommended): download from the [releases](https://github.com/tsl0922/ttyd/releases) page
- Install with [Homebrew](https://docs.brew.sh/Homebrew-on-Linux) : `brew install ttyd`
- Install the snap: `sudo snap install ttyd --classic`
- Build from source (debian/ubuntu):
```bash
sudo apt-get update
sudo apt-get install -y build-essential cmake git libjson-c-dev libwebsockets-dev
git clone https://github.com/tsl0922/ttyd.git
cd ttyd && mkdir build && cd build
cmake ..
make && sudo make install
```
You may also need to compile/install [libwebsockets](https://libwebsockets.org) from source if the `libwebsockets-dev` package is outdated.
- Install on OpenWrt: `opkg install ttyd`
- Install on Gentoo: clone the [repo](https://bitbucket.org/mgpagano/ttyd/src/master) and follow the directions [here](https://wiki.gentoo.org/wiki/Custom_repository#Creating_a_local_repository).
## Install on Windows
- Binary version (recommended): download from the [releases](https://github.com/tsl0922/ttyd/releases) page
- Install with [WinGet](https://github.com/microsoft/winget-cli): `winget install tsl0922.ttyd`
- Install with [Scoop](https://scoop.sh/#/apps?q=ttyd&s=2&d=1&o=true): `scoop install ttyd`
- [Compile on Windows](https://github.com/tsl0922/ttyd/wiki/Compile-on-Windows)
# Usage
## Command-line Options
```
USAGE:
ttyd [options] <command> [<arguments...>]
OPTIONS:
-p, --port Port to listen (default: 7681, use `0` for random port)
-i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)
-U, --socket-owner User owner of the UNIX domain socket file, when enabled (eg: user:group)
-c, --credential Credential for basic authentication (format: username:password)
-H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
-u, --uid User id to run with
-g, --gid Group id to run with
-s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)
-w, --cwd Working directory to be set for the child program
-a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)
-W, --writable Allow clients to write to the TTY (readonly by default)
-t, --client-option Send option to client (format: key=value), repeat to add more options
-T, --terminal-type Terminal type to report, default: xterm-256color
-O, --check-origin Do not allow websocket connection from different origin
-m, --max-clients Maximum clients to support (default: 0, no limit)
-o, --once Accept only one client and exit on disconnection
-q, --exit-no-conn Exit on all clients disconnection
-B, --browser Open terminal with the default system browser
-I, --index Custom index.html path
-b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
-P, --ping-interval Websocket ping interval(sec) (default: 5)
-6, --ipv6 Enable IPv6 support
-S, --ssl Enable SSL
-C, --ssl-cert SSL certificate file path
-K, --ssl-key SSL key file path
-A, --ssl-ca SSL CA file path for client certificate verification
-d, --debug Set log level (default: 7)
-v, --version Print the version and exit
-h, --help Print this text and exit
```
Read the example usage on the [wiki](https://github.com/tsl0922/ttyd/wiki/Example-Usage).
## Browser Support
Modern browsers, See [Browser Support](https://github.com/xtermjs/xterm.js#browser-support).
## Alternatives
* [Wetty](https://github.com/krishnasrinivas/wetty): [Node](https://nodejs.org) based web terminal (SSH/login)
* [GoTTY](https://github.com/yudai/gotty): [Go](https://golang.org) based web terminal

View File

@@ -1,32 +0,0 @@
#include <winver.h>
#define VERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
#define VERSION_STR "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0\0"
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION
PRODUCTVERSION VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS 0
FILEOS VOS__WINDOWS32
FILETYPE VFT_DLL
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "ttyd\0"
VALUE "ProductName", "ttyd\0"
VALUE "ProductVersion", VERSION_STR
VALUE "FileVersion", VERSION_STR
VALUE "InternalName", "ttyd\0"
VALUE "OriginalFilename", "ttyd.exe\0"
VALUE "LegalCopyright", "Copyright (C) 2016-2025 Shuanglei Tao\0"
VALUE "Comment", "\0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -1,47 +0,0 @@
find_package(Git)
function(get_git_version var1 var2)
if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} describe --tags --match "[0-9]*.[0-9]*.[0-9]*" --abbrev=8
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE status
OUTPUT_VARIABLE GIT_VERSION
)
if (${status})
set(GIT_VERSION "0.0.0")
else()
string(STRIP ${GIT_VERSION} GIT_VERSION)
string(REGEX REPLACE "-[0-9]+-g" "-" GIT_VERSION ${GIT_VERSION})
endif()
else()
set(GIT_VERSION "0.0.0")
endif()
string(REGEX MATCH "^[0-9]+.[0-9]+.[0-9]+" SEM_VER "${GIT_VERSION}")
message("-- Git Tag: ${GIT_VERSION}, Sem Ver: ${SEM_VER}")
set(${var1} ${GIT_VERSION} PARENT_SCOPE)
set(${var2} ${SEM_VER} PARENT_SCOPE)
endfunction()
function(get_git_head var1)
if(GIT_EXECUTABLE)
execute_process(
COMMAND ${GIT_EXECUTABLE} --git-dir ${CMAKE_CURRENT_SOURCE_DIR}/.git rev-parse --short HEAD
RESULT_VARIABLE status
OUTPUT_VARIABLE GIT_COMMIT
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(${status})
set(GIT_COMMIT "unknown")
endif()
message("-- Git Commit: ${GIT_COMMIT}")
set(${var1} ${GIT_COMMIT} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -1,78 +0,0 @@
# Install script for directory: /home/init/桌面/ttyd
# Set the install prefix
if(NOT DEFINED CMAKE_INSTALL_PREFIX)
set(CMAKE_INSTALL_PREFIX "/usr/local")
endif()
string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
# Set the install configuration name.
if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME)
if(BUILD_TYPE)
string(REGEX REPLACE "^[^A-Za-z0-9_]+" ""
CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}")
else()
set(CMAKE_INSTALL_CONFIG_NAME "")
endif()
message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"")
endif()
# Set the component getting installed.
if(NOT CMAKE_INSTALL_COMPONENT)
if(COMPONENT)
message(STATUS "Install component: \"${COMPONENT}\"")
set(CMAKE_INSTALL_COMPONENT "${COMPONENT}")
else()
set(CMAKE_INSTALL_COMPONENT)
endif()
endif()
# Install shared libraries without execute permission?
if(NOT DEFINED CMAKE_INSTALL_SO_NO_EXE)
set(CMAKE_INSTALL_SO_NO_EXE "1")
endif()
# Is this installation the result of a crosscompile?
if(NOT DEFINED CMAKE_CROSSCOMPILING)
set(CMAKE_CROSSCOMPILING "FALSE")
endif()
# Set default install directory permissions.
if(NOT DEFINED CMAKE_OBJDUMP)
set(CMAKE_OBJDUMP "/usr/bin/objdump")
endif()
if(CMAKE_INSTALL_COMPONENT STREQUAL "prog" OR NOT CMAKE_INSTALL_COMPONENT)
if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd" AND
NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd")
file(RPATH_CHECK
FILE "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd"
RPATH "")
endif()
file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/bin" TYPE EXECUTABLE FILES "/home/init/桌面/ttyd/ttyd")
if(EXISTS "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd" AND
NOT IS_SYMLINK "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd")
if(CMAKE_INSTALL_DO_STRIP)
execute_process(COMMAND "/usr/bin/strip" "$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/bin/ttyd")
endif()
endif()
endif()
if(CMAKE_INSTALL_COMPONENT STREQUAL "prog" OR NOT CMAKE_INSTALL_COMPONENT)
include("/home/init/桌面/ttyd/CMakeFiles/ttyd.dir/install-cxx-module-bmi-noconfig.cmake" OPTIONAL)
endif()
if(CMAKE_INSTALL_COMPONENT STREQUAL "doc" OR NOT CMAKE_INSTALL_COMPONENT)
file(INSTALL DESTINATION "${CMAKE_INSTALL_PREFIX}/share/man/man1" TYPE FILE FILES "/home/init/桌面/ttyd/man/ttyd.1")
endif()
if(CMAKE_INSTALL_COMPONENT)
set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt")
else()
set(CMAKE_INSTALL_MANIFEST "install_manifest.txt")
endif()
string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT
"${CMAKE_INSTALL_MANIFEST_FILES}")
file(WRITE "/home/init/桌面/ttyd/${CMAKE_INSTALL_MANIFEST}"
"${CMAKE_INSTALL_MANIFEST_CONTENT}")

View File

@@ -1,14 +0,0 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[{*.json, *.scss}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View File

@@ -1 +0,0 @@
dist/

View File

@@ -1,20 +0,0 @@
{
"extends": "./node_modules/gts/",
"overrides": [
{
"files": ["**/*.ts", "**/*.tsx"],
"parserOptions": {
"jsxPragma": "h"
},
"rules": {
"@typescript-eslint/no-duplicate-enum-values": "off"
}
},
{
"files": ["gulpfile.js", "webpack.config.js"],
"rules": {
"node/no-unpublished-require": "off"
}
}
]
}

View File

@@ -1,9 +0,0 @@
node_modules
dist
*.log
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View File

@@ -1,6 +0,0 @@
module.exports = {
...require('gts/.prettierrc.json'),
"bracketSpacing": true,
"tabWidth": 4,
"printWidth": 120,
}

View File

@@ -1,34 +0,0 @@
diff --git a/src/zsession.js b/src/zsession.js
index 5f0b8f9d8afa6fba0acd6dd0477afa186f7aad9a..c7ea98e0f08c97d63d321f784a5dd8bf66888743 100644
--- a/src/zsession.js
+++ b/src/zsession.js
@@ -548,20 +548,17 @@ Zmodem.Session.Receive = class ZmodemReceiveSession extends Zmodem.Session {
if (this._got_ZFIN) {
if (this._input_buffer.length < 2) return;
- //if its OO, then set this._bytes_after_OO
- if (Zmodem.ZMLIB.find_subarray(this._input_buffer, OVER_AND_OUT) === 0) {
+ if (Zmodem.ZMLIB.find_subarray(this._input_buffer, OVER_AND_OUT) !== 0) {
+ console.warn( "PROTOCOL: Only thing after ZFIN should be “OO” (79,79), not: " + this._input_buffer.join() );
+ }
- //This doubles as an indication that the session has ended.
- //We need to set this right away so that handlers like
- //"session_end" will have access to it.
- this._bytes_after_OO = _trim_OO(this._bytes_being_consumed.slice(0));
- this._on_session_end();
+ //This doubles as an indication that the session has ended.
+ //We need to set this right away so that handlers like
+ //"session_end" will have access to it.
+ this._bytes_after_OO = _trim_OO(this._bytes_being_consumed.slice(0));
+ this._on_session_end();
- return;
- }
- else {
- throw( "PROTOCOL: Only thing after ZFIN should be “OO” (79,79), not: " + this._input_buffer.join() );
- }
+ return;
}
var parsed;

View File

@@ -1 +0,0 @@
nodeLinker: node-modules

View File

@@ -1,14 +0,0 @@
## Prerequisites
> **NOTE:** yarn v2 is required.
Install [Yarn](https://yarnpkg.com/getting-started/install), and run: `yarn install`.
## Development
1. Start ttyd: `ttyd bash`
2. Start the dev server: `yarn run start`
## Publish
Run `yarn run build`, this will compile the inlined html to `../src/html.h`.

View File

@@ -1,68 +0,0 @@
const { src, dest, task, series } = require('gulp');
const clean = require('gulp-clean');
const gzip = require('gulp-gzip');
const inlineSource = require('gulp-inline-source');
const rename = require('gulp-rename');
const through2 = require('through2');
const genHeader = (size, buf, len) => {
let idx = 0;
let data = 'unsigned char index_html[] = {\n ';
for (const value of buf) {
idx++;
const current = value < 0 ? value + 256 : value;
data += '0x';
data += (current >>> 4).toString(16);
data += (current & 0xf).toString(16);
if (idx === len) {
data += '\n';
} else {
data += idx % 12 === 0 ? ',\n ' : ', ';
}
}
data += '};\n';
data += `unsigned int index_html_len = ${len};\n`;
data += `unsigned int index_html_size = ${size};\n`;
return data;
};
let fileSize = 0;
task('clean', () => {
return src('dist', { read: false, allowEmpty: true }).pipe(clean());
});
task('inline', () => {
const options = {
compress: false,
};
return src('dist/index.html').pipe(inlineSource(options)).pipe(rename('inline.html')).pipe(dest('dist/'));
});
task(
'default',
series('inline', () => {
return src('dist/inline.html')
.pipe(
through2.obj((file, enc, cb) => {
fileSize = file.contents.length;
return cb(null, file);
})
)
.pipe(gzip())
.pipe(
through2.obj((file, enc, cb) => {
const buf = file.contents;
file.contents = Buffer.from(genHeader(fileSize, buf, buf.length));
return cb(null, file);
})
)
.pipe(rename('html.h'))
.pipe(dest('../src/'));
})
);

15575
ttyd/html/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,75 +0,0 @@
{
"private": true,
"name": "ttyd",
"version": "1.0.0",
"description": "Share your terminal over the web",
"repository": {
"url": "git@github.com:tsl0922/ttyd.git",
"type": "git"
},
"author": "Shuanglei Tao <tsl0922@gmail.com>",
"license": "MIT",
"scripts": {
"prestart": "gulp clean",
"start": "NODE_ENV=development && webpack serve",
"build": "NODE_ENV=production webpack && gulp",
"inline": "NODE_ENV=production webpack && gulp inline",
"check": "gts check",
"fix": "gts fix"
},
"engines": {
"node": ">=12"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.1.1",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"css-minimizer-webpack-plugin": "^6.0.0",
"eslint": "^8.57.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-webpack-plugin": "^4.0.1",
"gts": "^5.2.0",
"gulp": "^4.0.2",
"gulp-clean": "^0.4.0",
"gulp-gzip": "^1.4.2",
"gulp-inline-source": "^4.0.0",
"gulp-rename": "^2.0.0",
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.8.1",
"sass": "^1.71.1",
"sass-loader": "^14.1.1",
"scssfmt": "^1.0.7",
"style-loader": "^3.3.4",
"terser-webpack-plugin": "^5.3.10",
"through2": "^4.0.2",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"util": "^0.12.5",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.2",
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-image": "^0.8.0",
"@xterm/addon-unicode11": "^0.8.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"decko": "^1.2.0",
"file-saver": "^2.0.5",
"preact": "^10.19.6",
"raw-loader": "^4.0.2",
"trzsz": "^1.1.5",
"whatwg-fetch": "^3.6.20",
"zmodem.js": "^0.1.10"
},
"resolutions": {
"zmodem.js@^0.1.10": "patch:zmodem.js@npm%3A0.1.10#./.yarn/patches/zmodem.js-npm-0.1.10-e5537fa2ed.patch"
},
"packageManager": "yarn@3.6.3"
}

View File

@@ -1,68 +0,0 @@
import { h, Component } from 'preact';
import { Terminal } from './terminal';
import type { ITerminalOptions, ITheme } from '@xterm/xterm';
import type { ClientOptions, FlowControl } from './terminal/xterm';
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const path = window.location.pathname.replace(/[/]+$/, '');
const wsUrl = [protocol, '//', window.location.host, path, '/ws', window.location.search].join('');
const tokenUrl = [window.location.protocol, '//', window.location.host, path, '/token'].join('');
const clientOptions = {
rendererType: 'webgl',
disableLeaveAlert: false,
disableResizeOverlay: false,
enableZmodem: false,
enableTrzsz: false,
enableSixel: false,
closeOnDisconnect: false,
isWindows: false,
unicodeVersion: '11',
} as ClientOptions;
const termOptions = {
fontSize: 13,
fontFamily: 'Consolas,Liberation Mono,Menlo,Courier,monospace',
theme: {
foreground: '#d2d2d2',
background: '#2b2b2b',
cursor: '#adadad',
black: '#000000',
red: '#d81e00',
green: '#5ea702',
yellow: '#cfae00',
blue: '#427ab3',
magenta: '#89658e',
cyan: '#00a7aa',
white: '#dbded8',
brightBlack: '#686a66',
brightRed: '#f54235',
brightGreen: '#99e343',
brightYellow: '#fdeb61',
brightBlue: '#84b0d8',
brightMagenta: '#bc94b7',
brightCyan: '#37e6e8',
brightWhite: '#f1f1f0',
} as ITheme,
allowProposedApi: true,
} as ITerminalOptions;
const flowControl = {
limit: 100000,
highWater: 10,
lowWater: 4,
} as FlowControl;
export class App extends Component {
render() {
return (
<Terminal
id="terminal-container"
wsUrl={wsUrl}
tokenUrl={tokenUrl}
clientOptions={clientOptions}
termOptions={termOptions}
flowControl={flowControl}
/>
);
}
}

View File

@@ -1,27 +0,0 @@
import { h, Component, ComponentChildren } from 'preact';
import './modal.scss';
interface Props {
show: boolean;
children: ComponentChildren;
}
export class Modal extends Component<Props> {
constructor(props: Props) {
super(props);
}
render({ show, children }: Props) {
return (
show && (
<div className="modal">
<div className="modal-background" />
<div className="modal-content">
<div className="box">{children}</div>
</div>
</div>
)
);
}
}

View File

@@ -1,81 +0,0 @@
.modal {
bottom: 0;
left: 0;
right: 0;
top: 0;
align-items: center;
display: flex;
overflow: hidden;
position: fixed;
z-index: 40;
}
.modal-background {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
background-color: #4a4a4acc;
}
.modal-content {
margin: 0 20px;
max-height: calc(100vh - 160px);
overflow: auto;
position: relative;
width: 100%;
.box {
background-color: #fff;
color: #4a4a4a;
display: block;
padding: 1.25rem;
}
header {
font-weight: bold;
text-align: center;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #ddd;
}
.file-input {
height: .01em;
left: 0;
outline: none;
position: absolute;
top: 0;
width: .01em;
}
.file-cta {
cursor: pointer;
background-color: #f5f5f5;
color: #6200ee;
outline: none;
align-items: center;
box-shadow: none;
display: inline-flex;
height: 2.25em;
justify-content: flex-start;
line-height: 1.5;
position: relative;
vertical-align: top;
border-color: #dbdbdb;
border-radius: 3px;
font-size: 1em;
font-weight: 500;
padding: calc(.375em - 1px) 1em;
white-space: nowrap;
}
}
@media print, screen and (min-width: 769px) {
.modal-content {
margin: 0 auto;
max-height: calc(100vh - 40px);
width: 640px;
}
}

View File

@@ -1,59 +0,0 @@
import { bind } from 'decko';
import { Component, h } from 'preact';
import { Xterm, XtermOptions } from './xterm';
import '@xterm/xterm/css/xterm.css';
import { Modal } from '../modal';
interface Props extends XtermOptions {
id: string;
}
interface State {
modal: boolean;
}
export class Terminal extends Component<Props, State> {
private container: HTMLElement;
private xterm: Xterm;
constructor(props: Props) {
super();
this.xterm = new Xterm(props, this.showModal);
}
async componentDidMount() {
await this.xterm.refreshToken();
this.xterm.open(this.container);
this.xterm.connect();
}
componentWillUnmount() {
this.xterm.dispose();
}
render({ id }: Props, { modal }: State) {
return (
<div id={id} ref={c => (this.container = c as HTMLElement)}>
<Modal show={modal}>
<label class="file-label">
<input onChange={this.sendFile} class="file-input" type="file" multiple />
<span class="file-cta">Choose files</span>
</label>
</Modal>
</div>
);
}
@bind
showModal() {
this.setState({ modal: true });
}
@bind
sendFile(event: Event) {
this.setState({ modal: false });
const files = (event.target as HTMLInputElement).files;
if (files) this.xterm.sendFile(files);
}
}

View File

@@ -1,73 +0,0 @@
// ported from hterm.Terminal.prototype.showOverlay
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
import { bind } from 'decko';
import { ITerminalAddon, Terminal } from '@xterm/xterm';
export class OverlayAddon implements ITerminalAddon {
private terminal: Terminal;
private overlayNode: HTMLElement;
private overlayTimeout?: number;
constructor() {
this.overlayNode = document.createElement('div');
this.overlayNode.style.cssText = `border-radius: 15px;
font-size: xx-large;
opacity: 0.75;
padding: 0.2em 0.5em 0.2em 0.5em;
position: absolute;
-webkit-user-select: none;
-webkit-transition: opacity 180ms ease-in;
-moz-user-select: none;
-moz-transition: opacity 180ms ease-in;`;
this.overlayNode.addEventListener(
'mousedown',
e => {
e.preventDefault();
e.stopPropagation();
},
true
);
}
activate(terminal: Terminal): void {
this.terminal = terminal;
}
dispose(): void {}
@bind
showOverlay(msg: string, timeout?: number): void {
const { terminal, overlayNode } = this;
if (!terminal.element) return;
overlayNode.style.color = '#101010';
overlayNode.style.backgroundColor = '#f0f0f0';
overlayNode.textContent = msg;
overlayNode.style.opacity = '0.75';
if (!overlayNode.parentNode) {
terminal.element.appendChild(overlayNode);
}
const divSize = terminal.element.getBoundingClientRect();
const overlaySize = overlayNode.getBoundingClientRect();
overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
if (!timeout) return;
this.overlayTimeout = window.setTimeout(() => {
overlayNode.style.opacity = '0';
this.overlayTimeout = window.setTimeout(() => {
if (overlayNode.parentNode) {
overlayNode.parentNode.removeChild(overlayNode);
}
this.overlayTimeout = undefined;
overlayNode.style.opacity = '0.75';
}, 200);
}, timeout || 1500);
}
}

View File

@@ -1,182 +0,0 @@
import { bind } from 'decko';
import { saveAs } from 'file-saver';
import { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm';
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
import { TrzszFilter } from 'trzsz';
export interface ZmodeOptions {
zmodem: boolean;
trzsz: boolean;
windows: boolean;
trzszDragInitTimeout: number;
onSend: () => void;
sender: (data: string | Uint8Array) => void;
writer: (data: string | Uint8Array) => void;
}
export class ZmodemAddon implements ITerminalAddon {
private disposables: IDisposable[] = [];
private terminal: Terminal;
private sentry: Zmodem.Sentry;
private session: Zmodem.Session;
private denier: () => void;
private trzszFilter: TrzszFilter;
constructor(private options: ZmodeOptions) {}
activate(terminal: Terminal) {
this.terminal = terminal;
if (this.options.zmodem) this.zmodemInit();
if (this.options.trzsz) this.trzszInit();
}
dispose() {
for (const d of this.disposables) {
d.dispose();
}
this.disposables.length = 0;
}
consume(data: ArrayBuffer) {
try {
if (this.options.trzsz) {
this.trzszFilter.processServerOutput(data);
} else {
this.sentry.consume(data);
}
} catch (e) {
console.error('[ttyd] zmodem consume: ', e);
this.reset();
}
}
@bind
private reset() {
this.terminal.options.disableStdin = false;
this.terminal.focus();
}
private addDisposableListener(target: EventTarget, type: string, listener: EventListener) {
target.addEventListener(type, listener);
this.disposables.push({ dispose: () => target.removeEventListener(type, listener) });
}
@bind
private trzszInit() {
const { terminal } = this;
const { sender, writer, zmodem } = this.options;
this.trzszFilter = new TrzszFilter({
writeToTerminal: data => {
if (!this.trzszFilter.isTransferringFiles() && zmodem) {
this.sentry.consume(data);
} else {
writer(typeof data === 'string' ? data : new Uint8Array(data as ArrayBuffer));
}
},
sendToServer: data => sender(data),
terminalColumns: terminal.cols,
isWindowsShell: this.options.windows,
dragInitTimeout: this.options.trzszDragInitTimeout,
});
const element = terminal.element as EventTarget;
this.addDisposableListener(element, 'dragover', event => event.preventDefault());
this.addDisposableListener(element, 'drop', event => {
event.preventDefault();
this.trzszFilter
.uploadFiles((event as DragEvent).dataTransfer?.items as DataTransferItemList)
.then(() => console.log('[ttyd] upload success'))
.catch(err => console.log('[ttyd] upload failed: ' + err));
});
this.disposables.push(terminal.onResize(size => this.trzszFilter.setTerminalColumns(size.cols)));
}
@bind
private zmodemInit() {
const { sender, writer } = this.options;
const { terminal, reset, zmodemDetect } = this;
this.session = null;
this.sentry = new Zmodem.Sentry({
to_terminal: octets => writer(new Uint8Array(octets)),
sender: octets => sender(new Uint8Array(octets)),
on_retract: () => reset(),
on_detect: detection => zmodemDetect(detection),
});
this.disposables.push(
terminal.onKey(e => {
const event = e.domEvent;
if (event.ctrlKey && event.key === 'c') {
if (this.denier) this.denier();
}
})
);
}
@bind
private zmodemDetect(detection: Zmodem.Detection): void {
const { terminal, receiveFile } = this;
terminal.options.disableStdin = true;
this.denier = () => detection.deny();
this.session = detection.confirm();
this.session.on('session_end', () => this.reset());
if (this.session.type === 'send') {
this.options.onSend();
} else {
receiveFile();
}
}
@bind
public sendFile(files: FileList) {
const { session, writeProgress } = this;
Zmodem.Browser.send_files(session, files, {
on_progress: (_, offer) => writeProgress(offer),
})
.then(() => session.close())
.catch(() => this.reset());
}
@bind
private receiveFile() {
const { session, writeProgress } = this;
session.on('offer', offer => {
offer.on('input', () => writeProgress(offer));
offer
.accept()
.then(payloads => {
const blob = new Blob(payloads, { type: 'application/octet-stream' });
saveAs(blob, offer.get_details().name);
})
.catch(() => this.reset());
});
session.start();
}
@bind
private writeProgress(offer: Zmodem.Offer) {
const { bytesHuman } = this;
const file = offer.get_details();
const name = file.name;
const size = file.size;
const offset = offer.get_offset();
const percent = ((100 * offset) / size).toFixed(2);
this.options.writer(`${name} ${percent}% ${bytesHuman(offset, 2)}/${bytesHuman(size, 2)}\r`);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private bytesHuman(bytes: any, precision: number): string {
if (!/^([-+])?|(\.\d+)(\d+(\.\d+)?|(\d+\.)|Infinity)$/.test(bytes)) {
return '-';
}
if (bytes === 0) return '0';
if (typeof precision === 'undefined') precision = 1;
const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
const num = Math.floor(Math.log(bytes) / Math.log(1024));
const value = (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision);
return `${value} ${units[num]}`;
}
}

View File

@@ -1 +0,0 @@
ls\n

View File

@@ -1,539 +0,0 @@
import { bind } from 'decko';
import type { IDisposable, ITerminalOptions } from '@xterm/xterm';
import { Terminal } from '@xterm/xterm';
import { CanvasAddon } from '@xterm/addon-canvas';
import { ClipboardAddon } from '@xterm/addon-clipboard';
import { WebglAddon } from '@xterm/addon-webgl';
import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { ImageAddon } from '@xterm/addon-image';
import { Unicode11Addon } from '@xterm/addon-unicode11';
import { OverlayAddon } from './addons/overlay';
import { ZmodemAddon } from './addons/zmodem';
import '@xterm/xterm/css/xterm.css';
interface TtydTerminal extends Terminal {
fit(): void;
}
declare global {
interface Window {
term: TtydTerminal;
}
}
enum Command {
// server side
OUTPUT = '0',
SET_WINDOW_TITLE = '1',
SET_PREFERENCES = '2',
// client side
INPUT = '0',
RESIZE_TERMINAL = '1',
PAUSE = '2',
RESUME = '3',
}
type Preferences = ITerminalOptions & ClientOptions;
export type RendererType = 'dom' | 'canvas' | 'webgl';
export interface ClientOptions {
rendererType: RendererType;
disableLeaveAlert: boolean;
disableResizeOverlay: boolean;
enableZmodem: boolean;
enableTrzsz: boolean;
enableSixel: boolean;
titleFixed?: string;
isWindows: boolean;
trzszDragInitTimeout: number;
unicodeVersion: string;
closeOnDisconnect: boolean;
}
export interface FlowControl {
limit: number;
highWater: number;
lowWater: number;
}
export interface XtermOptions {
wsUrl: string;
tokenUrl: string;
flowControl: FlowControl;
clientOptions: ClientOptions;
termOptions: ITerminalOptions;
}
function toDisposable(f: () => void): IDisposable {
return { dispose: f };
}
function addEventListener(target: EventTarget, type: string, listener: EventListener): IDisposable {
target.addEventListener(type, listener);
return toDisposable(() => target.removeEventListener(type, listener));
}
export class Xterm {
private disposables: IDisposable[] = [];
private textEncoder = new TextEncoder();
private textDecoder = new TextDecoder();
private written = 0;
private pending = 0;
private terminal: Terminal;
private fitAddon = new FitAddon();
private overlayAddon = new OverlayAddon();
private clipboardAddon = new ClipboardAddon();
private webLinksAddon = new WebLinksAddon();
private webglAddon?: WebglAddon;
private canvasAddon?: CanvasAddon;
private zmodemAddon?: ZmodemAddon;
private socket?: WebSocket;
private token: string;
private opened = false;
private title?: string;
private titleFixed?: string;
private resizeOverlay = true;
private reconnect = true;
private doReconnect = true;
private closeOnDisconnect = false;
private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data));
constructor(
private options: XtermOptions,
private sendCb: () => void
) {}
dispose() {
for (const d of this.disposables) {
d.dispose();
}
this.disposables.length = 0;
}
@bind
private register<T extends IDisposable>(d: T): T {
this.disposables.push(d);
return d;
}
@bind
public sendFile(files: FileList) {
this.zmodemAddon?.sendFile(files);
}
@bind
public async refreshToken() {
try {
const resp = await fetch(this.options.tokenUrl);
if (resp.ok) {
const json = await resp.json();
this.token = json.token;
}
} catch (e) {
console.error(`[ttyd] fetch ${this.options.tokenUrl}: `, e);
}
}
@bind
private onWindowUnload(event: BeforeUnloadEvent) {
event.preventDefault();
if (this.socket?.readyState === WebSocket.OPEN) {
const message = 'Close terminal? this will also terminate the command.';
event.returnValue = message;
return message;
}
return undefined;
}
@bind
public open(parent: HTMLElement) {
this.terminal = new Terminal(this.options.termOptions);
const { terminal, fitAddon, overlayAddon, clipboardAddon, webLinksAddon } = this;
window.term = terminal as TtydTerminal;
window.term.fit = () => {
this.fitAddon.fit();
};
terminal.loadAddon(fitAddon);
terminal.loadAddon(overlayAddon);
terminal.loadAddon(clipboardAddon);
terminal.loadAddon(webLinksAddon);
terminal.open(parent);
fitAddon.fit();
}
@bind
private initListeners() {
const { terminal, fitAddon, overlayAddon, register, sendData } = this;
register(
terminal.onTitleChange(data => {
if (data && data !== '' && !this.titleFixed) {
document.title = data + ' | ' + this.title;
}
})
);
register(terminal.onData(data => sendData(data)));
register(terminal.onBinary(data => sendData(Uint8Array.from(data, v => v.charCodeAt(0)))));
register(
terminal.onResize(({ cols, rows }) => {
const msg = JSON.stringify({ columns: cols, rows: rows });
this.socket?.send(this.textEncoder.encode(Command.RESIZE_TERMINAL + msg));
if (this.resizeOverlay) overlayAddon.showOverlay(`${cols}x${rows}`, 300);
})
);
register(
terminal.onSelectionChange(() => {
if (this.terminal.getSelection() === '') return;
try {
document.execCommand('copy');
} catch (e) {
return;
}
this.overlayAddon?.showOverlay('\u2702', 200);
})
);
register(addEventListener(window, 'resize', () => fitAddon.fit()));
register(addEventListener(window, 'beforeunload', this.onWindowUnload));
}
@bind
public writeData(data: string | Uint8Array) {
const { terminal, textEncoder } = this;
const { limit, highWater, lowWater } = this.options.flowControl;
this.written += data.length;
if (this.written > limit) {
terminal.write(data, () => {
this.pending = Math.max(this.pending - 1, 0);
if (this.pending < lowWater) {
this.socket?.send(textEncoder.encode(Command.RESUME));
}
});
this.pending++;
this.written = 0;
if (this.pending > highWater) {
this.socket?.send(textEncoder.encode(Command.PAUSE));
}
} else {
terminal.write(data);
}
}
@bind
public sendData(data: string | Uint8Array) {
const { socket, textEncoder } = this;
if (socket?.readyState !== WebSocket.OPEN) return;
if (typeof data === 'string') {
const payload = new Uint8Array(data.length * 3 + 1);
payload[0] = Command.INPUT.charCodeAt(0);
const stats = textEncoder.encodeInto(data, payload.subarray(1));
socket.send(payload.subarray(0, (stats.written as number) + 1));
} else {
const payload = new Uint8Array(data.length + 1);
payload[0] = Command.INPUT.charCodeAt(0);
payload.set(data, 1);
socket.send(payload);
}
}
@bind
public connect() {
this.socket = new WebSocket(this.options.wsUrl, ['tty']);
const { socket, register } = this;
socket.binaryType = 'arraybuffer';
register(addEventListener(socket, 'open', this.onSocketOpen));
register(addEventListener(socket, 'message', this.onSocketData as EventListener));
register(addEventListener(socket, 'close', this.onSocketClose as EventListener));
register(addEventListener(socket, 'error', () => (this.doReconnect = false)));
}
@bind
private onSocketOpen() {
console.log('[ttyd] websocket connection opened');
const { textEncoder, terminal, overlayAddon } = this;
const msg = JSON.stringify({ AuthToken: this.token, columns: terminal.cols, rows: terminal.rows });
this.socket?.send(textEncoder.encode(msg));
if (this.opened) {
terminal.reset();
terminal.options.disableStdin = false;
overlayAddon.showOverlay('Reconnected', 300);
} else {
this.opened = true;
}
this.doReconnect = this.reconnect;
this.initListeners();
terminal.focus();
console.log('000');
}
@bind
private onSocketClose(event: CloseEvent) {
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
const { refreshToken, connect, doReconnect, overlayAddon } = this;
overlayAddon.showOverlay('Connection Closed');
this.dispose();
// 1000: CLOSE_NORMAL
if (event.code !== 1000 && doReconnect) {
overlayAddon.showOverlay('Reconnecting...');
refreshToken().then(connect);
} else if (this.closeOnDisconnect) {
window.close();
} else {
const { terminal } = this;
const keyDispose = terminal.onKey(e => {
const event = e.domEvent;
if (event.key === 'Enter') {
keyDispose.dispose();
overlayAddon.showOverlay('Reconnecting...');
refreshToken().then(connect);
}
});
overlayAddon.showOverlay('Press ⏎ to Reconnect');
}
}
@bind
private parseOptsFromUrlQuery(query: string): Preferences {
const { terminal } = this;
const { clientOptions } = this.options;
const prefs = {} as Preferences;
const queryObj = Array.from(new URLSearchParams(query) as unknown as Iterable<[string, string]>);
for (const [k, queryVal] of queryObj) {
let v = clientOptions[k];
if (v === undefined) v = terminal.options[k];
switch (typeof v) {
case 'boolean':
prefs[k] = queryVal === 'true' || queryVal === '1';
break;
case 'number':
case 'bigint':
prefs[k] = Number.parseInt(queryVal, 10);
break;
case 'string':
prefs[k] = queryVal;
break;
case 'object':
prefs[k] = JSON.parse(queryVal);
break;
default:
console.warn(`[ttyd] maybe unknown option: ${k}=${queryVal}, treating as string`);
prefs[k] = queryVal;
break;
}
}
return prefs;
}
@bind
private onSocketData(event: MessageEvent) {
const { textDecoder } = this;
const rawData = event.data as ArrayBuffer;
const cmd = String.fromCharCode(new Uint8Array(rawData)[0]);
const data = rawData.slice(1);
switch (cmd) {
case Command.OUTPUT:
this.writeFunc(data);
break;
case Command.SET_WINDOW_TITLE:
this.title = textDecoder.decode(data);
document.title = this.title;
break;
case Command.SET_PREFERENCES:
this.applyPreferences({
...this.options.clientOptions,
...JSON.parse(textDecoder.decode(data)),
...this.parseOptsFromUrlQuery(window.location.search),
} as Preferences);
break;
default:
console.warn(`[ttyd] unknown command: ${cmd}`);
break;
}
}
@bind
private applyPreferences(prefs: Preferences) {
const { terminal, fitAddon, register } = this;
if (prefs.enableZmodem || prefs.enableTrzsz) {
this.zmodemAddon = new ZmodemAddon({
zmodem: prefs.enableZmodem,
trzsz: prefs.enableTrzsz,
windows: prefs.isWindows,
trzszDragInitTimeout: prefs.trzszDragInitTimeout,
onSend: this.sendCb,
sender: this.sendData,
writer: this.writeData,
});
this.writeFunc = data => this.zmodemAddon?.consume(data);
terminal.loadAddon(register(this.zmodemAddon));
}
for (const [key, value] of Object.entries(prefs)) {
switch (key) {
case 'rendererType':
this.setRendererType(value);
break;
case 'disableLeaveAlert':
if (value) {
window.removeEventListener('beforeunload', this.onWindowUnload);
console.log('[ttyd] Leave site alert disabled');
}
break;
case 'disableResizeOverlay':
if (value) {
console.log('[ttyd] Resize overlay disabled');
this.resizeOverlay = false;
}
break;
case 'disableReconnect':
if (value) {
console.log('[ttyd] Reconnect disabled');
this.reconnect = false;
this.doReconnect = false;
}
break;
case 'enableZmodem':
if (value) console.log('[ttyd] Zmodem enabled');
break;
case 'enableTrzsz':
if (value) console.log('[ttyd] trzsz enabled');
break;
case 'trzszDragInitTimeout':
if (value) console.log(`[ttyd] trzsz drag init timeout: ${value}`);
break;
case 'enableSixel':
if (value) {
terminal.loadAddon(register(new ImageAddon()));
console.log('[ttyd] Sixel enabled');
}
break;
case 'closeOnDisconnect':
if (value) {
console.log('[ttyd] close on disconnect enabled (Reconnect disabled)');
this.closeOnDisconnect = true;
this.reconnect = false;
this.doReconnect = false;
}
break;
case 'titleFixed':
if (!value || value === '') return;
console.log(`[ttyd] setting fixed title: ${value}`);
this.titleFixed = value;
document.title = value;
break;
case 'isWindows':
if (value) console.log('[ttyd] is windows');
break;
case 'unicodeVersion':
switch (value) {
case 6:
case '6':
console.log('[ttyd] setting Unicode version: 6');
break;
case 11:
case '11':
default:
console.log('[ttyd] setting Unicode version: 11');
terminal.loadAddon(new Unicode11Addon());
terminal.unicode.activeVersion = '11';
break;
}
break;
default:
console.log(`[ttyd] option: ${key}=${JSON.stringify(value)}`);
if (terminal.options[key] instanceof Object) {
console.log(111);
terminal.options[key] = Object.assign({}, terminal.options[key], value);
} else {
console.log(222);
console.log(JSON.stringify(value));
terminal.options[key] = value;
this.sendData(JSON.stringify(value));
}
if (key.indexOf('font') === 0) fitAddon.fit();
break;
}
}
}
@bind
private setRendererType(value: RendererType) {
const { terminal } = this;
const disposeCanvasRenderer = () => {
try {
this.canvasAddon?.dispose();
} catch {
// ignore
}
this.canvasAddon = undefined;
};
const disposeWebglRenderer = () => {
try {
this.webglAddon?.dispose();
} catch {
// ignore
}
this.webglAddon = undefined;
};
const enableCanvasRenderer = () => {
if (this.canvasAddon) return;
this.canvasAddon = new CanvasAddon();
disposeWebglRenderer();
try {
this.terminal.loadAddon(this.canvasAddon);
console.log('[ttyd] canvas renderer loaded');
} catch (e) {
console.log('[ttyd] canvas renderer could not be loaded, falling back to dom renderer', e);
disposeCanvasRenderer();
}
};
const enableWebglRenderer = () => {
if (this.webglAddon) return;
this.webglAddon = new WebglAddon();
disposeCanvasRenderer();
try {
this.webglAddon.onContextLoss(() => {
this.webglAddon?.dispose();
});
terminal.loadAddon(this.webglAddon);
console.log('[ttyd] WebGL renderer loaded');
} catch (e) {
console.log('[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer', e);
disposeWebglRenderer();
enableCanvasRenderer();
}
};
switch (value) {
case 'canvas':
enableCanvasRenderer();
break;
case 'webgl':
enableWebglRenderer();
break;
case 'dom':
disposeWebglRenderer();
disposeCanvasRenderer();
console.log('[ttyd] dom renderer loaded');
break;
default:
break;
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,9 +0,0 @@
if (process.env.NODE_ENV === 'development') {
require('preact/debug');
}
import 'whatwg-fetch';
import { h, render } from 'preact';
import { App } from './components/app';
import './style/index.scss';
render(<App />, document.body);

View File

@@ -1,18 +0,0 @@
html,
body {
height: 100%;
min-height: 100%;
margin: 0;
overflow: hidden;
}
#terminal-container {
width: auto;
height: 100%;
margin: 0 auto;
padding: 0;
.terminal {
padding: 5px;
height: calc(100% - 10px);
}
}

View File

@@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="mobile-web-app-capable" content="yes">
<title><%= htmlWebpackPlugin.options.title %></title>
<link inline rel="icon" type="image/png" href="favicon.png">
<% for (const css in htmlWebpackPlugin.files.css) { %>
<link inline rel="stylesheet" type="text/css" href="<%= htmlWebpackPlugin.files.css[css] %>">
<% } %>
</head>
<body>
<% for (const js in htmlWebpackPlugin.files.js) { %>
<script inline type="text/javascript" src="<%= htmlWebpackPlugin.files.js[js] %>"></script>
<% } %>
</body>
</html>

View File

@@ -1,19 +0,0 @@
{
"extends": "./node_modules/gts/tsconfig-google.json",
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"jsx": "react",
"jsxFactory": "h",
"allowJs": true,
"noImplicitAny": false,
"declaration": false,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"lib": ["es2019", "dom"],
},
"include": [
"src/**/*.tsx",
"src/**/*.ts",
]
}

View File

@@ -1,101 +0,0 @@
const path = require('path');
const { merge } = require('webpack-merge');
const ESLintPlugin = require('eslint-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const devMode = process.env.NODE_ENV !== 'production';
const baseConfig = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './index.tsx',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: devMode ? '[name].js' : '[name].[contenthash].js',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.s?[ac]ss$/,
use: [devMode ? 'style-loader' : MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, '.'),
extensions: ['js', 'jsx', 'ts', 'tsx'],
}),
new CopyWebpackPlugin({
patterns: [{ from: './favicon.png', to: '.' }],
}),
new MiniCssExtractPlugin({
filename: devMode ? '[name].css' : '[name].[contenthash].css',
chunkFilename: devMode ? '[id].css' : '[id].[contenthash].css',
}),
new HtmlWebpackPlugin({
inject: false,
minify: {
removeComments: true,
collapseWhitespace: true,
},
title: 'ttyd - Terminal',
template: './template.html',
}),
],
performance: {
hints: false,
},
};
const devConfig = {
mode: 'development',
devServer: {
static: path.join(__dirname, 'dist'),
compress: true,
port: 9000,
client: {
overlay: {
errors: true,
warnings: false,
},
},
proxy: [
{
context: ['/token', '/ws'],
target: 'http://localhost:7681',
ws: true,
},
],
webSocketServer: {
type: 'sockjs',
options: {
path: '/sockjs-node',
},
},
},
devtool: 'inline-source-map',
};
const prodConfig = {
mode: 'production',
optimization: {
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
},
devtool: 'source-map',
};
module.exports = merge(baseConfig, devMode ? devConfig : prodConfig);

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +0,0 @@
/usr/local/bin/ttyd
/usr/local/share/man/man1/ttyd.1

View File

@@ -1,6 +0,0 @@
# Building the man page
```bash
go get github.com/cpuguy83/go-md2man
go-md2man < ttyd.man.md > ttyd.1
```

View File

@@ -1,353 +0,0 @@
.nh
.TH ttyd 1 "September 2016" ttyd "User Manual"
.SH NAME
.PP
ttyd - Share your terminal over the web
.SH SYNOPSIS
.PP
\fBttyd\fP [options] <command> [<arguments...>]
.SH DESCRIPTION
.PP
ttyd is a command-line tool for sharing terminal over the web that runs in *nix and windows systems, with the following features:
.RS
.IP \(bu 2
Built on top of Libwebsockets with libuv for speed
.IP \(bu 2
Fully-featured terminal based on Xterm.js with CJK (Chinese, Japanese, Korean) and IME support
.IP \(bu 2
Graphical ZMODEM integration with lrzsz support
.IP \(bu 2
Sixel image output support
.IP \(bu 2
SSL support based on OpenSSL
.IP \(bu 2
Run any custom command with options
.IP \(bu 2
Basic authentication support and many other custom options
.IP \(bu 2
Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
.RE
.SH OPTIONS
.PP
-p, --port
Port to listen (default: 7681, use \fB\fC0\fR for random port)
.PP
-i, --interface
Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)
.PP
-U, --socket-owner
User owner of the UNIX domain socket file, when enabled (eg: user:group)
.PP
-c, --credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)
.PP
-H, --auth-header
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
.PP
-u, --uid
User id to run with
.PP
-g, --gid
Group id to run with
.PP
-s, --signal
Signal to send to the command when exit it (default: 1, SIGHUP)
.PP
-w, --cwd
Working directory to be set for the child program
.PP
-a, --url-arg
Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)
.PP
-W, --writable
Allow clients to write to the TTY (readonly by default)
.PP
-t, --client-option
Send option to client (format: key=value), repeat to add more options, see \fBCLIENT OPTIONS\fP for details
.PP
-T, --terminal-type
Terminal type to report, default: xterm-256color
.PP
-O, --check-origin
Do not allow websocket connection from different origin
.PP
-m, --max-clients
Maximum clients to support (default: 0, no limit)
.PP
-o, --once
Accept only one client and exit on disconnection
.PP
-q, --exit-no-conn
Exit on all clients disconnection
.PP
-B, --browser
Open terminal with the default system browser
.PP
-I, --index
Custom index.html path
.PP
-b, --base-path
Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
.PP
-P, --ping-interval
Websocket ping interval(sec) (default: 5)
.PP
-f, --srv-buf-size
Maximum chunk of file (in bytes) that can be sent at once, a larger value may improve throughput (default: 4096)
.PP
-6, --ipv6
Enable IPv6 support
.PP
-S, --ssl
Enable SSL
.PP
-C, --ssl-cert
SSL certificate file path
.PP
-K, --ssl-key
SSL key file path
.PP
-A, --ssl-ca
SSL CA file path for client certificate verification
.PP
-d, --debug
Set log level (default: 7)
.PP
-v, --version
Print the version and exit
.PP
-h, --help
Print this text and exit
.SH CLIENT OPTIONS
.PP
ttyd has a mechanism to pass server side command-line arguments to the browser page which is called \fBclient options\fP:
.PP
.RS
.nf
-t, --client-option Send option to client (format: key=value), repeat to add more options
.fi
.RE
.SH Basic usage
.RS
.IP \(bu 2
\fB\fC-t rendererType=canvas\fR: use the \fB\fCcanvas\fR renderer for xterm.js (default: \fB\fCwebgl\fR)
.IP \(bu 2
\fB\fC-t disableLeaveAlert=true\fR: disable the leave page alert
.IP \(bu 2
\fB\fC-t disableResizeOverlay=true\fR: disable the terminal resize overlay
.IP \(bu 2
\fB\fC-t disableReconnect=true\fR: prevent the terminal from reconnecting on connection error/close
.IP \(bu 2
\fB\fC-t enableZmodem=true\fR: enable ZMODEM
\[la]https://en.wikipedia.org/wiki/ZMODEM\[ra] / lrzsz
\[la]https://ohse.de/uwe/software/lrzsz.html\[ra] file transfer support
.IP \(bu 2
\fB\fC-t enableTrzsz=true\fR: enable trzsz
\[la]https://trzsz.github.io\[ra] file transfer support
.IP \(bu 2
\fB\fC-t enableSixel=true\fR: enable Sixel
\[la]https://en.wikipedia.org/wiki/Sixel\[ra] image output support (Usage
\[la]https://saitoha.github.io/libsixel/\[ra])
.IP \(bu 2
\fB\fC-t closeOnDisconnect=true\fR: close the terminal on disconnection, this will disable reconnect
.IP \(bu 2
\fB\fC-t titleFixed=hello\fR: set a fixed title for the browser window
.IP \(bu 2
\fB\fC-t fontSize=20\fR: change the font size of the terminal
.IP \(bu 2
\fB\fC-t unicodeVersion=11\fR: set xterm unicode support level (default: 11, use 6 to disable unicode addon)
.IP \(bu 2
\fB\fC-t trzszDragInitTimeout=3000\fR: set the timeout in milliseconds for initializing drag and drop files to upload. (default: 3000)
.RE
.SH Advanced usage
.PP
You can use the client option to change all the settings of xterm defined in ITerminalOptions
\[la]https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/\[ra], examples:
.RS
.IP \(bu 2
\fB\fC-t cursorStyle=bar\fR: set cursor style to \fB\fCbar\fR
.IP \(bu 2
\fB\fC-t lineHeight=1.5\fR: set line-height to \fB\fC1.5\fR
.IP \(bu 2
\fB\fC-t 'theme={"background": "green"}'\fR: set background color to \fB\fCgreen\fR
.RE
.PP
to try the example options above, run:
.PP
.RS
.nf
ttyd -t cursorStyle=bar -t lineHeight=1.5 -t 'theme={"background": "green"}' bash
.fi
.RE
.SH EXAMPLES
.PP
ttyd starts web server at port 7681 by default, you can use the -p option to change it, the command will be started with arguments as options. For example, run:
.PP
.RS
.nf
ttyd -p 8080 bash -x
.fi
.RE
.PP
Then open http://localhost:8080 with a browser, you will get a bash shell with debug mode enabled. More examples:
.RS
.IP \(bu 2
If you want to login with your system accounts on the web browser, run \fB\fCttyd login\fR\&.
.IP \(bu 2
You can even run a non-shell command like vim, try: \fB\fCttyd vim\fR, the web browser will show you a vim editor.
.IP \(bu 2
Sharing single process with multiple clients: \fB\fCttyd tmux new -A -s ttyd vim\fR, run \fB\fCtmux new -A -s ttyd\fR to connect to the tmux session from terminal.
.RE
.SH SSL how-to
.PP
Generate SSL CA and self signed server/client certificates:
.PP
.RS
.nf
# CA certificate (FQDN must be different from server/client)
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=Acme Root CA" -out ca.crt
# server certificate (for multiple domains, change subjectAltName to: DNS:example.com,DNS:www.example.com)
openssl req -newkey rsa:2048 -nodes -keyout server.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=localhost" -out server.csr
openssl x509 -sha256 -req -extfile <(printf "subjectAltName=DNS:localhost") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# client certificate (the p12/pem format may be useful for some clients)
openssl req -newkey rsa:2048 -nodes -keyout client.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=client" -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
openssl pkcs12 -in client.p12 -out client.pem -clcerts
.fi
.RE
.PP
Then start ttyd:
.PP
.RS
.nf
ttyd --ssl --ssl-cert server.crt --ssl-key server.key --ssl-ca ca.crt bash
.fi
.RE
.PP
You may want to test the client certificate verification with \fIcurl\fP(1):
.PP
.RS
.nf
curl --insecure --cert client.p12[:password] -v https://localhost:7681
.fi
.RE
.PP
If you don't want to enable client certificate verification, remove the \fB\fC--ssl-ca\fR option.
.SH Docker and ttyd
.PP
Docker containers are jailed environments which are more secure, this is useful for protecting the host system, you may use ttyd with docker like this:
.RS
.IP \(bu 2
Sharing single docker container with multiple clients: docker run -it --rm -p 7681:7681 tsl0922/ttyd.
.IP \(bu 2
Creating new docker container for each client: ttyd docker run -it --rm ubuntu.
.RE
.SH Nginx reverse proxy
.PP
Sample config to proxy ttyd under the \fB\fC/ttyd\fR path:
.PP
.RS
.nf
location ~ ^/ttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:7681/$1;
}
.fi
.RE
.SH AUTHOR
.PP
Shuanglei Tao <tsl0922@gmail.com> Visit https://github.com/tsl0922/ttyd to get more information and report bugs.

View File

@@ -1,218 +0,0 @@
ttyd 1 "September 2016" ttyd "User Manual"
==================================================
# NAME
ttyd - Share your terminal over the web
# SYNOPSIS
**ttyd** [options] \<command\> [\<arguments...\>]
# DESCRIPTION
ttyd is a command-line tool for sharing terminal over the web that runs in *nix and windows systems, with the following features:
- Built on top of Libwebsockets with libuv for speed
- Fully-featured terminal based on Xterm.js with CJK (Chinese, Japanese, Korean) and IME support
- Graphical ZMODEM integration with lrzsz support
- Sixel image output support
- SSL support based on OpenSSL
- Run any custom command with options
- Basic authentication support and many other custom options
- Cross platform: macOS, Linux, FreeBSD/OpenBSD, OpenWrt/LEDE, Windows
# OPTIONS
-p, --port <port>
Port to listen (default: 7681, use `0` for random port)
-i, --interface <interface>
Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)
-U, --socket-owner
User owner of the UNIX domain socket file, when enabled (eg: user:group)
-c, --credential USER[:PASSWORD]
Credential for Basic Authentication (format: username:password)
-H, --auth-header <name>
HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication
-u, --uid <uid>
User id to run with
-g, --gid <gid>
Group id to run with
-s, --signal <signal string>
Signal to send to the command when exit it (default: 1, SIGHUP)
-w, --cwd <path>
Working directory to be set for the child program
-a, --url-arg
Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)
-W, --writable
Allow clients to write to the TTY (readonly by default)
-t, --client-option <key=value>
Send option to client (format: key=value), repeat to add more options, see **CLIENT OPTIONS** for details
-T, --terminal-type
Terminal type to report, default: xterm-256color
-O, --check-origin
Do not allow websocket connection from different origin
-m, --max-clients
Maximum clients to support (default: 0, no limit)
-o, --once
Accept only one client and exit on disconnection
-q, --exit-no-conn
Exit on all clients disconnection
-B, --browser
Open terminal with the default system browser
-I, --index <index file>
Custom index.html path
-b, --base-path
Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)
-P, --ping-interval
Websocket ping interval(sec) (default: 5)
-f, --srv-buf-size
Maximum chunk of file (in bytes) that can be sent at once, a larger value may improve throughput (default: 4096)
-6, --ipv6
Enable IPv6 support
-S, --ssl
Enable SSL
-C, --ssl-cert <cert path>
SSL certificate file path
-K, --ssl-key <key path>
SSL key file path
-A, --ssl-ca <ca path>
SSL CA file path for client certificate verification
-d, --debug <level>
Set log level (default: 7)
-v, --version
Print the version and exit
-h, --help
Print this text and exit
# CLIENT OPTIONS
ttyd has a mechanism to pass server side command-line arguments to the browser page which is called **client options**:
```bash
-t, --client-option Send option to client (format: key=value), repeat to add more options
```
## Basic usage
- `-t rendererType=canvas`: use the `canvas` renderer for xterm.js (default: `webgl`)
- `-t disableLeaveAlert=true`: disable the leave page alert
- `-t disableResizeOverlay=true`: disable the terminal resize overlay
- `-t disableReconnect=true`: prevent the terminal from reconnecting on connection error/close
- `-t enableZmodem=true`: enable [ZMODEM](https://en.wikipedia.org/wiki/ZMODEM) / [lrzsz](https://ohse.de/uwe/software/lrzsz.html) file transfer support
- `-t enableTrzsz=true`: enable [trzsz](https://trzsz.github.io) file transfer support
- `-t enableSixel=true`: enable [Sixel](https://en.wikipedia.org/wiki/Sixel) image output support ([Usage](https://saitoha.github.io/libsixel/))
- `-t closeOnDisconnect=true`: close the terminal on disconnection, this will disable reconnect
- `-t titleFixed=hello`: set a fixed title for the browser window
- `-t fontSize=20`: change the font size of the terminal
- `-t unicodeVersion=11`: set xterm unicode support level (default: 11, use 6 to disable unicode addon)
- `-t trzszDragInitTimeout=3000`: set the timeout in milliseconds for initializing drag and drop files to upload. (default: 3000)
## Advanced usage
You can use the client option to change all the settings of xterm defined in [ITerminalOptions](https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/), examples:
- `-t cursorStyle=bar`: set cursor style to `bar`
- `-t lineHeight=1.5`: set line-height to `1.5`
- `-t 'theme={"background": "green"}'`: set background color to `green`
to try the example options above, run:
```bash
ttyd -t cursorStyle=bar -t lineHeight=1.5 -t 'theme={"background": "green"}' bash
```
# EXAMPLES
ttyd starts web server at port 7681 by default, you can use the -p option to change it, the command will be started with arguments as options. For example, run:
```
ttyd -p 8080 bash -x
```
Then open http://localhost:8080 with a browser, you will get a bash shell with debug mode enabled. More examples:
- If you want to login with your system accounts on the web browser, run `ttyd login`.
- You can even run a non-shell command like vim, try: `ttyd vim`, the web browser will show you a vim editor.
- Sharing single process with multiple clients: `ttyd tmux new -A -s ttyd vim`, run `tmux new -A -s ttyd` to connect to the tmux session from terminal.
# SSL how-to
Generate SSL CA and self signed server/client certificates:
```
# CA certificate (FQDN must be different from server/client)
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 365 -key ca.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=Acme Root CA" -out ca.crt
# server certificate (for multiple domains, change subjectAltName to: DNS:example.com,DNS:www.example.com)
openssl req -newkey rsa:2048 -nodes -keyout server.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=localhost" -out server.csr
openssl x509 -sha256 -req -extfile <(printf "subjectAltName=DNS:localhost") -days 365 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
# client certificate (the p12/pem format may be useful for some clients)
openssl req -newkey rsa:2048 -nodes -keyout client.key -subj "/C=CN/ST=GD/L=SZ/O=Acme, Inc./CN=client" -out client.csr
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
openssl pkcs12 -in client.p12 -out client.pem -clcerts
```
Then start ttyd:
```
ttyd --ssl --ssl-cert server.crt --ssl-key server.key --ssl-ca ca.crt bash
```
You may want to test the client certificate verification with *curl*(1):
```
curl --insecure --cert client.p12[:password] -v https://localhost:7681
```
If you don't want to enable client certificate verification, remove the `--ssl-ca` option.
# Docker and ttyd
Docker containers are jailed environments which are more secure, this is useful for protecting the host system, you may use ttyd with docker like this:
- Sharing single docker container with multiple clients: docker run -it --rm -p 7681:7681 tsl0922/ttyd.
- Creating new docker container for each client: ttyd docker run -it --rm ubuntu.
# Nginx reverse proxy
Sample config to proxy ttyd under the `/ttyd` path:
```nginx
location ~ ^/ttyd(.*)$ {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:7681/$1;
}
```
# AUTHOR
Shuanglei Tao \<tsl0922@gmail.com\> Visit https://github.com/tsl0922/ttyd to get more information and report bugs.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -1,191 +0,0 @@
#!/bin/bash
#
# Example:
# env BUILD_TARGET=mips ./scripts/cross-build.sh
#
set -eo pipefail
CROSS_ROOT="${CROSS_ROOT:-/opt/cross}"
STAGE_ROOT="${STAGE_ROOT:-/opt/stage}"
BUILD_ROOT="${BUILD_ROOT:-/opt/build}"
BUILD_TARGET="${BUILD_TARGET:-x86_64}"
ZLIB_VERSION="${ZLIB_VERSION:-1.3.1}"
JSON_C_VERSION="${JSON_C_VERSION:-0.17}"
MBEDTLS_VERSION="${MBEDTLS_VERSION:-2.28.5}"
LIBUV_VERSION="${LIBUV_VERSION:-1.44.2}"
LIBWEBSOCKETS_VERSION="${LIBWEBSOCKETS_VERSION:-4.3.3}"
build_zlib() {
echo "=== Building zlib-${ZLIB_VERSION} (${TARGET})..."
curl -fSsLo- "https://zlib.net/zlib-${ZLIB_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
pushd "${BUILD_DIR}"/zlib-"${ZLIB_VERSION}"
env CHOST="${TARGET}" ./configure --static --archs="-fPIC" --prefix="${STAGE_DIR}"
make -j"$(nproc)" install
popd
}
build_json-c() {
echo "=== Building json-c-${JSON_C_VERSION} (${TARGET})..."
curl -fSsLo- "https://s3.amazonaws.com/json-c_releases/releases/json-c-${JSON_C_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
pushd "${BUILD_DIR}/json-c-${JSON_C_VERSION}"
rm -rf build && mkdir -p build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
-DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_INSTALL_PREFIX="${STAGE_DIR}" \
-DBUILD_SHARED_LIBS=OFF \
-DBUILD_TESTING=OFF \
-DDISABLE_THREAD_LOCAL_STORAGE=ON \
..
make -j"$(nproc)" install
popd
}
build_mbedtls() {
echo "=== Building mbedtls-${MBEDTLS_VERSION} (${TARGET})..."
curl -fSsLo- "https://github.com/ARMmbed/mbedtls/archive/v${MBEDTLS_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
pushd "${BUILD_DIR}/mbedtls-${MBEDTLS_VERSION}"
rm -rf build && mkdir -p build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
-DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_INSTALL_PREFIX="${STAGE_DIR}" \
-DENABLE_TESTING=OFF \
..
make -j"$(nproc)" install
popd
}
build_libuv() {
echo "=== Building libuv-${LIBUV_VERSION} (${TARGET})..."
curl -fSsLo- "https://dist.libuv.org/dist/v${LIBUV_VERSION}/libuv-v${LIBUV_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
pushd "${BUILD_DIR}/libuv-v${LIBUV_VERSION}"
./autogen.sh
env CFLAGS=-fPIC ./configure --disable-shared --enable-static --prefix="${STAGE_DIR}" --host="${TARGET}"
make -j"$(nproc)" install
popd
}
install_cmake_cross_file() {
cat << EOF > "${BUILD_DIR}/cross-${TARGET}.cmake"
SET(CMAKE_SYSTEM_NAME $1)
set(CMAKE_C_COMPILER "${TARGET}-gcc")
set(CMAKE_CXX_COMPILER "${TARGET}-g++")
set(CMAKE_FIND_ROOT_PATH "${STAGE_DIR}")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(OPENSSL_USE_STATIC_LIBS TRUE)
EOF
}
build_libwebsockets() {
echo "=== Building libwebsockets-${LIBWEBSOCKETS_VERSION} (${TARGET})..."
curl -fSsLo- "https://github.com/warmcat/libwebsockets/archive/v${LIBWEBSOCKETS_VERSION}.tar.gz" | tar xz -C "${BUILD_DIR}"
pushd "${BUILD_DIR}/libwebsockets-${LIBWEBSOCKETS_VERSION}"
sed -i 's/ websockets_shared//g' cmake/libwebsockets-config.cmake.in
sed -i 's/ OR PC_OPENSSL_FOUND//g' lib/tls/CMakeLists.txt
sed -i '/PC_OPENSSL/d' lib/tls/CMakeLists.txt
rm -rf build && mkdir -p build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
-DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_INSTALL_PREFIX="${STAGE_DIR}" \
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
-DCMAKE_EXE_LINKER_FLAGS="-static" \
-DLWS_WITHOUT_TESTAPPS=ON \
-DLWS_WITH_MBEDTLS=ON \
-DLWS_WITH_LIBUV=ON \
-DLWS_STATIC_PIC=ON \
-DLWS_WITH_SHARED=OFF \
-DLWS_UNIX_SOCK=ON \
-DLWS_IPV6=ON \
-DLWS_ROLE_RAW_FILE=OFF \
-DLWS_WITH_HTTP2=ON \
-DLWS_WITH_HTTP_BASIC_AUTH=OFF \
-DLWS_WITH_UDP=OFF \
-DLWS_WITHOUT_CLIENT=ON \
-DLWS_WITHOUT_EXTENSIONS=OFF \
-DLWS_WITH_LEJP=OFF \
-DLWS_WITH_LEJP_CONF=OFF \
-DLWS_WITH_LWSAC=OFF \
-DLWS_WITH_SEQUENCER=OFF \
..
make -j"$(nproc)" install
popd
}
build_ttyd() {
echo "=== Building ttyd (${TARGET})..."
rm -rf build && mkdir -p build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE="${BUILD_DIR}/cross-${TARGET}.cmake" \
-DCMAKE_INSTALL_PREFIX="${STAGE_DIR}" \
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
-DCMAKE_C_FLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -flto" \
-DCMAKE_EXE_LINKER_FLAGS="-static -no-pie -Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections" \
-DCMAKE_BUILD_TYPE=RELEASE \
..
make install
}
build() {
TARGET="$1"
ALIAS="$2"
STAGE_DIR="${STAGE_ROOT}/${TARGET}"
BUILD_DIR="${BUILD_ROOT}/${TARGET}"
MUSL_CC_URL="https://github.com/tsl0922/musl-toolchains/releases/download/2021-11-23"
COMPONENTS="1"
SYSTEM="Linux"
if [ "$ALIAS" = "win32" ]; then
COMPONENTS=2
SYSTEM="Windows"
fi
echo "=== Installing toolchain ${ALIAS} (${TARGET})..."
mkdir -p "${CROSS_ROOT}" && export PATH="${PATH}:${CROSS_ROOT}/bin"
curl -fSsLo- "${MUSL_CC_URL}/${TARGET}-cross.tgz" | tar xz -C "${CROSS_ROOT}" --strip-components=${COMPONENTS}
echo "=== Building target ${ALIAS} (${TARGET})..."
rm -rf "${STAGE_DIR}" "${BUILD_DIR}"
mkdir -p "${STAGE_DIR}" "${BUILD_DIR}"
export PKG_CONFIG_PATH="${STAGE_DIR}/lib/pkgconfig"
install_cmake_cross_file ${SYSTEM}
build_zlib
build_json-c
build_libuv
build_mbedtls
build_libwebsockets
build_ttyd
}
case ${BUILD_TARGET} in
amd64) BUILD_TARGET="x86_64" ;;
arm64) BUILD_TARGET="aarch64" ;;
armv7) BUILD_TARGET="armv7l" ;;
esac
case ${BUILD_TARGET} in
i686|x86_64|aarch64|mips|mipsel|mips64|mips64el|s390x)
build "${BUILD_TARGET}-linux-musl" "${BUILD_TARGET}"
;;
arm)
build "${BUILD_TARGET}-linux-musleabi" "${BUILD_TARGET}"
;;
armhf)
build arm-linux-musleabihf "${BUILD_TARGET}"
;;
armv7l)
build armv7l-linux-musleabihf "${BUILD_TARGET}"
;;
win32)
build x86_64-w64-mingw32 "${BUILD_TARGET}"
;;
*)
echo "unknown cross target: ${BUILD_TARGET}" && exit 1
esac

View File

@@ -1,27 +0,0 @@
#!/bin/bash
set -eo pipefail
build_libwebsockets() {
svn co https://github.com/msys2/MINGW-packages/trunk/mingw-w64-libwebsockets
sed -i 's/openssl/mbedtls/' mingw-w64-libwebsockets/PKGBUILD
sed -i '/-DCMAKE_INSTALL_PREFIX=${MINGW_PREFIX}/a \ -DLWS_WITH_MBEDTLS=ON \\' mingw-w64-libwebsockets/PKGBUILD
sed -i '/-DCMAKE_INSTALL_PREFIX=${MINGW_PREFIX}/a \ -DLWS_WITH_LIBUV=ON \\' mingw-w64-libwebsockets/PKGBUILD
pushd mingw-w64-libwebsockets
makepkg-mingw --cleanbuild --syncdeps --force --noconfirm
pacman -U *.pkg.tar.zst --noconfirm
popd
}
build_libwebsockets
# workaround for the lib name change
cp ${MINGW_PREFIX}/lib/libuv_a.a ${MINGW_PREFIX}/lib/libuv.a
rm -rf build && mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=RELEASE \
-DCMAKE_FIND_LIBRARY_SUFFIXES=".a" \
-DCMAKE_C_FLAGS="-Os -ffunction-sections -fdata-sections -fno-unwind-tables -fno-asynchronous-unwind-tables -flto" \
-DCMAKE_EXE_LINKER_FLAGS="-static -no-pie -Wl,-s -Wl,-Bsymbolic -Wl,--gc-sections" \
..
cmake --build .

View File

@@ -1,49 +0,0 @@
name: ttyd
adopt-info: ttyd
summary: Share your terminal over the web
description: |
ttyd is a simple command-line tool for sharing terminal over the web
grade: stable
confinement: classic
base: core20
compression: lzo
license: MIT
assumes:
- command-chain
apps:
ttyd:
command: usr/bin/ttyd
command-chain:
- bin/homeishome-launch
parts:
ttyd:
source: https://github.com/tsl0922/ttyd
source-type: git
plugin: cmake
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
build-environment:
- LDFLAGS: "-pthread"
override-pull: |
snapcraftctl pull
snapcraftctl set-version "$(git describe --tags | sed 's/^v//' | cut -d "-" -f1)"
build-packages:
- build-essential
- libjson-c-dev
- libwebsockets-dev
stage-packages:
- libjson-c4
- libwebsockets15
homeishome-launch:
plugin: nil
stage-snaps:
- homeishome-launch

File diff suppressed because it is too large Load Diff

View File

@@ -1,240 +0,0 @@
#include <libwebsockets.h>
#include <string.h>
#include <zlib.h>
#include "html.h"
#include "server.h"
#include "utils.h"
enum { AUTH_OK, AUTH_FAIL, AUTH_ERROR };
static char *html_cache = NULL;
static size_t html_cache_len = 0;
static int send_unauthorized(struct lws *wsi, unsigned int code, enum lws_token_indexes header) {
unsigned char buffer[1024 + LWS_PRE], *p, *end;
p = buffer + LWS_PRE;
end = p + sizeof(buffer) - LWS_PRE;
if (lws_add_http_header_status(wsi, code, &p, end) ||
lws_add_http_header_by_token(wsi, header, (unsigned char *)"Basic realm=\"ttyd\"", 18, &p, end) ||
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
return AUTH_FAIL;
return lws_http_transaction_completed(wsi) ? AUTH_FAIL : AUTH_ERROR;
}
static int check_auth(struct lws *wsi, struct pss_http *pss) {
if (server->auth_header != NULL) {
if (lws_hdr_custom_length(wsi, server->auth_header, strlen(server->auth_header)) > 0) return AUTH_OK;
return send_unauthorized(wsi, HTTP_STATUS_PROXY_AUTH_REQUIRED, WSI_TOKEN_HTTP_PROXY_AUTHENTICATE);
}
if(server->credential != NULL) {
char buf[256];
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
if (len >= 7 && strstr(buf, "Basic ")) {
if (!strcmp(buf + 6, server->credential)) return AUTH_OK;
}
return send_unauthorized(wsi, HTTP_STATUS_UNAUTHORIZED, WSI_TOKEN_HTTP_WWW_AUTHENTICATE);
}
return AUTH_OK;
}
static bool accept_gzip(struct lws *wsi) {
char buf[256];
int len = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING);
return len > 0 && strstr(buf, "gzip") != NULL;
}
static bool uncompress_html(char **output, size_t *output_len) {
if (html_cache == NULL || html_cache_len == 0) {
z_stream stream;
memset(&stream, 0, sizeof(stream));
if (inflateInit2(&stream, 16 + 15) != Z_OK) return false;
html_cache_len = index_html_size;
html_cache = xmalloc(html_cache_len);
stream.avail_in = index_html_len;
stream.avail_out = html_cache_len;
stream.next_in = (void *)index_html;
stream.next_out = (void *)html_cache;
int ret = inflate(&stream, Z_SYNC_FLUSH);
inflateEnd(&stream);
if (ret != Z_STREAM_END) {
free(html_cache);
html_cache = NULL;
html_cache_len = 0;
return false;
}
}
*output = html_cache;
*output_len = html_cache_len;
return true;
}
static void pss_buffer_free(struct pss_http *pss) {
if (pss->buffer != (char *)index_html && pss->buffer != html_cache) free(pss->buffer);
}
static void access_log(struct lws *wsi, const char *path) {
char rip[50];
lws_get_peer_simple(lws_get_network_wsi(wsi), rip, sizeof(rip));
lwsl_notice("HTTP %s - %s\n", path, rip);
}
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
struct pss_http *pss = (struct pss_http *)user;
unsigned char buffer[4096 + LWS_PRE], *p, *end;
char buf[256];
bool done = false;
switch (reason) {
case LWS_CALLBACK_HTTP:
access_log(wsi, (const char *)in);
snprintf(pss->path, sizeof(pss->path), "%s", (const char *)in);
switch (check_auth(wsi, pss)) {
case AUTH_OK:
break;
case AUTH_FAIL:
return 0;
case AUTH_ERROR:
default:
return 1;
}
p = buffer + LWS_PRE;
end = p + sizeof(buffer) - LWS_PRE;
if (strcmp(pss->path, endpoints.token) == 0) {
const char *credential = server->credential != NULL ? server->credential : "";
size_t n = sprintf(buf, "{\"token\": \"%s\"}", credential);
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)"application/json;charset=utf-8", 30, &p, end) ||
lws_add_http_header_content_length(wsi, (unsigned long)n, &p, end) ||
lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
return 1;
pss->buffer = pss->ptr = strdup(buf);
pss->len = n;
lws_callback_on_writable(wsi);
break;
}
// redirects `/base-path` to `/base-path/`
if (strcmp(pss->path, endpoints.parent) == 0) {
if (lws_add_http_header_status(wsi, HTTP_STATUS_FOUND, &p, end) ||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, (unsigned char *)endpoints.index,
(int)strlen(endpoints.index), &p, end) ||
lws_add_http_header_content_length(wsi, 0, &p, end) || lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
return 1;
goto try_to_reuse;
}
if (strcmp(pss->path, endpoints.index) != 0) {
lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
goto try_to_reuse;
}
const char *content_type = "text/html";
if (server->index != NULL) {
int n = lws_serve_http_file(wsi, server->index, content_type, NULL, 0);
if (n < 0 || (n > 0 && lws_http_transaction_completed(wsi))) return 1;
} else {
char *output = (char *)index_html;
size_t output_len = index_html_len;
if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end) ||
lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (const unsigned char *)content_type, 9, &p,
end))
return 1;
#ifdef LWS_WITH_HTTP_STREAM_COMPRESSION
if (!uncompress_html(&output, &output_len)) return 1;
#else
if (accept_gzip(wsi)) {
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING, (unsigned char *)"gzip", 4, &p, end))
return 1;
} else {
if (!uncompress_html(&output, &output_len)) return 1;
}
#endif
if (lws_add_http_header_content_length(wsi, (unsigned long)output_len, &p, end) ||
lws_finalize_http_header(wsi, &p, end) ||
lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS) < 0)
return 1;
pss->buffer = pss->ptr = output;
pss->len = output_len;
lws_callback_on_writable(wsi);
}
break;
case LWS_CALLBACK_HTTP_WRITEABLE:
if (!pss->buffer || pss->len == 0) {
goto try_to_reuse;
}
do {
int n = sizeof(buffer) - LWS_PRE;
int m = lws_get_peer_write_allowance(wsi);
if (m == 0) {
lws_callback_on_writable(wsi);
return 0;
} else if (m != -1 && m < n) {
n = m;
}
if (pss->ptr + n > pss->buffer + pss->len) {
n = (int)(pss->len - (pss->ptr - pss->buffer));
done = true;
}
memcpy(buffer + LWS_PRE, pss->ptr, n);
pss->ptr += n;
if (lws_write_http(wsi, buffer + LWS_PRE, (size_t)n) < n) {
pss_buffer_free(pss);
return -1;
}
} while (!lws_send_pipe_choked(wsi) && !done);
if (!done && pss->ptr < pss->buffer + pss->len) {
lws_callback_on_writable(wsi);
break;
}
pss_buffer_free(pss);
goto try_to_reuse;
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
goto try_to_reuse;
#if (defined(LWS_OPENSSL_SUPPORT) || defined(LWS_WITH_TLS)) && !defined(LWS_WITH_MBEDTLS)
case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
if (!len || (SSL_get_verify_result((SSL *)in) != X509_V_OK)) {
int err = X509_STORE_CTX_get_error((X509_STORE_CTX *)user);
int depth = X509_STORE_CTX_get_error_depth((X509_STORE_CTX *)user);
const char *msg = X509_verify_cert_error_string(err);
lwsl_err("client certificate verification error: %s (%d), depth: %d\n", msg, err, depth);
return 1;
}
break;
#endif
default:
break;
}
return 0;
/* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */
try_to_reuse:
if (lws_http_transaction_completed(wsi)) return -1;
return 0;
}

View File

@@ -1,395 +0,0 @@
#include <errno.h>
#include <json.h>
#include <libwebsockets.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pty.h"
#include "server.h"
#include "utils.h"
// initial message list
static char initial_cmds[] = {SET_WINDOW_TITLE, SET_PREFERENCES};
static int send_initial_message(struct lws *wsi, int index) {
unsigned char message[LWS_PRE + 1 + 4096];
unsigned char *p = &message[LWS_PRE];
char buffer[128];
int n = 0;
char cmd = initial_cmds[index];
switch (cmd) {
case SET_WINDOW_TITLE:
gethostname(buffer, sizeof(buffer) - 1);
n = sprintf((char *)p, "%c%s (%s)", cmd, server->command, buffer);
break;
case SET_PREFERENCES:
n = sprintf((char *)p, "%c%s", cmd, server->prefs_json);
break;
default:
break;
}
return lws_write(wsi, p, (size_t)n, LWS_WRITE_BINARY);
}
static json_object *parse_window_size(const char *buf, size_t len, uint16_t *cols, uint16_t *rows) {
json_tokener *tok = json_tokener_new();
json_object *obj = json_tokener_parse_ex(tok, buf, len);
struct json_object *o = NULL;
if (json_object_object_get_ex(obj, "columns", &o)) *cols = (uint16_t)json_object_get_int(o);
if (json_object_object_get_ex(obj, "rows", &o)) *rows = (uint16_t)json_object_get_int(o);
json_tokener_free(tok);
return obj;
}
static bool check_host_origin(struct lws *wsi) {
char buf[256];
memset(buf, 0, sizeof(buf));
int len = lws_hdr_copy(wsi, buf, (int)sizeof(buf), WSI_TOKEN_ORIGIN);
if (len <= 0) return false;
const char *prot, *address, *path;
int port;
if (lws_parse_uri(buf, &prot, &address, &port, &path)) return false;
if (port == 80 || port == 443) {
sprintf(buf, "%s", address);
} else {
sprintf(buf, "%s:%d", address, port);
}
char host_buf[256];
memset(host_buf, 0, sizeof(host_buf));
len = lws_hdr_copy(wsi, host_buf, (int)sizeof(host_buf), WSI_TOKEN_HOST);
return len > 0 && strcasecmp(buf, host_buf) == 0;
}
static pty_ctx_t *pty_ctx_init(struct pss_tty *pss) {
pty_ctx_t *ctx = xmalloc(sizeof(pty_ctx_t));
ctx->pss = pss;
ctx->ws_closed = false;
return ctx;
}
static void pty_ctx_free(pty_ctx_t *ctx) { free(ctx); }
static void process_read_cb(pty_process *process, pty_buf_t *buf, bool eof) {
pty_ctx_t *ctx = (pty_ctx_t *)process->ctx;
if (ctx->ws_closed) {
pty_buf_free(buf);
return;
}
if (eof && !process_running(process))
ctx->pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006;
else
ctx->pss->pty_buf = buf;
lws_callback_on_writable(ctx->pss->wsi);
}
static void process_exit_cb(pty_process *process) {
pty_ctx_t *ctx = (pty_ctx_t *)process->ctx;
if (ctx->ws_closed) {
lwsl_notice("process killed with signal %d, pid: %d\n", process->exit_signal, process->pid);
goto done;
}
lwsl_notice("process exited with code %d, pid: %d\n", process->exit_code, process->pid);
ctx->pss->process = NULL;
ctx->pss->lws_close_status = process->exit_code == 0 ? 1000 : 1006;
lws_callback_on_writable(ctx->pss->wsi);
done:
pty_ctx_free(ctx);
}
static char **build_args(struct pss_tty *pss) {
int i, n = 0;
char **argv = xmalloc((server->argc + pss->argc + 1) * sizeof(char *));
for (i = 0; i < server->argc; i++) {
argv[n++] = server->argv[i];
}
for (i = 0; i < pss->argc; i++) {
argv[n++] = pss->args[i];
}
argv[n] = NULL;
return argv;
}
static char **build_env(struct pss_tty *pss) {
int i = 0, n = 2;
char **envp = xmalloc(n * sizeof(char *));
// TERM
envp[i] = xmalloc(36);
snprintf(envp[i], 36, "TERM=%s", server->terminal_type);
i++;
// TTYD_USER
if (strlen(pss->user) > 0) {
envp = xrealloc(envp, (++n) * sizeof(char *));
envp[i] = xmalloc(40);
snprintf(envp[i], 40, "TTYD_USER=%s", pss->user);
i++;
}
envp[i] = NULL;
return envp;
}
static bool spawn_process(struct pss_tty *pss, uint16_t columns, uint16_t rows) {
pty_process *process = process_init((void *)pty_ctx_init(pss), server->loop, build_args(pss), build_env(pss));
if (server->cwd != NULL) process->cwd = strdup(server->cwd);
if (columns > 0) process->columns = columns;
if (rows > 0) process->rows = rows;
if (pty_spawn(process, process_read_cb, process_exit_cb) != 0) {
lwsl_err("pty_spawn: %d (%s)\n", errno, strerror(errno));
process_free(process);
return false;
}
lwsl_notice("started process, pid: %d\n", process->pid);
pss->process = process;
lws_callback_on_writable(pss->wsi);
return true;
}
static void wsi_output(struct lws *wsi, pty_buf_t *buf) {
if (buf == NULL) return;
char *message = xmalloc(LWS_PRE + 1 + buf->len);
char *ptr = message + LWS_PRE;
*ptr = OUTPUT;
memcpy(ptr + 1, buf->base, buf->len);
size_t n = buf->len + 1;
if (lws_write(wsi, (unsigned char *)ptr, n, LWS_WRITE_BINARY) < n) {
lwsl_err("write OUTPUT to WS\n");
}
free(message);
}
static bool check_auth(struct lws *wsi, struct pss_tty *pss) {
if (server->auth_header != NULL) {
return lws_hdr_custom_copy(wsi, pss->user, sizeof(pss->user), server->auth_header, strlen(server->auth_header)) > 0;
}
if (server->credential != NULL) {
char buf[256];
size_t n = lws_hdr_copy(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_AUTHORIZATION);
return n >= 7 && strstr(buf, "Basic ") && !strcmp(buf + 6, server->credential);
}
return true;
}
int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
struct pss_tty *pss = (struct pss_tty *)user;
char buf[256];
size_t n = 0;
switch (reason) {
case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
if (server->once && server->client_count > 0) {
lwsl_warn("refuse to serve WS client due to the --once option.\n");
return 1;
}
if (server->max_clients > 0 && server->client_count == server->max_clients) {
lwsl_warn("refuse to serve WS client due to the --max-clients option.\n");
return 1;
}
if (!check_auth(wsi, pss)) return 1;
n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_GET_URI);
#if defined(LWS_ROLE_H2)
if (n <= 0) n = lws_hdr_copy(wsi, pss->path, sizeof(pss->path), WSI_TOKEN_HTTP_COLON_PATH);
#endif
if (strncmp(pss->path, endpoints.ws, n) != 0) {
lwsl_warn("refuse to serve WS client for illegal ws path: %s\n", pss->path);
return 1;
}
if (server->check_origin && !check_host_origin(wsi)) {
lwsl_warn(
"refuse to serve WS client from different origin due to the "
"--check-origin option.\n");
return 1;
}
break;
case LWS_CALLBACK_ESTABLISHED:
pss->initialized = false;
pss->authenticated = false;
pss->wsi = wsi;
pss->lws_close_status = LWS_CLOSE_STATUS_NOSTATUS;
if (server->url_arg) {
while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n++) > 0) {
if (strncmp(buf, "arg=", 4) == 0) {
pss->args = xrealloc(pss->args, (pss->argc + 1) * sizeof(char *));
pss->args[pss->argc] = strdup(&buf[4]);
pss->argc++;
}
}
}
server->client_count++;
lws_get_peer_simple(lws_get_network_wsi(wsi), pss->address, sizeof(pss->address));
lwsl_notice("WS %s - %s, clients: %d\n", pss->path, pss->address, server->client_count);
break;
case LWS_CALLBACK_SERVER_WRITEABLE:
if (!pss->initialized) {
if (pss->initial_cmd_index == sizeof(initial_cmds)) {
pss->initialized = true;
pty_resume(pss->process);
break;
}
if (send_initial_message(wsi, pss->initial_cmd_index) < 0) {
lwsl_err("failed to send initial message, index: %d\n", pss->initial_cmd_index);
lws_close_reason(wsi, LWS_CLOSE_STATUS_UNEXPECTED_CONDITION, NULL, 0);
return -1;
}
pss->initial_cmd_index++;
lws_callback_on_writable(wsi);
break;
}
if (pss->lws_close_status > LWS_CLOSE_STATUS_NOSTATUS) {
lws_close_reason(wsi, pss->lws_close_status, NULL, 0);
return 1;
}
if (pss->pty_buf != NULL) {
wsi_output(wsi, pss->pty_buf);
pty_buf_free(pss->pty_buf);
pss->pty_buf = NULL;
pty_resume(pss->process);
}
break;
case LWS_CALLBACK_RECEIVE:
if (pss->buffer == NULL) {
pss->buffer = xmalloc(len);
pss->len = len;
memcpy(pss->buffer, in, len);
} else {
pss->buffer = xrealloc(pss->buffer, pss->len + len);
memcpy(pss->buffer + pss->len, in, len);
pss->len += len;
}
const char command = pss->buffer[0];
// check auth
if (server->credential != NULL && !pss->authenticated && command != JSON_DATA) {
lwsl_warn("WS client not authenticated\n");
return 1;
}
// check if there are more fragmented messages
if (lws_remaining_packet_payload(wsi) > 0 || !lws_is_final_fragment(wsi)) {
return 0;
}
switch (command) {
case INPUT:
if (!server->writable) break;
int err = pty_write(pss->process, pty_buf_init(pss->buffer + 1, pss->len - 1));
if (err) {
lwsl_err("uv_write: %s (%s)\n", uv_err_name(err), uv_strerror(err));
return -1;
}
break;
case RESIZE_TERMINAL:
if (pss->process == NULL) break;
json_object_put(
parse_window_size(pss->buffer + 1, pss->len - 1, &pss->process->columns, &pss->process->rows));
pty_resize(pss->process);
break;
case PAUSE:
pty_pause(pss->process);
break;
case RESUME:
pty_resume(pss->process);
break;
case JSON_DATA:
if (pss->process != NULL) break;
uint16_t columns = 0;
uint16_t rows = 0;
json_object *obj = parse_window_size(pss->buffer, pss->len, &columns, &rows);
if (server->credential != NULL) {
struct json_object *o = NULL;
if (json_object_object_get_ex(obj, "AuthToken", &o)) {
const char *token = json_object_get_string(o);
if (token != NULL && !strcmp(token, server->credential))
pss->authenticated = true;
else
lwsl_warn("WS authentication failed with token: %s\n", token);
}
if (!pss->authenticated) {
json_object_put(obj);
lws_close_reason(wsi, LWS_CLOSE_STATUS_POLICY_VIOLATION, NULL, 0);
return -1;
}
}
json_object_put(obj);
if (!spawn_process(pss, columns, rows)) return 1;
break;
default:
lwsl_warn("ignored unknown message type: %c\n", command);
break;
}
if (pss->buffer != NULL) {
free(pss->buffer);
pss->buffer = NULL;
}
break;
case LWS_CALLBACK_CLOSED:
if (pss->wsi == NULL) break;
server->client_count--;
lwsl_notice("WS closed from %s, clients: %d\n", pss->address, server->client_count);
if (pss->buffer != NULL) free(pss->buffer);
if (pss->pty_buf != NULL) pty_buf_free(pss->pty_buf);
for (int i = 0; i < pss->argc; i++) {
free(pss->args[i]);
}
if (pss->process != NULL) {
((pty_ctx_t *)pss->process->ctx)->ws_closed = true;
if (process_running(pss->process)) {
pty_pause(pss->process);
lwsl_notice("killing process, pid: %d\n", pss->process->pid);
pty_kill(pss->process, server->sig_code);
}
}
if ((server->once || server->exit_no_conn) && server->client_count == 0) {
lwsl_notice("exiting due to the --once/--exit-no-conn option.\n");
force_exit = true;
lws_cancel_service(context);
exit(0);
}
break;
default:
break;
}
return 0;
}

View File

@@ -1,485 +0,0 @@
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef _WIN32
#include <sys/ioctl.h>
#include <sys/wait.h>
#if defined(__OpenBSD__) || defined(__APPLE__)
#include <util.h>
#elif defined(__FreeBSD__)
#include <libutil.h>
#else
#include <pty.h>
#endif
#if defined(__APPLE__)
#include <crt_externs.h>
#define environ (*_NSGetEnviron())
#else
extern char **environ;
#endif
#endif
#include "pty.h"
#include "utils.h"
#ifdef _WIN32
HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
void (WINAPI *pClosePseudoConsole)(HPCON);
#endif
static void alloc_cb(uv_handle_t *unused, size_t suggested_size, uv_buf_t *buf) {
buf->base = xmalloc(suggested_size);
buf->len = suggested_size;
}
static void close_cb(uv_handle_t *handle) { free(handle); }
static void async_free_cb(uv_handle_t *handle) {
free((uv_async_t *) handle -> data);
}
pty_buf_t *pty_buf_init(char *base, size_t len) {
pty_buf_t *buf = xmalloc(sizeof(pty_buf_t));
buf->base = xmalloc(len);
memcpy(buf->base, base, len);
buf->len = len;
return buf;
}
void pty_buf_free(pty_buf_t *buf) {
if (buf == NULL) return;
if (buf->base != NULL) free(buf->base);
free(buf);
}
static void read_cb(uv_stream_t *stream, ssize_t n, const uv_buf_t *buf) {
uv_read_stop(stream);
pty_process *process = (pty_process *) stream->data;
if (n <= 0) {
if (n == UV_ENOBUFS || n == 0) return;
process->read_cb(process, NULL, true);
goto done;
}
process->read_cb(process, pty_buf_init(buf->base, (size_t) n), false);
done:
free(buf->base);
}
static void write_cb(uv_write_t *req, int unused) {
pty_buf_t *buf = (pty_buf_t *) req->data;
pty_buf_free(buf);
free(req);
}
pty_process *process_init(void *ctx, uv_loop_t *loop, char *argv[], char *envp[]) {
pty_process *process = xmalloc(sizeof(pty_process));
memset(process, 0, sizeof(pty_process));
process->ctx = ctx;
process->loop = loop;
process->argv = argv;
process->envp = envp;
process->columns = 80;
process->rows = 24;
process->exit_code = -1;
return process;
}
bool process_running(pty_process *process) {
return process != NULL && process->pid > 0 && uv_kill(process->pid, 0) == 0;
}
void process_free(pty_process *process) {
if (process == NULL) return;
#ifdef _WIN32
if (process->si.lpAttributeList != NULL) {
DeleteProcThreadAttributeList(process->si.lpAttributeList);
free(process->si.lpAttributeList);
}
if (process->pty != NULL) pClosePseudoConsole(process->pty);
if (process->handle != NULL) CloseHandle(process->handle);
#else
close(process->pty);
uv_thread_join(&process->tid);
#endif
if (process->in != NULL) uv_close((uv_handle_t *) process->in, close_cb);
if (process->out != NULL) uv_close((uv_handle_t *) process->out, close_cb);
if (process->argv != NULL) free(process->argv);
if (process->cwd != NULL) free(process->cwd);
char **p = process->envp;
for (; *p; p++) free(*p);
free(process->envp);
}
void pty_pause(pty_process *process) {
if (process == NULL) return;
if (process->paused) return;
uv_read_stop((uv_stream_t *) process->out);
}
void pty_resume(pty_process *process) {
if (process == NULL) return;
if (!process->paused) return;
process->out->data = process;
uv_read_start((uv_stream_t *) process->out, alloc_cb, read_cb);
}
int pty_write(pty_process *process, pty_buf_t *buf) {
if (process == NULL) {
pty_buf_free(buf);
return UV_ESRCH;
}
uv_buf_t b = uv_buf_init(buf->base, buf->len);
uv_write_t *req = xmalloc(sizeof(uv_write_t));
req->data = buf;
return uv_write(req, (uv_stream_t *) process->in, &b, 1, write_cb);
}
bool pty_resize(pty_process *process) {
if (process == NULL) return false;
if (process->columns <= 0 || process->rows <= 0) return false;
#ifdef _WIN32
COORD size = {(int16_t) process->columns, (int16_t) process->rows};
return pResizePseudoConsole(process->pty, size) == S_OK;
#else
struct winsize size = {process->rows, process->columns, 0, 0};
return ioctl(process->pty, TIOCSWINSZ, &size) == 0;
#endif
}
bool pty_kill(pty_process *process, int sig) {
if (process == NULL) return false;
#ifdef _WIN32
return TerminateProcess(process->handle, 1) != 0;
#else
return uv_kill(-process->pid, sig) == 0;
#endif
}
#ifdef _WIN32
bool conpty_init() {
uv_lib_t kernel;
if (uv_dlopen("kernel32.dll", &kernel)) {
uv_dlclose(&kernel);
return false;
}
static struct {
char *name;
FARPROC *ptr;
} conpty_entry[] = {{"CreatePseudoConsole", (FARPROC *) &pCreatePseudoConsole},
{"ResizePseudoConsole", (FARPROC *) &pResizePseudoConsole},
{"ClosePseudoConsole", (FARPROC *) &pClosePseudoConsole},
{NULL, NULL}};
for (int i = 0; conpty_entry[i].name != NULL && conpty_entry[i].ptr != NULL; i++) {
if (uv_dlsym(&kernel, conpty_entry[i].name, (void **) conpty_entry[i].ptr)) {
uv_dlclose(&kernel);
return false;
}
}
return true;
}
static WCHAR *to_utf16(char *str) {
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
if (len <= 0) return NULL;
WCHAR *wstr = xmalloc((len + 1) * sizeof(WCHAR));
if (len != MultiByteToWideChar(CP_UTF8, 0, str, -1, wstr, len)) {
free(wstr);
return NULL;
}
wstr[len] = L'\0';
return wstr;
}
// convert argv to cmdline for CreateProcessW
static WCHAR *join_args(char **argv) {
char args[256] = {0};
char **ptr = argv;
for (; *ptr; ptr++) {
char *quoted = (char *) quote_arg(*ptr);
size_t arg_len = strlen(args) + 1;
size_t quoted_len = strlen(quoted);
if (arg_len == 1) memset(args, 0, 2);
if (arg_len != 1) strcat(args, " ");
strncat(args, quoted, quoted_len);
if (quoted != *ptr) free(quoted);
}
if (args[255] != '\0') args[255] = '\0'; // truncate
return to_utf16(args);
}
static bool conpty_setup(HPCON *hnd, COORD size, STARTUPINFOEXW *si_ex, char **in_name, char **out_name) {
static int count = 0;
char buf[256];
HPCON pty = INVALID_HANDLE_VALUE;
SECURITY_ATTRIBUTES sa = {0};
HANDLE in_pipe = INVALID_HANDLE_VALUE;
HANDLE out_pipe = INVALID_HANDLE_VALUE;
const DWORD open_mode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE;
const DWORD pipe_mode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
DWORD pid = GetCurrentProcessId();
bool ret = false;
sa.nLength = sizeof(sa);
snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-in-%d-%d", pid, count);
*in_name = strdup(buf);
snprintf(buf, sizeof(buf), "\\\\.\\pipe\\ttyd-term-out-%d-%d", pid, count);
*out_name = strdup(buf);
in_pipe = CreateNamedPipeA(*in_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa);
out_pipe = CreateNamedPipeA(*out_name, open_mode, pipe_mode, 1, 0, 0, 30000, &sa);
if (in_pipe == INVALID_HANDLE_VALUE || out_pipe == INVALID_HANDLE_VALUE) {
print_error("CreateNamedPipeA");
goto failed;
}
HRESULT hr = pCreatePseudoConsole(size, in_pipe, out_pipe, 0, &pty);
if (FAILED(hr)) {
print_error("CreatePseudoConsole");
goto failed;
}
si_ex->StartupInfo.cb = sizeof(STARTUPINFOEXW);
si_ex->StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
si_ex->StartupInfo.hStdError = NULL;
si_ex->StartupInfo.hStdInput = NULL;
si_ex->StartupInfo.hStdOutput = NULL;
size_t bytes_required;
InitializeProcThreadAttributeList(NULL, 1, 0, &bytes_required);
si_ex->lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST) xmalloc(bytes_required);
if (!InitializeProcThreadAttributeList(si_ex->lpAttributeList, 1, 0, &bytes_required)) {
print_error("InitializeProcThreadAttributeList");
goto failed;
}
if (!UpdateProcThreadAttribute(si_ex->lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pty, sizeof(HPCON),
NULL, NULL)) {
print_error("UpdateProcThreadAttribute");
goto failed;
}
count++;
*hnd = pty;
ret = true;
goto done;
failed:
ret = false;
free(*in_name);
*in_name = NULL;
free(*out_name);
*out_name = NULL;
done:
if (in_pipe != INVALID_HANDLE_VALUE) CloseHandle(in_pipe);
if (out_pipe != INVALID_HANDLE_VALUE) CloseHandle(out_pipe);
return ret;
}
static void connect_cb(uv_connect_t *req, int status) { free(req); }
static void CALLBACK conpty_exit(void *context, BOOLEAN unused) {
pty_process *process = (pty_process *) context;
uv_async_send(&process->async);
}
static void async_cb(uv_async_t *async) {
pty_process *process = (pty_process *) async->data;
UnregisterWait(process->wait);
DWORD exit_code;
GetExitCodeProcess(process->handle, &exit_code);
process->exit_code = (int) exit_code;
process->exit_signal = 1;
process->exit_cb(process);
uv_close((uv_handle_t *) async, async_free_cb);
process_free(process);
}
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) {
char *in_name = NULL;
char *out_name = NULL;
DWORD flags = EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT;
COORD size = {(int16_t) process->columns, (int16_t) process->rows};
if (!conpty_setup(&process->pty, size, &process->si, &in_name, &out_name)) return 1;
SetConsoleCtrlHandler(NULL, FALSE);
int status = 1;
process->in = xmalloc(sizeof(uv_pipe_t));
process->out = xmalloc(sizeof(uv_pipe_t));
uv_pipe_init(process->loop, process->in, 0);
uv_pipe_init(process->loop, process->out, 0);
uv_connect_t *in_req = xmalloc(sizeof(uv_connect_t));
uv_connect_t *out_req = xmalloc(sizeof(uv_connect_t));
uv_pipe_connect(in_req, process->in, in_name, connect_cb);
uv_pipe_connect(out_req, process->out, out_name, connect_cb);
PROCESS_INFORMATION pi = {0};
WCHAR *cmdline, *cwd;
cmdline = join_args(process->argv);
if (cmdline == NULL) goto cleanup;
if (process->envp != NULL) {
char **p = process->envp;
for (; *p; p++) {
WCHAR *env = to_utf16(*p);
if (env == NULL) goto cleanup;
_wputenv(env);
free(env);
}
}
if (process->cwd != NULL) {
cwd = to_utf16(process->cwd);
if (cwd == NULL) goto cleanup;
}
if (!CreateProcessW(NULL, cmdline, NULL, NULL, FALSE, flags, NULL, cwd, &process->si.StartupInfo, &pi)) {
print_error("CreateProcessW");
DWORD exitCode = 0;
if (GetExitCodeProcess(pi.hProcess, &exitCode)) printf("== exit code: %d\n", exitCode);
goto cleanup;
}
process->pid = pi.dwProcessId;
process->handle = pi.hProcess;
process->paused = true;
process->read_cb = read_cb;
process->exit_cb = exit_cb;
process->async.data = process;
uv_async_init(process->loop, &process->async, async_cb);
if (!RegisterWaitForSingleObject(&process->wait, pi.hProcess, conpty_exit, process, INFINITE, WT_EXECUTEONLYONCE)) {
print_error("RegisterWaitForSingleObject");
goto cleanup;
}
status = 0;
cleanup:
if (in_name != NULL) free(in_name);
if (out_name != NULL) free(out_name);
if (cmdline != NULL) free(cmdline);
if (cwd != NULL) free(cwd);
return status;
}
#else
static bool fd_set_cloexec(const int fd) {
int flags = fcntl(fd, F_GETFD);
if (flags < 0) return false;
return (flags & FD_CLOEXEC) == 0 || fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1;
}
static bool fd_duplicate(int fd, uv_pipe_t *pipe) {
int fd_dup = dup(fd);
if (fd_dup < 0) return false;
if (!fd_set_cloexec(fd_dup)) return false;
int status = uv_pipe_open(pipe, fd_dup);
if (status) close(fd_dup);
return status == 0;
}
static void wait_cb(void *arg) {
pty_process *process = (pty_process *) arg;
pid_t pid;
int stat;
do
pid = waitpid(process->pid, &stat, 0);
while (pid != process->pid && errno == EINTR);
if (WIFEXITED(stat)) {
process->exit_code = WEXITSTATUS(stat);
}
if (WIFSIGNALED(stat)) {
int sig = WTERMSIG(stat);
process->exit_code = 128 + sig;
process->exit_signal = sig;
}
uv_async_send(&process->async);
}
static void async_cb(uv_async_t *async) {
pty_process *process = (pty_process *) async->data;
process->exit_cb(process);
uv_close((uv_handle_t *) async, async_free_cb);
process_free(process);
}
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb) {
int status = 0;
uv_disable_stdio_inheritance();
int master, pid;
struct winsize size = {process->rows, process->columns, 0, 0};
pid = forkpty(&master, NULL, NULL, &size);
if (pid < 0) {
status = -errno;
return status;
} else if (pid == 0) {
setsid();
if (process->cwd != NULL) chdir(process->cwd);
if (process->envp != NULL) {
char **p = process->envp;
for (; *p; p++) putenv(*p);
}
int ret = execvp(process->argv[0], process->argv);
if (ret < 0) {
perror("execvp failed\n");
_exit(-errno);
}
}
int flags = fcntl(master, F_GETFL);
if (flags == -1) {
status = -errno;
goto error;
}
if (fcntl(master, F_SETFL, flags | O_NONBLOCK) == -1) {
status = -errno;
goto error;
}
if (!fd_set_cloexec(master)) {
status = -errno;
goto error;
}
process->in = xmalloc(sizeof(uv_pipe_t));
process->out = xmalloc(sizeof(uv_pipe_t));
uv_pipe_init(process->loop, process->in, 0);
uv_pipe_init(process->loop, process->out, 0);
if (!fd_duplicate(master, process->in) || !fd_duplicate(master, process->out)) {
status = -errno;
goto error;
}
process->pty = master;
process->pid = pid;
process->paused = true;
process->read_cb = read_cb;
process->exit_cb = exit_cb;
process->async.data = process;
uv_async_init(process->loop, &process->async, async_cb);
uv_thread_create(&process->tid, wait_cb, process);
return 0;
error:
close(master);
uv_kill(pid, SIGKILL);
waitpid(pid, NULL, 0);
return status;
}
#endif

View File

@@ -1,68 +0,0 @@
#ifndef TTYD_PTY_H
#define TTYD_PTY_H
#include <stdbool.h>
#include <stdint.h>
#include <uv.h>
#ifdef _WIN32
#ifndef HPCON
#define HPCON VOID *
#endif
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
#endif
bool conpty_init();
#endif
typedef struct {
char *base;
size_t len;
} pty_buf_t;
struct pty_process_;
typedef struct pty_process_ pty_process;
typedef void (*pty_read_cb)(pty_process *, pty_buf_t *, bool);
typedef void (*pty_exit_cb)(pty_process *);
struct pty_process_ {
int pid, exit_code, exit_signal;
uint16_t columns, rows;
#ifdef _WIN32
STARTUPINFOEXW si;
HPCON pty;
HANDLE handle;
HANDLE wait;
#else
pid_t pty;
uv_thread_t tid;
#endif
char **argv;
char **envp;
char *cwd;
uv_loop_t *loop;
uv_async_t async;
uv_pipe_t *in;
uv_pipe_t *out;
bool paused;
pty_read_cb read_cb;
pty_exit_cb exit_cb;
void *ctx;
};
pty_buf_t *pty_buf_init(char *base, size_t len);
void pty_buf_free(pty_buf_t *buf);
pty_process *process_init(void *ctx, uv_loop_t *loop, char *argv[], char *envp[]);
bool process_running(pty_process *process);
void process_free(pty_process *process);
int pty_spawn(pty_process *process, pty_read_cb read_cb, pty_exit_cb exit_cb);
void pty_pause(pty_process *process);
void pty_resume(pty_process *process);
int pty_write(pty_process *process, pty_buf_t *buf);
bool pty_resize(pty_process *process);
bool pty_kill(pty_process *process, int sig);
#endif // TTYD_PTY_H

View File

@@ -1,634 +0,0 @@
#include "server.h"
#include <errno.h>
#include <getopt.h>
#include <json.h>
#include <libwebsockets.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "utils.h"
#ifndef TTYD_VERSION
#define TTYD_VERSION "unknown"
#endif
volatile bool force_exit = false;
struct lws_context *context;
struct server *server;
struct endpoints endpoints = {"/ws", "/", "/token", ""};
extern int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
extern int callback_tty(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len);
// websocket protocols
static const struct lws_protocols protocols[] = {{"http-only", callback_http, sizeof(struct pss_http), 0},
{"tty", callback_tty, sizeof(struct pss_tty), 0},
{NULL, NULL, 0, 0}};
#ifndef LWS_WITHOUT_EXTENSIONS
// websocket extensions
static const struct lws_extension extensions[] = {
{"permessage-deflate", lws_extension_callback_pm_deflate, "permessage-deflate"},
{"deflate-frame", lws_extension_callback_pm_deflate, "deflate_frame"},
{NULL, NULL, NULL}};
#endif
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
static const uint32_t backoff_ms[] = {1000, 2000, 3000, 4000, 5000};
static lws_retry_bo_t retry = {
.retry_ms_table = backoff_ms,
.retry_ms_table_count = LWS_ARRAY_SIZE(backoff_ms),
.conceal_count = LWS_ARRAY_SIZE(backoff_ms),
.secs_since_valid_ping = 5,
.secs_since_valid_hangup = 10,
.jitter_percent = 0,
};
#endif
// command line options
static const struct option options[] = {{"port", required_argument, NULL, 'p'},
{"interface", required_argument, NULL, 'i'},
{"socket-owner", required_argument, NULL, 'U'},
{"credential", required_argument, NULL, 'c'},
{"auth-header", required_argument, NULL, 'H'},
{"uid", required_argument, NULL, 'u'},
{"gid", required_argument, NULL, 'g'},
{"signal", required_argument, NULL, 's'},
{"cwd", required_argument, NULL, 'w'},
{"index", required_argument, NULL, 'I'},
{"base-path", required_argument, NULL, 'b'},
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
{"ping-interval", required_argument, NULL, 'P'},
#endif
{"srv-buf-size", required_argument, NULL, 'f'},
{"ipv6", no_argument, NULL, '6'},
{"ssl", no_argument, NULL, 'S'},
{"ssl-cert", required_argument, NULL, 'C'},
{"ssl-key", required_argument, NULL, 'K'},
{"ssl-ca", required_argument, NULL, 'A'},
{"url-arg", no_argument, NULL, 'a'},
{"writable", no_argument, NULL, 'W'},
{"terminal-type", required_argument, NULL, 'T'},
{"client-option", required_argument, NULL, 't'},
{"check-origin", no_argument, NULL, 'O'},
{"max-clients", required_argument, NULL, 'm'},
{"once", no_argument, NULL, 'o'},
{"exit-no-conn", no_argument, NULL, 'q'},
{"browser", no_argument, NULL, 'B'},
{"debug", required_argument, NULL, 'd'},
{"version", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, 0, 0}};
static const char *opt_string = "p:i:U:c:H:u:g:s:w:I:b:P:f:6aSC:K:A:Wt:T:Om:oqBd:vh";
static void print_help() {
// clang-format off
fprintf(stderr, "ttyd is a tool for sharing terminal over the web\n\n"
"USAGE:\n"
" ttyd [options] <command> [<arguments...>]\n\n"
"VERSION:\n"
" %s\n\n"
"OPTIONS:\n"
" -p, --port Port to listen (default: 7681, use `0` for random port)\n"
" -i, --interface Network interface to bind (eg: eth0), or UNIX domain socket path (eg: /var/run/ttyd.sock)\n"
" -U, --socket-owner User owner of the UNIX domain socket file, when enabled (eg: user:group)\n"
" -c, --credential Credential for basic authentication (format: username:password)\n"
" -H, --auth-header HTTP Header name for auth proxy, this will configure ttyd to let a HTTP reverse proxy handle authentication\n"
" -u, --uid User id to run with\n"
" -g, --gid Group id to run with\n"
" -s, --signal Signal to send to the command when exit it (default: 1, SIGHUP)\n"
" -w, --cwd Working directory to be set for the child program\n"
" -a, --url-arg Allow client to send command line arguments in URL (eg: http://localhost:7681?arg=foo&arg=bar)\n"
" -W, --writable Allow clients to write to the TTY (readonly by default)\n"
" -t, --client-option Send option to client (format: key=value), repeat to add more options\n"
" -T, --terminal-type Terminal type to report, default: xterm-256color\n"
" -O, --check-origin Do not allow websocket connection from different origin\n"
" -m, --max-clients Maximum clients to support (default: 0, no limit)\n"
" -o, --once Accept only one client and exit on disconnection\n"
" -q, --exit-no-conn Exit on all clients disconnection\n"
" -B, --browser Open terminal with the default system browser\n"
" -I, --index Custom index.html path\n"
" -b, --base-path Expected base path for requests coming from a reverse proxy (eg: /mounted/here, max length: 128)\n"
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
" -P, --ping-interval Websocket ping interval(sec) (default: 5)\n"
#endif
" -f, --srv-buf-size Maximum chunk of file (in bytes) that can be sent at once, a larger value may improve throughput (default: 4096)\n"
#ifdef LWS_WITH_IPV6
" -6, --ipv6 Enable IPv6 support\n"
#endif
#if defined(LWS_OPENSSL_SUPPORT) || defined(LWS_WITH_TLS)
" -S, --ssl Enable SSL\n"
" -C, --ssl-cert SSL certificate file path\n"
" -K, --ssl-key SSL key file path\n"
" -A, --ssl-ca SSL CA file path for client certificate verification\n"
#endif
" -d, --debug Set log level (default: 7)\n"
" -v, --version Print the version and exit\n"
" -h, --help Print this text and exit\n\n"
"Visit https://github.com/tsl0922/ttyd to get more information and report bugs.\n",
TTYD_VERSION
);
// clang-format on
}
static void print_config() {
lwsl_notice("tty configuration:\n");
if (server->credential != NULL) lwsl_notice(" credential: %s\n", server->credential);
lwsl_notice(" start command: %s\n", server->command);
lwsl_notice(" close signal: %s (%d)\n", server->sig_name, server->sig_code);
lwsl_notice(" terminal type: %s\n", server->terminal_type);
if (endpoints.parent[0]) {
lwsl_notice("endpoints:\n");
lwsl_notice(" base-path: %s\n", endpoints.parent);
lwsl_notice(" index : %s\n", endpoints.index);
lwsl_notice(" token : %s\n", endpoints.token);
lwsl_notice(" websocket: %s\n", endpoints.ws);
}
if (server->auth_header != NULL) lwsl_notice(" auth header: %s\n", server->auth_header);
if (server->check_origin) lwsl_notice(" check origin: true\n");
if (server->url_arg) lwsl_notice(" allow url arg: true\n");
if (server->max_clients > 0) lwsl_notice(" max clients: %d\n", server->max_clients);
if (server->once) lwsl_notice(" once: true\n");
if (server->exit_no_conn) lwsl_notice(" exit_no_conn: true\n");
if (server->index != NULL) lwsl_notice(" custom index.html: %s\n", server->index);
if (server->cwd != NULL) lwsl_notice(" working directory: %s\n", server->cwd);
if (!server->writable) lwsl_warn("The --writable option is not set, will start in readonly mode\n");
}
static struct server *server_new(int argc, char **argv, int start) {
struct server *ts;
size_t cmd_len = 0;
ts = xmalloc(sizeof(struct server));
memset(ts, 0, sizeof(struct server));
ts->client_count = 0;
ts->sig_code = SIGHUP;
sprintf(ts->terminal_type, "%s", "xterm-256color");
get_sig_name(ts->sig_code, ts->sig_name, sizeof(ts->sig_name));
if (start == argc) return ts;
int cmd_argc = argc - start;
char **cmd_argv = &argv[start];
ts->argv = xmalloc(sizeof(char *) * (cmd_argc + 1));
for (int i = 0; i < cmd_argc; i++) {
ts->argv[i] = strdup(cmd_argv[i]);
cmd_len += strlen(ts->argv[i]);
if (i != cmd_argc - 1) {
cmd_len++; // for space
}
}
ts->argv[cmd_argc] = NULL;
ts->argc = cmd_argc;
ts->command = xmalloc(cmd_len + 1);
char *ptr = ts->command;
for (int i = 0; i < cmd_argc; i++) {
size_t len = strlen(ts->argv[i]);
ptr = memcpy(ptr, ts->argv[i], len + 1) + len;
if (i != cmd_argc - 1) {
*ptr++ = ' ';
}
}
*ptr = '\0'; // null terminator
ts->loop = xmalloc(sizeof *ts->loop);
uv_loop_init(ts->loop);
return ts;
}
static void server_free(struct server *ts) {
if (ts == NULL) return;
if (ts->credential != NULL) free(ts->credential);
if (ts->auth_header != NULL) free(ts->auth_header);
if (ts->index != NULL) free(ts->index);
if (ts->cwd != NULL) free(ts->cwd);
free(ts->command);
free(ts->prefs_json);
char **p = ts->argv;
for (; *p; p++) free(*p);
free(ts->argv);
if (strlen(ts->socket_path) > 0) {
struct stat st;
if (!stat(ts->socket_path, &st)) {
unlink(ts->socket_path);
}
}
uv_loop_close(ts->loop);
free(ts->loop);
free(ts);
}
static void signal_cb(uv_signal_t *watcher, int signum) {
char sig_name[20];
switch (watcher->signum) {
case SIGINT:
case SIGTERM:
get_sig_name(watcher->signum, sig_name, sizeof(sig_name));
lwsl_notice("received signal: %s (%d), exiting...\n", sig_name, watcher->signum);
break;
default:
signal(SIGABRT, SIG_DFL);
abort();
}
if (force_exit) exit(EXIT_FAILURE);
force_exit = true;
lws_cancel_service(context);
uv_stop(server->loop);
lwsl_notice("send ^C to force exit.\n");
}
static int parse_int(char *name, char *str) {
char *endptr;
errno = 0;
long val = strtol(str, &endptr, 0);
if (errno != 0 || endptr == str) {
fprintf(stderr, "ttyd: invalid value for %s: %s\n", name, str);
exit(EXIT_FAILURE);
}
return (int)val;
}
static int calc_command_start(int argc, char **argv) {
// make a copy of argc and argv
int argc_copy = argc;
char **argv_copy = xmalloc(sizeof(char *) * argc);
for (int i = 0; i < argc; i++) {
argv_copy[i] = strdup(argv[i]);
}
// do not print error message for invalid option
opterr = 0;
while (getopt_long(argc_copy, argv_copy, opt_string, options, NULL) != -1)
;
int start = argc;
if (optind < argc) {
char *command = argv_copy[optind];
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], command) == 0) {
start = i;
break;
}
}
}
// free argv copy
for (int i = 0; i < argc; i++) {
free(argv_copy[i]);
}
free(argv_copy);
// reset for next use
opterr = 1;
optind = 0;
return start;
}
int main(int argc, char **argv) {
if (argc == 1) {
print_help();
return 0;
}
#ifdef _WIN32
if (!conpty_init()) {
fprintf(stderr, "ERROR: ConPTY init failed! Make sure you are on Windows 10 1809 or later.");
return 1;
}
#endif
int start = calc_command_start(argc, argv);
server = server_new(argc, argv, start);
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = 7681;
info.iface = NULL;
info.protocols = protocols;
info.gid = -1;
info.uid = -1;
info.max_http_header_pool = 16;
info.options = LWS_SERVER_OPTION_LIBUV | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_DISABLE_IPV6;
#ifndef LWS_WITHOUT_EXTENSIONS
info.extensions = extensions;
#endif
info.max_http_header_data = 65535;
int debug_level = LLL_ERR | LLL_WARN | LLL_NOTICE;
char iface[128] = "";
char socket_owner[128] = "";
bool browser = false;
bool ssl = false;
char cert_path[1024] = "";
char key_path[1024] = "";
char ca_path[1024] = "";
struct json_object *client_prefs = json_object_new_object();
#ifdef _WIN32
json_object_object_add(client_prefs, "isWindows", json_object_new_boolean(true));
#endif
// parse command line options
int c;
while ((c = getopt_long(start, argv, opt_string, options, NULL)) != -1) {
switch (c) {
case 'h':
print_help();
return 0;
case 'v':
printf("ttyd version %s\n", TTYD_VERSION);
return 0;
case 'd':
debug_level = parse_int("debug", optarg);
break;
case 'a':
server->url_arg = true;
break;
case 'W':
server->writable = true;
break;
case 'O':
server->check_origin = true;
break;
case 'm':
server->max_clients = parse_int("max-clients", optarg);
break;
case 'o':
server->once = true;
break;
case 'q':
server->exit_no_conn = true;
break;
case 'B':
browser = true;
break;
case 'p':
info.port = parse_int("port", optarg);
if (info.port < 0) {
fprintf(stderr, "ttyd: invalid port: %s\n", optarg);
return -1;
}
break;
case 'i':
strncpy(iface, optarg, sizeof(iface) - 1);
iface[sizeof(iface) - 1] = '\0';
break;
case 'U':
strncpy(socket_owner, optarg, sizeof(socket_owner) - 1);
socket_owner[sizeof(socket_owner) - 1] = '\0';
break;
case 'c':
if (strchr(optarg, ':') == NULL) {
fprintf(stderr, "ttyd: invalid credential, format: username:password\n");
return -1;
}
char b64_text[256];
lws_b64_encode_string(optarg, strlen(optarg), b64_text, sizeof(b64_text));
server->credential = strdup(b64_text);
break;
case 'H':
server->auth_header = strdup(optarg);
break;
case 'u':
info.uid = parse_int("uid", optarg);
break;
case 'g':
info.gid = parse_int("gid", optarg);
break;
case 's': {
int sig = get_sig(optarg);
if (sig > 0) {
server->sig_code = sig;
get_sig_name(sig, server->sig_name, sizeof(server->sig_name));
} else {
fprintf(stderr, "ttyd: invalid signal: %s\n", optarg);
return -1;
}
} break;
case 'w':
server->cwd = strdup(optarg);
break;
case 'I':
if (!strncmp(optarg, "~/", 2)) {
const char *home = getenv("HOME");
server->index = malloc(strlen(home) + strlen(optarg) - 1);
sprintf(server->index, "%s%s", home, optarg + 1);
} else {
server->index = strdup(optarg);
}
struct stat st;
if (stat(server->index, &st) == -1) {
fprintf(stderr, "Can not stat index.html: %s, error: %s\n", server->index, strerror(errno));
return -1;
}
if (S_ISDIR(st.st_mode)) {
fprintf(stderr, "Invalid index.html path: %s, is it a dir?\n", server->index);
return -1;
}
break;
case 'b': {
char path[128];
strncpy(path, optarg, 128);
size_t len = strlen(path);
while (len && path[len - 1] == '/') path[--len] = 0; // trim trailing /
if (!len) break;
#define sc(f) \
strncpy(path + len, endpoints.f, 128 - len); \
endpoints.f = strdup(path);
sc(ws) sc(index) sc(token) sc(parent)
#undef sc
} break;
#if LWS_LIBRARY_VERSION_NUMBER >= 4000000
case 'P': {
int interval = parse_int("ping-interval", optarg);
if (interval < 0) {
fprintf(stderr, "ttyd: invalid ping interval: %s\n", optarg);
return -1;
}
retry.secs_since_valid_ping = interval;
retry.secs_since_valid_hangup = interval + 7;
} break;
#endif
case 'f': {
int serv_buf_size = parse_int("srv-buf-size", optarg);
if (serv_buf_size < 0) {
fprintf(stderr, "ttyd: invalid srv-buf-size: %s\n", optarg);
return -1;
}
info.pt_serv_buf_size = serv_buf_size;
} break;
case '6':
info.options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6);
break;
#if defined(LWS_OPENSSL_SUPPORT) || defined(LWS_WITH_TLS)
case 'S':
ssl = true;
break;
case 'C':
strncpy(cert_path, optarg, sizeof(cert_path) - 1);
cert_path[sizeof(cert_path) - 1] = '\0';
break;
case 'K':
strncpy(key_path, optarg, sizeof(key_path) - 1);
key_path[sizeof(key_path) - 1] = '\0';
break;
case 'A':
strncpy(ca_path, optarg, sizeof(ca_path) - 1);
ca_path[sizeof(ca_path) - 1] = '\0';
break;
#endif
case 'T':
strncpy(server->terminal_type, optarg, sizeof(server->terminal_type) - 1);
server->terminal_type[sizeof(server->terminal_type) - 1] = '\0';
break;
case '?':
break;
case 't':
optind--;
for (; optind < start && *argv[optind] != '-'; optind++) {
char *option = optarg;
char *key = strsep(&option, "=");
if (key == NULL) {
fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg);
return -1;
}
char *value = strsep(&option, "=");
if (value == NULL) {
fprintf(stderr, "ttyd: invalid client option: %s, format: key=value\n", optarg);
return -1;
}
struct json_object *obj = json_tokener_parse(value);
json_object_object_add(client_prefs, key, obj != NULL ? obj : json_object_new_string(value));
}
break;
default:
print_help();
return -1;
}
}
server->prefs_json = strdup(json_object_to_json_string(client_prefs));
json_object_put(client_prefs);
if (server->command == NULL || strlen(server->command) == 0) {
fprintf(stderr, "ttyd: missing start command\n");
return -1;
}
lws_set_log_level(debug_level, NULL);
char server_hdr[128] = "";
sprintf(server_hdr, "ttyd/%s (libwebsockets/%s)", TTYD_VERSION, LWS_LIBRARY_VERSION);
info.server_string = server_hdr;
#if LWS_LIBRARY_VERSION_NUMBER < 4000000
info.ws_ping_pong_interval = 5;
#else
info.retry_and_idle_policy = &retry;
#endif
if (strlen(iface) > 0) {
info.iface = iface;
if (endswith(info.iface, ".sock") || endswith(info.iface, ".socket")) {
#if defined(LWS_USE_UNIX_SOCK) || defined(LWS_WITH_UNIX_SOCK)
info.options |= LWS_SERVER_OPTION_UNIX_SOCK;
info.port = 0; // warmcat/libwebsockets#1985
strncpy(server->socket_path, info.iface, sizeof(server->socket_path) - 1);
if (strlen(socket_owner) > 0) {
info.unix_socket_perms = socket_owner;
}
#else
fprintf(stderr, "libwebsockets is not compiled with UNIX domain socket support");
return -1;
#endif
}
}
#if defined(LWS_OPENSSL_SUPPORT) || defined(LWS_WITH_TLS)
if (ssl) {
info.ssl_cert_filepath = cert_path;
info.ssl_private_key_filepath = key_path;
#ifndef LWS_WITH_MBEDTLS
info.ssl_options_set = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
#endif
if (strlen(ca_path) > 0) {
info.ssl_ca_filepath = ca_path;
info.options |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
}
info.options |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT | LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
}
#endif
lwsl_notice("ttyd %s (libwebsockets %s)\n", TTYD_VERSION, LWS_LIBRARY_VERSION);
print_config();
// lws custom header requires lower case name, and terminating :
if (server->auth_header != NULL) {
size_t auth_header_len = strlen(server->auth_header);
server->auth_header = xrealloc(server->auth_header, auth_header_len + 2);
strcat(server->auth_header + auth_header_len, ":");
lowercase(server->auth_header);
}
void *foreign_loops[1];
foreign_loops[0] = server->loop;
info.foreign_loops = foreign_loops;
info.options |= LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
context = lws_create_context(&info);
if (context == NULL) {
lwsl_err("libwebsockets context creation failed\n");
return 1;
}
struct lws_vhost *vhost = lws_create_vhost(context, &info);
if (vhost == NULL) {
lwsl_err("libwebsockets vhost creation failed\n");
return 1;
}
int port = lws_get_vhost_listen_port(vhost);
lwsl_notice(" Listening on port: %d\n", port);
if (browser) {
char url[30];
sprintf(url, "%s://localhost:%d", ssl ? "https" : "http", port);
open_uri(url);
}
#define sig_count 2
int sig_nums[] = {SIGINT, SIGTERM};
uv_signal_t signals[sig_count];
for (int i = 0; i < sig_count; i++) {
uv_signal_init(server->loop, &signals[i]);
uv_signal_start(&signals[i], signal_cb, sig_nums[i]);
}
lws_service(context, 0);
for (int i = 0; i < sig_count; i++) {
uv_signal_stop(&signals[i]);
}
#undef sig_count
lws_context_destroy(context);
// cleanup
server_free(server);
return 0;
}

View File

@@ -1,86 +0,0 @@
#include <libwebsockets.h>
#include <stdbool.h>
#include <uv.h>
#include "pty.h"
// client message
#define INPUT '0'
#define RESIZE_TERMINAL '1'
#define PAUSE '2'
#define RESUME '3'
#define JSON_DATA '{'
// server message
#define OUTPUT '0'
#define SET_WINDOW_TITLE '1'
#define SET_PREFERENCES '2'
// url paths
struct endpoints {
char *ws;
char *index;
char *token;
char *parent;
};
extern volatile bool force_exit;
extern struct lws_context *context;
extern struct server *server;
extern struct endpoints endpoints;
struct pss_http {
char path[128];
char *buffer;
char *ptr;
size_t len;
};
struct pss_tty {
bool initialized;
int initial_cmd_index;
bool authenticated;
char user[30];
char address[50];
char path[128];
char **args;
int argc;
struct lws *wsi;
char *buffer;
size_t len;
pty_process *process;
pty_buf_t *pty_buf;
int lws_close_status;
};
typedef struct {
struct pss_tty *pss;
bool ws_closed;
} pty_ctx_t;
struct server {
int client_count; // client count
char *prefs_json; // client preferences
char *credential; // encoded basic auth credential
char *auth_header; // header name used for auth proxy
char *index; // custom index.html
char *command; // full command line
char **argv; // command with arguments
int argc; // command + arguments count
char *cwd; // working directory
int sig_code; // close signal
char sig_name[20]; // human readable signal string
bool url_arg; // allow client to send cli arguments in URL
bool writable; // whether clients to write to the TTY
bool check_origin; // whether allow websocket connection from different origin
int max_clients; // maximum clients to support
bool once; // whether accept only one client and exit on disconnection
bool exit_no_conn; // whether exit on all clients disconnection
char socket_path[255]; // UNIX domain socket path
char terminal_type[30]; // terminal type to report
uv_loop_t *loop; // the libuv event loop
};

View File

@@ -1,163 +0,0 @@
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(__linux__) && !defined(__ANDROID__)
const char *sys_signame[NSIG] = {
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "UNUSED", "FPE", "KILL", "USR1",
"SEGV", "USR2", "PIPE", "ALRM", "TERM", "STKFLT", "CHLD", "CONT", "STOP", "TSTP", "TTIN",
"TTOU", "URG", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "IO", "PWR", "SYS", NULL};
#endif
#if defined(_WIN32) || defined(__CYGWIN__)
#include <windows.h>
#undef NSIG
#define NSIG 33
const char *sys_signame[NSIG] = {
"zero", "HUP", "INT", "QUIT", "ILL", "TRAP", "IOT", "EMT", "FPE", "KILL", "BUS",
"SEGV", "SYS", "PIPE", "ALRM", "TERM", "URG", "STOP", "TSTP", "CONT", "CHLD", "TTIN",
"TTOU", "IO", "XCPU", "XFSZ", "VTALRM", "PROF", "WINCH", "PWR", "USR1", "USR2", NULL};
#endif
void *xmalloc(size_t size) {
if (size == 0) return NULL;
void *p = malloc(size);
if (!p) abort();
return p;
}
void *xrealloc(void *p, size_t size) {
if ((size == 0) && (p == NULL)) return NULL;
p = realloc(p, size);
if (!p) abort();
return p;
}
char *uppercase(char *s) {
while(*s) {
*s = (char)toupper((int)*s);
s++;
}
return s;
}
char *lowercase(char *s) {
while(*s) {
*s = (char)tolower((int)*s);
s++;
}
return s;
}
bool endswith(const char *str, const char *suffix) {
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
return str_len > suffix_len && !strcmp(str + (str_len - suffix_len), suffix);
}
int get_sig_name(int sig, char *buf, size_t len) {
int n = snprintf(buf, len, "SIG%s", sig < NSIG ? sys_signame[sig] : "unknown");
uppercase(buf);
return n;
}
int get_sig(const char *sig_name) {
for (int sig = 1; sig < NSIG; sig++) {
const char *name = sys_signame[sig];
if (name != NULL && (strcasecmp(name, sig_name) == 0 || strcasecmp(name, sig_name + 3) == 0))
return sig;
}
return atoi(sig_name);
}
int open_uri(char *uri) {
#ifdef __APPLE__
char command[256];
sprintf(command, "open %s > /dev/null 2>&1", uri);
return system(command);
#elif defined(_WIN32) || defined(__CYGWIN__)
return ShellExecute(0, 0, uri, 0, 0, SW_SHOW) > (HINSTANCE)32 ? 0 : 1;
#else
// check if X server is running
if (system("xset -q > /dev/null 2>&1")) return 1;
char command[256];
sprintf(command, "xdg-open %s > /dev/null 2>&1", uri);
return system(command);
#endif
}
#ifdef _WIN32
char *strsep(char **sp, char *sep) {
char *p, *s;
if (sp == NULL || *sp == NULL || **sp == '\0') return (NULL);
s = *sp;
p = s + strcspn(s, sep);
if (*p != '\0') *p++ = '\0';
*sp = p;
return s;
}
const char *quote_arg(const char *arg) {
int len = 0, n = 0;
int force_quotes = 0;
char *q, *d;
const char *p = arg;
if (!*p) force_quotes = 1;
while (*p) {
if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'')
force_quotes = 1;
else if (*p == '"')
n++;
else if (*p == '\\') {
int count = 0;
while (*p == '\\') {
count++;
p++;
len++;
}
if (*p == '"' || !*p) n += count * 2 + 1;
continue;
}
len++;
p++;
}
if (!force_quotes && n == 0) return arg;
d = q = xmalloc(len + n + 3);
*d++ = '"';
while (*arg) {
if (*arg == '"')
*d++ = '\\';
else if (*arg == '\\') {
int count = 0;
while (*arg == '\\') {
count++;
*d++ = *arg++;
}
if (*arg == '"' || !*arg) {
while (count-- > 0) *d++ = '\\';
if (!*arg) break;
*d++ = '\\';
}
}
*d++ = *arg++;
}
*d++ = '"';
*d++ = '\0';
return q;
}
void print_error(char *func) {
LPVOID buffer;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&buffer, 0, NULL);
wprintf(L"== %s failed with error %d: %s", func, dw, buffer);
LocalFree(buffer);
}
#endif

View File

@@ -1,39 +0,0 @@
#ifndef TTYD_UTIL_H
#define TTYD_UTIL_H
#define container_of(ptr, type, member) \
({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
// malloc with NULL check
void *xmalloc(size_t size);
// realloc with NULL check
void *xrealloc(void *p, size_t size);
// Convert a string to upper case
char *uppercase(char *s);
// Convert a string to lower case
char *lowercase(char *s);
// Check whether str ends with suffix
bool endswith(const char *str, const char *suffix);
// Get human readable signal string
int get_sig_name(int sig, char *buf, size_t len);
// Get signal code from string like SIGHUP
int get_sig(const char *sig_name);
// Open uri with the default application of system
int open_uri(char *uri);
#ifdef _WIN32
char *strsep(char **sp, char *sep);
const char *quote_arg(const char *arg);
void print_error(char *func);
#endif
#endif // TTYD_UTIL_H