diff --git a/go.mod b/go.mod index 264317e..a0453f8 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/barnybug/go-cast v0.0.0-20201201064555-a87ccbc26692 // indirect github.com/dop251/goja_nodejs v0.0.0-20230320130059-dcf93ba651dd // indirect + github.com/gabriel-vasile/mimetype v1.4.1 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/pprof v0.0.0-20230309165930-d61513b1440d // indirect @@ -44,6 +45,7 @@ require ( github.com/huandu/xstrings v1.3.3 // indirect github.com/igm/sockjs-go/v3 v3.0.2 // indirect github.com/imdario/mergo v0.3.12 // indirect + github.com/keegancsmith/rpc v1.3.0 // indirect github.com/miekg/dns v1.1.53 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.0 // indirect diff --git a/go.sum b/go.sum index 7050dca..48a87d9 100644 --- a/go.sum +++ b/go.sum @@ -501,6 +501,7 @@ github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXt github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q= +github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -877,6 +878,8 @@ github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaR github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/keegancsmith/rpc v1.3.0 h1:wGWOpjcNrZaY8GDYZJfvyxmlLljm3YQWF+p918DXtDk= +github.com/keegancsmith/rpc v1.3.0/go.mod h1:6O2xnOGjPyvIPbvp0MdrOe5r6cu1GZ4JoTzpzDhWeo0= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -1524,6 +1527,7 @@ golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= diff --git a/internal/agent/controller/app/app_handler.go b/internal/agent/controller/app/app_handler.go index dac70b9..b4df08c 100644 --- a/internal/agent/controller/app/app_handler.go +++ b/internal/agent/controller/app/app_handler.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "net" - "path/filepath" "text/template" "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" @@ -20,44 +19,54 @@ import ( fetchModule "forge.cadoles.com/arcad/edge/pkg/module/fetch" netModule "forge.cadoles.com/arcad/edge/pkg/module/net" shareModule "forge.cadoles.com/arcad/edge/pkg/module/share" - shareSqlite "forge.cadoles.com/arcad/edge/pkg/module/share/sqlite" "forge.cadoles.com/arcad/edge/pkg/storage" - "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" + "forge.cadoles.com/arcad/edge/pkg/storage/driver" + "forge.cadoles.com/arcad/edge/pkg/storage/share" "github.com/Masterminds/sprig/v3" "github.com/go-chi/chi/v5" "github.com/lestrrat-go/jwx/v2/jwa" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" + + // Register storage drivers + _ "forge.cadoles.com/arcad/edge/pkg/storage/driver/rpc" + _ "forge.cadoles.com/arcad/edge/pkg/storage/driver/sqlite" ) type Dependencies struct { - Bus bus.Bus - DocumentStore storage.DocumentStore - BlobStore storage.BlobStore - KeySet jwk.Set - AppRepository appModule.Repository - AppID app.ID - ShareRepository shareModule.Repository + Bus bus.Bus + DocumentStore storage.DocumentStore + BlobStore storage.BlobStore + ShareStore share.Store + KeySet jwk.Set + AppRepository appModule.Repository + AppID app.ID } -const defaultSQLiteParams = "?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000" - func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs *spec.Spec) ([]edgeHTTP.HandlerOptionFunc, error) { - dataDir, err := c.ensureAppDataDir(ctx, appKey) - if err != nil { - return nil, errors.Wrap(err, "could not retrieve app data dir") + appEntry, exists := specs.Apps[appKey] + if !exists { + return nil, errors.Errorf("could not find app entry '%s'", appKey) } - dbFile := filepath.Join(dataDir, appKey+".sqlite") - db, err := sqlite.Open(dbFile + defaultSQLiteParams) - if err != nil { - return nil, errors.Wrapf(err, "could not open database file '%s'", dbFile) + storage := appEntry.Storage + if storage == nil { + return nil, errors.Errorf("could not find app entry '%s' storage configuration", appKey) } - shareDBFile := filepath.Join(dataDir, "shared.sqlite") - shareDB, err := sqlite.Open(shareDBFile + defaultSQLiteParams) + documentStore, err := driver.NewDocumentStore(appEntry.Storage.DocumentStoreDSN) if err != nil { - return nil, errors.Wrapf(err, "could not open database file '%s'", shareDBFile) + return nil, errors.WithStack(err) + } + + blobStore, err := driver.NewBlobStore(appEntry.Storage.BlobStoreDSN) + if err != nil { + return nil, errors.WithStack(err) + } + + shareStore, err := driver.NewShareStore(appEntry.Storage.ShareStoreDSN) + if err != nil { + return nil, errors.WithStack(err) } keySet, err := getAuthKeySet(specs.Config) @@ -79,13 +88,13 @@ func (c *Controller) getHandlerOptions(ctx context.Context, appKey string, specs mounts = append(mounts, appModule.Mount(c.appRepository)) deps := Dependencies{ - Bus: memory.NewBus(), - DocumentStore: sqlite.NewDocumentStoreWithDB(db), - BlobStore: sqlite.NewBlobStoreWithDB(db), - KeySet: keySet, - AppRepository: c.appRepository, - AppID: app.ID(appKey), - ShareRepository: shareSqlite.NewRepositoryWithDB(shareDB), + Bus: memory.NewBus(), + DocumentStore: documentStore, + BlobStore: blobStore, + ShareStore: shareStore, + KeySet: keySet, + AppRepository: c.appRepository, + AppID: app.ID(appKey), } modules := c.getAppModules(deps) @@ -293,6 +302,6 @@ func (c *Controller) getAppModules(deps Dependencies) []app.ServerModuleFactory authModuleFactory(deps.KeySet), appModule.ModuleFactory(deps.AppRepository), fetchModule.ModuleFactory(deps.Bus), - shareModule.ModuleFactory(deps.AppID, deps.ShareRepository), + shareModule.ModuleFactory(deps.AppID, deps.ShareStore), } } diff --git a/internal/agent/controller/app/auth_module.go b/internal/agent/controller/app/auth_module.go index 3097bd8..b421916 100644 --- a/internal/agent/controller/app/auth_module.go +++ b/internal/agent/controller/app/auth_module.go @@ -4,7 +4,6 @@ import ( "net/http" "time" - "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" appSpec "forge.cadoles.com/Cadoles/emissary/internal/agent/controller/app/spec" "forge.cadoles.com/Cadoles/emissary/internal/jwk" "forge.cadoles.com/arcad/edge/pkg/app" @@ -58,7 +57,7 @@ func authModuleFactory(keySet jwk.Set) app.ServerModuleFactory { ) } -func getAuthMount(auth *spec.Auth, keySet jwk.Set) (auth.MountFunc, error) { +func getAuthMount(auth *appSpec.Auth, keySet jwk.Set) (auth.MountFunc, error) { switch { case auth.Local != nil: var rawKey any = auth.Local.Key @@ -81,7 +80,8 @@ func getAuthMount(auth *spec.Auth, keySet jwk.Set) (auth.MountFunc, error) { return authModule.Mount( authHTTP.NewLocalHandler( - jwa.HS256, key, + key, + jwa.HS256, authHTTP.WithRoutePrefix("/auth"), authHTTP.WithAccounts(auth.Local.Accounts...), authHTTP.WithCookieOptions(getCookieDomain, cookieDuration), @@ -111,8 +111,8 @@ func getAnonymousUserMiddleware(auth *appSpec.Auth) (func(http.Handler) http.Han } middleware := authModuleMiddleware.AnonymousUser( - anonymousUserSigningKey.Algorithm(), anonymousUserSigningKey, + auth.Local.SigningAlgorithm, authModuleMiddleware.WithCookieOptions(getCookieDomain, cookieDuration), ) diff --git a/internal/agent/controller/app/spec/schema.json b/internal/agent/controller/app/spec/schema.json index ad769a6..2bdd221 100644 --- a/internal/agent/controller/app/spec/schema.json +++ b/internal/agent/controller/app/spec/schema.json @@ -26,13 +26,34 @@ "zip", "tar.gz" ] + }, + "storage": { + "type": "object", + "properties": { + "blobStoreDsn": { + "type": "string" + }, + "documentStoreDsn": { + "type": "string" + }, + "shareStoreDsn": { + "type": "string" + } + }, + "required": [ + "blobStoreDsn", + "documentStoreDsn", + "shareStoreDsn" + ], + "additionalProperties": false } }, "required": [ "url", "sha256sum", "address", - "format" + "format", + "storage" ], "additionalProperties": false } @@ -84,6 +105,9 @@ "key": { "type": ["object", "string"] }, + "signingAlgorithm": { + "type": "string" + }, "accounts": { "type": "array", "items": { @@ -117,7 +141,8 @@ } }, "required": [ - "key" + "key", + "signingAlgorithm" ], "additionalProperties": false } diff --git a/internal/agent/controller/app/spec/spec.go b/internal/agent/controller/app/spec/spec.go index 7b1b258..d5c8af4 100644 --- a/internal/agent/controller/app/spec/spec.go +++ b/internal/agent/controller/app/spec/spec.go @@ -3,6 +3,7 @@ package spec import ( "forge.cadoles.com/Cadoles/emissary/internal/spec" edgeAuth "forge.cadoles.com/arcad/edge/pkg/module/auth/http" + "github.com/lestrrat-go/jwx/v2/jwa" ) const Name spec.Name = "app.emissary.cadoles.com" @@ -14,10 +15,17 @@ type Spec struct { } type AppEntry struct { - URL string `json:"url"` - SHA256Sum string `json:"sha256sum"` - Address string `json:"address"` - Format string `json:"format"` + URL string `json:"url"` + SHA256Sum string `json:"sha256sum"` + Address string `json:"address"` + Format string `json:"format"` + Storage *AppStorage `json:"storage"` +} + +type AppStorage struct { + ShareStoreDSN string `json:"shareStoreDsn"` + DocumentStoreDSN string `json:"documentStoreDsn"` + BlobStoreDSN string `json:"blobStoreDsn"` } type Auth struct { @@ -25,10 +33,11 @@ type Auth struct { } type LocalAuth struct { - Key any `json:"key"` - Accounts []edgeAuth.LocalAccount `json:"accounts"` - CookieDomain string `json:"cookieDomain"` - CookieDuration string `json:"cookieDuration"` + Key any `json:"key"` + SigningAlgorithm jwa.SignatureAlgorithm `json:"signingAlgorithm"` + Accounts []edgeAuth.LocalAccount `json:"accounts"` + CookieDomain string `json:"cookieDomain"` + CookieDuration string `json:"cookieDuration"` } type Config struct { diff --git a/internal/agent/controller/app/spec/testdata/spec-ok.json b/internal/agent/controller/app/spec/testdata/spec-ok.json index c1cd95f..ffedb90 100644 --- a/internal/agent/controller/app/spec/testdata/spec-ok.json +++ b/internal/agent/controller/app/spec/testdata/spec-ok.json @@ -6,7 +6,12 @@ "url": "http://example.com/edge.sdk.client.test_0.0.0.zip", "sha256sum": "58019192dacdae17755707719707db007e26dac856102280583fbd18427dd352", "address": ":8081", - "format": "zip" + "format": "zip", + "storage": { + "blobStoreDsn": "sqlite://apps/data/edge.sdk.client.test/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "shareStoreDsn": "sqlite://apps/data/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "documentStoreDsn": "sqlite://apps/data/edge.sdk.client.test/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000" + } } }, "config": { @@ -23,6 +28,7 @@ "q": "yJJLNc9w6O4y2icME8k99FugV9E7ObwUxF3v5JN3y1cmAT0h2njyE3iAGqaDZwcY1_jGCisjwoqX6i5E8xqhxX3Gcy3J7SmUAf8fhY8wU3zv9DK7skg2IdvanDb8Y1OM6GchbYZAOVPEg2IvVio8zI-Ih3DDwDk8Df0ufzoHRb8", "qi": "zOE-4R3cjPesm3MX-4PdwmsaF9QZLUVRUvvHJ08pKs6kAXP18hzjctAoOjhQDxlTYqNYNePfKzKwost3OJoPgRIc9w9qwUCK1gNOS4Z_xozCIaXgMddNFhkoAfZ4JaKjNCiinzjGfqG99Lf-yzmmREuuhRv7SdS3ST4VQjiJQew" }, + "signingAlgorithm": "RS256", "accounts": [ { "username": "foo", diff --git a/misc/spec-samples/app.emissary.cadoles.com.json b/misc/spec-samples/app.emissary.cadoles.com.json index 77a7ca3..361509a 100644 --- a/misc/spec-samples/app.emissary.cadoles.com.json +++ b/misc/spec-samples/app.emissary.cadoles.com.json @@ -4,13 +4,23 @@ "url": "https://emissary.cadol.es/files/apps/app.arcad.edge.hextris_v2023.4.20-2bbbe94.zip", "sha256sum": "67942ef4b623c46308c3f640b534bd4cb6b1d6021a422e40b62ab97658ba4586", "address": ":8083", - "format": "zip" + "format": "zip", + "storage": { + "blobStoreDsn": "sqlite://apps/data/app.arcad.edge.hextris/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "shareStoreDsn": "sqlite://apps/data/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "documentStoreDsn": "sqlite://apps/data/app.arcad.edge.hextris/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000" + } }, "edge.sdk.client.test": { "url": "https://emissary.cadol.es/files/apps/edge.sdk.client.test_v2023.4.20-20c4189.zip", "sha256sum": "1edeb4aa75c1675db49cf27367b1537234a04526848ea6657931ca63f26e5dae", "address": ":8084", - "format": "zip" + "format": "zip", + "storage": { + "blobStoreDsn": "sqlite://apps/data/edge.sdk.client.test/blobstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "shareStoreDsn": "sqlite://apps/data/sharestore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000", + "documentStoreDsn": "sqlite://apps/data/edge.sdk.client.test/documentstore.sqlite?_pragma=foreign_keys(1)&_pragma=busy_timeout=60000" + } } }, "config": { @@ -28,6 +38,7 @@ "auth": { "local": { "key": "absolutlynotsecret", + "signingAlgorithm": "HS256", "cookieDuration": "1h", "accounts": [ { diff --git a/misc/spec-samples/uci.emissary.cadoles.com.json b/misc/spec-samples/uci.emissary.cadoles.com.json new file mode 100644 index 0000000..b2cfa94 --- /dev/null +++ b/misc/spec-samples/uci.emissary.cadoles.com.json @@ -0,0 +1,2072 @@ +{ + "config": { + "packages": [ + { + "configs": [ + { + "name": "dnsmasq", + "options": [ + { + "name": "domainneeded", + "type": "option", + "value": "1" + }, + { + "name": "boguspriv", + "type": "option", + "value": "1" + }, + { + "name": "filterwin2k", + "type": "option", + "value": "0" + }, + { + "name": "localise_queries", + "type": "option", + "value": "1" + }, + { + "name": "rebind_localhost", + "type": "option", + "value": "1" + }, + { + "name": "local", + "type": "option", + "value": "/lan/" + }, + { + "name": "domain", + "type": "option", + "value": "lan" + }, + { + "name": "expandhosts", + "type": "option", + "value": "1" + }, + { + "name": "nonegcache", + "type": "option", + "value": "0" + }, + { + "name": "authoritative", + "type": "option", + "value": "1" + }, + { + "name": "readethers", + "type": "option", + "value": "1" + }, + { + "name": "leasefile", + "type": "option", + "value": "/tmp/dhcp.leases" + }, + { + "name": "resolvfile", + "type": "option", + "value": "/tmp/resolv.conf.d/resolv.conf.auto" + }, + { + "name": "nonwildcard", + "type": "option", + "value": "1" + }, + { + "name": "localservice", + "type": "option", + "value": "1" + }, + { + "name": "ednspacket_max", + "type": "option", + "value": "1232" + }, + { + "name": "rebind_protection", + "type": "option", + "value": "0" + }, + { + "name": "interface", + "type": "list", + "value": "lan" + }, + { + "name": "interface", + "type": "list", + "value": "guest" + }, + { + "name": "logqueries", + "type": "option", + "value": "1" + }, + { + "name": "address", + "type": "list", + "value": "/arcad.lan/10.10.10.1" + }, + { + "name": "address", + "type": "list", + "value": "/#/10.10.10.1" + } + ] + }, + { + "name": "dhcp", + "options": [ + { + "name": "interface", + "type": "option", + "value": "lan" + }, + { + "name": "start", + "type": "option", + "value": "100" + }, + { + "name": "limit", + "type": "option", + "value": "150" + }, + { + "name": "leasetime", + "type": "option", + "value": "12h" + }, + { + "name": "dhcpv4", + "type": "option", + "value": "server" + }, + { + "name": "dhcpv6", + "type": "option", + "value": "server" + }, + { + "name": "ra", + "type": "option", + "value": "server" + }, + { + "name": "ra_slaac", + "type": "option", + "value": "1" + }, + { + "name": "ra_flags", + "type": "list", + "value": "managed-config" + }, + { + "name": "ra_flags", + "type": "list", + "value": "other-config" + } + ], + "section": "lan" + }, + { + "name": "dhcp", + "options": [ + { + "name": "interface", + "type": "option", + "value": "wan" + }, + { + "name": "ignore", + "type": "option", + "value": "1" + } + ], + "section": "wan" + }, + { + "name": "odhcpd", + "options": [ + { + "name": "maindhcp", + "type": "option", + "value": "0" + }, + { + "name": "leasefile", + "type": "option", + "value": "/tmp/hosts/odhcpd" + }, + { + "name": "leasetrigger", + "type": "option", + "value": "/usr/sbin/odhcpd-update" + }, + { + "name": "loglevel", + "type": "option", + "value": "4" + } + ], + "section": "odhcpd" + }, + { + "name": "dhcp", + "options": [ + { + "name": "interface", + "type": "option", + "value": "guest" + }, + { + "name": "start", + "type": "option", + "value": "100" + }, + { + "name": "limit", + "type": "option", + "value": "150" + }, + { + "name": "leasetime", + "type": "option", + "value": "12h" + }, + { + "name": "dhcp_option", + "type": "list", + "value": "160,http://arcad.lan" + } + ], + "section": "guest" + } + ], + "name": "dhcp" + }, + { + "configs": [ + { + "name": "dropbear", + "options": [ + { + "name": "PasswordAuth", + "type": "option", + "value": "on" + }, + { + "name": "RootPasswordAuth", + "type": "option", + "value": "on" + }, + { + "name": "Port", + "type": "option", + "value": "22" + } + ] + } + ], + "name": "dropbear" + }, + { + "configs": [ + { + "name": "main", + "options": [ + { + "name": "reconciliation_interval", + "type": "option", + "value": "60" + }, + { + "name": "server_url", + "type": "option", + "value": "https://emissary.cadol.es" + } + ], + "section": "agent" + } + ], + "name": "emissary" + }, + { + "configs": [ + { + "name": "defaults", + "options": [ + { + "name": "syn_flood", + "type": "option", + "value": "1" + }, + { + "name": "input", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "output", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "forward", + "type": "option", + "value": "REJECT" + } + ] + }, + { + "name": "zone", + "options": [ + { + "name": "name", + "type": "option", + "value": "lan" + }, + { + "name": "network", + "type": "list", + "value": "lan" + }, + { + "name": "input", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "output", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "forward", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "zone", + "options": [ + { + "name": "name", + "type": "option", + "value": "wan" + }, + { + "name": "network", + "type": "list", + "value": "wan" + }, + { + "name": "network", + "type": "list", + "value": "wan6" + }, + { + "name": "input", + "type": "option", + "value": "REJECT" + }, + { + "name": "output", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "forward", + "type": "option", + "value": "REJECT" + }, + { + "name": "masq", + "type": "option", + "value": "1" + }, + { + "name": "mtu_fix", + "type": "option", + "value": "1" + } + ] + }, + { + "name": "forwarding", + "options": [ + { + "name": "src", + "type": "option", + "value": "lan" + }, + { + "name": "dest", + "type": "option", + "value": "wan" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-DHCP-Renew" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "udp" + }, + { + "name": "dest_port", + "type": "option", + "value": "68" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "family", + "type": "option", + "value": "ipv4" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-Ping" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "icmp" + }, + { + "name": "icmp_type", + "type": "option", + "value": "echo-request" + }, + { + "name": "family", + "type": "option", + "value": "ipv4" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-IGMP" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "igmp" + }, + { + "name": "family", + "type": "option", + "value": "ipv4" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-DHCPv6" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "udp" + }, + { + "name": "dest_port", + "type": "option", + "value": "546" + }, + { + "name": "family", + "type": "option", + "value": "ipv6" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-MLD" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "icmp" + }, + { + "name": "src_ip", + "type": "option", + "value": "fe80::/10" + }, + { + "name": "icmp_type", + "type": "list", + "value": "130/0" + }, + { + "name": "icmp_type", + "type": "list", + "value": "131/0" + }, + { + "name": "icmp_type", + "type": "list", + "value": "132/0" + }, + { + "name": "icmp_type", + "type": "list", + "value": "143/0" + }, + { + "name": "family", + "type": "option", + "value": "ipv6" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-ICMPv6-Input" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "icmp" + }, + { + "name": "icmp_type", + "type": "list", + "value": "echo-request" + }, + { + "name": "icmp_type", + "type": "list", + "value": "echo-reply" + }, + { + "name": "icmp_type", + "type": "list", + "value": "destination-unreachable" + }, + { + "name": "icmp_type", + "type": "list", + "value": "packet-too-big" + }, + { + "name": "icmp_type", + "type": "list", + "value": "time-exceeded" + }, + { + "name": "icmp_type", + "type": "list", + "value": "bad-header" + }, + { + "name": "icmp_type", + "type": "list", + "value": "unknown-header-type" + }, + { + "name": "icmp_type", + "type": "list", + "value": "router-solicitation" + }, + { + "name": "icmp_type", + "type": "list", + "value": "neighbour-solicitation" + }, + { + "name": "icmp_type", + "type": "list", + "value": "router-advertisement" + }, + { + "name": "icmp_type", + "type": "list", + "value": "neighbour-advertisement" + }, + { + "name": "limit", + "type": "option", + "value": "1000/sec" + }, + { + "name": "family", + "type": "option", + "value": "ipv6" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-ICMPv6-Forward" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest", + "type": "option", + "value": "*" + }, + { + "name": "proto", + "type": "option", + "value": "icmp" + }, + { + "name": "icmp_type", + "type": "list", + "value": "echo-request" + }, + { + "name": "icmp_type", + "type": "list", + "value": "echo-reply" + }, + { + "name": "icmp_type", + "type": "list", + "value": "destination-unreachable" + }, + { + "name": "icmp_type", + "type": "list", + "value": "packet-too-big" + }, + { + "name": "icmp_type", + "type": "list", + "value": "time-exceeded" + }, + { + "name": "icmp_type", + "type": "list", + "value": "bad-header" + }, + { + "name": "icmp_type", + "type": "list", + "value": "unknown-header-type" + }, + { + "name": "limit", + "type": "option", + "value": "1000/sec" + }, + { + "name": "family", + "type": "option", + "value": "ipv6" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-IPSec-ESP" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest", + "type": "option", + "value": "lan" + }, + { + "name": "proto", + "type": "option", + "value": "esp" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow-ISAKMP" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest", + "type": "option", + "value": "lan" + }, + { + "name": "dest_port", + "type": "option", + "value": "500" + }, + { + "name": "proto", + "type": "option", + "value": "udp" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow SSH on WAN" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "tcp" + }, + { + "name": "dest_port", + "type": "option", + "value": "22" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow HTTP on WAN" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "tcp" + }, + { + "name": "dest_port", + "type": "option", + "value": "80" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow HTTPS on WAN" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "tcp" + }, + { + "name": "dest_port", + "type": "option", + "value": "443" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "zone", + "options": [ + { + "name": "name", + "type": "option", + "value": "guest" + }, + { + "name": "input", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "output", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "forward", + "type": "option", + "value": "REJECT" + }, + { + "name": "network", + "type": "list", + "value": "guest" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8080 on WAN" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "tcp" + }, + { + "name": "dest_port", + "type": "option", + "value": "8080" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8443 on WAN" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "proto", + "type": "option", + "value": "tcp" + }, + { + "name": "dest_port", + "type": "option", + "value": "8443" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "proto", + "type": "list", + "value": "udp" + }, + { + "name": "dest_port", + "type": "option", + "value": "5353" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "name", + "type": "option", + "value": "Allow mDNS from device" + }, + { + "name": "src", + "type": "option", + "value": "*" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "proto", + "type": "list", + "value": "udp" + }, + { + "name": "src_port", + "type": "option", + "value": "5353" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + }, + { + "name": "name", + "type": "option", + "value": "Allow mDNS to device" + }, + { + "name": "src", + "type": "option", + "value": "*" + } + ] + }, + { + "name": "redirect", + "options": [ + { + "name": "target", + "type": "option", + "value": "DNAT" + }, + { + "name": "proto", + "type": "list", + "value": "udp" + }, + { + "name": "src", + "type": "option", + "value": "guest" + }, + { + "name": "src_dport", + "type": "option", + "value": "53" + }, + { + "name": "dest_ip", + "type": "option", + "value": "10.10.10.1" + }, + { + "name": "dest_port", + "type": "option", + "value": "53" + }, + { + "name": "name", + "type": "option", + "value": "Redirect DNS" + } + ] + }, + { + "name": "redirect", + "options": [ + { + "name": "target", + "type": "option", + "value": "DNAT" + }, + { + "name": "name", + "type": "option", + "value": "Redirect HTTP" + }, + { + "name": "proto", + "type": "list", + "value": "tcp" + }, + { + "name": "src", + "type": "option", + "value": "guest" + }, + { + "name": "src_dport", + "type": "option", + "value": "80" + }, + { + "name": "dest_ip", + "type": "option", + "value": "10.10.10.1" + }, + { + "name": "dest_port", + "type": "option", + "value": "80" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8081 for edge.sdk.client.test on WAN" + }, + { + "name": "proto", + "type": "list", + "value": "tcp" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest_port", + "type": "option", + "value": "8081" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8082 for app.arcad.edge.hextris on WAN" + }, + { + "name": "proto", + "type": "list", + "value": "tcp" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest_port", + "type": "option", + "value": "8082" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8083 for arcad.diffusion on WAN" + }, + { + "name": "proto", + "type": "list", + "value": "tcp" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest_port", + "type": "option", + "value": "8083" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + }, + { + "name": "rule", + "options": [ + { + "name": "name", + "type": "option", + "value": "Allow 8084 for app.arcad.edge.menu on WAN" + }, + { + "name": "proto", + "type": "list", + "value": "tcp" + }, + { + "name": "src", + "type": "option", + "value": "wan" + }, + { + "name": "dest_port", + "type": "option", + "value": "8084" + }, + { + "name": "target", + "type": "option", + "value": "ACCEPT" + } + ] + } + ], + "name": "firewall" + }, + { + "configs": [ + { + "name": "core", + "options": [ + { + "name": "lang", + "type": "option", + "value": "auto" + }, + { + "name": "mediaurlbase", + "type": "option", + "value": "/luci-static/bootstrap" + }, + { + "name": "resourcebase", + "type": "option", + "value": "/luci-static/resources" + }, + { + "name": "ubuspath", + "type": "option", + "value": "/ubus/" + } + ], + "section": "main" + }, + { + "name": "extern", + "options": [ + { + "name": "uci", + "type": "option", + "value": "/etc/config/" + }, + { + "name": "dropbear", + "type": "option", + "value": "/etc/dropbear/" + }, + { + "name": "openvpn", + "type": "option", + "value": "/etc/openvpn/" + }, + { + "name": "passwd", + "type": "option", + "value": "/etc/passwd" + }, + { + "name": "opkg", + "type": "option", + "value": "/etc/opkg.conf" + }, + { + "name": "firewall", + "type": "option", + "value": "/etc/firewall.user" + }, + { + "name": "uploads", + "type": "option", + "value": "/lib/uci/upload/" + } + ], + "section": "flash_keep" + }, + { + "name": "internal", + "options": null, + "section": "languages" + }, + { + "name": "internal", + "options": [ + { + "name": "sessionpath", + "type": "option", + "value": "/tmp/luci-sessions" + }, + { + "name": "sessiontime", + "type": "option", + "value": "3600" + } + ], + "section": "sauth" + }, + { + "name": "internal", + "options": [ + { + "name": "enable", + "type": "option", + "value": "1" + } + ], + "section": "ccache" + }, + { + "name": "internal", + "options": [ + { + "name": "Bootstrap", + "type": "option", + "value": "/luci-static/bootstrap" + }, + { + "name": "BootstrapDark", + "type": "option", + "value": "/luci-static/bootstrap-dark" + }, + { + "name": "BootstrapLight", + "type": "option", + "value": "/luci-static/bootstrap-light" + } + ], + "section": "themes" + }, + { + "name": "internal", + "options": [ + { + "name": "rollback", + "type": "option", + "value": "90" + }, + { + "name": "holdoff", + "type": "option", + "value": "4" + }, + { + "name": "timeout", + "type": "option", + "value": "5" + }, + { + "name": "display", + "type": "option", + "value": "1.5" + } + ], + "section": "apply" + }, + { + "name": "internal", + "options": [ + { + "name": "dns", + "type": "option", + "value": "openwrt.org" + }, + { + "name": "ping", + "type": "option", + "value": "openwrt.org" + }, + { + "name": "route", + "type": "option", + "value": "openwrt.org" + } + ], + "section": "diag" + } + ], + "name": "luci" + }, + { + "configs": [ + { + "name": "interface", + "options": [ + { + "name": "proto", + "type": "option", + "value": "static" + }, + { + "name": "ipaddr", + "type": "option", + "value": "127.0.0.1" + }, + { + "name": "netmask", + "type": "option", + "value": "255.0.0.0" + }, + { + "name": "device", + "type": "option", + "value": "lo" + } + ], + "section": "loopback" + }, + { + "name": "interface", + "options": [ + { + "name": "proto", + "type": "option", + "value": "dhcp" + }, + { + "name": "device", + "type": "option", + "value": "eth0" + } + ], + "section": "wan" + }, + { + "name": "interface", + "options": [ + { + "name": "proto", + "type": "option", + "value": "static" + }, + { + "name": "device", + "type": "option", + "value": "wlan0" + }, + { + "name": "ipaddr", + "type": "option", + "value": "10.10.10.1" + }, + { + "name": "netmask", + "type": "option", + "value": "255.255.255.0" + } + ], + "section": "guest" + } + ], + "name": "network" + }, + { + "configs": [ + { + "name": "rpcd", + "options": [ + { + "name": "socket", + "type": "option", + "value": "/var/run/ubus/ubus.sock" + }, + { + "name": "timeout", + "type": "option", + "value": "30" + } + ] + }, + { + "name": "login", + "options": [ + { + "name": "username", + "type": "option", + "value": "root" + }, + { + "name": "password", + "type": "option", + "value": "$p$root" + }, + { + "name": "read", + "type": "list", + "value": "*" + }, + { + "name": "write", + "type": "list", + "value": "*" + } + ] + } + ], + "name": "rpcd" + }, + { + "configs": [ + { + "name": "system", + "options": [ + { + "name": "hostname", + "type": "option", + "value": "arcad-will" + }, + { + "name": "timezone", + "type": "option", + "value": "UTC" + }, + { + "name": "ttylogin", + "type": "option", + "value": "0" + }, + { + "name": "log_size", + "type": "option", + "value": "64" + }, + { + "name": "urandom_seed", + "type": "option", + "value": "0" + } + ] + }, + { + "name": "timeserver", + "options": [ + { + "name": "enabled", + "type": "option", + "value": "1" + }, + { + "name": "enable_server", + "type": "option", + "value": "0" + }, + { + "name": "server", + "type": "list", + "value": "0.openwrt.pool.ntp.org" + }, + { + "name": "server", + "type": "list", + "value": "1.openwrt.pool.ntp.org" + }, + { + "name": "server", + "type": "list", + "value": "2.openwrt.pool.ntp.org" + }, + { + "name": "server", + "type": "list", + "value": "3.openwrt.pool.ntp.org" + } + ], + "section": "ntp" + } + ], + "name": "system" + }, + { + "configs": [ + { + "name": "network", + "options": [ + { + "name": "init", + "type": "option", + "value": "network" + }, + { + "name": "affects", + "type": "list", + "value": "dhcp" + } + ] + }, + { + "name": "wireless", + "options": [ + { + "name": "affects", + "type": "list", + "value": "network" + } + ] + }, + { + "name": "firewall", + "options": [ + { + "name": "init", + "type": "option", + "value": "firewall" + }, + { + "name": "affects", + "type": "list", + "value": "luci-splash" + }, + { + "name": "affects", + "type": "list", + "value": "qos" + }, + { + "name": "affects", + "type": "list", + "value": "miniupnpd" + } + ] + }, + { + "name": "olsr", + "options": [ + { + "name": "init", + "type": "option", + "value": "olsrd" + } + ] + }, + { + "name": "dhcp", + "options": [ + { + "name": "init", + "type": "option", + "value": "dnsmasq" + }, + { + "name": "affects", + "type": "list", + "value": "odhcpd" + } + ] + }, + { + "name": "odhcpd", + "options": [ + { + "name": "init", + "type": "option", + "value": "odhcpd" + } + ] + }, + { + "name": "dropbear", + "options": [ + { + "name": "init", + "type": "option", + "value": "dropbear" + } + ] + }, + { + "name": "httpd", + "options": [ + { + "name": "init", + "type": "option", + "value": "httpd" + } + ] + }, + { + "name": "fstab", + "options": [ + { + "name": "exec", + "type": "option", + "value": "/sbin/block mount" + } + ] + }, + { + "name": "qos", + "options": [ + { + "name": "init", + "type": "option", + "value": "qos" + } + ] + }, + { + "name": "system", + "options": [ + { + "name": "init", + "type": "option", + "value": "led" + }, + { + "name": "exec", + "type": "option", + "value": "/etc/init.d/log reload" + }, + { + "name": "affects", + "type": "list", + "value": "luci_statistics" + }, + { + "name": "affects", + "type": "list", + "value": "dhcp" + } + ] + }, + { + "name": "luci_splash", + "options": [ + { + "name": "init", + "type": "option", + "value": "luci_splash" + } + ] + }, + { + "name": "upnpd", + "options": [ + { + "name": "init", + "type": "option", + "value": "miniupnpd" + } + ] + }, + { + "name": "ntpclient", + "options": [ + { + "name": "init", + "type": "option", + "value": "ntpclient" + } + ] + }, + { + "name": "samba", + "options": [ + { + "name": "init", + "type": "option", + "value": "samba" + } + ] + }, + { + "name": "tinyproxy", + "options": [ + { + "name": "init", + "type": "option", + "value": "tinyproxy" + } + ] + } + ], + "name": "ucitrack" + }, + { + "configs": [ + { + "name": "uhttpd", + "options": [ + { + "name": "listen_http", + "type": "list", + "value": "0.0.0.0:8080" + }, + { + "name": "listen_http", + "type": "list", + "value": "[::]:8080" + }, + { + "name": "listen_https", + "type": "list", + "value": "0.0.0.0:8443" + }, + { + "name": "listen_https", + "type": "list", + "value": "[::]:8443" + }, + { + "name": "redirect_https", + "type": "option", + "value": "0" + }, + { + "name": "home", + "type": "option", + "value": "/www" + }, + { + "name": "rfc1918_filter", + "type": "option", + "value": "1" + }, + { + "name": "max_requests", + "type": "option", + "value": "3" + }, + { + "name": "max_connections", + "type": "option", + "value": "100" + }, + { + "name": "cert", + "type": "option", + "value": "/etc/uhttpd.crt" + }, + { + "name": "key", + "type": "option", + "value": "/etc/uhttpd.key" + }, + { + "name": "cgi_prefix", + "type": "option", + "value": "/cgi-bin" + }, + { + "name": "lua_prefix", + "type": "list", + "value": "/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua" + }, + { + "name": "script_timeout", + "type": "option", + "value": "60" + }, + { + "name": "network_timeout", + "type": "option", + "value": "30" + }, + { + "name": "http_keepalive", + "type": "option", + "value": "20" + }, + { + "name": "tcp_keepalive", + "type": "option", + "value": "1" + }, + { + "name": "ubus_prefix", + "type": "option", + "value": "/ubus" + } + ], + "section": "main" + }, + { + "name": "cert", + "options": [ + { + "name": "days", + "type": "option", + "value": "730" + }, + { + "name": "key_type", + "type": "option", + "value": "ec" + }, + { + "name": "bits", + "type": "option", + "value": "2048" + }, + { + "name": "ec_curve", + "type": "option", + "value": "P-256" + }, + { + "name": "country", + "type": "option", + "value": "ZZ" + }, + { + "name": "state", + "type": "option", + "value": "Somewhere" + }, + { + "name": "location", + "type": "option", + "value": "Unknown" + }, + { + "name": "commonname", + "type": "option", + "value": "OpenWrt" + } + ], + "section": "defaults" + } + ], + "name": "uhttpd" + }, + { + "configs": [ + { + "name": "wifi-device", + "options": [ + { + "name": "type", + "type": "option", + "value": "mac80211" + }, + { + "name": "path", + "type": "option", + "value": "platform/soc/3f300000.mmcnr/mmc_host/mmc1/mmc1:0001/mmc1:0001:1" + }, + { + "name": "channel", + "type": "option", + "value": "1" + }, + { + "name": "band", + "type": "option", + "value": "2g" + }, + { + "name": "htmode", + "type": "option", + "value": "HT20" + }, + { + "name": "cell_density", + "type": "option", + "value": "0" + } + ], + "section": "radio0" + }, + { + "name": "wifi-iface", + "options": [ + { + "name": "device", + "type": "option", + "value": "radio0" + }, + { + "name": "mode", + "type": "option", + "value": "ap" + }, + { + "name": "ssid", + "type": "option", + "value": "Arcad - Connect and Play" + }, + { + "name": "encryption", + "type": "option", + "value": "none" + }, + { + "name": "network", + "type": "option", + "value": "guest" + } + ], + "section": "default_radio0" + } + ], + "name": "wireless" + } + ] + }, + "postImportCommands": [ + { + "args": [], + "command": "reload_config" + } + ] +} \ No newline at end of file