diff --git a/.air.toml b/.air.toml deleted file mode 100644 index 498951f..0000000 --- a/.air.toml +++ /dev/null @@ -1,52 +0,0 @@ -root = "." -testdata_dir = "testdata" -tmp_dir = "tmp" - -[build] - args_bin = [] - bin = "./tmp/main" - cmd = "go build -o ./tmp/main ." - delay = 1000 - exclude_dir = ["assets", "tmp", "vendor", "testdata"] - exclude_file = [] - exclude_regex = ["_test.go"] - exclude_unchanged = false - follow_symlink = false - full_bin = "" - include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html"] - include_file = [] - kill_delay = "0s" - log = "build-errors.log" - poll = false - poll_interval = 0 - post_cmd = [] - pre_cmd = [] - rerun = false - rerun_delay = 500 - send_interrupt = false - stop_on_error = false - -[color] - app = "" - build = "yellow" - main = "magenta" - runner = "green" - watcher = "cyan" - -[log] - main_only = false - silent = false - time = false - -[misc] - clean_on_exit = false - -[proxy] - app_port = 0 - enabled = false - proxy_port = 0 - -[screen] - clear_on_rebuild = false - keep_scroll = true diff --git a/assets/config/config.yml b/assets/config/config.yml index 8a72a36..ae57894 100644 --- a/assets/config/config.yml +++ b/assets/config/config.yml @@ -1,20 +1,37 @@ http: routers: keycloak: + entryPoint: https service: keycloak - rules: Host(keycloak.z.com).Path(`/`) + rules: Domain(`keycloak.z.com`).Path(`/`) + tls: + certProvider: default app1: - service: app1_srv - rules: Host(`app1.z.com`).Path(`/`) + entryPoint: https + service: app1 + rules: Domain(`app1.z.com`).Path(`/`) + tls: + certProvider: default app2: - service: app2_srv - rules: Host(`app2.z.com`).Path(`/`) + entryPoint: https + service: app2 + rules: Domain(`app2.z.com`).Path(`/`) + tls: + certProvider: default -services: - keycloak: http://192.168.10.2:8080 - app1_srv: http://192.168.10.2:3001 - app2_srv: http://192.168.10.2:3002 + services: + keycloak: http://192.168.10.2:8080 + app1: http://192.168.10.2:3001 + app2: http://192.168.10.2:3002 entryPoints: https: address: :443 + tls: + enabled: true + +tls: + certProviders: + default: + key: ./assets/certs/z.com.key.pem + cert: ./assets/certs/z.com.cert.pem \ No newline at end of file diff --git a/go.mod b/go.mod index 6e7632a..0e11652 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,18 @@ -module z/zprox +module github.com/zeevdiukman/zprox go 1.24.0 +require github.com/gookit/goutil v0.6.18 // indirect + require ( + github.com/zeevdiukman/go-interpreter v0.0.0-20250305163450-43423a7e8ba7 github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31 github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5 + github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba ) require ( github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gookit/goutil v0.6.18 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect diff --git a/go.sum b/go.sum index ee181f4..fd56009 100644 --- a/go.sum +++ b/go.sum @@ -63,10 +63,14 @@ github.com/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123 h1:AYl8UZu7+ github.com/zeevdiukman/go-config v0.0.0-20250305101848-6cef80370123/go.mod h1:oK8ESatUsIFPpj/p/0iMW3HPs8+97F52O40xjotcY64= github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f h1:1UyqJ/MzVw+Oxl8kryguBsObG7qVw+IhlKTE/HhpLGE= github.com/zeevdiukman/go-helper v0.0.0-20250305091316-396bd5057e2f/go.mod h1:buB5zo+BkiM7kNOI2o33rmXBSlnjH1zpN0DtgNDbnCc= +github.com/zeevdiukman/go-interpreter v0.0.0-20250305163450-43423a7e8ba7 h1:3iW+LgqdYt9OvhcFX5aK3eI5Kr9Ue/wTpiZqBFEWMT8= +github.com/zeevdiukman/go-interpreter v0.0.0-20250305163450-43423a7e8ba7/go.mod h1:fofniVuiVQ022IcHdrRsO67GXj2jIp/D8K+DP7APUrQ= github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31 h1:OaOVvayXo4yf/gg8/IuZzGnFxTXEXGOVXiNVxPC0OgM= github.com/zeevdiukman/go-reverseproxy v0.0.0-20250305093102-9882ad3edb31/go.mod h1:RAFNKzQy/q2eDB/2WI88dHuiVC8Qqp6iAPDZ+btXACk= github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5 h1:ssP7N61mi0MLfnaH/ob7xOlu+prPwULl0nZ2hPw/QdE= github.com/zeevdiukman/go-router v0.0.0-20250305093130-650cd1d241f5/go.mod h1:wY15gRD14GOWs8j6bazZypGkMfCbSdr5cQK77MwzReA= +github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba h1:BHG0fbASaIBXr+JyIaIt+/x1MIH+wAB5MTj2VQ1nlnU= +github.com/zeevdiukman/go-server v0.0.0-20250305093228-f3ab0096fcba/go.mod h1:HQARDR3c1btC+vNSDekVtC1KM/tVFrJqr3yGrPN3pbo= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..bda7fa1 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,241 @@ +package config + +import ( + "regexp" + "strings" + + "github.com/zeevdiukman/go-config" +) + +const VIPER_NAME string = "config" +const VIPER_TYPE string = "yaml" +const VIPER_PATH string = "./assets/config/." +const SERVICES_KEY string = "http.services" +const ROUTERS_KEY string = "http.routers" +const ENTRYPOINTS_KEY string = "entrypoints" +const TLS_KEY string = "tls.certproviders" + +type Config struct { + HTTP *HTTP + TLS *TLS + EntryPoints EntryPoints +} +type HTTP struct { + Routers Routers + Services Services +} +type TLS struct { + CertProviders CertProviders +} +type CertProviders map[string]*CertProvider +type CertProvider struct { + Key string + Cert string +} + +type Services map[string]*Service +type Service struct { + URL string +} +type EntryPoints map[string]*EntryPoint +type EntryPoint struct { + Address string + TLS *EntryPointTLS +} +type EntryPointTLS struct { + Enabled bool +} +type Routers map[string]*Router +type Router struct { + EntryPoint string + Service string + Rules *Rules + TLS *RouterTLS +} +type RouterTLS struct { + CertProvider string +} +type Rules struct { + Raw string + Map map[string]*Rule +} + +type Rule struct { + Name string + Value string +} + +func NewConfig() *Config { + + c := config.NewConfig(VIPER_NAME, VIPER_TYPE, VIPER_PATH) + c.Load() + + appConfig := &Config{ + HTTP: &HTTP{ + Routers: Routers{}, + Services: Services{}, + }, + TLS: &TLS{ + CertProviders: CertProviders{}, + }, + EntryPoints: EntryPoints{}, + } + + appConfig.InitRouters(c) + appConfig.InitServices(c) + appConfig.InitEntryPoints(c) + appConfig.InitTLS(c) + + return appConfig +} +func (r Routers) ForEach(fn func(routerName string, routerConfig *Router)) { + for k, v := range r { + fn(k, v) + } +} +func (ep EntryPoints) ForEach(fn func(entryPointName string, entryPointConfig *EntryPoint)) { + forEach(ep, fn) +} +func (s Services) ForEach(fn func(serviceName string, serviceConfig *Service)) { + forEach(s, fn) +} +func (c *Config) InitRouters(vConfig *config.Config) { + c.HTTP.Routers = make(Routers) + for k, v := range vConfig.Sub(ROUTERS_KEY).AllSettings() { + val := v.(map[string]any) + + var tls string = "" + var services string = "" + var rawRules string = "" + var entryPoint string = "" + + if value, ok := val["service"]; ok { + services = value.(string) + } + if value, ok := val["rules"]; ok { + rawRules = value.(string) + } + if value, ok := val["entrypoint"]; ok { + entryPoint = value.(string) + } + if value, ok := val["tls"]; ok { + tls = value.(map[string]any)["certprovider"].(string) + } + rls := &Rules{ + Raw: rawRules, + Map: make(map[string]*Rule), + } + + r := &Router{ + Service: services, + Rules: rls, + EntryPoint: entryPoint, + TLS: &RouterTLS{CertProvider: tls}, + } + r.ForRuleFunc("Domain", func(domain string) { + r.Rules.Map["Domain"] = &Rule{ + Name: "Domain", + Value: domain, + } + }) + r.ForRuleFunc("Path", func(path string) { + r.Rules.Map["Path"] = &Rule{ + Name: "Path", + Value: path, + } + }) + + c.HTTP.Routers[k] = r + } +} +func (c *Config) InitServices(vConfig *config.Config) { + c.HTTP.Services = make(map[string]*Service) + for k, v := range vConfig.Sub(SERVICES_KEY).AllSettings() { + c.HTTP.Services[k] = &Service{ + URL: v.(string), + } + } +} +func (c *Config) InitEntryPoints(vConfig *config.Config) { + c.EntryPoints = make(EntryPoints) + for k, v := range vConfig.Sub(ENTRYPOINTS_KEY).AllSettings() { + v := v.(map[string]any) + address := "" + enabled := false + if v, ok := v["tls"]; ok { + if v, ok := v.(map[string]any)["enabled"]; ok { + enabled = v.(bool) + } + } + + if value, ok := v["address"]; ok { + address = value.(string) + } + c.EntryPoints[k] = &EntryPoint{ + Address: address, + TLS: &EntryPointTLS{ + Enabled: enabled, + }, + } + } +} + +func (c *Config) InitTLS(vConfig *config.Config) { + c.TLS.CertProviders = make(map[string]*CertProvider) + + for k, v := range vConfig.Sub(TLS_KEY).AllSettings() { + cert := v.(map[string]any)["cert"].(string) + key := v.(map[string]any)["key"].(string) + c.TLS.CertProviders[k] = &CertProvider{ + Cert: cert, + Key: key, + } + } +} +func forEach[K comparable, V comparable](mp map[K]V, fn func(key K, value V)) { + for k, v := range mp { + fn(k, v) + } +} + +func (r *Router) ForRuleFunc(ruleName string, fn func(string)) { + rules := r.Rules.Raw + rulesSlice := splitStatementByRegex(rules, "\\(`(.*?)`\\)") + for idx, rule := range rulesSlice { + rule, _ = strings.CutPrefix(rule, ".") + if rule == ruleName { + fn(rulesSlice[idx+1]) + } + } +} + +func splitStatementByRegex(str string, regx string) []string { + + input := str + re := regexp.MustCompile(regx) + matches := re.FindAllStringSubmatch(input, -1) + if len(matches) == 0 { + return []string{str} // Return original if no backticks found + } + + result := []string{} + lastIndex := 0 + + for _, match := range matches { + + startIndex := strings.Index(input, match[0]) + + if startIndex > lastIndex { + result = append(result, input[lastIndex:startIndex]) + } + + result = append(result, match[1]) // Append the content within backticks + lastIndex = startIndex + len(match[0]) + } + + if lastIndex < len(input) { + result = append(result, input[lastIndex:]) + } + + return result +} diff --git a/internal/interpreter/interpreter.go b/internal/interpreter/interpreter.go new file mode 100644 index 0000000..be1fb12 --- /dev/null +++ b/internal/interpreter/interpreter.go @@ -0,0 +1,43 @@ +package interpreter + +import ( + "strings" + + "github.com/zeevdiukman/go-interpreter" +) + +type Interp struct { + *interpreter.Interpreter +} + +func New() *Interp { + interp := &Interp{interpreter.New()} + + return interp +} +func (interp *Interp) PathAction(rules string, fn func(fn interpreter.Function)) { + name := "router_rules" + + regx := interp.Regex(name, "\\(`(.*?)`\\)") + interp.Func(name, func(args ...any) string { + // argsStmntName := args[0].(string) + argsPathAction := args[0].(func(string)) + interp.AddStatment(name, rules).SplitStatementByRegex(regx) + interp.Statments.ForEach(func(stmnt *interpreter.Statment) { + if stmnt.Name == name { + for idx, splitVal := range stmnt.RegexSplit { + splitVal, _ = strings.CutPrefix(splitVal, ".") + switch splitVal { + case "Path": + { + path := stmnt.RegexSplit[idx+1] + argsPathAction(path) + } + } + } + } + }) + return "" + }) + interp.Funcs[name].Function() +} diff --git a/main.go b/main.go index 8e052cb..a51c4dc 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,34 @@ +/* +Example: + + rp1 := reverseproxy.New(ctx, "http://localhost:8080") + rp2 := reverseproxy.New(ctx, "http://localhost:3001") + rp3 := reverseproxy.New(ctx, "http://localhost:3002") + router := router.NewRouter() + pr1 := router.NewHostRouter("keycloak.z.com") + pr1.Handler(rp1) + + go func() { + s := server.New().Name("proxy1").Port(443).Router(router) + s.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") + err := s.ListenAndServeTLS() + if err != nil { + log.Println(err.Error()) + } + }() +*/ package main import ( - "fmt" + "context" + "strconv" + "strings" - "github.com/gookit/goutil/dump" - "github.com/zeevdiukman/go-config" "github.com/zeevdiukman/go-helper" + "github.com/zeevdiukman/go-reverseproxy" + "github.com/zeevdiukman/go-router" + "github.com/zeevdiukman/go-server" + "github.com/zeevdiukman/zprox/internal/config" ) // type ReverseProxy struct { @@ -16,78 +39,127 @@ import ( const CERTS_PATH string = "./assets/certs/" +type App struct { + // Routers struct { + // Maps map[string]*router.HostRouter + // } + // map[string]*router.HostRouter + // Rules map[string]map[string]string + // Services map[string]string + DomainRouters DomainRouters + EntryPoints EntryPoints +} + +type DomainRouters map[string]*router.DomainRouter +type EntryPoints map[string]*EntryPoint +type EntryPoint struct { + *server.Server + *router.Router + HostRouters HostRouters +} +type HostRouters map[string]*router.DomainRouter + +func New() *App { + return &App{} +} + +func (ep EntryPoints) ForEach(f func(string, *EntryPoint)) { + forEach(ep, f) +} + +func forEach[K comparable, V any](mp map[K]V, f func(K, V)) { + for k, v := range mp { + f(k, v) + } +} + +// main is the entry point of the z application. +// It initializes and configures the application, sets up entry points, +// configures routers, and starts the servers. It also starts test HTTP servers +// for demonstration purposes. func main() { helper.AppRunner(true, func() { - // ctx := context.Background() - c := config.NewConfig("config", "yaml", "./assets/config") - c.Load() + helper.Clear() + ctx := context.Background() + app := New() + conf := config.NewConfig() + activeEntryPoints := make(map[string]bool) + conf.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) { + conf.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) { + if rConfig.EntryPoint == entryPointName { + activeEntryPoints[entryPointName] = true - // routersMap := make(map[string]*router.HostRouter) - routersMap := make(map[string]map[string]string) - - firstPart, subKeys := c.GetSubKeys("http.routers", false) - subKeys.ForEach(func(idx int, key config.Key) { - fullKey := helper.AddDotBetween(firstPart, string(key)) - p := key.GetKeyPartByLevel(0) - name := string(p) - - fmt.Println(name + ":") - if _, ok := routersMap[string(name)]; !ok { - routersMap[string(name)] = make(map[string]string) - } - - key.ForEach(func(idx int, part config.KeyPart, isFirst bool) { - if !isFirst { - field := string(part) - - val := c.Get(fullKey).(string) - - routersMap[string(name)][field] = val } }) - - // router.NewHostRouter("keycloak.z.com") - // router.NewHostRouter("keycloak.z.com").Handler(proxy) - }) - dump.Println(routersMap) + conf.EntryPoints.ForEach(func(entryPointName string, entryPointConfig *config.EntryPoint) { + if isUsed, ok := activeEntryPoints[entryPointName]; ok && isUsed { + if _, ok := app.EntryPoints[entryPointName]; !ok { + app.EntryPoints = make(map[string]*EntryPoint) + } + r := router.NewRouter() + _, portStr, _ := strings.Cut(entryPointConfig.Address, ":") + port, _ := strconv.Atoi(portStr) + s := server.New().Name(entryPointName).Port(port) + s.Router(r) + app.EntryPoints[entryPointName] = &EntryPoint{ + Server: s, + Router: r, + HostRouters: make(map[string]*router.DomainRouter), + } + } + }) + conf.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) { + if isUsed, ok := activeEntryPoints[rConfig.EntryPoint]; ok && isUsed { + domain := rConfig.Rules.Map["Domain"].Value + serviceURL := conf.HTTP.Services[rConfig.Service].URL + if _, ok := app.EntryPoints[rConfig.EntryPoint]; ok { + entryPoint := app.EntryPoints[rConfig.EntryPoint] + if _, ok := entryPoint.HostRouters[domain]; !ok { + entryPoint.HostRouters = make(map[string]*router.DomainRouter) + } + rpHandler := reverseproxy.New(ctx, serviceURL) + r := app.EntryPoints[rConfig.EntryPoint].Router + hostRouter := r.NewDomainRouter(domain) + hostRouter.Handler(rpHandler) + app.EntryPoints[rConfig.EntryPoint].HostRouters[domain] = hostRouter + } + } + }) + app.EntryPoints.ForEach(func(s string, ep *EntryPoint) { + conf.HTTP.Routers.ForEach(func(rName string, rConfig *config.Router) { + if s == rConfig.EntryPoint { - // proxy := reverseproxy.New(ctx, name) - - /*******************************************************************************/ - - // z := zgate.NewGate() - // z.NewEntryPoint("https") - // z.EntryPoints["https"].Port(443).CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") - - // if err := c.Load(); err != nil { - // log.Fatalln("Error:", err.Error()) - // } - /*-------------------------------------------------*/ - // rp1 := reverseproxy.New(ctx, "http://localhost:8080") - // rp2 := reverseproxy.New(ctx, "http://localhost:3001") - // rp3 := reverseproxy.New(ctx, "http://localhost:3002") - - // router := router.NewRouter() - // pr1 := router.NewHostRouter("keycloak.z.com") - // pr1.Handler(rp1) - // pr2 := router.NewHostRouter("app1.z.com") - // pr2.Handler(rp2) - // pr3 := router.NewHostRouter("app2.z.com") - // pr3.Handler(rp3) - - // go func() { - // s := server.New().Name("proxy1").Port(443).Router(router) - // s.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") - // err := s.ListenAndServeTLS() - // if err != nil { - // log.Println(err.Error()) - // } - // }() - /*-------------------------------------------------*/ + } + }) + ep.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") + go ep.ListenAndServeTLS() + }) helper.StartTestHTTPServer(3001, "app1") helper.StartTestHTTPServer(3002, "app2") - }) } + +// func main() { +// Test() +// } +func Test() { + ctx := context.Background() + conf := config.NewConfig() + rp1 := reverseproxy.New(ctx, conf.HTTP.Services["keycloak"].URL) + rp2 := reverseproxy.New(ctx, conf.HTTP.Services["app1"].URL) + rp3 := reverseproxy.New(ctx, conf.HTTP.Services["app2"].URL) + + r := router.NewRouter() + r.NewDomainRouter(conf.HTTP.Routers["keycloak"].Rules.Map["Domain"].Value).Handler(rp1) + r.NewDomainRouter(conf.HTTP.Routers["app1"].Rules.Map["Domain"].Value).Handler(rp2) + r.NewDomainRouter(conf.HTTP.Routers["app2"].Rules.Map["Domain"].Value).Handler(rp3) + + s := server.New().Name("proxy1").Port(443).Router(r) + s.CertKey(CERTS_PATH, "z.com.cert.pem", "z.com.key.pem") + go s.ListenAndServeTLS() + helper.StartTestHTTPServer(3001, "app1") + helper.StartTestHTTPServer(3002, "app2") + select {} +} diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 0ded17d..8a9dae2 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 6084aa4..aaac018 100755 Binary files a/tmp/main and b/tmp/main differ