test
This commit is contained in:
8
.env
8
.env
@@ -1,6 +1,8 @@
|
|||||||
|
PSQL_HOST=127.0.0.1
|
||||||
|
PSQL_NAME=hyst2
|
||||||
|
PSQL_USER=hyst2
|
||||||
|
PSQL_PASS=123456
|
||||||
|
|
||||||
JWT_SECRET=CHANGEME
|
JWT_SECRET=CHANGEME
|
||||||
HOST=127.0.0.1
|
|
||||||
PORT=443
|
|
||||||
OBFS_PASSWORD=test
|
|
||||||
SNI=vk.com
|
SNI=vk.com
|
||||||
NAME_FORMAT={username}
|
NAME_FORMAT={username}
|
||||||
@@ -11,3 +11,4 @@ WORKDIR /app
|
|||||||
COPY --from=builder /src/api /app/api
|
COPY --from=builder /src/api /app/api
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD ["/app/api"]
|
CMD ["/app/api"]
|
||||||
|
USER nobody
|
||||||
4
Makefile
4
Makefile
@@ -1,3 +1,3 @@
|
|||||||
build:
|
build:
|
||||||
docker build -t git.nix13.pw/scuroneko/h2auth:latest .
|
docker build -t git.nix13.pw/scuroneko/h2node:latest .
|
||||||
docker push git.nix13.pw/scuroneko/h2auth:latest
|
docker push git.nix13.pw/scuroneko/h2node:latest
|
||||||
44
app/config.go
Normal file
44
app/config.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
JWTSecret string
|
||||||
|
Host string
|
||||||
|
SNI string
|
||||||
|
NameFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg *Config
|
||||||
|
var hysteriaCfg *HysteriaConfig
|
||||||
|
|
||||||
|
func LoadConfig() {
|
||||||
|
cfg = &Config{
|
||||||
|
JWTSecret: os.Getenv("JWT_SECRET"),
|
||||||
|
Host: LoadIP(),
|
||||||
|
SNI: os.Getenv("SNI"),
|
||||||
|
NameFormat: os.Getenv("NAME_FORMAT"),
|
||||||
|
}
|
||||||
|
hysteriaCfg = &HysteriaConfig{}
|
||||||
|
|
||||||
|
f, err := os.Open("config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
data, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(data, hysteriaCfg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.Println(cfg, hysteriaCfg)
|
||||||
|
}
|
||||||
38
app/database.go
Normal file
38
app/database.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/vinovest/sqlx"
|
||||||
|
)
|
||||||
|
import _ "github.com/lib/pq"
|
||||||
|
|
||||||
|
var db *sqlx.DB = nil
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if db == nil {
|
||||||
|
connectPsql()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDSN() string {
|
||||||
|
user := os.Getenv("PSQL_USER")
|
||||||
|
password := os.Getenv("PSQL_PASS")
|
||||||
|
database := os.Getenv("PSQL_NAME")
|
||||||
|
host, exists := os.LookupEnv("PSQL_HOST")
|
||||||
|
if !exists {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable", user, password, host, database)
|
||||||
|
}
|
||||||
|
func connectPsql() {
|
||||||
|
var err error
|
||||||
|
db, err = sqlx.Connect("postgres", getDSN())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func ClosePsql() error {
|
||||||
|
return db.Close()
|
||||||
|
}
|
||||||
114
app/provider.go
114
app/provider.go
@@ -1,9 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"github.com/vinovest/sqlx"
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -11,86 +9,46 @@ type User struct {
|
|||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
type Provider struct {
|
type UserUsage struct {
|
||||||
Users []User `json:"users"`
|
UserID int `json:"user_id" db:"user_id"`
|
||||||
LastUserID int `json:"last_user_id"`
|
Recv int64 `json:"recv" db:"recv"`
|
||||||
path string
|
Sent int64 `json:"sent" db:"sent"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProvider создаёт новый провайдер с пустыми данными (файл не трогает)
|
type UserRepository struct {
|
||||||
func NewProvider() *Provider {
|
db *sqlx.DB
|
||||||
return &Provider{
|
|
||||||
Users: make([]User, 0),
|
|
||||||
LastUserID: 0,
|
|
||||||
path: "./provider.json",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadProvider загружает данные из файла, если файла нет — возвращает новый провайдер
|
func NewUserRepository(db *sqlx.DB) *UserRepository {
|
||||||
func LoadProvider() (*Provider, error) {
|
return &UserRepository{db}
|
||||||
data, err := os.ReadFile("./provider.json")
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
// файл не существует, возвращаем новый провайдер
|
|
||||||
return NewProvider(), nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &Provider{path: "./provider.json"}
|
|
||||||
if err := json.Unmarshal(data, p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save сохраняет текущее состояние в файл
|
func (rep UserRepository) AddUser(username, password string) (User, error) {
|
||||||
func (p *Provider) Save() error {
|
sql := "INSERT INTO users (username, password) VALUES (?, ?) RETURNING *;"
|
||||||
data, err := json.MarshalIndent(p, "", " ") // для читаемости
|
sql = rep.db.Rebind(sql)
|
||||||
if err != nil {
|
var user User
|
||||||
|
err := db.Get(&user, sql, username, password)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rep UserRepository) GetById(id int) (User, error) {
|
||||||
|
var user User
|
||||||
|
sql := "SELECT * FROM users WHERE id=?;"
|
||||||
|
sql = rep.db.Rebind(sql)
|
||||||
|
err := rep.db.Get(&user, sql, id)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
func (rep UserRepository) GetUsers() ([]User, error) {
|
||||||
|
users := make([]User, 0)
|
||||||
|
sql := "SELECT * FROM users;"
|
||||||
|
sql = rep.db.Rebind(sql)
|
||||||
|
err := rep.db.Select(&users, sql)
|
||||||
|
return users, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rep UserRepository) DeleteUser(id int) error {
|
||||||
|
sql := "DELETE FROM users WHERE id=?;"
|
||||||
|
sql = rep.db.Rebind(sql)
|
||||||
|
_, err := rep.db.Exec(sql, id)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
return os.WriteFile(p.path, data, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) AddUser(username, password string) error {
|
|
||||||
p.LastUserID++
|
|
||||||
p.Users = append(p.Users, User{
|
|
||||||
ID: p.LastUserID,
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
})
|
|
||||||
return p.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) GetById(id int) (User, error) {
|
|
||||||
for _, user := range p.Users {
|
|
||||||
if user.ID == id {
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return User{}, errors.New("user not found")
|
|
||||||
}
|
|
||||||
func (p *Provider) GetUser(password string) (User, error) {
|
|
||||||
for _, user := range p.Users {
|
|
||||||
if user.Password == password {
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return User{}, errors.New("user not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Provider) DeleteUser(id int) error {
|
|
||||||
index := -1
|
|
||||||
for i, user := range p.Users {
|
|
||||||
if user.ID == id {
|
|
||||||
index = i
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if index == -1 {
|
|
||||||
return errors.New("user not found")
|
|
||||||
}
|
|
||||||
p.Users = append(p.Users[:index], p.Users[index+1:]...)
|
|
||||||
return p.Save()
|
|
||||||
}
|
}
|
||||||
|
|||||||
105
app/routes.go
105
app/routes.go
@@ -1,12 +1,17 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AddUserReq struct {
|
type AddUserReq struct {
|
||||||
@@ -31,22 +36,8 @@ func AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := LoadProvider()
|
rep := NewUserRepository(db)
|
||||||
if err != nil {
|
user, err := rep.AddUser(req.Username, req.Password)
|
||||||
WriteError(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = provider.AddUser(req.Username, req.Password)
|
|
||||||
if err != nil {
|
|
||||||
WriteError(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = provider.Save()
|
|
||||||
if err != nil {
|
|
||||||
WriteError(w, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := provider.GetUser(req.Password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
@@ -69,17 +60,8 @@ func DeleteUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := LoadProvider()
|
rep := NewUserRepository(db)
|
||||||
if err != nil {
|
err = rep.DeleteUser(req.ID)
|
||||||
WriteError(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = provider.DeleteUser(req.ID)
|
|
||||||
if err != nil {
|
|
||||||
WriteError(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = provider.Save()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
@@ -94,12 +76,13 @@ func AllUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := LoadProvider()
|
rep := NewUserRepository(db)
|
||||||
|
users, err := rep.GetUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteResponse(w, provider)
|
WriteResponse(w, users)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetConnectURLReq struct {
|
type GetConnectURLReq struct {
|
||||||
@@ -114,25 +97,58 @@ func GetUserURL(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := LoadProvider()
|
rep := NewUserRepository(db)
|
||||||
|
user, err := rep.GetById(req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := provider.GetById(req.ID)
|
//if user.Password != req.Pass {
|
||||||
|
// WriteError(w, errors.New("invalid password"))
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
urlTemplate := "hysteria2://%s@%s%s?obfs=salamander&obfs-password=%s&type=hysteria&mport&security=tls&sni=%s&alpn=h3&fp=chrome&allowInsecure=0#%s"
|
||||||
|
authString := encodeURL(user)
|
||||||
|
u := fmt.Sprintf(
|
||||||
|
urlTemplate,
|
||||||
|
authString,
|
||||||
|
cfg.Host,
|
||||||
|
hysteriaCfg.Listen,
|
||||||
|
hysteriaCfg.Obfs.Salamander.Password,
|
||||||
|
cfg.SNI,
|
||||||
|
formatConfigName(cfg.NameFormat, user),
|
||||||
|
)
|
||||||
|
WriteResponse(w, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Sub(w http.ResponseWriter, r *http.Request) {
|
||||||
|
idS := chi.URLParam(r, "id")
|
||||||
|
id, err := strconv.Atoi(idS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteError(w, err)
|
WriteError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user.Password != req.Pass {
|
|
||||||
WriteError(w, errors.New("invalid password"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
urlTemplate := "hysteria2://%s@%s:%s?obfs=salamander&obfs-password=%s&type=hysteria&mport&security=tls&sni=%s&alpn=h3&fp=chrome&allowInsecure=0#%s"
|
rep := NewUserRepository(db)
|
||||||
|
user, err := rep.GetById(id)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
urlTemplate := "hysteria2://%s@%s%s?obfs=salamander&obfs-password=%s&type=hysteria&mport&security=tls&sni=%s&alpn=h3&fp=chrome&allowInsecure=0#%s"
|
||||||
authString := encodeURL(user)
|
authString := encodeURL(user)
|
||||||
u := fmt.Sprintf(urlTemplate, authString, cfg.Host, cfg.Port, cfg.ObfsPassword, cfg.SNI, formatConfigName(cfg.NameFormat, user))
|
u := fmt.Sprintf(
|
||||||
WriteResponse(w, u)
|
urlTemplate,
|
||||||
|
authString,
|
||||||
|
cfg.Host,
|
||||||
|
hysteriaCfg.Listen,
|
||||||
|
hysteriaCfg.Obfs.Salamander.Password,
|
||||||
|
cfg.SNI,
|
||||||
|
formatConfigName(cfg.NameFormat, user),
|
||||||
|
)
|
||||||
|
w.Write([]byte(base64.StdEncoding.EncodeToString([]byte(u))))
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthReq struct {
|
type AuthReq struct {
|
||||||
@@ -154,7 +170,7 @@ func DoAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req := new(AuthReq)
|
req := new(AuthReq)
|
||||||
if err := json.Unmarshal(data, req); err != nil {
|
if err = json.Unmarshal(data, req); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return
|
return
|
||||||
@@ -164,17 +180,18 @@ func DoAuth(w http.ResponseWriter, r *http.Request) {
|
|||||||
reqUser, err := decodeURL(req.Auth)
|
reqUser, err := decodeURL(req.Auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
provider, err := LoadProvider()
|
rep := NewUserRepository(db)
|
||||||
|
user, err := rep.GetById(reqUser.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := provider.GetUser(reqUser.Password)
|
log.Println(err)
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
114
app/types.go
Normal file
114
app/types.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
type HysteriaTLSConfig struct {
|
||||||
|
Cert string `yaml:"cert"`
|
||||||
|
Key string `yaml:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaObfsConfig struct {
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
Salamander struct {
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
} `yaml:"salamander"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaQuicConfig struct {
|
||||||
|
InitStreamReceiveWindow *int64 `yaml:"initStreamReceiveWindow"`
|
||||||
|
MaxStreamReceiveWindow *int64 `yaml:"maxStreamReceiveWindow"`
|
||||||
|
InitConnReceiveWindow *int64 `yaml:"initConnReceiveWindow"`
|
||||||
|
MaxConnReceiveWindow *int64 `yaml:"maxConnReceiveWindow"`
|
||||||
|
MaxIdleTimeout *string `yaml:"maxIdleTimeout"`
|
||||||
|
MaxIncomingStream *int64 `yaml:"maxIncomingStream"`
|
||||||
|
DisablePathMTUDiscovery *bool `yaml:"disablePathMTUDiscovery"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaBandwidthConfig struct {
|
||||||
|
UP *string `yaml:"up"`
|
||||||
|
Down *string `yaml:"down"`
|
||||||
|
IgnoreClientBandwidth *bool `yaml:"ignoreClientBandwidth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaAuthType string
|
||||||
|
type HysteriaAuthConfig struct {
|
||||||
|
Type HysteriaAuthType `yaml:"type"`
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
Userpass map[string]string `yaml:"userpass,omitempty"`
|
||||||
|
Http struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
Insecure bool `yaml:"insecure"`
|
||||||
|
} `yaml:"http,omitempty"`
|
||||||
|
Command string `yaml:"command,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaTCPUDPResolverConfig struct {
|
||||||
|
Address string `yaml:"addr"`
|
||||||
|
Timeout string `yaml:"timeout"`
|
||||||
|
}
|
||||||
|
type HysteriaTLSResolverConfig struct {
|
||||||
|
Address string `yaml:"addr"`
|
||||||
|
Timeout string `yaml:"timeout"`
|
||||||
|
SNI string `yaml:"sni"`
|
||||||
|
Insecure bool `yaml:"insecure"`
|
||||||
|
}
|
||||||
|
type HysteriaResolverType string
|
||||||
|
type HysteriaResolverConfig struct {
|
||||||
|
Type HysteriaResolverType `yaml:"type"`
|
||||||
|
TCP *HysteriaTCPUDPResolverConfig `yaml:"tcp,omitempty"`
|
||||||
|
UDP *HysteriaTCPUDPResolverConfig `yaml:"udp,omitempty"`
|
||||||
|
TLS *HysteriaTLSResolverConfig `yaml:"tls,omitempty"`
|
||||||
|
HTTPS *HysteriaTLSResolverConfig `yaml:"https,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaSniffConfig struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaACLConfig struct{}
|
||||||
|
type HysteriaOutboundConfig struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Type string `yaml:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaTrafficStatsConfig struct {
|
||||||
|
Listen string `yaml:"listen"`
|
||||||
|
Secret string `yaml:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaMasqueradeType string
|
||||||
|
type HysteriaMasqueradeFile struct {
|
||||||
|
Dir string `yaml:"dir"`
|
||||||
|
}
|
||||||
|
type HysteriaMasqueradeProxy struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
RewriteHost *bool `yaml:"rewriteHost,omitempty"`
|
||||||
|
Insecure *bool `yaml:"insecure,omitempty"`
|
||||||
|
}
|
||||||
|
type HysteriaMasqueradeString struct {
|
||||||
|
Content string `yaml:"content"`
|
||||||
|
Headers map[string]string `yaml:"headers,omitempty"`
|
||||||
|
StatusCode int `yaml:"statusCode"`
|
||||||
|
}
|
||||||
|
type HysteriaMasqueradeConfig struct {
|
||||||
|
Type HysteriaMasqueradeType `yaml:"type"`
|
||||||
|
File *HysteriaMasqueradeFile `yaml:"file,omitempty"`
|
||||||
|
Proxy *HysteriaMasqueradeProxy `yaml:"proxy,omitempty"`
|
||||||
|
String *HysteriaMasqueradeString `yaml:"string,omitempty"`
|
||||||
|
|
||||||
|
ListenHTTP *string `yaml:"listenHTTP,omitempty"`
|
||||||
|
ListenHTTPS *string `yaml:"listenHTTPS,omitempty"`
|
||||||
|
ForceHTTPS *bool `yaml:"forceHTTPS,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HysteriaConfig struct {
|
||||||
|
Listen string `yaml:"listen"`
|
||||||
|
TLS HysteriaTLSConfig `yaml:"tls"`
|
||||||
|
Obfs *HysteriaObfsConfig `yaml:"obfs,omitempty"`
|
||||||
|
Quic *HysteriaQuicConfig `yaml:"quic,omitempty"`
|
||||||
|
SpeedTest *bool `yaml:"speedTest,omitempty"`
|
||||||
|
Auth HysteriaAuthConfig `yaml:"auth"`
|
||||||
|
Resolver *HysteriaResolverConfig `yaml:"resolver,omitempty"`
|
||||||
|
Sniff *HysteriaSniffConfig `yaml:"sniff,omitempty"`
|
||||||
|
ACL *HysteriaACLConfig `yaml:"acl,omitempty"`
|
||||||
|
Outbounds []HysteriaOutboundConfig `yaml:"outbounds,omitempty"`
|
||||||
|
TrafficStats *HysteriaTrafficStatsConfig `yaml:"trafficStats,omitempty"`
|
||||||
|
}
|
||||||
35
app/utils.go
35
app/utils.go
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -47,31 +46,9 @@ func decodeURL(tokenString string) (User, error) {
|
|||||||
return zero, nil
|
return zero, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
JWTSecret string
|
|
||||||
Host string
|
|
||||||
Port string
|
|
||||||
ObfsPassword string
|
|
||||||
SNI string
|
|
||||||
NameFormat string
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg *Config
|
|
||||||
|
|
||||||
func LoadConfig() {
|
|
||||||
cfg = &Config{
|
|
||||||
JWTSecret: os.Getenv("JWT_SECRET"),
|
|
||||||
Host: os.Getenv("HOST"),
|
|
||||||
Port: os.Getenv("PORT"),
|
|
||||||
ObfsPassword: os.Getenv("OBFS_PASSWORD"),
|
|
||||||
SNI: os.Getenv("SNI"),
|
|
||||||
NameFormat: os.Getenv("NAME_FORMAT"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var ip string
|
var ip string
|
||||||
|
|
||||||
func LoadIP() {
|
func LoadIP() string {
|
||||||
res, err := http.Get("https://api.ipify.org")
|
res, err := http.Get("https://api.ipify.org")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -82,10 +59,18 @@ func LoadIP() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
ip = string(data)
|
ip = string(data)
|
||||||
log.Println(ip)
|
return ip
|
||||||
}
|
}
|
||||||
func formatConfigName(format string, user User) string {
|
func formatConfigName(format string, user User) string {
|
||||||
s := strings.ReplaceAll(format, "{username}", user.Username)
|
s := strings.ReplaceAll(format, "{username}", user.Username)
|
||||||
s = strings.ReplaceAll(s, "{host}", ip)
|
s = strings.ReplaceAll(s, "{host}", ip)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Ptr[T any](t T) *T { return &t }
|
||||||
|
func Val[T any](t *T, def T) T {
|
||||||
|
if t == nil {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return *t
|
||||||
|
}
|
||||||
|
|||||||
39
config.yaml
Normal file
39
config.yaml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
listen: :51052
|
||||||
|
tls:
|
||||||
|
cert: /tls/nix13.pw.crt
|
||||||
|
key: /tls/nix13.pw.key
|
||||||
|
auth:
|
||||||
|
type: http
|
||||||
|
http:
|
||||||
|
url: http://h2auth:8080/auth
|
||||||
|
insecure: false
|
||||||
|
trafficStats:
|
||||||
|
listen: :9998
|
||||||
|
secret: some_secret
|
||||||
|
obfs:
|
||||||
|
type: salamander
|
||||||
|
salamander:
|
||||||
|
password: test
|
||||||
|
masquerade:
|
||||||
|
type: proxy
|
||||||
|
proxy:
|
||||||
|
url: https://news.ycombinator.com/
|
||||||
|
rewriteHost: true
|
||||||
|
resolver:
|
||||||
|
type: udp
|
||||||
|
tcp:
|
||||||
|
addr: 1.1.1.1:53
|
||||||
|
timeout: 4s
|
||||||
|
udp:
|
||||||
|
addr: 1.1.1.1:53
|
||||||
|
timeout: 4s
|
||||||
|
tls:
|
||||||
|
addr: 1.1.1.1:853
|
||||||
|
timeout: 10s
|
||||||
|
sni: cloudflare-dns.com
|
||||||
|
insecure: false
|
||||||
|
https:
|
||||||
|
addr: 1.1.1.1:443
|
||||||
|
timeout: 10s
|
||||||
|
sni: cloudflare-dns.com
|
||||||
|
insecure: false
|
||||||
13
docker-compose.yaml
Normal file
13
docker-compose.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
services:
|
||||||
|
psql:
|
||||||
|
image: postgres:18.2-alpine3.23
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: ${PSQL_NAME}
|
||||||
|
POSTGRES_USER: ${PSQL_USER}
|
||||||
|
POSTGRES_PASSWORD: ${PSQL_PASS}
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
16
go.mod
16
go.mod
@@ -1,5 +1,17 @@
|
|||||||
module Hyst2Auth
|
module Hyst2Node
|
||||||
|
|
||||||
go 1.26
|
go 1.26
|
||||||
|
|
||||||
require github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.2.5
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
|
github.com/lib/pq v1.11.2
|
||||||
|
github.com/vinovest/sqlx v1.7.2
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
github.com/muir/list v1.2.1 // indirect
|
||||||
|
github.com/muir/sqltoken v0.4.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
28
go.sum
28
go.sum
@@ -1,2 +1,30 @@
|
|||||||
|
filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
|
||||||
|
filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0 h1:Y0zIbQXhQKmQgTp44Y1dp3wTXcn804QoTptLZT1vtvo=
|
||||||
|
github.com/go-sql-driver/mysql v1.9.0/go.mod h1:pDetrLJeA3oMujJuvXc8RJoasr589B6A9fwzD3QMrqw=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||||
|
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/muir/list v1.2.1 h1:lmF8fz2B1WbXkzHr/Eh0oWPJArDBzWqIifOwbA4gWSo=
|
||||||
|
github.com/muir/list v1.2.1/go.mod h1:v0l2f997MxCohQlD7PTejJqyYKwFVz/i3mTpDl4LAf0=
|
||||||
|
github.com/muir/sqltoken v0.4.0 h1:gBcpeTcy9LxuLZ1PJypGs6zG1/emVmuOPYR7saKbPyk=
|
||||||
|
github.com/muir/sqltoken v0.4.0/go.mod h1:+OSmbGI22QcVZ6DCzlHT8EAzEq/mqtqedtPP91Le+3A=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/vinovest/sqlx v1.7.2 h1:t/IahCJqO71GJYnhOcACiUXlMiiMomMHtxtUthdcBfo=
|
||||||
|
github.com/vinovest/sqlx v1.7.2/go.mod h1:o49uG4W/ZYZompljKx5GZ7qx6OFklPjSHXP63nSmND8=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
36
main.go
36
main.go
@@ -1,28 +1,40 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"Hyst2Auth/app"
|
"Hyst2Node/app"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app.LoadIP()
|
if err := godotenv.Load(".env"); err != nil {
|
||||||
app.LoadConfig()
|
log.Println("Error loading .env file. If you use docker, then you can ignore this.")
|
||||||
_, err := app.LoadProvider()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r := http.NewServeMux()
|
app.LoadConfig()
|
||||||
r.HandleFunc("/add", app.AddUser)
|
|
||||||
r.HandleFunc("/delete", app.DeleteUser)
|
|
||||||
r.HandleFunc("/users", app.AllUsers)
|
|
||||||
|
|
||||||
r.HandleFunc("/connect", app.GetUserURL)
|
r := chi.NewRouter()
|
||||||
|
r.Use(middleware.RequestID)
|
||||||
|
r.Use(middleware.RealIP)
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
r.Use(middleware.Recoverer)
|
||||||
|
r.Use(middleware.Timeout(60 * time.Second))
|
||||||
|
|
||||||
r.HandleFunc("/auth", app.DoAuth)
|
r.Post("/add", app.AddUser)
|
||||||
|
r.Post("/delete", app.DeleteUser)
|
||||||
|
r.Get("/users", app.AllUsers)
|
||||||
|
|
||||||
|
r.Post("/connect", app.GetUserURL)
|
||||||
|
r.Get("/sub/{id}", app.Sub)
|
||||||
|
|
||||||
|
r.Post("/auth", app.DoAuth)
|
||||||
|
|
||||||
|
defer app.ClosePsql()
|
||||||
log.Println("Listening on :8080")
|
log.Println("Listening on :8080")
|
||||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
11
scripts/00-init.sql
Normal file
11
scripts/00-init.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
||||||
|
CREATE UNIQUE INDEX users_uindex ON users(id);
|
||||||
|
CREATE TABLE user_usage(
|
||||||
|
user_id INT REFERENCES users(id),
|
||||||
|
recv INT8 NOT NULL DEFAULT 0,
|
||||||
|
sent INT8 NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user