From 33e6168e5af91ab6ac9aaa76baab6f02ed7832df Mon Sep 17 00:00:00 2001 From: okxlin Date: Wed, 5 Jun 2024 19:05:34 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0rathole=E5=88=B0?= =?UTF-8?q?=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/rathole/0.5.0/.env.sample | 6 ++ apps/rathole/0.5.0/data.yml | 33 +++++++++ apps/rathole/0.5.0/data/client.toml | 13 ++++ apps/rathole/0.5.0/data/full_example.toml | 70 ++++++++++++++++++ apps/rathole/0.5.0/data/server.toml | 13 ++++ .../0.5.0/data/tls/create_self_signed_cert.sh | 63 ++++++++++++++++ apps/rathole/0.5.0/data/tls/identity.pfx | Bin 0 -> 3453 bytes apps/rathole/0.5.0/data/tls/rootCA.crt | 20 +++++ apps/rathole/0.5.0/docker-compose.yml | 13 ++++ apps/rathole/README.md | 19 +++++ apps/rathole/data.yml | 19 +++++ apps/rathole/latest/.env.sample | 6 ++ apps/rathole/latest/data.yml | 33 +++++++++ apps/rathole/latest/data/client.toml | 13 ++++ apps/rathole/latest/data/full_example.toml | 70 ++++++++++++++++++ apps/rathole/latest/data/server.toml | 13 ++++ .../data/tls/create_self_signed_cert.sh | 63 ++++++++++++++++ apps/rathole/latest/data/tls/identity.pfx | Bin 0 -> 3453 bytes apps/rathole/latest/data/tls/rootCA.crt | 20 +++++ apps/rathole/latest/docker-compose.yml | 13 ++++ apps/rathole/logo.png | Bin 0 -> 6682 bytes 21 files changed, 500 insertions(+) create mode 100644 apps/rathole/0.5.0/.env.sample create mode 100644 apps/rathole/0.5.0/data.yml create mode 100644 apps/rathole/0.5.0/data/client.toml create mode 100644 apps/rathole/0.5.0/data/full_example.toml create mode 100644 apps/rathole/0.5.0/data/server.toml create mode 100644 apps/rathole/0.5.0/data/tls/create_self_signed_cert.sh create mode 100644 apps/rathole/0.5.0/data/tls/identity.pfx create mode 100644 apps/rathole/0.5.0/data/tls/rootCA.crt create mode 100644 apps/rathole/0.5.0/docker-compose.yml create mode 100644 apps/rathole/README.md create mode 100644 apps/rathole/data.yml create mode 100644 apps/rathole/latest/.env.sample create mode 100644 apps/rathole/latest/data.yml create mode 100644 apps/rathole/latest/data/client.toml create mode 100644 apps/rathole/latest/data/full_example.toml create mode 100644 apps/rathole/latest/data/server.toml create mode 100644 apps/rathole/latest/data/tls/create_self_signed_cert.sh create mode 100644 apps/rathole/latest/data/tls/identity.pfx create mode 100644 apps/rathole/latest/data/tls/rootCA.crt create mode 100644 apps/rathole/latest/docker-compose.yml create mode 100644 apps/rathole/logo.png diff --git a/apps/rathole/0.5.0/.env.sample b/apps/rathole/0.5.0/.env.sample new file mode 100644 index 00000000..46da082e --- /dev/null +++ b/apps/rathole/0.5.0/.env.sample @@ -0,0 +1,6 @@ +CONTAINER_NAME="rathole" +PANEL_APP_PORT_HTTP=7333 +SERVICE_MODE="server" +CONFIG_PATH="./data/server.toml" +#SERVICE_MODE="client" +#CONFIG_PATH="./data/client.toml" \ No newline at end of file diff --git a/apps/rathole/0.5.0/data.yml b/apps/rathole/0.5.0/data.yml new file mode 100644 index 00000000..aa831c01 --- /dev/null +++ b/apps/rathole/0.5.0/data.yml @@ -0,0 +1,33 @@ +additionalProperties: + formFields: + - default: 7333 + disabled: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port (determined by the configuration file) + labelZh: 端口 (由配置文件决定) + required: true + type: number + - default: "./data/server.toml" + edit: true + envKey: CONFIG_PATH + labelEn: Config Path + labelZh: 配置路径 + required: true + type: select + values: + - label: Client Config + value: ./data/client.toml + - label: Server Config + value: ./data/server.toml + - default: "server" + edit: true + envKey: SERVICE_MODE + labelEn: Service Mode + labelZh: 服务模式 + required: true + type: select + values: + - label: Server + value: server + - label: Client + value: client diff --git a/apps/rathole/0.5.0/data/client.toml b/apps/rathole/0.5.0/data/client.toml new file mode 100644 index 00000000..fa663dcd --- /dev/null +++ b/apps/rathole/0.5.0/data/client.toml @@ -0,0 +1,13 @@ +[client] +remote_addr = "127.0.0.1:7333" +default_token = "puKAproP46cofREn3sTa" + +[client.transport] +type = "tls" +[client.transport.tls] +trusted_root = "tls/rootCA.crt" +hostname = "localhost" + +[client.services.my_nas_ssh] +token = "use_a_secret_that_only_you_know" # 必须与服务器相同以通过验证 +local_addr = "127.0.0.1:22" # 需要被转发的服务的地址 diff --git a/apps/rathole/0.5.0/data/full_example.toml b/apps/rathole/0.5.0/data/full_example.toml new file mode 100644 index 00000000..91085266 --- /dev/null +++ b/apps/rathole/0.5.0/data/full_example.toml @@ -0,0 +1,70 @@ +[client] +remote_addr = "example.com:2333" # Necessary. The address of the server +default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones +heartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds +retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second + +[client.transport] # The whole block is optional. Specify which transport to use +type = "tcp" # Optional. Possible values: ["tcp", "tls", "noise"]. Default: "tcp" + +[client.transport.tcp] # Optional. Also affects `noise` and `tls` +proxy = "socks5://user:passwd@127.0.0.1:1080" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported. +nodelay = true # Optional. Override the `client.transport.nodelay` per service +keepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds +keepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds + +[client.transport.tls] # Necessary if `type` is "tls" +trusted_root = "ca.pem" # Necessary. The certificate of CA that signed the server's certificate +hostname = "example.com" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr` + +[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation +pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" # Optional. Default value as shown +local_private_key = "key_encoded_in_base64" # Optional +remote_public_key = "key_encoded_in_base64" # Optional + +[client.transport.websocket] # Necessary if `type` is "websocket" +tls = true # If `true` then it will use settings in `client.transport.tls` + +[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration +type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp" +token = "whatever" # Necessary if `client.default_token` not set +local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded +nodelay = true # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: true +retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config + +[client.services.service2] # Multiple services can be defined +local_addr = "127.0.0.1:1082" + +[server] +bind_addr = "0.0.0.0:2333" # Necessary. The address that the server listens for clients. Generally only the port needs to be change. +default_token = "default_token_if_not_specify" # Optional +heartbeat_interval = 30 # Optional. The interval between two application-layer heartbeat. Set to 0 to disable sending heartbeat. Default: 30 seconds + +[server.transport] # Same as `[client.transport]` +type = "tcp" + +[server.transport.tcp] # Same as the client +nodelay = true +keepalive_secs = 20 +keepalive_interval = 8 + +[server.transport.tls] # Necessary if `type` is "tls" +pkcs12 = "identify.pfx" # Necessary. pkcs12 file of server's certificate and private key +pkcs12_password = "password" # Necessary. Password of the pkcs12 file + +[server.transport.noise] # Same as `[client.transport.noise]` +pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" +local_private_key = "key_encoded_in_base64" +remote_public_key = "key_encoded_in_base64" + +[server.transport.websocket] # Necessary if `type` is "websocket" +tls = true # If `true` then it will use settings in `server.transport.tls` + +[server.services.service1] # The service name must be identical to the client side +type = "tcp" # Optional. Same as the client `[client.services.X.type] +token = "whatever" # Necessary if `server.default_token` not set +bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change. +nodelay = true # Optional. Same as the client + +[server.services.service2] +bind_addr = "0.0.0.1:8082" \ No newline at end of file diff --git a/apps/rathole/0.5.0/data/server.toml b/apps/rathole/0.5.0/data/server.toml new file mode 100644 index 00000000..93fff0c7 --- /dev/null +++ b/apps/rathole/0.5.0/data/server.toml @@ -0,0 +1,13 @@ +[server] +bind_addr = "0.0.0.0:7333" +default_token = "puKAproP46cofREn3sTa" + +[server.transport] +type = "tls" +[server.transport.tls] +pkcs12 = "tls/identity.pfx" +pkcs12_password = "1234" + +[server.services.my_nas_ssh] +token = "use_a_secret_that_only_you_know" # 用于验证的 token +bind_addr = "0.0.0.0:5202" # `5202` 配置了将 `my_nas_ssh` 暴露给互联网的端口 \ No newline at end of file diff --git a/apps/rathole/0.5.0/data/tls/create_self_signed_cert.sh b/apps/rathole/0.5.0/data/tls/create_self_signed_cert.sh new file mode 100644 index 00000000..55302a63 --- /dev/null +++ b/apps/rathole/0.5.0/data/tls/create_self_signed_cert.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +# create CA +openssl req -x509 \ + -sha256 -days 5000 \ + -nodes \ + -newkey rsa:2048 \ + -subj "/CN=MyOwnCA/C=US/L=San Fransisco" \ + -keyout rootCA.key -out rootCA.crt + +# create server private key +openssl genrsa -out server.key 2048 + +# create certificate signing request (CSR) +cat > csr.conf < cert.conf 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 literal 0 HcmV?d00001 diff --git a/apps/rathole/0.5.0/data/tls/rootCA.crt b/apps/rathole/0.5.0/data/tls/rootCA.crt new file mode 100644 index 00000000..b48e63d0 --- /dev/null +++ b/apps/rathole/0.5.0/data/tls/rootCA.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIUHPYndZflmbDV/30C+BHQSiNvUTQwDQYJKoZIhvcNAQEL +BQAwNzEQMA4GA1UEAwwHTXlPd25DQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNh +biBGcmFuc2lzY28wHhcNMjQwMjE1MDUwNDQ5WhcNMjUwMjA1MDUwNDQ5WjA3MRAw +DgYDVQQDDAdNeU93bkNBMQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5z +aXNjbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKIx0LdgvDrXGoGw +XJ9s3Y+nr34NMPPLTbo/C2Yj1pD4mxZKK7d1VuwuBNM1h/WQLhA9+x4ZcKYZ1S1g +3BRMuAdm/ZJyeeI1QDRqUlZD16ehPnY0Zy9sZX7oMKVS0m7l8zDv4nvDp9prC5yf +8eoI7zoAWiMv/xPacYXFTAJbUb0VgovFyf3rzgIzs/NBF675FxrQtbhM2j4DdMkJ +9UwRi+qmqtH/Z/Ddy4oMkPflEgKSgDEidmqa552CRExO3c+1ZbMEzq8iOUZ3Vb+g +enfo0SwQUxQ9PEUOAd13siEXs51jZ7JqNmj1d/lEIbAuX8znWDqLYz9FUN4QNsim +8Q/trBcCAwEAAaNTMFEwHQYDVR0OBBYEFP7eOqvUgs8/LOMonEZ6ubRaLkQMMB8G +A1UdIwQYMBaAFP7eOqvUgs8/LOMonEZ6ubRaLkQMMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBABfLdbsbchr8Ep4mCv75ojWe11Mdd3Eg8EOePukC +w918zqU6dZMmbnLtoXFk6QgFZnvD5MpmU4/d/BmvL9+CJJ9mJPwR2Vb/rIOPXV13 ++kjHo/NwNbw5TdmPMbneyCjMdxRqmYKGoWYwbsI09YCK5Cb0J2fYmMrcACSVIUvz +WC7CPPwTA3zvzf9xab+naoE1dbThRDGvVPXEFFOSMIXC0UzCvG0Lj3NTyXyu4XJ0 +TUcQUlnptLSejb+uh/5MSqwnEoc1dm2mW/oij1Gqg29+6WNw6wPv/cnC7VvlY4Eu +CR9tvTjMNb7G6VRok9W0HJec6dNf3FJJ1pVzVL8bKI19G54= +-----END CERTIFICATE----- diff --git a/apps/rathole/0.5.0/docker-compose.yml b/apps/rathole/0.5.0/docker-compose.yml new file mode 100644 index 00000000..03b29883 --- /dev/null +++ b/apps/rathole/0.5.0/docker-compose.yml @@ -0,0 +1,13 @@ + +services: + rathole: + image: "rapiz1/rathole:v0.5.0" + container_name: ${CONTAINER_NAME} + restart: always + network_mode: "host" + volumes: + - "${CONFIG_PATH}:/app/config.toml" + - "./data/tls/:/app/tls/" + command: --${SERVICE_MODE} /app/config.toml + labels: + createdBy: "Apps" diff --git a/apps/rathole/README.md b/apps/rathole/README.md new file mode 100644 index 00000000..6bc2e3b7 --- /dev/null +++ b/apps/rathole/README.md @@ -0,0 +1,19 @@ +# rathole + +![rathole-logo](https://github.com/rapiz1/rathole/raw/main/docs/img/rathole-logo.png) + +[![GitHub stars](https://img.shields.io/github/stars/rapiz1/rathole)](https://github.com/rapiz1/rathole/stargazers) +[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/rapiz1/rathole)](https://github.com/rapiz1/rathole/releases) +![GitHub Workflow Status (branch)](https://img.shields.io/github/actions/workflow/status/rapiz1/rathole/rust.yml?branch=main) +[![GitHub all releases](https://img.shields.io/github/downloads/rapiz1/rathole/total)](https://github.com/rapiz1/rathole/releases) +[![Docker Pulls](https://img.shields.io/docker/pulls/rapiz1/rathole)](https://hub.docker.com/r/rapiz1/rathole) + +[English](https://github.com/rapiz1/rathole/blob/main/README.md) | [简体中文](https://github.com/rapiz1/rathole/blob/main/README-zh.md) + +安全、稳定、高性能的内网穿透工具,用 Rust 语言编写 + +rathole,类似于 [frp](https://github.com/fatedier/frp) 和 [ngrok](https://github.com/inconshreveable/ngrok),可以让 NAT 后的设备上的服务通过具有公网 IP 的服务器暴露在公网上。 + +# 使用说明 +- 配置文件如何编写参考原项目说明。 +- 所需的证书可以使用应用目录下的`create_self_signed_cert.sh`脚本来生成。 diff --git a/apps/rathole/data.yml b/apps/rathole/data.yml new file mode 100644 index 00000000..94f22276 --- /dev/null +++ b/apps/rathole/data.yml @@ -0,0 +1,19 @@ +name: Rathole +tags: + - 实用工具 +title: 安全、稳定、高性能的内网穿透工具 +description: 安全、稳定、高性能的内网穿透工具 +additionalProperties: + key: rathole + name: Rathole + tags: + - Tool + shortDescZh: 安全、稳定、高性能的内网穿透工具 + shortDescEn: A secure, stable and high-performance reverse proxy for NAT traversal + type: tool + crossVersionUpdate: true + limit: 0 + recommend: 0 + website: https://github.com/rapiz1/rathole + github: https://github.com/rapiz1/rathole + document: https://github.com/rapiz1/rathole/blob/main/README-zh.md diff --git a/apps/rathole/latest/.env.sample b/apps/rathole/latest/.env.sample new file mode 100644 index 00000000..46da082e --- /dev/null +++ b/apps/rathole/latest/.env.sample @@ -0,0 +1,6 @@ +CONTAINER_NAME="rathole" +PANEL_APP_PORT_HTTP=7333 +SERVICE_MODE="server" +CONFIG_PATH="./data/server.toml" +#SERVICE_MODE="client" +#CONFIG_PATH="./data/client.toml" \ No newline at end of file diff --git a/apps/rathole/latest/data.yml b/apps/rathole/latest/data.yml new file mode 100644 index 00000000..aa831c01 --- /dev/null +++ b/apps/rathole/latest/data.yml @@ -0,0 +1,33 @@ +additionalProperties: + formFields: + - default: 7333 + disabled: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port (determined by the configuration file) + labelZh: 端口 (由配置文件决定) + required: true + type: number + - default: "./data/server.toml" + edit: true + envKey: CONFIG_PATH + labelEn: Config Path + labelZh: 配置路径 + required: true + type: select + values: + - label: Client Config + value: ./data/client.toml + - label: Server Config + value: ./data/server.toml + - default: "server" + edit: true + envKey: SERVICE_MODE + labelEn: Service Mode + labelZh: 服务模式 + required: true + type: select + values: + - label: Server + value: server + - label: Client + value: client diff --git a/apps/rathole/latest/data/client.toml b/apps/rathole/latest/data/client.toml new file mode 100644 index 00000000..fa663dcd --- /dev/null +++ b/apps/rathole/latest/data/client.toml @@ -0,0 +1,13 @@ +[client] +remote_addr = "127.0.0.1:7333" +default_token = "puKAproP46cofREn3sTa" + +[client.transport] +type = "tls" +[client.transport.tls] +trusted_root = "tls/rootCA.crt" +hostname = "localhost" + +[client.services.my_nas_ssh] +token = "use_a_secret_that_only_you_know" # 必须与服务器相同以通过验证 +local_addr = "127.0.0.1:22" # 需要被转发的服务的地址 diff --git a/apps/rathole/latest/data/full_example.toml b/apps/rathole/latest/data/full_example.toml new file mode 100644 index 00000000..91085266 --- /dev/null +++ b/apps/rathole/latest/data/full_example.toml @@ -0,0 +1,70 @@ +[client] +remote_addr = "example.com:2333" # Necessary. The address of the server +default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones +heartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds +retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second + +[client.transport] # The whole block is optional. Specify which transport to use +type = "tcp" # Optional. Possible values: ["tcp", "tls", "noise"]. Default: "tcp" + +[client.transport.tcp] # Optional. Also affects `noise` and `tls` +proxy = "socks5://user:passwd@127.0.0.1:1080" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported. +nodelay = true # Optional. Override the `client.transport.nodelay` per service +keepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds +keepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds + +[client.transport.tls] # Necessary if `type` is "tls" +trusted_root = "ca.pem" # Necessary. The certificate of CA that signed the server's certificate +hostname = "example.com" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr` + +[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation +pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" # Optional. Default value as shown +local_private_key = "key_encoded_in_base64" # Optional +remote_public_key = "key_encoded_in_base64" # Optional + +[client.transport.websocket] # Necessary if `type` is "websocket" +tls = true # If `true` then it will use settings in `client.transport.tls` + +[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration +type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp" +token = "whatever" # Necessary if `client.default_token` not set +local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded +nodelay = true # Optional. Determine whether to enable TCP_NODELAY for data transmission, if applicable, to improve the latency but decrease the bandwidth. Default: true +retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config + +[client.services.service2] # Multiple services can be defined +local_addr = "127.0.0.1:1082" + +[server] +bind_addr = "0.0.0.0:2333" # Necessary. The address that the server listens for clients. Generally only the port needs to be change. +default_token = "default_token_if_not_specify" # Optional +heartbeat_interval = 30 # Optional. The interval between two application-layer heartbeat. Set to 0 to disable sending heartbeat. Default: 30 seconds + +[server.transport] # Same as `[client.transport]` +type = "tcp" + +[server.transport.tcp] # Same as the client +nodelay = true +keepalive_secs = 20 +keepalive_interval = 8 + +[server.transport.tls] # Necessary if `type` is "tls" +pkcs12 = "identify.pfx" # Necessary. pkcs12 file of server's certificate and private key +pkcs12_password = "password" # Necessary. Password of the pkcs12 file + +[server.transport.noise] # Same as `[client.transport.noise]` +pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" +local_private_key = "key_encoded_in_base64" +remote_public_key = "key_encoded_in_base64" + +[server.transport.websocket] # Necessary if `type` is "websocket" +tls = true # If `true` then it will use settings in `server.transport.tls` + +[server.services.service1] # The service name must be identical to the client side +type = "tcp" # Optional. Same as the client `[client.services.X.type] +token = "whatever" # Necessary if `server.default_token` not set +bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change. +nodelay = true # Optional. Same as the client + +[server.services.service2] +bind_addr = "0.0.0.1:8082" \ No newline at end of file diff --git a/apps/rathole/latest/data/server.toml b/apps/rathole/latest/data/server.toml new file mode 100644 index 00000000..93fff0c7 --- /dev/null +++ b/apps/rathole/latest/data/server.toml @@ -0,0 +1,13 @@ +[server] +bind_addr = "0.0.0.0:7333" +default_token = "puKAproP46cofREn3sTa" + +[server.transport] +type = "tls" +[server.transport.tls] +pkcs12 = "tls/identity.pfx" +pkcs12_password = "1234" + +[server.services.my_nas_ssh] +token = "use_a_secret_that_only_you_know" # 用于验证的 token +bind_addr = "0.0.0.0:5202" # `5202` 配置了将 `my_nas_ssh` 暴露给互联网的端口 \ No newline at end of file diff --git a/apps/rathole/latest/data/tls/create_self_signed_cert.sh b/apps/rathole/latest/data/tls/create_self_signed_cert.sh new file mode 100644 index 00000000..55302a63 --- /dev/null +++ b/apps/rathole/latest/data/tls/create_self_signed_cert.sh @@ -0,0 +1,63 @@ +#!/bin/sh + +# create CA +openssl req -x509 \ + -sha256 -days 5000 \ + -nodes \ + -newkey rsa:2048 \ + -subj "/CN=MyOwnCA/C=US/L=San Fransisco" \ + -keyout rootCA.key -out rootCA.crt + +# create server private key +openssl genrsa -out server.key 2048 + +# create certificate signing request (CSR) +cat > csr.conf < cert.conf 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 literal 0 HcmV?d00001 diff --git a/apps/rathole/latest/data/tls/rootCA.crt b/apps/rathole/latest/data/tls/rootCA.crt new file mode 100644 index 00000000..b48e63d0 --- /dev/null +++ b/apps/rathole/latest/data/tls/rootCA.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDTzCCAjegAwIBAgIUHPYndZflmbDV/30C+BHQSiNvUTQwDQYJKoZIhvcNAQEL +BQAwNzEQMA4GA1UEAwwHTXlPd25DQTELMAkGA1UEBhMCVVMxFjAUBgNVBAcMDVNh +biBGcmFuc2lzY28wHhcNMjQwMjE1MDUwNDQ5WhcNMjUwMjA1MDUwNDQ5WjA3MRAw +DgYDVQQDDAdNeU93bkNBMQswCQYDVQQGEwJVUzEWMBQGA1UEBwwNU2FuIEZyYW5z +aXNjbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKIx0LdgvDrXGoGw +XJ9s3Y+nr34NMPPLTbo/C2Yj1pD4mxZKK7d1VuwuBNM1h/WQLhA9+x4ZcKYZ1S1g +3BRMuAdm/ZJyeeI1QDRqUlZD16ehPnY0Zy9sZX7oMKVS0m7l8zDv4nvDp9prC5yf +8eoI7zoAWiMv/xPacYXFTAJbUb0VgovFyf3rzgIzs/NBF675FxrQtbhM2j4DdMkJ +9UwRi+qmqtH/Z/Ddy4oMkPflEgKSgDEidmqa552CRExO3c+1ZbMEzq8iOUZ3Vb+g +enfo0SwQUxQ9PEUOAd13siEXs51jZ7JqNmj1d/lEIbAuX8znWDqLYz9FUN4QNsim +8Q/trBcCAwEAAaNTMFEwHQYDVR0OBBYEFP7eOqvUgs8/LOMonEZ6ubRaLkQMMB8G +A1UdIwQYMBaAFP7eOqvUgs8/LOMonEZ6ubRaLkQMMA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBABfLdbsbchr8Ep4mCv75ojWe11Mdd3Eg8EOePukC +w918zqU6dZMmbnLtoXFk6QgFZnvD5MpmU4/d/BmvL9+CJJ9mJPwR2Vb/rIOPXV13 ++kjHo/NwNbw5TdmPMbneyCjMdxRqmYKGoWYwbsI09YCK5Cb0J2fYmMrcACSVIUvz +WC7CPPwTA3zvzf9xab+naoE1dbThRDGvVPXEFFOSMIXC0UzCvG0Lj3NTyXyu4XJ0 +TUcQUlnptLSejb+uh/5MSqwnEoc1dm2mW/oij1Gqg29+6WNw6wPv/cnC7VvlY4Eu +CR9tvTjMNb7G6VRok9W0HJec6dNf3FJJ1pVzVL8bKI19G54= +-----END CERTIFICATE----- diff --git a/apps/rathole/latest/docker-compose.yml b/apps/rathole/latest/docker-compose.yml new file mode 100644 index 00000000..03ba01d2 --- /dev/null +++ b/apps/rathole/latest/docker-compose.yml @@ -0,0 +1,13 @@ + +services: + rathole: + image: "rapiz1/rathole:latest" + container_name: ${CONTAINER_NAME} + restart: always + network_mode: "host" + volumes: + - "${CONFIG_PATH}:/app/config.toml" + - "./data/tls/:/app/tls/" + command: --${SERVICE_MODE} /app/config.toml + labels: + createdBy: "Apps" diff --git a/apps/rathole/logo.png b/apps/rathole/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5547fae82211047e01b765bb07bfc808e480bd0a GIT binary patch literal 6682 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