Merge branch 'main' into anvil-tools-dev

main
Digimer 2 years ago committed by GitHub
commit 8f491e01ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      Anvil/Tools/Account.pm
  2. 43
      anvil.spec.in
  3. 303
      striker-ui-api/package-lock.json
  4. 10
      striker-ui-api/package.json
  5. 42
      striker-ui-api/src/app.ts
  6. 27
      striker-ui-api/src/index.ts
  7. 379
      striker-ui-api/src/lib/accessModule.ts
  8. 55
      striker-ui-api/src/lib/assertAuthentication.ts
  9. 1
      striker-ui-api/src/lib/consts/AN_VARIABLE_NAME_LIST.ts
  10. 3
      striker-ui-api/src/lib/consts/API_ROOT_PATH.ts
  11. 1
      striker-ui-api/src/lib/consts/DELETED.ts
  12. 2
      striker-ui-api/src/lib/consts/EXIT_CODE_LIST.ts
  13. 5
      striker-ui-api/src/lib/consts/PROCESS_OWNER.ts
  14. 22
      striker-ui-api/src/lib/consts/REG_EXP_PATTERNS.ts
  15. 8
      striker-ui-api/src/lib/consts/SERVER_PATHS.ts
  16. 4
      striker-ui-api/src/lib/consts/SERVER_PORT.ts
  17. 10
      striker-ui-api/src/lib/consts/index.ts
  18. 1
      striker-ui-api/src/lib/formatSql.ts
  19. 56
      striker-ui-api/src/lib/getSessionSecret.ts
  20. 10
      striker-ui-api/src/lib/isObject.ts
  21. 3
      striker-ui-api/src/lib/request_handlers/anvil/buildQueryAnvilDetail.ts
  22. 2
      striker-ui-api/src/lib/request_handlers/auth/index.ts
  23. 18
      striker-ui-api/src/lib/request_handlers/auth/login.ts
  24. 17
      striker-ui-api/src/lib/request_handlers/auth/logout.ts
  25. 55
      striker-ui-api/src/lib/request_handlers/buildGetRequestHandler.ts
  26. 18
      striker-ui-api/src/lib/request_handlers/command/buildHostPowerHandler.ts
  27. 93
      striker-ui-api/src/lib/request_handlers/command/getHostSSH.ts
  28. 68
      striker-ui-api/src/lib/request_handlers/command/runManifest.ts
  29. 8
      striker-ui-api/src/lib/request_handlers/command/updateSystem.ts
  30. 10
      striker-ui-api/src/lib/request_handlers/fence/getFenceTemplate.ts
  31. 11
      striker-ui-api/src/lib/request_handlers/file/buildQueryFileDetail.ts
  32. 16
      striker-ui-api/src/lib/request_handlers/file/createFile.ts
  33. 29
      striker-ui-api/src/lib/request_handlers/file/deleteFile.ts
  34. 10
      striker-ui-api/src/lib/request_handlers/file/getFile.ts
  35. 6
      striker-ui-api/src/lib/request_handlers/file/getFileDetail.ts
  36. 5
      striker-ui-api/src/lib/request_handlers/file/index.ts
  37. 134
      striker-ui-api/src/lib/request_handlers/file/updateFile.ts
  38. 101
      striker-ui-api/src/lib/request_handlers/host/configStriker.ts
  39. 96
      striker-ui-api/src/lib/request_handlers/host/createHostConnection.ts
  40. 28
      striker-ui-api/src/lib/request_handlers/host/deleteHostConnection.ts
  41. 8
      striker-ui-api/src/lib/request_handlers/host/getHostConnection.ts
  42. 18
      striker-ui-api/src/lib/request_handlers/host/prepareHost.ts
  43. 38
      striker-ui-api/src/lib/request_handlers/host/setHostInstallTarget.ts
  44. 37
      striker-ui-api/src/lib/request_handlers/manifest/buildManifest.ts
  45. 4
      striker-ui-api/src/lib/request_handlers/manifest/createManifest.ts
  46. 14
      striker-ui-api/src/lib/request_handlers/manifest/deleteManifest.ts
  47. 19
      striker-ui-api/src/lib/request_handlers/manifest/getManifestDetail.ts
  48. 21
      striker-ui-api/src/lib/request_handlers/manifest/getManifestTemplate.ts
  49. 4
      striker-ui-api/src/lib/request_handlers/manifest/updateManifest.ts
  50. 145
      striker-ui-api/src/lib/request_handlers/server/createServer.ts
  51. 3
      striker-ui-api/src/lib/request_handlers/server/getServer.ts
  52. 37
      striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts
  53. 24
      striker-ui-api/src/lib/request_handlers/ssh-key/deleteSSHKeyConflict.ts
  54. 11
      striker-ui-api/src/lib/request_handlers/ssh-key/getSSHKeyConflict.ts
  55. 2
      striker-ui-api/src/lib/request_handlers/ups/getUPS.ts
  56. 13
      striker-ui-api/src/lib/request_handlers/ups/getUPSTemplate.ts
  57. 59
      striker-ui-api/src/lib/request_handlers/user/deleteUser.ts
  58. 1
      striker-ui-api/src/lib/request_handlers/user/index.ts
  59. 44
      striker-ui-api/src/lib/rrouters.ts
  60. 29
      striker-ui-api/src/lib/sanitize.ts
  61. 22
      striker-ui-api/src/lib/shell.ts
  62. 28
      striker-ui-api/src/lib/traverse.ts
  63. 127
      striker-ui-api/src/passport.ts
  64. 13
      striker-ui-api/src/routes/auth.ts
  65. 8
      striker-ui-api/src/routes/echo.ts
  66. 199
      striker-ui-api/src/routes/file.ts
  67. 38
      striker-ui-api/src/routes/index.ts
  68. 2
      striker-ui-api/src/routes/manifest.ts
  69. 51
      striker-ui-api/src/routes/static.ts
  70. 7
      striker-ui-api/src/routes/user.ts
  71. 201
      striker-ui-api/src/session.ts
  72. 12
      striker-ui-api/src/types/AccessModule.d.ts
  73. 8
      striker-ui-api/src/types/AnvilOverview.d.ts
  74. 3
      striker-ui-api/src/types/AnvilSyncSharedFunction.d.ts
  75. 9
      striker-ui-api/src/types/ApiAn.d.ts
  76. 4
      striker-ui-api/src/types/ApiAuth.d.ts
  77. 15
      striker-ui-api/src/types/ApiCommand.d.ts
  78. 0
      striker-ui-api/src/types/ApiFence.d.ts
  79. 89
      striker-ui-api/src/types/ApiHost.d.ts
  80. 0
      striker-ui-api/src/types/ApiManifest.d.ts
  81. 0
      striker-ui-api/src/types/ApiNetworkInterface.d.ts
  82. 0
      striker-ui-api/src/types/ApiServer.d.ts
  83. 12
      striker-ui-api/src/types/ApiSshKey.d.ts
  84. 4
      striker-ui-api/src/types/ApiUps.d.ts
  85. 7
      striker-ui-api/src/types/ApiUser.d.ts
  86. 0
      striker-ui-api/src/types/BuildGetRequestHandlerFunction.d.ts
  87. 2
      striker-ui-api/src/types/BuildQueryFunction.d.ts
  88. 0
      striker-ui-api/src/types/CallFunction.d.ts
  89. 11
      striker-ui-api/src/types/CreateHostConnectionRequestBody.d.ts
  90. 9
      striker-ui-api/src/types/DBInsertOrUpdateFunctionCommon.d.ts
  91. 3
      striker-ui-api/src/types/DBJobAnvilSyncSharedOptions.d.ts
  92. 3
      striker-ui-api/src/types/DeleteHostConnectionRequestBody.d.ts
  93. 1
      striker-ui-api/src/types/DeleteSSHKeyConflictRequestBody.d.ts
  94. 5
      striker-ui-api/src/types/ExecModuleSubroutineFunction.d.ts
  95. 2
      striker-ui-api/src/types/GetAnvilDataFunction.d.ts
  96. 16
      striker-ui-api/src/types/GetPeerDataFunction.d.ts
  97. 25
      striker-ui-api/src/types/HostConnectionOverview.d.ts
  98. 6
      striker-ui-api/src/types/HostOverview.d.ts
  99. 19
      striker-ui-api/src/types/InitializeStrikerForm.d.ts
  100. 4
      striker-ui-api/src/types/InsertOrUpdateJobFunction.d.ts
  101. Some files were not shown because too many files have changed in this diff Show More

@ -253,10 +253,14 @@ sub login
my $self = shift; my $self = shift;
my $parameter = shift; my $parameter = shift;
my $anvil = $self->parent; my $anvil = $self->parent;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $debug = defined $parameter->{debug} ? $parameter->{debug} : 3;
my $password = $parameter->{password} // $anvil->data->{cgi}{password}{value};
my $username = $parameter->{username} // $anvil->data->{cgi}{username}{value};
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Account->login()" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Account->login()" }});
if ((not $anvil->data->{cgi}{username}{value}) or (not $anvil->data->{cgi}{password}{value})) if ((not $username) or (not $password))
{ {
# The user forgot something... # The user forgot something...
$anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "error_0027"}) }}); $anvil->data->{form}{error_massage} = $anvil->Template->get({file => "main.html", name => "error_message", variables => { error_message => $anvil->Words->string({key => "error_0027"}) }});
@ -275,7 +279,7 @@ FROM
WHERE WHERE
user_algorithm != 'DELETED' user_algorithm != 'DELETED'
AND AND
user_name = ".$anvil->Database->quote($anvil->data->{cgi}{username}{value})." user_name = ".$anvil->Database->quote($username)."
;"; ;";
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query }});
@ -309,7 +313,7 @@ AND
# Test the passed-in password. # Test the passed-in password.
my $test_password_answer = $anvil->Account->encrypt_password({ my $test_password_answer = $anvil->Account->encrypt_password({
debug => 2, debug => 2,
password => $anvil->data->{cgi}{password}{value}, password => $password,
salt => $user_salt, salt => $user_salt,
algorithm => $user_algorithm, algorithm => $user_algorithm,
hash_count => $user_hash_count, hash_count => $user_hash_count,
@ -345,7 +349,7 @@ AND
}); });
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { session_uuid => $session_uuid }}); $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { session_uuid => $session_uuid }});
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0183", variables => { user => $anvil->data->{cgi}{username}{value} }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0183", variables => { user => $username }});
$anvil->Account->_write_cookies({ $anvil->Account->_write_cookies({
debug => $debug, debug => $debug,
hash => $session_hash, hash => $session_hash,
@ -360,7 +364,7 @@ AND
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0184", variables => { $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0184", variables => {
user_agent => $ENV{HTTP_USER_AGENT} ? $ENV{HTTP_USER_AGENT} : "#!string!log_0185!#", user_agent => $ENV{HTTP_USER_AGENT} ? $ENV{HTTP_USER_AGENT} : "#!string!log_0185!#",
source_ip => $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : "#!string!log_0185!#", source_ip => $ENV{REMOTE_ADDR} ? $ENV{REMOTE_ADDR} : "#!string!log_0185!#",
user => $anvil->data->{cgi}{username}{value}, user => $username,
}}); }});
# Slow them down a bit... # Slow them down a bit...

@ -5,6 +5,7 @@
%define debug_package %{nil} %define debug_package %{nil}
%define anviluser admin %define anviluser admin
%define anvilgroup admin %define anvilgroup admin
%define suiapi striker-ui-api
Name: anvil Name: anvil
Version: @version@ Version: @version@
@ -134,7 +135,6 @@ Requires: firefox
Requires: gcc Requires: gcc
Requires: gdm Requires: gdm
Requires: gnome-terminal Requires: gnome-terminal
Requires: httpd
Requires: nmap Requires: nmap
Requires: nodejs Requires: nodejs
Requires: openssh-askpass Requires: openssh-askpass
@ -243,14 +243,32 @@ setenforce 0
systemctl enable --now chronyd.service systemctl enable --now chronyd.service
systemctl enable --now anvil-daemon.service systemctl enable --now anvil-daemon.service
systemctl enable --now scancore.service systemctl enable --now scancore.service
systemctl enable --now striker-ui-api.service
systemctl restart striker-ui-api.service %pre striker
getent passwd %{suiapi} >/dev/null \
|| useradd \
--comment "Striker UI API" \
--home-dir %{_datadir}/%{suiapi} \
--shell %{_sbindir}/nologin \
--user-group \
%{suiapi}
if [ $1 -gt 1 ]; then # >1=Upgrade
# Transfer files owned by apache to Striker UI API user.
chown -R --from apache %{suiapi}: /mnt
chown -R --from apache %{suiapi}: %{_localstatedir}/www
fi
%post striker %post striker
### NOTE: PostgreSQL is initialized and enabled by striker-prep-database later. ### NOTE: PostgreSQL is initialized and enabled by striker-prep-database later.
echo "Enabling and starting apache."
systemctl enable httpd.service # Always reload to handle service file changes.
systemctl start httpd.service systemctl daemon-reload
systemctl enable %{suiapi}.service
# Striker UI API needs explicit restart for changes to take effect.
systemctl restart %{suiapi}.service
restorecon -rv /%{_localstatedir}/www restorecon -rv /%{_localstatedir}/www
if ! $(ls -l /etc/systemd/system/default.target | grep -q graphical); if ! $(ls -l /etc/systemd/system/default.target | grep -q graphical);
then then
@ -329,6 +347,11 @@ touch /etc/anvil/type.dr
# sed -i.anvil 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config # sed -i.anvil 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config
# setenforce 1 # setenforce 1
%preun striker
if [ $1 == 0 ]; then # 0=Uninstall, 1=First install, >1=Upgrade (version count)
systemctl disable --now %{suiapi}.service
fi
%postun striker %postun striker
### TODO: Stopping postgres breaks the Anvil! during OS updates. Need to find a ### TODO: Stopping postgres breaks the Anvil! during OS updates. Need to find a
### way to run this only during uninstalls, and not during updates. ### way to run this only during uninstalls, and not during updates.
@ -340,11 +363,13 @@ touch /etc/anvil/type.dr
# firewall-cmd --zone=public --remove-service=postgresql # firewall-cmd --zone=public --remove-service=postgresql
# firewall-cmd --zone=public --remove-service=postgresql --permanent # firewall-cmd --zone=public --remove-service=postgresql --permanent
# echo "Disabling and stopping postgresql-9.6." # echo "Disabling and stopping postgresql-9.6."
# systemctl disable httpd.service
# systemctl stop httpd.service
# systemctl disable postgresql.service # systemctl disable postgresql.service
# systemctl stop postgresql.service # systemctl stop postgresql.service
if [ $1 == 0 ]; then # 0=Uninstall
systemctl daemon-reload
fi
# Remove the system type file. # Remove the system type file.
if [ -e '/etc/anvil/type.striker' ] if [ -e '/etc/anvil/type.striker' ]
then then
@ -376,7 +401,7 @@ fi
%{_sbindir}/* %{_sbindir}/*
%{_sysconfdir}/anvil/anvil.version %{_sysconfdir}/anvil/anvil.version
%{_datadir}/perl5/* %{_datadir}/perl5/*
%{_datadir}/striker-ui-api/* %{_datadir}/%{suiapi}/*
%{_mandir}/* %{_mandir}/*
%files striker %files striker

@ -8,9 +8,14 @@
"name": "striker-ui-api", "name": "striker-ui-api",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"core-js": "^3.30.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.2", "express": "^4.18.2",
"multer": "^1.4.4" "express-session": "^1.17.3",
"multer": "^1.4.4",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"regenerator-runtime": "^0.13.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.8", "@babel/core": "^7.17.8",
@ -18,8 +23,11 @@
"@babel/preset-typescript": "^7.16.7", "@babel/preset-typescript": "^7.16.7",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/express-session": "^1.17.7",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^17.0.22", "@types/node": "^17.0.22",
"@types/passport": "^1.0.12",
"@types/passport-local": "^1.0.35",
"@typescript-eslint/eslint-plugin": "^5.16.0", "@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0", "@typescript-eslint/parser": "^5.16.0",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
@ -2412,6 +2420,15 @@
"@types/range-parser": "*" "@types/range-parser": "*"
} }
}, },
"node_modules/@types/express-session": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz",
"integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.10", "version": "7.0.10",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
@ -2445,6 +2462,36 @@
"integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==",
"dev": true "dev": true
}, },
"node_modules/@types/passport": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.12.tgz",
"integrity": "sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==",
"dev": true,
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/passport-local": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz",
"integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==",
"dev": true,
"dependencies": {
"@types/express": "*",
"@types/passport": "*",
"@types/passport-strategy": "*"
}
},
"node_modules/@types/passport-strategy": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
"integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
"dev": true,
"dependencies": {
"@types/express": "*",
"@types/passport": "*"
}
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
@ -3504,6 +3551,16 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
}, },
"node_modules/core-js": {
"version": "3.30.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz",
"integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/core-js-compat": { "node_modules/core-js-compat": {
"version": "3.21.1", "version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz",
@ -4272,6 +4329,51 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/express-session": {
"version": "1.17.3",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
"dependencies": {
"cookie": "0.4.2",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/express-session/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/express/node_modules/safe-buffer": { "node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -5485,6 +5587,14 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": { "node_modules/once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -5582,6 +5692,42 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/passport": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
"dependencies": {
"passport-strategy": "1.x.x"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -5629,6 +5775,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -5740,6 +5891,14 @@
} }
] ]
}, },
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -5816,10 +5975,9 @@
} }
}, },
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.9", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"dev": true
}, },
"node_modules/regenerator-transform": { "node_modules/regenerator-transform": {
"version": "0.14.5", "version": "0.14.5",
@ -6446,6 +6604,17 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -8507,6 +8676,15 @@
"@types/range-parser": "*" "@types/range-parser": "*"
} }
}, },
"@types/express-session": {
"version": "1.17.7",
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.7.tgz",
"integrity": "sha512-L25080PBYoRLu472HY/HNCxaXY8AaGgqGC8/p/8+BYMhG0RDOLQ1wpXOpAzr4Gi5TGozTKyJv5BVODM5UNyVMw==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.10", "version": "7.0.10",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
@ -8540,6 +8718,36 @@
"integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==",
"dev": true "dev": true
}, },
"@types/passport": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.12.tgz",
"integrity": "sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/passport-local": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/@types/passport-local/-/passport-local-1.0.35.tgz",
"integrity": "sha512-K4eLTJ8R0yYW8TvCqkjB0pTKoqfUSdl5PfZdidTjV2ETV3604fQxtY6BHKjQWAx50WUS0lqzBvKv3LoI1ZBPeA==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/passport": "*",
"@types/passport-strategy": "*"
}
},
"@types/passport-strategy": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
"integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/passport": "*"
}
},
"@types/qs": { "@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
@ -9329,6 +9537,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
}, },
"core-js": {
"version": "3.30.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.30.1.tgz",
"integrity": "sha512-ZNS5nbiSwDTq4hFosEDqm65izl2CWmLz0hARJMyNQBgkUZMIF51cQiMvIQKA6hvuaeWxQDP3hEedM1JZIgTldQ=="
},
"core-js-compat": { "core-js-compat": {
"version": "3.21.1", "version": "3.21.1",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz",
@ -9937,6 +10150,33 @@
} }
} }
}, },
"express-session": {
"version": "1.17.3",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz",
"integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==",
"requires": {
"cookie": "0.4.2",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"dependencies": {
"cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -10808,6 +11048,11 @@
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
}, },
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": { "once": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -10878,6 +11123,29 @@
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
}, },
"passport": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
"requires": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
}
},
"passport-local": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz",
"integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==",
"requires": {
"passport-strategy": "1.x.x"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA=="
},
"path-exists": { "path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -10913,6 +11181,11 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true "dev": true
}, },
"pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"picocolors": { "picocolors": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -10980,6 +11253,11 @@
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true "dev": true
}, },
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ=="
},
"randombytes": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -11044,10 +11322,9 @@
} }
}, },
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.9", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"dev": true
}, },
"regenerator-transform": { "regenerator-transform": {
"version": "0.14.5", "version": "0.14.5",
@ -11498,6 +11775,14 @@
"integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
"dev": true "dev": true
}, },
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"unbox-primitive": { "unbox-primitive": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",

@ -11,9 +11,14 @@
"start": "npm run build && node out/index.js" "start": "npm run build && node out/index.js"
}, },
"dependencies": { "dependencies": {
"core-js": "^3.30.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.18.2", "express": "^4.18.2",
"multer": "^1.4.4" "express-session": "^1.17.3",
"multer": "^1.4.4",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"regenerator-runtime": "^0.13.11"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.17.8", "@babel/core": "^7.17.8",
@ -21,8 +26,11 @@
"@babel/preset-typescript": "^7.16.7", "@babel/preset-typescript": "^7.16.7",
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/express-session": "^1.17.7",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/node": "^17.0.22", "@types/node": "^17.0.22",
"@types/passport": "^1.0.12",
"@types/passport-local": "^1.0.35",
"@typescript-eslint/eslint-plugin": "^5.16.0", "@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0", "@typescript-eslint/parser": "^5.16.0",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",

@ -1,18 +1,38 @@
import cors from 'cors'; import cors from 'cors';
import express from 'express'; import express, { json } from 'express';
import path from 'path';
import API_ROOT_PATH from './lib/consts/API_ROOT_PATH';
import { guardApi } from './lib/assertAuthentication';
import passport from './passport';
import routes from './routes'; import routes from './routes';
import { rrouters } from './lib/rrouters';
import session from './session';
export default (async () => {
const app = express();
app.use(json());
app.use(
cors({
origin: true,
credentials: true,
}),
);
// Add session handler to the chain **after** adding other handlers that do
// not depend on session(s).
app.use(await session);
const app = express(); app.use(passport.initialize());
app.use(passport.authenticate('session'));
app.use(express.json()); rrouters(app, routes.private, {
app.use(cors()); assign: (router) => [guardApi, router],
route: '/api',
});
rrouters(app, routes.public, { route: '/api' });
Object.entries(routes).forEach(([route, router]) => { app.use(routes.static);
app.use(path.join(API_ROOT_PATH, route), router);
});
export default app; return app;
})();

@ -1,7 +1,26 @@
import { getgid, getuid, setgid, setuid } from 'process';
import { PGID, PUID, PORT, ECODE_DROP_PRIVILEGES } from './lib/consts';
import app from './app'; import app from './app';
import { stderr, stdout } from './lib/shell';
(async () => {
stdout(`Starting process with ownership ${getuid()}:${getgid()}`);
(await app).listen(PORT, () => {
try {
// Group must be set before user to avoid permission error.
setgid(PGID);
setuid(PUID);
stdout(`Process ownership changed to ${getuid()}:${getgid()}.`);
} catch (error) {
stderr(`Failed to change process ownership; CAUSE: ${error}`);
import SERVER_PORT from './lib/consts/SERVER_PORT'; process.exit(ECODE_DROP_PRIVILEGES);
}
app.listen(SERVER_PORT, () => { stdout(`Listening on localhost:${PORT}.`);
console.log(`Listening on localhost:${SERVER_PORT}.`); });
}); })();

@ -1,109 +1,214 @@
import { spawnSync, SpawnSyncOptions } from 'child_process'; import { ChildProcess, spawn, SpawnOptions } from 'child_process';
import EventEmitter from 'events';
import { readFileSync } from 'fs';
import { SERVER_PATHS, PGID, PUID } from './consts';
import { formatSql } from './formatSql';
import {
date,
stderr as sherr,
stdout as shout,
stdoutVar as shvar,
uuid,
} from './shell';
class Access extends EventEmitter {
private ps: ChildProcess;
private queue: string[] = [];
constructor({
eventEmitterOptions = {},
spawnOptions = {},
}: {
eventEmitterOptions?: ConstructorParameters<typeof EventEmitter>[0];
spawnOptions?: SpawnOptions;
} = {}) {
super(eventEmitterOptions);
this.ps = this.start(spawnOptions);
}
import SERVER_PATHS from './consts/SERVER_PATHS'; private start({
args = [],
gid = PGID,
stdio = 'pipe',
timeout = 10000,
uid = PUID,
...restSpawnOptions
}: AccessStartOptions = {}) {
shvar(
{ gid, stdio, timeout, uid, ...restSpawnOptions },
`Starting anvil-access-module daemon with options: `,
);
import { stderr as sherr, stdout as shout } from './shell'; const ps = spawn(SERVER_PATHS.usr.sbin['anvil-access-module'].self, args, {
gid,
stdio,
timeout,
uid,
...restSpawnOptions,
});
const formatQuery = (query: string) => query.replace(/\s+/g, ' '); let stderr = '';
let stdout = '';
const execAnvilAccessModule = ( ps.stderr?.setEncoding('utf-8').on('data', (chunk: string) => {
args: string[], stderr += chunk;
options: SpawnSyncOptions = {
encoding: 'utf-8', const scriptId: string | undefined = this.queue[0];
timeout: 10000,
}, if (scriptId) {
) => { sherr(`${Access.event(scriptId, 'stderr')}: ${stderr}`);
const { error, stdout, stderr } = spawnSync(
SERVER_PATHS.usr.sbin['anvil-access-module'].self, stderr = '';
args, }
options, });
);
ps.stdout?.setEncoding('utf-8').on('data', (chunk: string) => {
stdout += chunk;
let nindex: number = stdout.indexOf('\n');
if (error) { // 1. ~a is the shorthand for -(a + 1)
throw error; // 2. negatives are evaluated to true
while (~nindex) {
const scriptId = this.queue.shift();
if (scriptId) this.emit(scriptId, stdout.substring(0, nindex));
stdout = stdout.substring(nindex + 1);
nindex = stdout.indexOf('\n');
}
});
return ps;
} }
if (stderr.length > 0) { private stop() {
throw new Error(stderr.toString()); this.ps.once('error', () => !this.ps.killed && this.ps.kill('SIGKILL'));
this.ps.kill();
} }
let output; private restart(options?: AccessStartOptions) {
this.ps.once('close', () => this.start(options));
try { this.stop();
output = JSON.parse(stdout.toString()); }
} catch (stdoutParseError) {
output = stdout;
sherr( private static event(scriptId: string, category: 'stderr'): string {
`Failed to parse anvil-access-module output [${output}]; CAUSE: [${stdoutParseError}]`, return `${scriptId}-${category}`;
);
} }
return { public interact<T>(command: string, ...args: string[]) {
stdout: output, const { stdin } = this.ps;
};
}; const scriptId = uuid();
const script = `${command} ${args.join(' ')}\n`;
const promise = new Promise<T>((resolve, reject) => {
this.once(scriptId, (data) => {
let result: T;
try {
result = JSON.parse(data);
} catch (error) {
return reject(`Failed to parse line ${scriptId}; got [${data}]`);
}
return resolve(result);
});
});
shvar({ scriptId, script }, 'Access interact: ');
this.queue.push(scriptId);
stdin?.write(script);
const execModuleSubroutine = ( return promise;
subName: string, }
}
const access = new Access();
const subroutine = async <T extends unknown[]>(
subroutine: string,
{ {
spawnSyncOptions, params = [],
subModuleName, pre = ['Database'],
subParams, }: {
}: ExecModuleSubroutineOptions = {}, params?: unknown[];
pre?: string[];
} = {},
) => { ) => {
const args = ['--sub', subName]; const chain = `${pre.join('->')}->${subroutine}`;
// Defaults to "Database" in anvil-access-module. const subParams: string[] = params.map<string>((p) => {
if (subModuleName) { let result: string;
args.push('--sub-module', subModuleName);
}
if (subParams) { try {
args.push('--sub-params', JSON.stringify(subParams)); result = JSON.stringify(p);
} } catch (error) {
result = String(p);
}
return `'${result}'`;
});
shout( const { sub_results: results } = await access.interact<{ sub_results: T }>(
`...${subModuleName}->${subName} with params: ${JSON.stringify( 'x',
subParams, chain,
null, ...subParams,
2,
)}`,
); );
const { stdout } = execAnvilAccessModule(args, spawnSyncOptions); shvar(results, `${chain} results: `);
return { return results;
stdout: stdout['sub_results'],
};
}; };
const dbInsertOrUpdateJob = ( const query = <T extends (number | null | string)[][]>(script: string) =>
{ job_progress = 0, line = 0, ...rest }: DBJobParams, access.interact<T>('r', formatSql(script));
{ spawnSyncOptions }: DBInsertOrUpdateJobOptions = {},
) => const write = async (script: string) => {
execModuleSubroutine('insert_or_update_jobs', { const { write_code: wcode } = await access.interact<{ write_code: number }>(
spawnSyncOptions, 'w',
subParams: { job_progress, line, ...rest }, formatSql(script),
}).stdout; );
const dbInsertOrUpdateVariable: DBInsertOrUpdateVariableFunction = ( return wcode;
subParams, };
{ spawnSyncOptions } = {},
) => const insertOrUpdateJob = async ({
execModuleSubroutine('insert_or_update_variables', { job_progress = 0,
spawnSyncOptions, line = 0,
subParams, ...rest
}).stdout; }: JobParams) => {
const [uuid]: [string] = await subroutine('insert_or_update_jobs', {
const dbJobAnvilSyncShared = ( params: [{ job_progress, line, ...rest }],
});
return uuid;
};
const insertOrUpdateVariable: InsertOrUpdateVariableFunction = async (
params,
) => {
const [uuid]: [string] = await subroutine('insert_or_update_variables', {
params: [params],
});
return uuid;
};
const anvilSyncShared = (
jobName: string, jobName: string,
jobData: string, jobData: string,
jobTitle: string, jobTitle: string,
jobDescription: string, jobDescription: string,
{ jobHostUUID }: DBJobAnvilSyncSharedOptions = { jobHostUUID: undefined }, { jobHostUUID }: JobAnvilSyncSharedOptions = { jobHostUUID: undefined },
) => { ) => {
const subParams: DBJobParams = { const subParams: JobParams = {
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-sync-shared'].self, job_command: SERVER_PATHS.usr.sbin['anvil-sync-shared'].self,
job_data: jobData, job_data: jobData,
@ -116,45 +221,54 @@ const dbJobAnvilSyncShared = (
subParams.job_host_uuid = jobHostUUID; subParams.job_host_uuid = jobHostUUID;
} }
return dbInsertOrUpdateJob(subParams); return insertOrUpdateJob(subParams);
}; };
const dbQuery = (query: string, options?: SpawnSyncOptions) => { const refreshTimestamp = () => {
shout(formatQuery(query)); let result: string;
try {
result = date('--rfc-3339', 'ns').trim();
} catch (shError) {
throw new Error(
`Failed to get timestamp for database use; CAUSE: ${shError}`,
);
}
return execAnvilAccessModule(['--query', query], options); return result;
}; };
const dbSubRefreshTimestamp = () => const getData = async <T>(...keys: string[]) => {
execModuleSubroutine('refresh_timestamp').stdout; const chain = `data->${keys.join('->')}`;
const {
sub_results: [data],
} = await access.interact<{ sub_results: [T] }>('x', chain);
shvar(data, `${chain} data: `);
return data;
};
const dbWrite = (query: string, options?: SpawnSyncOptions) => { const getFenceSpec = async () => {
shout(formatQuery(query)); await subroutine('get_fence_data', { pre: ['Striker'] });
return execAnvilAccessModule(['--query', query, '--mode', 'write'], options); return getData<unknown>('fence_data');
}; };
const getAnvilData = <HashType>( const getHostData = async () => {
dataStruct: AnvilDataStruct, await subroutine('get_hosts');
{ predata, ...spawnSyncOptions }: GetAnvilDataOptions = {},
): HashType => return getData<AnvilDataHostListHash>('hosts');
execAnvilAccessModule( };
[
'--predata',
JSON.stringify(predata),
'--data',
JSON.stringify(dataStruct),
],
spawnSyncOptions,
).stdout;
const getLocalHostName = () => { const getLocalHostName = () => {
let result: string; let result: string;
try { try {
result = execModuleSubroutine('host_name', { result = readFileSync(SERVER_PATHS.etc.hostname.self, {
subModuleName: 'Get', encoding: 'utf-8',
}).stdout; }).trim();
} catch (subError) { } catch (subError) {
throw new Error(`Failed to get local host name; CAUSE: ${subError}`); throw new Error(`Failed to get local host name; CAUSE: ${subError}`);
} }
@ -164,13 +278,13 @@ const getLocalHostName = () => {
return result; return result;
}; };
const getLocalHostUUID = () => { const getLocalHostUuid = () => {
let result: string; let result: string;
try { try {
result = execModuleSubroutine('host_uuid', { result = readFileSync(SERVER_PATHS.etc.anvil['host.uuid'].self, {
subModuleName: 'Get', encoding: 'utf-8',
}).stdout; }).trim();
} catch (subError) { } catch (subError) {
throw new Error(`Failed to get local host UUID; CAUSE: ${subError}`); throw new Error(`Failed to get local host UUID; CAUSE: ${subError}`);
} }
@ -180,9 +294,18 @@ const getLocalHostUUID = () => {
return result; return result;
}; };
const getPeerData: GetPeerDataFunction = ( const getManifestData = async (manifestUuid?: string) => {
await subroutine('load_manifest', {
params: [{ manifest_uuid: manifestUuid }],
pre: ['Striker'],
});
return getData<AnvilDataManifestListHash>('manifests');
};
const getPeerData: GetPeerDataFunction = async (
target, target,
{ password, port, ...restOptions } = {}, { password, port } = {},
) => { ) => {
const [ const [
rawIsConnected, rawIsConnected,
@ -193,11 +316,13 @@ const getPeerData: GetPeerDataFunction = (
internet: rawIsInetConnected, internet: rawIsInetConnected,
os_registered: rawIsOSRegistered, os_registered: rawIsOSRegistered,
}, },
] = execModuleSubroutine('get_peer_data', { ]: [connected: string, data: PeerDataHash] = await subroutine(
subModuleName: 'Striker', 'get_peer_data',
subParams: { password, port, target }, {
...restOptions, params: [{ password, port, target }],
}).stdout as [connected: string, data: PeerDataHash]; pre: ['Striker'],
},
);
return { return {
hostName, hostName,
@ -209,16 +334,26 @@ const getPeerData: GetPeerDataFunction = (
}; };
}; };
const getUpsSpec = async () => {
await subroutine('get_ups_data', { pre: ['Striker'] });
return getData<AnvilDataUPSHash>('ups_data');
};
export { export {
dbInsertOrUpdateJob as job, insertOrUpdateJob as job,
dbInsertOrUpdateVariable as variable, insertOrUpdateVariable as variable,
dbJobAnvilSyncShared, anvilSyncShared,
dbQuery, refreshTimestamp as timestamp,
dbSubRefreshTimestamp, getData,
dbWrite, getFenceSpec,
getAnvilData, getHostData,
getLocalHostName, getLocalHostName,
getLocalHostUUID, getLocalHostUuid as getLocalHostUUID,
getManifestData,
getPeerData, getPeerData,
execModuleSubroutine as sub, getUpsSpec,
query,
subroutine as sub,
write,
}; };

@ -0,0 +1,55 @@
import { Handler } from 'express';
import { stdout } from './shell';
type AssertAuthenticationOptions = {
fail?: string | ((...args: Parameters<Handler>) => void);
failReturnTo?: boolean | string;
succeed?: string | ((...args: Parameters<Handler>) => void);
};
type AssertAuthenticationFunction = (
options?: AssertAuthenticationOptions,
) => Handler;
export const assertAuthentication: AssertAuthenticationFunction = ({
fail: initFail = (request, response) => response.status(404).send(),
failReturnTo,
succeed: initSucceed = (request, response, next) => next(),
}: AssertAuthenticationOptions = {}) => {
const fail: (...args: Parameters<Handler>) => void =
typeof initFail === 'string'
? (request, response) => response.redirect(initFail)
: initFail;
const succeed: (...args: Parameters<Handler>) => void =
typeof initSucceed === 'string'
? (request, response) => response.redirect(initSucceed)
: initSucceed;
let getReturnTo: ((...args: Parameters<Handler>) => string) | undefined;
if (failReturnTo === true) {
getReturnTo = ({ originalUrl, url }) => originalUrl || url;
} else if (typeof failReturnTo === 'string') {
getReturnTo = () => failReturnTo;
}
return (...args) => {
const { 0: request } = args;
const { originalUrl, session } = request;
const { passport } = session;
if (passport?.user) return succeed(...args);
session.returnTo = getReturnTo?.call(null, ...args);
stdout(
`Unauthenticated access to ${originalUrl}; set return to ${session.returnTo}`,
);
return fail(...args);
};
};
export const guardApi = assertAuthentication();

@ -0,0 +1 @@
export const VNAME_SESSION_SECRET = 'striker-ui-api::session::secret';

@ -1,3 +0,0 @@
const API_ROOT_PATH = '/api';
export default API_ROOT_PATH;

@ -0,0 +1 @@
export const DELETED = 'DELETED';

@ -0,0 +1,2 @@
export const ECODE_DROP_PRIVILEGES = 1;
export const ECODE_SESSION_SECRET = 2;

@ -0,0 +1,5 @@
import { resolveGid, resolveUid } from '../shell';
export const PUID = resolveUid(process.env.PUID ?? 'striker-ui-api');
export const PGID = resolveGid(process.env.PGID ?? PUID);

@ -1,23 +1,21 @@
const hex = '[0-9a-f]'; export const P_HEX = '[[:xdigit:]]';
const octet = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])'; export const P_OCTET = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])';
const alphanumeric = '[a-z0-9]'; export const P_ALPHANUM = '[a-z0-9]';
const alphanumericDash = '[a-z0-9-]'; export const P_ALPHANUM_DASH = '[a-z0-9-]';
const ipv4 = `(?:${octet}[.]){3}${octet}`; export const P_IPV4 = `(?:${P_OCTET}[.]){3}${P_OCTET}`;
export const P_UUID = `${P_HEX}{8}-${P_HEX}{4}-[1-5]${P_HEX}{3}-[89ab]${P_HEX}{3}-${P_HEX}{12}`;
export const REP_DOMAIN = new RegExp( export const REP_DOMAIN = new RegExp(
`^(?:${alphanumeric}(?:${alphanumericDash}{0,61}${alphanumeric})?[.])+${alphanumeric}${alphanumericDash}{0,61}${alphanumeric}$`, `^(?:${P_ALPHANUM}(?:${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM})?[.])+${P_ALPHANUM}${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM}$`,
); );
export const REP_INTEGER = /^\d+$/; export const REP_INTEGER = /^\d+$/;
export const REP_IPV4 = new RegExp(`^${ipv4}$`); export const REP_IPV4 = new RegExp(`^${P_IPV4}$`);
export const REP_IPV4_CSV = new RegExp(`(?:${ipv4},)*${ipv4}`); export const REP_IPV4_CSV = new RegExp(`(?:${P_IPV4},)*${P_IPV4}`);
// Peaceful string is temporarily defined as a string without single-quote, double-quote, slash (/), backslash (\\), angle brackets (< >), and curly brackets ({ }). // Peaceful string is temporarily defined as a string without single-quote, double-quote, slash (/), backslash (\\), angle brackets (< >), and curly brackets ({ }).
export const REP_PEACEFUL_STRING = /^[^'"/\\><}{]+$/; export const REP_PEACEFUL_STRING = /^[^'"/\\><}{]+$/;
export const REP_UUID = new RegExp( export const REP_UUID = new RegExp(`^${P_UUID}$`);
`^${hex}{8}-${hex}{4}-[1-5]${hex}{3}-[89ab]${hex}{3}-${hex}{12}$`,
'i',
);

@ -1,6 +1,10 @@
import path from 'path'; import path from 'path';
const EMPTY_SERVER_PATHS: ServerPath = { const EMPTY_SERVER_PATHS: ServerPath = {
etc: {
anvil: { 'host.uuid': {} },
hostname: {},
},
mnt: { mnt: {
shared: { shared: {
incoming: {}, incoming: {},
@ -10,10 +14,13 @@ const EMPTY_SERVER_PATHS: ServerPath = {
usr: { usr: {
bin: { bin: {
date: {}, date: {},
getent: {},
mkfifo: {}, mkfifo: {},
openssl: {},
psql: {}, psql: {},
rm: {}, rm: {},
sed: {}, sed: {},
uuidgen: {},
}, },
sbin: { sbin: {
'anvil-access-module': {}, 'anvil-access-module': {},
@ -31,6 +38,7 @@ const EMPTY_SERVER_PATHS: ServerPath = {
'striker-parse-os-list': {}, 'striker-parse-os-list': {},
}, },
}, },
var: { www: { html: {} } },
}; };
const generatePaths = ( const generatePaths = (

@ -1,3 +1 @@
const SERVER_PORT = process.env.SERVER_PORT ?? 8080; export const PORT = process.env.PORT ?? 8080;
export default SERVER_PORT;

@ -0,0 +1,10 @@
import SERVER_PATHS from './SERVER_PATHS';
export { SERVER_PATHS };
export * from './AN_VARIABLE_NAME_LIST';
export * from './DELETED';
export * from './EXIT_CODE_LIST';
export * from './PROCESS_OWNER';
export * from './REG_EXP_PATTERNS';
export * from './SERVER_PORT';

@ -0,0 +1 @@
export const formatSql = (script: string) => script.replace(/\s+/g, ' ');

@ -0,0 +1,56 @@
import assert from 'assert';
import { ECODE_SESSION_SECRET, VNAME_SESSION_SECRET } from './consts';
import { query, variable } from './accessModule';
import { openssl, stderr, stdout } from './shell';
export const getSessionSecret = async (): Promise<string> => {
let sessionSecret: string;
try {
const rows: [sessionSecret: string][] = await query(
`SELECT variable_value
FROM variables
WHERE variable_name = '${VNAME_SESSION_SECRET}';`,
);
assert(rows.length > 0, 'No existing session secret found.');
({
0: [sessionSecret],
} = rows);
stdout('Found an existing session secret.');
return sessionSecret;
} catch (queryError) {
stderr(`Failed to get session secret from database; CAUSE: ${queryError}`);
}
try {
sessionSecret = openssl('rand', '-base64', '32').trim();
stdout('Generated a new session secret.');
} catch (sysError) {
stderr(`Failed to generate session secret; CAUSE: ${sysError}`);
process.exit(ECODE_SESSION_SECRET);
}
try {
const vuuid = await variable({
file: __filename,
variable_name: VNAME_SESSION_SECRET,
variable_value: sessionSecret,
});
stdout(`Recorded session secret as variable identified by ${vuuid}.`);
} catch (subError) {
stderr(`Failed to record session secret; CAUSE: ${subError}`);
process.exit(ECODE_SESSION_SECRET);
}
return sessionSecret;
};

@ -0,0 +1,10 @@
export const isObject = (value: unknown) => {
const result: { is: boolean; obj: object } = { is: false, obj: {} };
if (typeof value === 'object' && value !== null) {
result.is = true;
result.obj = value;
}
return result;
};

@ -2,6 +2,7 @@ import NODE_AND_DR_RESERVED_MEMORY_SIZE from '../../consts/NODE_AND_DR_RESERVED_
import { OS_LIST } from '../../consts/OS_LIST'; import { OS_LIST } from '../../consts/OS_LIST';
import join from '../../join'; import join from '../../join';
import { stdoutVar } from '../../shell';
const buildQueryAnvilDetail = ({ const buildQueryAnvilDetail = ({
anvilUUIDs = ['*'], anvilUUIDs = ['*'],
@ -19,7 +20,7 @@ const buildQueryAnvilDetail = ({
separator: ', ', separator: ', ',
}); });
console.log(`condAnvilsUUID=[${condAnvilsUUID}]`); stdoutVar({ condAnvilsUUID });
const buildHostQuery = ({ const buildHostQuery = ({
isSummary = false, isSummary = false,

@ -0,0 +1,2 @@
export * from './login';
export * from './logout';

@ -0,0 +1,18 @@
import { RequestHandler } from 'express';
import { stdout } from '../../shell';
export const login: RequestHandler<unknown, unknown, AuthLoginRequestBody> = (
request,
response,
) => {
const { user } = request;
if (user) {
const { name: userName } = user;
stdout(`Successfully authenticated user [${userName}]`);
}
response.status(204).send();
};

@ -0,0 +1,17 @@
import { RequestHandler } from 'express';
import { stdout } from '../../shell';
export const logout: RequestHandler = (request, response) => {
request.session.destroy((error) => {
let scode = 204;
if (error) {
scode = 500;
stdout(`Failed to destroy session upon logout; CAUSE: ${error}`);
}
response.status(scode).send();
});
};

@ -1,64 +1,53 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { dbQuery } from '../accessModule'; import { query } from '../accessModule';
import call from '../call'; import call from '../call';
import { stderr, stdout, stdoutVar } from '../shell';
const buildGetRequestHandler = const buildGetRequestHandler =
( (
query: string | BuildQueryFunction, scriptOrCallback: string | BuildQueryFunction,
{ beforeRespond }: BuildGetRequestHandlerOptions = {}, { beforeRespond }: BuildGetRequestHandlerOptions = {},
) => ) =>
(request: Request, response: Response) => { async (request: Request, response: Response) => {
console.log('Calling CLI script to get data.'); stdout('Calling CLI script to get data.');
const buildQueryOptions: BuildQueryOptions = {}; const buildQueryOptions: BuildQueryOptions = {};
let queryStdout; let result: (number | null | string)[][];
try { try {
({ stdout: queryStdout } = dbQuery( const sqlscript: string =
call<string>(query, { typeof scriptOrCallback === 'function'
parameters: [request, buildQueryOptions], ? await scriptOrCallback(request, buildQueryOptions)
notCallableReturn: query, : scriptOrCallback;
}),
)); result = await query(sqlscript);
} catch (queryError) { } catch (queryError) {
console.log(`Failed to execute query; CAUSE: ${queryError}`); stderr(`Failed to execute query; CAUSE: ${queryError}`);
response.status(500).send(); response.status(500).send();
return; return;
} }
console.log( stdoutVar(result, `Query stdout pre-hooks (type=[${typeof result}]): `);
`Query stdout pre-hooks (type=[${typeof queryStdout}]): ${JSON.stringify(
queryStdout,
null,
2,
)}`,
);
const { afterQueryReturn } = buildQueryOptions; const { afterQueryReturn } = buildQueryOptions;
queryStdout = call(afterQueryReturn, { result = call(afterQueryReturn, {
parameters: [queryStdout], parameters: [result],
notCallableReturn: queryStdout, notCallableReturn: result,
}); });
queryStdout = call(beforeRespond, { result = call(beforeRespond, {
parameters: [queryStdout], parameters: [result],
notCallableReturn: queryStdout, notCallableReturn: result,
}); });
console.log( stdoutVar(result, `Query stdout post-hooks (type=[${typeof result}]): `);
`Query stdout post-hooks (type=[${typeof queryStdout}]): ${JSON.stringify(
queryStdout,
null,
2,
)}`,
);
response.json(queryStdout); response.json(result);
}; };
export default buildGetRequestHandler; export default buildGetRequestHandler;

@ -5,14 +5,14 @@ import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { job } from '../../accessModule'; import { job } from '../../accessModule';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
type DistinctDBJobParams = Omit< type DistinctJobParams = Omit<
DBJobParams, JobParams,
'file' | 'line' | 'job_data' | 'job_progress' 'file' | 'line' | 'job_data' | 'job_progress'
>; >;
const MANAGE_HOST_POWER_JOB_PARAMS: { const MANAGE_HOST_POWER_JOB_PARAMS: {
poweroff: DistinctDBJobParams; poweroff: DistinctJobParams;
reboot: DistinctDBJobParams; reboot: DistinctJobParams;
} = { } = {
poweroff: { poweroff: {
job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`, job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`,
@ -32,21 +32,19 @@ export const buildHostPowerHandler: (
task?: 'poweroff' | 'reboot', task?: 'poweroff' | 'reboot',
) => RequestHandler = ) => RequestHandler =
(task = 'reboot') => (task = 'reboot') =>
(request, response) => { async (request, response) => {
const subParams: DBJobParams = { const subParams: JobParams = {
file: __filename, file: __filename,
...MANAGE_HOST_POWER_JOB_PARAMS[task], ...MANAGE_HOST_POWER_JOB_PARAMS[task],
}; };
try { try {
job(subParams); await job(subParams);
} catch (subError) { } catch (subError) {
stderr(`Failed to ${task} host; CAUSE: ${subError}`); stderr(`Failed to ${task} host; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(204).send(); response.status(204).send();

@ -1,71 +1,68 @@
import assert from 'assert';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { REP_IPV4, REP_PEACEFUL_STRING } from '../../consts';
import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX'; import { HOST_KEY_CHANGED_PREFIX } from '../../consts/HOST_KEY_CHANGED_PREFIX';
import { dbQuery, getLocalHostUUID, getPeerData } from '../../accessModule'; import { getLocalHostUUID, getPeerData, query } from '../../accessModule';
import { sanitizeSQLParam } from '../../sanitizeSQLParam'; import { sanitize } from '../../sanitize';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const getHostSSH: RequestHandler< export const getHostSSH: RequestHandler<
unknown, unknown,
{ GetHostSshResponseBody,
badSSHKeys?: DeleteSSHKeyConflictRequestBody; GetHostSshRequestBody
hostName: string; > = async (request, response) => {
hostOS: string;
hostUUID: string;
isConnected: boolean;
isInetConnected: boolean;
isOSRegistered: boolean;
},
{
password: string;
port?: number;
ipAddress: string;
}
> = (request, response) => {
const { const {
body: { password, port = 22, ipAddress: target }, body: { password: rPassword, port: rPort = 22, ipAddress: rTarget } = {},
} = request; } = request;
let hostName: string; const password = sanitize(rPassword, 'string');
let hostOS: string; const port = sanitize(rPort, 'number');
let hostUUID: string; const target = sanitize(rTarget, 'string', { modifierType: 'sql' });
let isConnected: boolean;
let isInetConnected: boolean; try {
let isOSRegistered: boolean; assert(
REP_PEACEFUL_STRING.test(password),
`Password must be a peaceful string; got [${password}]`,
);
assert(
Number.isInteger(port),
`Port must be a valid integer; got [${port}]`,
);
assert(
REP_IPV4.test(target),
`IP address must be a valid IPv4 address; got [${target}]`,
);
} catch (assertError) {
stderr(`Assert failed when getting host SSH data; CAUSE: ${assertError}`);
return response.status(400).send();
}
const localHostUUID = getLocalHostUUID(); const localHostUUID = getLocalHostUUID();
let rsbody: GetHostSshResponseBody;
try { try {
({ rsbody = await getPeerData(target, { password, port });
hostName,
hostOS,
hostUUID,
isConnected,
isInetConnected,
isOSRegistered,
} = getPeerData(target, { password, port }));
} catch (subError) { } catch (subError) {
stderr(`Failed to get peer data; CAUSE: ${subError}`); stderr(`Failed to get peer data; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
let badSSHKeys: DeleteSSHKeyConflictRequestBody | undefined; if (!rsbody.isConnected) {
const rows: [stateNote: string, stateUUID: string][] = await query(`
if (!isConnected) {
const rows = dbQuery(`
SELECT sta.state_note, sta.state_uuid SELECT sta.state_note, sta.state_uuid
FROM states AS sta FROM states AS sta
WHERE sta.state_host_uuid = '${localHostUUID}' WHERE sta.state_host_uuid = '${localHostUUID}'
AND sta.state_name = '${HOST_KEY_CHANGED_PREFIX}${sanitizeSQLParam( AND sta.state_name = '${HOST_KEY_CHANGED_PREFIX}${target}';`);
target,
)}';`).stdout as [stateNote: string, stateUUID: string][];
if (rows.length > 0) { if (rows.length > 0) {
badSSHKeys = rows.reduce<DeleteSSHKeyConflictRequestBody>( rsbody.badSSHKeys = rows.reduce<DeleteSshKeyConflictRequestBody>(
(previous, [, stateUUID]) => { (previous, [, stateUUID]) => {
previous[localHostUUID].push(stateUUID); previous[localHostUUID].push(stateUUID);
@ -76,13 +73,5 @@ export const getHostSSH: RequestHandler<
} }
} }
response.status(200).send({ response.status(200).send(rsbody);
badSSHKeys,
hostName,
hostOS,
hostUUID,
isConnected,
isInetConnected,
isOSRegistered,
});
}; };

@ -4,7 +4,13 @@ import { RequestHandler } from 'express';
import { REP_PEACEFUL_STRING, REP_UUID } from '../../consts/REG_EXP_PATTERNS'; import { REP_PEACEFUL_STRING, REP_UUID } from '../../consts/REG_EXP_PATTERNS';
import SERVER_PATHS from '../../consts/SERVER_PATHS'; import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { getAnvilData, job, sub } from '../../accessModule'; import {
getData,
getHostData,
getManifestData,
job,
sub,
} from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
@ -12,7 +18,7 @@ export const runManifest: RequestHandler<
{ manifestUuid: string }, { manifestUuid: string },
undefined, undefined,
RunManifestRequestBody RunManifestRequestBody
> = (request, response) => { > = async (request, response) => {
const { const {
params: { manifestUuid }, params: { manifestUuid },
body: { body: {
@ -75,9 +81,7 @@ export const runManifest: RequestHandler<
assert(isHostListUnique, `Each entry in hosts must be unique`); assert(isHostListUnique, `Each entry in hosts must be unique`);
} catch (assertError) { } catch (assertError) {
handleAssertError(assertError); return handleAssertError(assertError);
return;
} }
let rawHostListData: AnvilDataHostListHash | undefined; let rawHostListData: AnvilDataHostListHash | undefined;
@ -85,43 +89,19 @@ export const runManifest: RequestHandler<
let rawSysData: AnvilDataSysHash | undefined; let rawSysData: AnvilDataSysHash | undefined;
try { try {
({ rawHostListData = await getHostData();
hosts: rawHostListData, rawManifestListData = await getManifestData(manifestUuid);
manifests: rawManifestListData, rawSysData = await getData('sys');
sys: rawSysData,
} = getAnvilData<{
hosts?: AnvilDataHostListHash;
manifests?: AnvilDataManifestListHash;
sys?: AnvilDataSysHash;
}>(
{ hosts: true, manifests: true, sys: true },
{
predata: [
['Database->get_hosts'],
[
'Striker->load_manifest',
{
debug,
manifest_uuid: manifestUuid,
},
],
],
},
));
} catch (subError) { } catch (subError) {
stderr( stderr(
`Failed to get install manifest ${manifestUuid}; CAUSE: ${subError}`, `Failed to get install manifest ${manifestUuid}; CAUSE: ${subError}`,
); );
response.status(500).send(); return response.status(500).send();
return;
} }
if (!rawHostListData || !rawManifestListData || !rawSysData) { if (!rawHostListData || !rawManifestListData || !rawSysData) {
response.status(404).send(); return response.status(404).send();
return;
} }
const { host_uuid: hostUuidMapToData } = rawHostListData; const { host_uuid: hostUuidMapToData } = rawHostListData;
@ -134,9 +114,9 @@ export const runManifest: RequestHandler<
} = rawManifestListData; } = rawManifestListData;
const { hosts: { by_uuid: mapToHostNameData = {} } = {} } = rawSysData; const { hosts: { by_uuid: mapToHostNameData = {} } = {} } = rawSysData;
const joinAnJobs: DBJobParams[] = []; const joinAnJobs: JobParams[] = [];
let anParams: Record<string, string> | undefined; let anParams: Record<string, string>;
try { try {
anParams = Object.values(hostList).reduce<Record<string, string>>( anParams = Object.values(hostList).reduce<Record<string, string>>(
@ -177,19 +157,19 @@ export const runManifest: RequestHandler<
} }
try { try {
const [newAnUuid] = sub('insert_or_update_anvils', { subParams: anParams }) const [newAnUuid]: [string] = await sub('insert_or_update_anvils', {
.stdout as [string]; params: [anParams],
});
joinAnJobs.forEach((jobParams) => { for (const jobParams of joinAnJobs) {
jobParams.job_data += `,anvil_uuid=${newAnUuid}`; jobParams.job_data += `,anvil_uuid=${newAnUuid}`;
job(jobParams);
}); await job(jobParams);
}
} catch (subError) { } catch (subError) {
stderr(`Failed to record new anvil node entry; CAUSE: ${subError}`); stderr(`Failed to record new anvil node entry; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(204).send(); response.status(204).send();

@ -5,9 +5,9 @@ import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { job } from '../../accessModule'; import { job } from '../../accessModule';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const updateSystem: RequestHandler = (request, response) => { export const updateSystem: RequestHandler = async (request, response) => {
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-update-system'].self, job_command: SERVER_PATHS.usr.sbin['anvil-update-system'].self,
job_description: 'job_0004', job_description: 'job_0004',
@ -17,9 +17,7 @@ export const updateSystem: RequestHandler = (request, response) => {
} catch (subError) { } catch (subError) {
stderr(`Failed to initiate system update; CAUSE: ${subError}`); stderr(`Failed to initiate system update; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(204).send(); response.status(204).send();

@ -1,15 +1,13 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { getAnvilData } from '../../accessModule';
import { getFenceSpec } from '../../accessModule';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const getFenceTemplate: RequestHandler = (request, response) => { export const getFenceTemplate: RequestHandler = async (request, response) => {
let rawFenceData; let rawFenceData;
try { try {
({ fence_data: rawFenceData } = getAnvilData<{ fence_data: unknown }>( rawFenceData = await getFenceSpec();
{ fence_data: true },
{ predata: [['Striker->get_fence_data']] },
));
} catch (subError) { } catch (subError) {
stderr(`Failed to get fence device template; CAUSE: ${subError}`); stderr(`Failed to get fence device template; CAUSE: ${subError}`);

@ -1,6 +1,9 @@
import { DELETED } from '../../consts';
import join from '../../join'; import join from '../../join';
import { stdoutVar } from '../../shell';
const buildQueryFileDetail = ({ export const buildQueryFileDetail = ({
fileUUIDs = ['*'], fileUUIDs = ['*'],
}: { }: {
fileUUIDs?: string[] | '*'; fileUUIDs?: string[] | '*';
@ -14,7 +17,7 @@ const buildQueryFileDetail = ({
separator: ', ', separator: ', ',
}); });
console.log(`condFilesUUID=[${condFileUUIDs}]`); stdoutVar({ condFileUUIDs });
return ` return `
SELECT SELECT
@ -33,8 +36,6 @@ const buildQueryFileDetail = ({
ON fil.file_uuid = fil_loc.file_location_file_uuid ON fil.file_uuid = fil_loc.file_location_file_uuid
JOIN anvils AS anv JOIN anvils AS anv
ON fil_loc.file_location_anvil_uuid = anv.anvil_uuid ON fil_loc.file_location_anvil_uuid = anv.anvil_uuid
WHERE fil.file_type != 'DELETED' WHERE fil.file_type != '${DELETED}'
${condFileUUIDs};`; ${condFileUUIDs};`;
}; };
export default buildQueryFileDetail;

@ -0,0 +1,16 @@
import { RequestHandler } from 'express';
import { anvilSyncShared } from '../../accessModule';
import { stdout, stdoutVar } from '../../shell';
export const createFile: RequestHandler = async ({ file, body }, response) => {
stdout('Receiving shared file.');
if (!file) return response.status(400).send();
stdoutVar({ body, file });
await anvilSyncShared('move_incoming', `file=${file.path}`, '0132', '0133');
response.status(201).send();
};

@ -0,0 +1,29 @@
import { RequestHandler } from 'express';
import { DELETED } from '../../consts';
import { anvilSyncShared, query, timestamp, write } from '../../accessModule';
export const deleteFile: RequestHandler = async (request, response) => {
const { fileUUID } = request.params;
const [[oldFileType]] = await query(
`SELECT file_type FROM files WHERE file_uuid = '${fileUUID}';`,
);
if (oldFileType !== DELETED) {
await write(
`UPDATE files
SET
file_type = '${DELETED}',
modified_date = '${timestamp()}'
WHERE file_uuid = '${fileUUID}';`,
);
await anvilSyncShared('purge', `file_uuid=${fileUUID}`, '0136', '0137', {
jobHostUUID: 'all',
});
}
response.status(204).send();
};

@ -1,10 +1,12 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { DELETED } from '../../consts';
import buildGetRequestHandler from '../buildGetRequestHandler'; import buildGetRequestHandler from '../buildGetRequestHandler';
import buildQueryFileDetail from './buildQueryFileDetail'; import { buildQueryFileDetail } from './buildQueryFileDetail';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
const getFile: RequestHandler = buildGetRequestHandler((request) => { export const getFile: RequestHandler = buildGetRequestHandler((request) => {
const { fileUUIDs } = request.query; const { fileUUIDs } = request.query;
let query = ` let query = `
@ -15,7 +17,7 @@ const getFile: RequestHandler = buildGetRequestHandler((request) => {
file_type, file_type,
file_md5sum file_md5sum
FROM files FROM files
WHERE file_type != 'DELETED';`; WHERE file_type != '${DELETED}';`;
if (fileUUIDs) { if (fileUUIDs) {
query = buildQueryFileDetail({ query = buildQueryFileDetail({
@ -27,5 +29,3 @@ const getFile: RequestHandler = buildGetRequestHandler((request) => {
return query; return query;
}); });
export default getFile;

@ -1,12 +1,10 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import buildGetRequestHandler from '../buildGetRequestHandler'; import buildGetRequestHandler from '../buildGetRequestHandler';
import buildQueryFileDetail from './buildQueryFileDetail'; import { buildQueryFileDetail } from './buildQueryFileDetail';
import { sanitizeSQLParam } from '../../sanitizeSQLParam'; import { sanitizeSQLParam } from '../../sanitizeSQLParam';
const getFileDetail: RequestHandler = buildGetRequestHandler( export const getFileDetail: RequestHandler = buildGetRequestHandler(
({ params: { fileUUID } }) => ({ params: { fileUUID } }) =>
buildQueryFileDetail({ fileUUIDs: [sanitizeSQLParam(fileUUID)] }), buildQueryFileDetail({ fileUUIDs: [sanitizeSQLParam(fileUUID)] }),
); );
export default getFileDetail;

@ -0,0 +1,5 @@
export * from './createFile';
export * from './deleteFile';
export * from './getFile';
export * from './getFileDetail';
export * from './updateFile';

@ -0,0 +1,134 @@
import { RequestHandler } from 'express';
import { anvilSyncShared, query, timestamp, write } from '../../accessModule';
import { stderr, stdoutVar } from '../../shell';
export const updateFile: RequestHandler = async (request, response) => {
const { body = {}, params } = request;
stdoutVar(body, 'Begin edit single file. body=');
const { fileUUID } = params;
const { fileName, fileLocations, fileType } = body;
const anvilSyncSharedFunctions = [];
let sqlscript = '';
if (fileName) {
const [[oldFileName]] = await query(
`SELECT file_name FROM files WHERE file_uuid = '${fileUUID}';`,
);
stdoutVar({ oldFileName, fileName });
if (fileName !== oldFileName) {
sqlscript += `
UPDATE files
SET
file_name = '${fileName}',
modified_date = '${timestamp()}'
WHERE file_uuid = '${fileUUID}';`;
anvilSyncSharedFunctions.push(() =>
anvilSyncShared(
'rename',
`file_uuid=${fileUUID}\nold_name=${oldFileName}\nnew_name=${fileName}`,
'0138',
'0139',
{ jobHostUUID: 'all' },
),
);
}
}
if (fileType) {
sqlscript += `
UPDATE files
SET
file_type = '${fileType}',
modified_date = '${timestamp()}'
WHERE file_uuid = '${fileUUID}';`;
anvilSyncSharedFunctions.push(() =>
anvilSyncShared('check_mode', `file_uuid=${fileUUID}`, '0143', '0144', {
jobHostUUID: 'all',
}),
);
}
if (fileLocations) {
fileLocations.forEach(
async ({
fileLocationUUID,
isFileLocationActive,
}: {
fileLocationUUID: string;
isFileLocationActive: boolean;
}) => {
let fileLocationActive = 0;
let jobName = 'purge';
let jobTitle = '0136';
let jobDescription = '0137';
if (isFileLocationActive) {
fileLocationActive = 1;
jobName = 'pull_file';
jobTitle = '0132';
jobDescription = '0133';
}
sqlscript += `
UPDATE file_locations
SET
file_location_active = '${fileLocationActive}',
modified_date = '${timestamp()}'
WHERE file_location_uuid = '${fileLocationUUID}';`;
const targetHosts: [
n1uuid: string,
n2uuid: string,
dr1uuid: null | string,
][] = await query(
`SELECT
anv.anvil_node1_host_uuid,
anv.anvil_node2_host_uuid,
anv.anvil_dr1_host_uuid
FROM anvils AS anv
JOIN file_locations AS fil_loc
ON anv.anvil_uuid = fil_loc.file_location_anvil_uuid
WHERE fil_loc.file_location_uuid = '${fileLocationUUID}';`,
);
targetHosts.flat().forEach((hostUUID: null | string) => {
if (hostUUID) {
anvilSyncSharedFunctions.push(() =>
anvilSyncShared(
jobName,
`file_uuid=${fileUUID}`,
jobTitle,
jobDescription,
{ jobHostUUID: hostUUID },
),
);
}
});
},
);
}
let wcode: number;
try {
wcode = await write(sqlscript);
} catch (queryError) {
stderr(`Failed to execute query; CAUSE: ${queryError}`);
return response.status(500).send();
}
anvilSyncSharedFunctions.forEach(async (fn, index) =>
stdoutVar(await fn(), `Anvil sync shared [${index}] output: `),
);
response.status(200).send(wcode);
};

@ -3,13 +3,15 @@ import { RequestHandler } from 'express';
import { import {
REP_DOMAIN, REP_DOMAIN,
REP_INTEGER,
REP_IPV4, REP_IPV4,
REP_IPV4_CSV, REP_IPV4_CSV,
} from '../../consts/REG_EXP_PATTERNS'; REP_PEACEFUL_STRING,
import SERVER_PATHS from '../../consts/SERVER_PATHS'; SERVER_PATHS,
} from '../../consts';
import { job } from '../../accessModule'; import { job } from '../../accessModule';
import { sanitize } from '../../sanitize';
import { stderr, stdoutVar } from '../../shell';
const fvar = (configStepCount: number, fieldName: string) => const fvar = (configStepCount: number, fieldName: string) =>
['form', `config_step${configStepCount}`, fieldName, 'value'].join('::'); ['form', `config_step${configStepCount}`, fieldName, 'value'].join('::');
@ -38,91 +40,90 @@ ${fvar(
export const configStriker: RequestHandler< export const configStriker: RequestHandler<
unknown, unknown,
undefined, undefined,
InitializeStrikerForm Partial<InitializeStrikerForm>
> = ({ body }, response) => { > = async (request, response) => {
console.log('Begin initialize Striker.'); const { body = {} } = request;
console.dir(body, { depth: null });
stdoutVar(body, 'Begin initialize Striker; body=');
const { const {
adminPassword = '', adminPassword: rAdminPassword = '',
domainName = '', domainName: rDomainName = '',
hostName = '', hostName: rHostName = '',
hostNumber = 0, hostNumber: rHostNumber = 0,
networkDNS = '', networkDNS: rNetworkDns = '',
networkGateway = '', networkGateway: rNetworkGateway = '',
networks = [], networks = [],
organizationName = '', organizationName: rOrganizationName = '',
organizationPrefix = '', organizationPrefix: rOrganizationPrefix = '',
} = body || {}; } = body;
const dataAdminPassword = String(adminPassword); const adminPassword = sanitize(rAdminPassword, 'string');
const dataDomainName = String(domainName); const domainName = sanitize(rDomainName, 'string');
const dataHostName = String(hostName); const hostName = sanitize(rHostName, 'string');
const dataHostNumber = String(hostNumber); const hostNumber = sanitize(rHostNumber, 'number');
const dataNetworkDNS = String(networkDNS); const networkDns = sanitize(rNetworkDns, 'string');
const dataNetworkGateway = String(networkGateway); const networkGateway = sanitize(rNetworkGateway, 'string');
const dataOrganizationName = String(organizationName); const organizationName = sanitize(rOrganizationName, 'string');
const dataOrganizationPrefix = String(organizationPrefix); const organizationPrefix = sanitize(rOrganizationPrefix, 'string');
try { try {
assert( assert(
!/['"/\\><}{]/g.test(dataAdminPassword), REP_PEACEFUL_STRING.test(adminPassword),
`Data admin password cannot contain single-quote, double-quote, slash, backslash, angle brackets, and curly brackets; got [${dataAdminPassword}]`, `Data admin password cannot contain single-quote, double-quote, slash, backslash, angle brackets, and curly brackets; got [${adminPassword}]`,
); );
assert( assert(
REP_DOMAIN.test(dataDomainName), REP_DOMAIN.test(domainName),
`Data domain name can only contain alphanumeric, hyphen, and dot characters; got [${dataDomainName}]`, `Data domain name can only contain alphanumeric, hyphen, and dot characters; got [${domainName}]`,
); );
assert( assert(
REP_DOMAIN.test(dataHostName), REP_DOMAIN.test(hostName),
`Data host name can only contain alphanumeric, hyphen, and dot characters; got [${dataHostName}]`, `Data host name can only contain alphanumeric, hyphen, and dot characters; got [${hostName}]`,
); );
assert( assert(
REP_INTEGER.test(dataHostNumber) && hostNumber > 0, Number.isInteger(hostNumber) && hostNumber > 0,
`Data host number can only contain digits; got [${dataHostNumber}]`, `Data host number can only contain digits; got [${hostNumber}]`,
); );
assert( assert(
REP_IPV4_CSV.test(dataNetworkDNS), REP_IPV4_CSV.test(networkDns),
`Data network DNS must be a comma separated list of valid IPv4 addresses; got [${dataNetworkDNS}]`, `Data network DNS must be a comma separated list of valid IPv4 addresses; got [${networkDns}]`,
); );
assert( assert(
REP_IPV4.test(dataNetworkGateway), REP_IPV4.test(networkGateway),
`Data network gateway must be a valid IPv4 address; got [${dataNetworkGateway}]`, `Data network gateway must be a valid IPv4 address; got [${networkGateway}]`,
); );
assert( assert(
dataOrganizationName.length > 0, REP_PEACEFUL_STRING.test(organizationName),
`Data organization name cannot be empty; got [${dataOrganizationName}]`, `Data organization name cannot be empty; got [${organizationName}]`,
); );
assert( assert(
/^[a-z0-9]{1,5}$/.test(dataOrganizationPrefix), /^[a-z0-9]{1,5}$/.test(organizationPrefix),
`Data organization prefix can only contain 1 to 5 lowercase alphanumeric characters; got [${dataOrganizationPrefix}]`, `Data organization prefix can only contain 1 to 5 lowercase alphanumeric characters; got [${organizationPrefix}]`,
); );
} catch (assertError) { } catch (assertError) {
console.log( stderr(
`Failed to assert value when trying to initialize striker; CAUSE: ${assertError}.`, `Failed to assert value when trying to initialize striker; CAUSE: ${assertError}.`,
); );
response.status(400).send(); return response.status(400).send();
return;
} }
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-configure-host'].self, job_command: SERVER_PATHS.usr.sbin['anvil-configure-host'].self,
job_data: `${fvar(1, 'domain')}=${domainName} job_data: `${fvar(1, 'domain')}=${domainName}
${fvar(1, 'organization')}=${organizationName} ${fvar(1, 'organization')}=${organizationName}
${fvar(1, 'prefix')}=${organizationPrefix} ${fvar(1, 'prefix')}=${organizationPrefix}
${fvar(1, 'sequence')}=${hostNumber} ${fvar(1, 'sequence')}=${hostNumber}
${fvar(2, 'dns')}=${networkDNS} ${fvar(2, 'dns')}=${networkDns}
${fvar(2, 'gateway')}=${networkGateway} ${fvar(2, 'gateway')}=${networkGateway}
${fvar(2, 'host_name')}=${hostName} ${fvar(2, 'host_name')}=${hostName}
${fvar(2, 'striker_password')}=${adminPassword} ${fvar(2, 'striker_password')}=${adminPassword}
@ -153,11 +154,9 @@ ${buildNetworkLinks(2, networkShortName, interfaces)}`;
job_description: 'job_0071', job_description: 'job_0071',
}); });
} catch (subError) { } catch (subError) {
console.log(`Failed to queue striker initialization; CAUSE: ${subError}`); stderr(`Failed to queue striker initialization; CAUSE: ${subError}`);
response.status(500).send();
return; return response.status(500).send();
} }
response.status(200).send(); response.status(200).send();

@ -3,7 +3,7 @@ import { RequestHandler } from 'express';
import SERVER_PATHS from '../../consts/SERVER_PATHS'; import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { import {
getAnvilData, getData,
getLocalHostUUID, getLocalHostUUID,
getPeerData, getPeerData,
job, job,
@ -16,7 +16,7 @@ export const createHostConnection: RequestHandler<
unknown, unknown,
undefined, undefined,
CreateHostConnectionRequestBody CreateHostConnectionRequestBody
> = (request, response) => { > = async (request, response) => {
const { const {
body: { body: {
dbName = 'anvil', dbName = 'anvil',
@ -46,16 +46,15 @@ export const createHostConnection: RequestHandler<
let peerHostUUID: string; let peerHostUUID: string;
try { try {
({ hostUUID: peerHostUUID, isConnected: isPeerReachable } = getPeerData( ({ hostUUID: peerHostUUID, isConnected: isPeerReachable } =
peerIPAddress, await getPeerData(peerIPAddress, {
{ password: commonPassword, port: peerSSHPort }, password: commonPassword,
)); port: peerSSHPort,
}));
} catch (subError) { } catch (subError) {
stderr(`Failed to get peer data; CAUSE: ${subError}`); stderr(`Failed to get peer data; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
stdoutVar({ peerHostUUID, isPeerReachable }); stdoutVar({ peerHostUUID, isPeerReachable });
@ -65,22 +64,18 @@ export const createHostConnection: RequestHandler<
`Cannot connect to peer; please verify credentials and SSH keys validity.`, `Cannot connect to peer; please verify credentials and SSH keys validity.`,
); );
response.status(400).send(); return response.status(400).send();
return;
} }
try { try {
localIPAddress = sub('find_matching_ip', { [localIPAddress] = await sub('find_matching_ip', {
subModuleName: 'System', params: [{ host: peerIPAddress }],
subParams: { host: peerIPAddress }, pre: ['System'],
}).stdout; });
} catch (subError) { } catch (subError) {
stderr(`Failed to get matching IP address; CAUSE: ${subError}`); stderr(`Failed to get matching IP address; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
stdoutVar({ localIPAddress }); stdoutVar({ localIPAddress });
@ -94,31 +89,34 @@ export const createHostConnection: RequestHandler<
stdoutVar({ pgpassFilePath, pgpassFileBody }); stdoutVar({ pgpassFilePath, pgpassFileBody });
try { try {
sub('write_file', { await sub('write_file', {
subModuleName: 'Storage', params: [
subParams: { {
body: pgpassFileBody, body: pgpassFileBody,
file: pgpassFilePath, file: pgpassFilePath,
mode: '0600', mode: '0600',
overwrite: 1, overwrite: 1,
secure: 1, secure: 1,
}, },
],
pre: ['Storage'],
}); });
} catch (subError) { } catch (subError) {
stderr(`Failed to write ${pgpassFilePath}; CAUSE: ${subError}`); stderr(`Failed to write ${pgpassFilePath}; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
try { try {
const [rawIsPeerDBReachable] = sub('call', { const [rawIsPeerDBReachable]: [output: string, returnCode: number] =
subModuleName: 'System', await sub('call', {
subParams: { params: [
shell_call: `PGPASSFILE="${pgpassFilePath}" ${SERVER_PATHS.usr.bin.psql.self} --host ${peerIPAddress} --port ${commonDBPort} --dbname ${commonDBName} --username ${commonDBUser} --no-password --tuples-only --no-align --command "SELECT 1"`, {
}, shell_call: `PGPASSFILE="${pgpassFilePath}" ${SERVER_PATHS.usr.bin.psql.self} --host ${peerIPAddress} --port ${commonDBPort} --dbname ${commonDBName} --username ${commonDBUser} --no-password --tuples-only --no-align --command "SELECT 1"`,
}).stdout as [output: string, returnCode: number]; },
],
pre: ['System'],
});
isPeerDBReachable = rawIsPeerDBReachable === '1'; isPeerDBReachable = rawIsPeerDBReachable === '1';
} catch (subError) { } catch (subError) {
@ -130,9 +128,7 @@ export const createHostConnection: RequestHandler<
} catch (fsError) { } catch (fsError) {
stderr(`Failed to remove ${pgpassFilePath}; CAUSE: ${fsError}`); stderr(`Failed to remove ${pgpassFilePath}; CAUSE: ${fsError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
stdoutVar({ isPeerDBReachable }); stdoutVar({ isPeerDBReachable });
@ -142,34 +138,28 @@ export const createHostConnection: RequestHandler<
`Cannot connect to peer database; please verify database credentials.`, `Cannot connect to peer database; please verify database credentials.`,
); );
response.status(400).send(); return response.status(400).send();
return;
} }
const localHostUUID = getLocalHostUUID(); const localHostUUID = getLocalHostUUID();
try { try {
const { const {
database: { [localHostUUID]: { port: rawLocalDBPort },
[localHostUUID]: { port: rawLocalDBPort }, } = await getData<AnvilDataDatabaseHash>('database');
},
} = getAnvilData<{ database: AnvilDataDatabaseHash }>({ database: true });
localDBPort = sanitize(rawLocalDBPort, 'number'); localDBPort = sanitize(rawLocalDBPort, 'number');
} catch (subError) { } catch (subError) {
stderr(`Failed to get local database data from hash; CAUSE: ${subError}`); stderr(`Failed to get local database data from hash; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
const jobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${peerHostUUID} --host ${peerIPAddress} --port ${commonDBPort} --ping ${commonPing}`; const jobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${peerHostUUID} --host ${peerIPAddress} --port ${commonDBPort} --ping ${commonPing}`;
const peerJobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${localHostUUID} --host ${localIPAddress} --port ${localDBPort} --ping ${commonPing}`; const peerJobCommand = `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --add --host-uuid ${localHostUUID} --host ${localIPAddress} --port ${localDBPort} --ping ${commonPing}`;
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: jobCommand, job_command: jobCommand,
job_data: `password=${commonPassword} job_data: `password=${commonPassword}
@ -181,9 +171,7 @@ peer_job_command=${peerJobCommand}`,
} catch (subError) { } catch (subError) {
stderr(`Failed to add peer ${peerHostUUID}; CAUSE: ${subError}`); stderr(`Failed to add peer ${peerHostUUID}; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(201).send(); response.status(201).send();

@ -10,33 +10,31 @@ export const deleteHostConnection: RequestHandler<
unknown, unknown,
undefined, undefined,
DeleteHostConnectionRequestBody DeleteHostConnectionRequestBody
> = (request, response) => { > = async (request, response) => {
const { body } = request; const { body } = request;
const hostUUIDs = Object.keys(body); const hostUuids = Object.keys(body);
hostUUIDs.forEach((key) => { for (const key of hostUuids) {
const hostUUID = toHostUUID(key); const hostUuid = toHostUUID(key);
const peerHostUUIDs = body[key]; const peerHostUuids = body[key];
peerHostUUIDs.forEach((peerHostUUID) => { for (const peerHostUuid of peerHostUuids) {
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --remove --host-uuid ${peerHostUUID}`, job_command: `${SERVER_PATHS.usr.sbin['striker-manage-peers'].self} --remove --host-uuid ${peerHostUuid}`,
job_description: 'job_0014', job_description: 'job_0014',
job_host_uuid: hostUUID, job_host_uuid: hostUuid,
job_name: 'striker-peer::delete', job_name: 'striker-peer::delete',
job_title: 'job_0013', job_title: 'job_0013',
}); });
} catch (subError) { } catch (subError) {
stderr(`Failed to delete peer ${peerHostUUID}; CAUSE: ${subError}`); stderr(`Failed to delete peer ${peerHostUuid}; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
}); }
}); }
response.status(204).send(); response.status(204).send();
}; };

@ -1,4 +1,4 @@
import { getAnvilData, getLocalHostUUID } from '../../accessModule'; import { getData, getLocalHostUUID } from '../../accessModule';
import { buildUnknownIDCondition } from '../../buildCondition'; import { buildUnknownIDCondition } from '../../buildCondition';
import buildGetRequestHandler from '../buildGetRequestHandler'; import buildGetRequestHandler from '../buildGetRequestHandler';
import { toLocal } from '../../convertHostUUID'; import { toLocal } from '../../convertHostUUID';
@ -39,7 +39,7 @@ const buildHostConnections = (
); );
export const getHostConnection = buildGetRequestHandler( export const getHostConnection = buildGetRequestHandler(
(request, buildQueryOptions) => { async (request, buildQueryOptions) => {
const { hostUUIDs: rawHostUUIDs } = request.query; const { hostUUIDs: rawHostUUIDs } = request.query;
let rawDatabaseData: AnvilDataDatabaseHash; let rawDatabaseData: AnvilDataDatabaseHash;
@ -59,9 +59,7 @@ export const getHostConnection = buildGetRequestHandler(
stdout(`condHostUUIDs=[${condHostUUIDs}]`); stdout(`condHostUUIDs=[${condHostUUIDs}]`);
try { try {
({ database: rawDatabaseData } = getAnvilData<{ database: AnvilDataDatabaseHash }>( rawDatabaseData = await getData<AnvilDataDatabaseHash>('database');
{ database: true },
));
} catch (subError) { } catch (subError) {
throw new Error(`Failed to get anvil data; CAUSE: ${subError}`); throw new Error(`Failed to get anvil data; CAUSE: ${subError}`);
} }

@ -6,8 +6,8 @@ import {
REP_IPV4, REP_IPV4,
REP_PEACEFUL_STRING, REP_PEACEFUL_STRING,
REP_UUID, REP_UUID,
} from '../../consts/REG_EXP_PATTERNS'; SERVER_PATHS,
import SERVER_PATHS from '../../consts/SERVER_PATHS'; } from '../../consts';
import { job, variable } from '../../accessModule'; import { job, variable } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
@ -17,7 +17,7 @@ export const prepareHost: RequestHandler<
unknown, unknown,
undefined, undefined,
PrepareHostRequestBody PrepareHostRequestBody
> = (request, response) => { > = async (request, response) => {
const { const {
body: { body: {
enterpriseUUID, enterpriseUUID,
@ -106,14 +106,12 @@ export const prepareHost: RequestHandler<
`Failed to assert value when trying to prepare host; CAUSE: ${assertError}`, `Failed to assert value when trying to prepare host; CAUSE: ${assertError}`,
); );
response.status(400).send(); return response.status(400).send();
return;
} }
try { try {
if (isHostUUIDProvided) { if (isHostUUIDProvided) {
variable({ await variable({
file: __filename, file: __filename,
update_value_only: 1, update_value_only: 1,
variable_name: 'system::configured', variable_name: 'system::configured',
@ -123,7 +121,7 @@ export const prepareHost: RequestHandler<
}); });
} }
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['striker-initialize-host'].self, job_command: SERVER_PATHS.usr.sbin['striker-initialize-host'].self,
job_data: `enterprise_uuid=${dataEnterpriseUUID} job_data: `enterprise_uuid=${dataEnterpriseUUID}
@ -141,9 +139,7 @@ type=${dataHostType}`,
} catch (subError) { } catch (subError) {
stderr(`Failed to init host; CAUSE: ${subError}`); stderr(`Failed to init host; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(200).send(); response.status(200).send();

@ -4,36 +4,38 @@ import { LOCAL } from '../../consts/LOCAL';
import SERVER_PATHS from '../../consts/SERVER_PATHS'; import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { job } from '../../accessModule'; import { job } from '../../accessModule';
import { stderr, stdout } from '../../shell'; import { stderr, stdoutVar } from '../../shell';
export const setHostInstallTarget: RequestHandler = (request, response) => { export const setHostInstallTarget: RequestHandler<
stdout( UpdateHostParams,
`Begin set host install target.\n${JSON.stringify(request.body, null, 2)}`, undefined,
); SetHostInstallTargetRequestBody
> = async (request, response) => {
const { isEnableInstallTarget } = const { body, params } = request;
request.body as SetHostInstallTargetRequestBody;
const { hostUUID: rawHostUUID } = request.params as UpdateHostParams; stdoutVar(body, `Begin set host install target; body=`);
const hostUUID: string | undefined =
rawHostUUID === LOCAL ? undefined : rawHostUUID; const { isEnableInstallTarget } = body;
const { hostUUID: rHostUuid } = params;
const hostUuid: string | undefined =
rHostUuid === LOCAL ? undefined : rHostUuid;
const task = isEnableInstallTarget ? 'enable' : 'disable'; const task = isEnableInstallTarget ? 'enable' : 'disable';
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: `${SERVER_PATHS.usr.sbin['striker-manage-install-target'].self} --${task}`, job_command: `${SERVER_PATHS.usr.sbin['striker-manage-install-target'].self} --${task}`,
job_description: 'job_0016', job_description: 'job_0016',
job_host_uuid: hostUUID, job_host_uuid: hostUuid,
job_name: `install-target::${task}`, job_name: `install-target::${task}`,
job_title: 'job_0015', job_title: 'job_0015',
}); });
} catch (subError) { } catch (subError) {
stderr(`Failed to ${task} install target; CAUSE: ${subError}`); stderr(`Failed to ${task} install target; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(200).send(); response.status(204).send();
}; };

@ -13,7 +13,7 @@ import { sub } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { stdout } from '../../shell'; import { stdout } from '../../shell';
export const buildManifest = ( export const buildManifest = async (
...[request]: Parameters< ...[request]: Parameters<
RequestHandler< RequestHandler<
{ manifestUuid?: string }, { manifestUuid?: string },
@ -257,24 +257,29 @@ export const buildManifest = (
{}, {},
); );
let result: { name: string; uuid: string } | undefined; let result: { name: string; uuid: string };
try { try {
const [uuid, name] = sub('generate_manifest', { const [uuid, name]: [manifestUuid: string, anvilName: string] = await sub(
subModuleName: 'Striker', 'generate_manifest',
subParams: { {
dns, params: [
domain, {
manifest_uuid: manifestUuid, dns,
mtu, domain,
ntp, manifest_uuid: manifestUuid,
prefix, mtu,
sequence, ntp,
...networkCountContainer, prefix,
...networkContainer, sequence,
...hostContainer, ...networkCountContainer,
...networkContainer,
...hostContainer,
},
],
pre: ['Striker'],
}, },
}).stdout as [manifestUuid: string, anvilName: string]; );
result = { name, uuid }; result = { name, uuid };
} catch (subError) { } catch (subError) {

@ -4,13 +4,13 @@ import { RequestHandler } from 'express';
import { buildManifest } from './buildManifest'; import { buildManifest } from './buildManifest';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const createManifest: RequestHandler = (...handlerArgs) => { export const createManifest: RequestHandler = async (...handlerArgs) => {
const [, response] = handlerArgs; const [, response] = handlerArgs;
let result: Record<string, string> = {}; let result: Record<string, string> = {};
try { try {
result = buildManifest(...handlerArgs); result = await buildManifest(...handlerArgs);
} catch (buildError) { } catch (buildError) {
stderr(`Failed to create new install manifest; CAUSE ${buildError}`); stderr(`Failed to create new install manifest; CAUSE ${buildError}`);

@ -7,7 +7,7 @@ export const deleteManifest: RequestHandler<
{ manifestUuid: string }, { manifestUuid: string },
undefined, undefined,
{ uuids: string[] } { uuids: string[] }
> = (request, response) => { > = async (request, response) => {
const { const {
params: { manifestUuid: rawManifestUuid }, params: { manifestUuid: rawManifestUuid },
body: { uuids: rawManifestUuidList } = {}, body: { uuids: rawManifestUuidList } = {},
@ -17,21 +17,19 @@ export const deleteManifest: RequestHandler<
? rawManifestUuidList ? rawManifestUuidList
: [rawManifestUuid]; : [rawManifestUuid];
manifestUuidList.forEach((uuid) => { for (const uuid of manifestUuidList) {
stdout(`Begin delete manifest ${uuid}.`); stdout(`Begin delete manifest ${uuid}.`);
try { try {
sub('insert_or_update_manifests', { await sub('insert_or_update_manifests', {
subParams: { delete: 1, manifest_uuid: uuid }, params: [{ delete: 1, manifest_uuid: uuid }],
}); });
} catch (subError) { } catch (subError) {
stderr(`Failed to delete manifest ${uuid}; CAUSE: ${subError}`); stderr(`Failed to delete manifest ${uuid}; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
}); }
response.status(204).send(); response.status(204).send();
}; };

@ -1,6 +1,6 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { getAnvilData } from '../../accessModule'; import { getManifestData } from '../../accessModule';
import { getEntityParts } from '../../disassembleEntityId'; import { getEntityParts } from '../../disassembleEntityId';
import { stderr, stdout } from '../../shell'; import { stderr, stdout } from '../../shell';
@ -67,25 +67,18 @@ const handleSortNetworks = <T extends [string, unknown]>(
return result; return result;
}; };
export const getManifestDetail: RequestHandler = (request, response) => { export const getManifestDetail: RequestHandler = async (request, response) => {
const { const {
params: { manifestUUID }, params: { manifestUUID: manifestUuid },
} = request; } = request;
let rawManifestListData: AnvilDataManifestListHash | undefined; let rawManifestListData: AnvilDataManifestListHash | undefined;
try { try {
({ manifests: rawManifestListData } = getAnvilData<{ rawManifestListData = await getManifestData(manifestUuid);
manifests?: AnvilDataManifestListHash;
}>(
{ manifests: true },
{
predata: [['Striker->load_manifest', { manifest_uuid: manifestUUID }]],
},
));
} catch (subError) { } catch (subError) {
stderr( stderr(
`Failed to get install manifest ${manifestUUID}; CAUSE: ${subError}`, `Failed to get install manifest ${manifestUuid}; CAUSE: ${subError}`,
); );
response.status(500).send(); response.status(500).send();
@ -109,7 +102,7 @@ export const getManifestDetail: RequestHandler = (request, response) => {
const { const {
manifest_uuid: { manifest_uuid: {
[manifestUUID]: { [manifestUuid]: {
parsed: { parsed: {
domain, domain,
fences: fenceUuidList = {}, fences: fenceUuidList = {},

@ -1,6 +1,6 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { dbQuery, getLocalHostName } from '../../accessModule'; import { getLocalHostName, query } from '../../accessModule';
import { import {
getHostNameDomain, getHostNameDomain,
getHostNamePrefix, getHostNamePrefix,
@ -8,17 +8,18 @@ import {
} from '../../disassembleHostName'; } from '../../disassembleHostName';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const getManifestTemplate: RequestHandler = (request, response) => { export const getManifestTemplate: RequestHandler = async (
let localHostName = ''; request,
response,
) => {
let localHostName: string;
try { try {
localHostName = getLocalHostName(); localHostName = getLocalHostName();
} catch (subError) { } catch (subError) {
stderr(String(subError)); stderr(String(subError));
response.status(500).send(); return response.status(500).send();
return;
} }
const localShortHostName = getShortHostName(localHostName); const localShortHostName = getShortHostName(localHostName);
@ -38,7 +39,7 @@ export const getManifestTemplate: RequestHandler = (request, response) => {
>; >;
try { try {
({ stdout: rawQueryResult } = dbQuery( rawQueryResult = await query(
`SELECT `SELECT
a.fence_uuid, a.fence_uuid,
a.fence_name, a.fence_name,
@ -71,13 +72,11 @@ export const getManifestTemplate: RequestHandler = (request, response) => {
ORDER BY manifest_name DESC ORDER BY manifest_name DESC
LIMIT 1 LIMIT 1
) AS c ON a.row_number = c.row_number;`, ) AS c ON a.row_number = c.row_number;`,
)); );
} catch (queryError) { } catch (queryError) {
stderr(`Failed to execute query; CAUSE: ${queryError}`); stderr(`Failed to execute query; CAUSE: ${queryError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
const queryResult = rawQueryResult.reduce< const queryResult = rawQueryResult.reduce<

@ -4,7 +4,7 @@ import { RequestHandler } from 'express';
import { buildManifest } from './buildManifest'; import { buildManifest } from './buildManifest';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const updateManifest: RequestHandler = (...args) => { export const updateManifest: RequestHandler = async (...args) => {
const [request, response] = args; const [request, response] = args;
const { const {
params: { manifestUuid }, params: { manifestUuid },
@ -13,7 +13,7 @@ export const updateManifest: RequestHandler = (...args) => {
let result: Record<string, string> = {}; let result: Record<string, string> = {};
try { try {
result = buildManifest(...args); result = await buildManifest(...args);
} catch (buildError) { } catch (buildError) {
stderr( stderr(
`Failed to update install manifest ${manifestUuid}; CAUSE: ${buildError}`, `Failed to update install manifest ${manifestUuid}; CAUSE: ${buildError}`,

@ -1,107 +1,120 @@
import assert from 'assert'; import assert from 'assert';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { REP_UUID, SERVER_PATHS } from '../../consts';
import { OS_LIST_MAP } from '../../consts/OS_LIST'; import { OS_LIST_MAP } from '../../consts/OS_LIST';
import { REP_INTEGER, REP_UUID } from '../../consts/REG_EXP_PATTERNS';
import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { dbQuery, job } from '../../accessModule'; import { job, query } from '../../accessModule';
import { stderr, stdout } from '../../shell'; import { sanitize } from '../../sanitize';
import { stderr, stdout, stdoutVar } from '../../shell';
export const createServer: RequestHandler = ({ body }, response) => { export const createServer: RequestHandler = async (request, response) => {
stdout(`Creating server.\n${JSON.stringify(body, null, 2)}`); const { body = {} } = request;
stdoutVar(body, 'Creating server; body=');
const { const {
serverName, serverName: rServerName,
cpuCores, cpuCores: rCpuCores,
memory, memory: rMemory,
virtualDisks: [ virtualDisks: [
{ storageSize = undefined, storageGroupUUID = undefined } = {}, {
storageSize: rStorageSize = undefined,
storageGroupUUID: rStorageGroupUuid = undefined,
} = {},
] = [], ] = [],
installISOFileUUID, installISOFileUUID: rInstallIsoUuid,
driverISOFileUUID, driverISOFileUUID: rDriverIsoUuid,
anvilUUID, anvilUUID: rAnvilUuid,
optimizeForOS, optimizeForOS: rOptimizeForOs,
} = body || {}; } = body;
const dataServerName = String(serverName); const serverName = sanitize(rServerName, 'string');
const dataOS = String(optimizeForOS); const os = sanitize(rOptimizeForOs, 'string');
const dataCPUCores = String(cpuCores); const cpuCores = sanitize(rCpuCores, 'number');
const dataRAM = String(memory); const memory = sanitize(rMemory, 'number');
const dataStorageGroupUUID = String(storageGroupUUID); const storageGroupUUID = sanitize(rStorageGroupUuid, 'string');
const dataStorageSize = String(storageSize); const storageSize = sanitize(rStorageSize, 'number');
const dataInstallISO = String(installISOFileUUID); const installIsoUuid = sanitize(rInstallIsoUuid, 'string');
const dataDriverISO = String(driverISOFileUUID) || 'none'; const driverIsoUuid = sanitize(rDriverIsoUuid, 'string', {
const dataAnvilUUID = String(anvilUUID); fallback: 'none',
});
const anvilUuid = sanitize(rAnvilUuid, 'string');
try { try {
assert( assert(
/^[0-9a-z_-]+$/i.test(dataServerName), /^[0-9a-z_-]+$/i.test(serverName),
`Data server name can only contain alphanumeric, underscore, and hyphen characters; got [${dataServerName}].`, `Data server name can only contain alphanumeric, underscore, and hyphen characters; got [${serverName}]`,
); );
const [[serverNameCount]] = dbQuery( const [[serverNameCount]] = await query(
`SELECT COUNT(server_uuid) FROM servers WHERE server_name = '${dataServerName}'`, `SELECT COUNT(server_uuid) FROM servers WHERE server_name = '${serverName}'`,
).stdout; );
assert( assert(
serverNameCount === 0, serverNameCount === 0,
`Data server name already exists; got [${dataServerName}]`, `Data server name already exists; got [${serverName}]`,
); );
assert( assert(
OS_LIST_MAP[dataOS] !== undefined, OS_LIST_MAP[os] !== undefined,
`Data OS not recognized; got [${dataOS}].`, `Data OS not recognized; got [${os}]`,
); );
assert( assert(
REP_INTEGER.test(dataCPUCores), Number.isInteger(cpuCores),
`Data CPU cores can only contain digits; got [${dataCPUCores}].`, `Data CPU cores can only contain digits; got [${cpuCores}]`,
); );
assert( assert(
REP_INTEGER.test(dataRAM), Number.isInteger(memory),
`Data RAM can only contain digits; got [${dataRAM}].`, `Data RAM can only contain digits; got [${memory}]`,
); );
assert( assert(
REP_UUID.test(dataStorageGroupUUID), REP_UUID.test(storageGroupUUID),
`Data storage group UUID must be a valid UUID; got [${dataStorageGroupUUID}].`, `Data storage group UUID must be a valid UUID; got [${storageGroupUUID}]`,
); );
assert( assert(
REP_INTEGER.test(dataStorageSize), Number.isInteger(storageSize),
`Data storage size can only contain digits; got [${dataStorageSize}].`, `Data storage size can only contain digits; got [${storageSize}]`,
); );
assert( assert(
REP_UUID.test(dataInstallISO), REP_UUID.test(installIsoUuid),
`Data install ISO must be a valid UUID; got [${dataInstallISO}].`, `Data install ISO must be a valid UUID; got [${installIsoUuid}]`,
); );
assert( assert(
dataDriverISO === 'none' || REP_UUID.test(dataDriverISO), driverIsoUuid === 'none' || REP_UUID.test(driverIsoUuid),
`Data driver ISO must be a valid UUID when provided; got [${dataDriverISO}].`, `Data driver ISO must be a valid UUID when provided; got [${driverIsoUuid}]`,
); );
assert( assert(
REP_UUID.test(dataAnvilUUID), REP_UUID.test(anvilUuid),
`Data anvil UUID must be a valid UUID; got [${dataAnvilUUID}].`, `Data anvil UUID must be a valid UUID; got [${anvilUuid}]`,
); );
} catch (assertError) { } catch (assertError) {
stdout( stdout(
`Failed to assert value when trying to provision a server; CAUSE: ${assertError}.`, `Failed to assert value when trying to provision a server; CAUSE: ${assertError}`,
); );
response.status(400).send(); return response.status(400).send();
return;
} }
const provisionServerJobData = `server_name=${dataServerName} const provisionServerJobData = `server_name=${serverName}
os=${dataOS} os=${os}
cpu_cores=${dataCPUCores} cpu_cores=${cpuCores}
ram=${dataRAM} ram=${memory}
storage_group_uuid=${dataStorageGroupUUID} storage_group_uuid=${storageGroupUUID}
storage_size=${dataStorageSize} storage_size=${storageSize}
install_iso=${dataInstallISO} install_iso=${installIsoUuid}
driver_iso=${dataDriverISO}`; driver_iso=${driverIsoUuid}`;
stdout(`provisionServerJobData=[${provisionServerJobData}]`); stdout(`provisionServerJobData=[${provisionServerJobData}]`);
const [[provisionServerJobHostUUID]] = dbQuery( const [[provisionServerJobHostUUID]]: [[string]] = await query(
`SELECT `SELECT
CASE CASE
WHEN pri_hos.primary_host_uuid IS NULL WHEN pri_hos.primary_host_uuid IS NULL
@ -120,7 +133,7 @@ driver_iso=${dataDriverISO}`;
AND sca_clu_nod.scan_cluster_node_crmd_member AND sca_clu_nod.scan_cluster_node_crmd_member
AND sca_clu_nod.scan_cluster_node_cluster_member AND sca_clu_nod.scan_cluster_node_cluster_member
AND (NOT sca_clu_nod.scan_cluster_node_maintenance_mode) AND (NOT sca_clu_nod.scan_cluster_node_maintenance_mode)
AND anv.anvil_uuid = '${dataAnvilUUID}' AND anv.anvil_uuid = '${anvilUuid}'
ORDER BY sca_clu_nod.scan_cluster_node_name ORDER BY sca_clu_nod.scan_cluster_node_name
LIMIT 1 LIMIT 1
) AS pri_hos ) AS pri_hos
@ -129,15 +142,15 @@ driver_iso=${dataDriverISO}`;
1 AS phr, 1 AS phr,
anv.anvil_node1_host_uuid AS node1_host_uuid anv.anvil_node1_host_uuid AS node1_host_uuid
FROM anvils AS anv FROM anvils AS anv
WHERE anv.anvil_uuid = '${dataAnvilUUID}' WHERE anv.anvil_uuid = '${anvilUuid}'
) AS nod_1 ) AS nod_1
ON pri_hos.phl = nod_1.phr;`, ON pri_hos.phl = nod_1.phr;`,
).stdout; );
stdout(`provisionServerJobHostUUID=[${provisionServerJobHostUUID}]`); stdout(`provisionServerJobHostUUID=[${provisionServerJobHostUUID}]`);
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-provision-server'].self, job_command: SERVER_PATHS.usr.sbin['anvil-provision-server'].self,
job_data: provisionServerJobData, job_data: provisionServerJobData,
@ -149,9 +162,7 @@ driver_iso=${dataDriverISO}`;
} catch (subError) { } catch (subError) {
stderr(`Failed to provision server; CAUSE: ${subError}`); stderr(`Failed to provision server; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
response.status(202).send(); response.status(202).send();

@ -1,6 +1,7 @@
import buildGetRequestHandler from '../buildGetRequestHandler'; import buildGetRequestHandler from '../buildGetRequestHandler';
import join from '../../join'; import join from '../../join';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { stdoutVar } from '../../shell';
export const getServer = buildGetRequestHandler( export const getServer = buildGetRequestHandler(
(request, buildQueryOptions) => { (request, buildQueryOptions) => {
@ -13,7 +14,7 @@ export const getServer = buildGetRequestHandler(
separator: ', ', separator: ', ',
}); });
console.log(`condAnvilsUUID=[${condAnvilUUIDs}]`); stdoutVar({ condAnvilUUIDs });
if (buildQueryOptions) { if (buildQueryOptions) {
buildQueryOptions.afterQueryReturn = (queryStdout) => { buildQueryOptions.afterQueryReturn = (queryStdout) => {

@ -3,10 +3,9 @@ import { RequestHandler } from 'express';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import path from 'path'; import path from 'path';
import { REP_UUID } from '../../consts/REG_EXP_PATTERNS'; import { REP_UUID, SERVER_PATHS } from '../../consts';
import SERVER_PATHS from '../../consts/SERVER_PATHS';
import { dbQuery, getLocalHostUUID, job } from '../../accessModule'; import { getLocalHostUUID, job, query } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { mkfifo, rm, stderr, stdout } from '../../shell'; import { mkfifo, rm, stderr, stdout } from '../../shell';
@ -18,7 +17,7 @@ const rmfifo = (path: string) => {
} }
}; };
export const getServerDetail: RequestHandler = (request, response) => { export const getServerDetail: RequestHandler = async (request, response) => {
const { serverUUID } = request.params; const { serverUUID } = request.params;
const { ss, resize } = request.query; const { ss, resize } = request.query;
@ -39,9 +38,7 @@ export const getServerDetail: RequestHandler = (request, response) => {
`Failed to assert value when trying to get server detail; CAUSE: ${assertError}.`, `Failed to assert value when trying to get server detail; CAUSE: ${assertError}.`,
); );
response.status(500).send(); return response.status(500).send();
return;
} }
if (isScreenshot) { if (isScreenshot) {
@ -52,24 +49,20 @@ export const getServerDetail: RequestHandler = (request, response) => {
} catch (subError) { } catch (subError) {
stderr(String(subError)); stderr(String(subError));
response.status(500).send(); return response.status(500).send();
return;
} }
stdout(`requestHostUUID=[${requestHostUUID}]`); stdout(`requestHostUUID=[${requestHostUUID}]`);
try { try {
[[serverHostUUID]] = dbQuery(` [[serverHostUUID]] = await query(`
SELECT server_host_uuid SELECT server_host_uuid
FROM servers FROM servers
WHERE server_uuid = '${serverUUID}';`).stdout; WHERE server_uuid = '${serverUUID}';`);
} catch (queryError) { } catch (queryError) {
stderr(`Failed to get server host UUID; CAUSE: ${queryError}`); stderr(`Failed to get server host UUID; CAUSE: ${queryError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
stdout(`serverHostUUID=[${serverHostUUID}]`); stdout(`serverHostUUID=[${serverHostUUID}]`);
@ -94,9 +87,9 @@ export const getServerDetail: RequestHandler = (request, response) => {
namedPipeReadStream.once('close', () => { namedPipeReadStream.once('close', () => {
stdout(`On close; removing named pipe at ${imageFilePath}.`); stdout(`On close; removing named pipe at ${imageFilePath}.`);
response.status(200).send({ screenshot: imageData });
rmfifo(imageFilePath); rmfifo(imageFilePath);
return response.status(200).send({ screenshot: imageData });
}); });
namedPipeReadStream.on('data', (data) => { namedPipeReadStream.on('data', (data) => {
@ -123,11 +116,9 @@ export const getServerDetail: RequestHandler = (request, response) => {
`Failed to prepare named pipe and/or receive image data; CAUSE: ${prepPipeError}`, `Failed to prepare named pipe and/or receive image data; CAUSE: ${prepPipeError}`,
); );
response.status(500).send();
rmfifo(imageFilePath); rmfifo(imageFilePath);
return; return response.status(500).send();
} }
let resizeArgs = sanitize(resize, 'string'); let resizeArgs = sanitize(resize, 'string');
@ -137,7 +128,7 @@ export const getServerDetail: RequestHandler = (request, response) => {
} }
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-get-server-screenshot'].self, job_command: SERVER_PATHS.usr.sbin['anvil-get-server-screenshot'].self,
job_data: `server-uuid=${serverUUID} job_data: `server-uuid=${serverUUID}
@ -152,9 +143,7 @@ out-file-id=${epoch}`,
} catch (subError) { } catch (subError) {
stderr(`Failed to queue fetch server screenshot job; CAUSE: ${subError}`); stderr(`Failed to queue fetch server screenshot job; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
} else { } else {
// For getting sever detail data. // For getting sever detail data.

@ -9,33 +9,31 @@ import { stderr } from '../../shell';
export const deleteSSHKeyConflict: RequestHandler< export const deleteSSHKeyConflict: RequestHandler<
unknown, unknown,
undefined, undefined,
DeleteSSHKeyConflictRequestBody DeleteSshKeyConflictRequestBody
> = (request, response) => { > = async (request, response) => {
const { body } = request; const { body } = request;
const hostUUIDs = Object.keys(body); const hostUuids = Object.keys(body);
hostUUIDs.forEach((key) => { for (const uuid of hostUuids) {
const hostUUID = toHostUUID(key); const hostUuid = toHostUUID(uuid);
const stateUUIDs = body[key]; const stateUuids = body[uuid];
try { try {
job({ await job({
file: __filename, file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-manage-keys'].self, job_command: SERVER_PATHS.usr.sbin['anvil-manage-keys'].self,
job_data: stateUUIDs.join(','), job_data: stateUuids.join(','),
job_description: 'job_0057', job_description: 'job_0057',
job_host_uuid: hostUUID, job_host_uuid: hostUuid,
job_name: 'manage::broken_keys', job_name: 'manage::broken_keys',
job_title: 'job_0056', job_title: 'job_0056',
}); });
} catch (subError) { } catch (subError) {
stderr(`Failed to delete bad SSH keys; CAUSE: ${subError}`); stderr(`Failed to delete bad SSH keys; CAUSE: ${subError}`);
response.status(500).send(); return response.status(500).send();
return;
} }
}); }
response.status(204).send(); response.status(204).send();
}; };

@ -22,16 +22,7 @@ export const getSSHKeyConflict = buildGetRequestHandler(
ON sta.state_host_uuid = hos.host_uuid ON sta.state_host_uuid = hos.host_uuid
WHERE sta.state_name LIKE '${HOST_KEY_CHANGED_PREFIX}%';`; WHERE sta.state_name LIKE '${HOST_KEY_CHANGED_PREFIX}%';`;
const afterQueryReturn = buildQueryResultReducer<{ const afterQueryReturn = buildQueryResultReducer<{
[hostUUID: string]: { [hostUUID: string]: SshKeyConflict;
[stateUUID: string]: {
badFile: string;
badLine: number;
hostName: string;
hostUUID: string;
ipAddress: string;
stateUUID: string;
};
};
}>((previous, [hostName, hostUUID, stateName, stateNote, stateUUID]) => { }>((previous, [hostName, hostUUID, stateName, stateNote, stateUUID]) => {
const hostUUIDKey = toLocal(hostUUID, localHostUUID); const hostUUIDKey = toLocal(hostUUID, localHostUUID);

@ -14,7 +14,7 @@ export const getUPS: RequestHandler = buildGetRequestHandler(
FROM upses FROM upses
ORDER BY ups_name ASC;`; ORDER BY ups_name ASC;`;
const afterQueryReturn: QueryResultModifierFunction | undefined = const afterQueryReturn: QueryResultModifierFunction | undefined =
buildQueryResultReducer<{ [upsUUID: string]: UPSOverview }>( buildQueryResultReducer<{ [upsUUID: string]: UpsOverview }>(
(previous, [upsUUID, upsName, upsAgent, upsIPAddress]) => { (previous, [upsUUID, upsName, upsAgent, upsIPAddress]) => {
previous[upsUUID] = { previous[upsUUID] = {
upsAgent, upsAgent,

@ -1,16 +1,13 @@
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { getAnvilData } from '../../accessModule'; import { getUpsSpec } from '../../accessModule';
import { stderr } from '../../shell'; import { stderr } from '../../shell';
export const getUPSTemplate: RequestHandler = (request, response) => { export const getUPSTemplate: RequestHandler = async (request, response) => {
let rawUPSData: AnvilDataUPSHash; let rawUPSData: AnvilDataUPSHash;
try { try {
({ ups_data: rawUPSData } = getAnvilData<{ ups_data: AnvilDataUPSHash }>( rawUPSData = await getUpsSpec();
{ ups_data: true },
{ predata: [['Striker->get_ups_data']] },
));
} catch (subError) { } catch (subError) {
stderr(`Failed to get ups template; CAUSE: ${subError}`); stderr(`Failed to get ups template; CAUSE: ${subError}`);
@ -21,13 +18,13 @@ export const getUPSTemplate: RequestHandler = (request, response) => {
const upsData: AnvilDataUPSHash = Object.entries( const upsData: AnvilDataUPSHash = Object.entries(
rawUPSData, rawUPSData,
).reduce<UPSTemplate>((previous, [upsTypeId, value]) => { ).reduce<UpsTemplate>((previous, [upsTypeId, value]) => {
const { brand, description: rawDescription, ...rest } = value; const { brand, description: rawDescription, ...rest } = value;
const matched = rawDescription.match( const matched = rawDescription.match(
/^(.+)\s+[-]\s+[<][^>]+href=[\\"]+([^\s]+)[\\"]+.+[>]([^<]+)[<]/, /^(.+)\s+[-]\s+[<][^>]+href=[\\"]+([^\s]+)[\\"]+.+[>]([^<]+)[<]/,
); );
const result: UPSTemplate[string] = { const result: UpsTemplate[string] = {
...rest, ...rest,
brand, brand,
description: rawDescription, description: rawDescription,

@ -0,0 +1,59 @@
import assert from 'assert';
import { RequestHandler } from 'express';
import { DELETED, REP_UUID } from '../../consts';
import { write } from '../../accessModule';
import join from '../../join';
import { sanitize } from '../../sanitize';
import { stderr, stdoutVar } from '../../shell';
export const deleteUser: RequestHandler<
DeleteUserParamsDictionary,
undefined,
DeleteUserRequestBody
> = async (request, response) => {
const {
body: { uuids: rawUserUuidList } = {},
params: { userUuid },
} = request;
const userUuidList = sanitize(rawUserUuidList, 'string[]');
const ulist = userUuidList.length > 0 ? userUuidList : [userUuid];
stdoutVar({ ulist });
try {
let failedIndex = 0;
assert(
ulist.every((uuid, index) => {
failedIndex = index;
return REP_UUID.test(uuid);
}),
`All UUIDs must be valid UUIDv4; failed at ${failedIndex}, got [${ulist[failedIndex]}]`,
);
} catch (assertError) {
stderr(`Failed to assert value during delete user; CAUSE: ${assertError}`);
return response.status(400).send();
}
try {
const wcode = await write(
`UPDATE users
SET user_algorithm = '${DELETED}'
WHERE user_uuid IN (${join(ulist)});`,
);
assert(wcode === 0, `Write exited with code ${wcode}`);
} catch (error) {
stderr(`Failed to delete user(s); CAUSE: ${error}`);
return response.status(500).send();
}
response.status(204).send();
};

@ -1 +1,2 @@
export * from './deleteUser';
export * from './getUser'; export * from './getUser';

@ -0,0 +1,44 @@
import { Application, Handler, Router } from 'express';
import path from 'path';
import { stdout } from './shell';
export const rrouters = <
A extends Application,
M extends MapToRouter<R>,
R extends Router,
H extends Handler,
>(
app: A,
union: Readonly<M> | R,
{
assign = (router) => [router],
key,
route = '/',
}: {
assign?: (router: R) => Array<R | H>;
key?: keyof M;
route?: string;
} = {},
) => {
if ('route' in union) {
const handlers = assign(union as R);
const { length: hcount } = handlers;
stdout(`Set up route ${route} with ${hcount} handler(s)`);
app.use(route, ...handlers);
} else if (key) {
rrouters(app, union[key], {
assign,
route: path.posix.join(route, String(key)),
});
} else {
Object.entries(union).forEach(([extend, subunion]) => {
rrouters(app, subunion, {
assign,
route: path.posix.join(route, extend),
});
});
}
};

@ -12,6 +12,7 @@ type MapToReturnFunction = {
[ReturnTypeName in keyof MapToReturnType]: ( [ReturnTypeName in keyof MapToReturnType]: (
value: unknown, value: unknown,
modifier: (unmodified: unknown) => string, modifier: (unmodified: unknown) => string,
fallback?: MapToReturnType[ReturnTypeName],
) => MapToReturnType[ReturnTypeName]; ) => MapToReturnType[ReturnTypeName];
}; };
@ -29,10 +30,10 @@ const MAP_TO_MODIFIER_FUNCTION: MapToModifierFunction = {
const MAP_TO_RETURN_FUNCTION: MapToReturnFunction = { const MAP_TO_RETURN_FUNCTION: MapToReturnFunction = {
boolean: (value) => value !== undefined, boolean: (value) => value !== undefined,
number: (value) => parseFloat(String(value)) || 0, number: (value, mod, fallback = 0) => parseFloat(String(value)) || fallback,
string: (value, mod) => (value ? mod(value) : ''), string: (value, mod, fallback = '') => (value ? mod(value) : fallback),
'string[]': (value, mod) => { 'string[]': (value, mod, fallback = []) => {
let result: string[] = []; let result: string[] = fallback;
if (value instanceof Array) { if (value instanceof Array) {
result = value.reduce<string[]>((reduceContainer, element) => { result = value.reduce<string[]>((reduceContainer, element) => {
@ -54,18 +55,24 @@ export const sanitize = <ReturnTypeName extends keyof MapToReturnType>(
value: unknown, value: unknown,
returnType: ReturnTypeName, returnType: ReturnTypeName,
{ {
fallback,
modifierType = 'none', modifierType = 'none',
modifier = MAP_TO_MODIFIER_FUNCTION[modifierType], modifier = MAP_TO_MODIFIER_FUNCTION[modifierType],
}: { }: {
fallback?: MapToReturnType[ReturnTypeName];
modifier?: ModifierFunction; modifier?: ModifierFunction;
modifierType?: keyof MapToModifierFunction; modifierType?: keyof MapToModifierFunction;
} = {}, } = {},
): MapToReturnType[ReturnTypeName] => ): MapToReturnType[ReturnTypeName] =>
MAP_TO_RETURN_FUNCTION[returnType](value, (unmodified: unknown) => { MAP_TO_RETURN_FUNCTION[returnType](
const input = String(unmodified); value,
(unmodified: unknown) => {
const input = String(unmodified);
return call<string>(modifier, { return call<string>(modifier, {
notCallableReturn: input, notCallableReturn: input,
parameters: [input], parameters: [input],
}); });
}) as MapToReturnType[ReturnTypeName]; },
fallback,
) as MapToReturnType[ReturnTypeName];

@ -32,15 +32,33 @@ const systemCall = (
export const date = (...args: string[]) => export const date = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.date.self, args); systemCall(SERVER_PATHS.usr.bin.date.self, args);
export const getent = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.getent.self, args);
export const mkfifo = (...args: string[]) => export const mkfifo = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.mkfifo.self, args); systemCall(SERVER_PATHS.usr.bin.mkfifo.self, args);
export const openssl = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.openssl.self, args);
export const rm = (...args: string[]) => export const rm = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.rm.self, args); systemCall(SERVER_PATHS.usr.bin.rm.self, args);
export const uuidgen = (...args: string[]) =>
systemCall(SERVER_PATHS.usr.bin.uuidgen.self, args);
export const resolveId = (id: number | string, database: string) =>
Number.parseInt(getent(database, String(id)).split(':', 3)[2]);
export const resolveGid = (id: number | string) => resolveId(id, 'group');
export const resolveUid = (id: number | string) => resolveId(id, 'passwd');
export const stderr = (message: string) => print(message, { stream: 'stderr' }); export const stderr = (message: string) => print(message, { stream: 'stderr' });
export const stdout = (message: string) => print(message); export const stdout = (message: string) => print(message);
export const stdoutVar = (variable: { [name: string]: unknown }) => export const stdoutVar = (variable: unknown, label = 'Variables: ') =>
print(`Variables: ${JSON.stringify(variable, null, 2)}`); print(`${label}${JSON.stringify(variable, null, 2)}`);
export const uuid = () => uuidgen('--random').trim();

@ -0,0 +1,28 @@
type NestedObject<T> = {
[key: number | string]: NestedObject<T> | T;
};
export const traverse = <T, O extends NestedObject<V>, V = unknown>(
obj: O,
init: T,
onKey: (previous: T, obj: O, key: string) => { is: boolean; next: O },
{
onEnd,
previous = init,
}: {
onEnd?: (previous: T, obj: O, key: string) => void;
previous?: T;
} = {},
) => {
Object.keys(obj).forEach((key: string) => {
const { is: proceed, next } = onKey(previous, obj, key);
if (proceed) {
traverse(next, init, onKey, { previous });
} else {
onEnd?.call(null, previous, obj, key);
}
});
return previous;
};

@ -0,0 +1,127 @@
import passport from 'passport';
import { Strategy as LocalStrategy } from 'passport-local';
import { DELETED } from './lib/consts';
import { query, sub } from './lib/accessModule';
import { sanitize } from './lib/sanitize';
import { stdout } from './lib/shell';
passport.use(
'login',
new LocalStrategy(async (username, password, done) => {
stdout(`Attempting passport local strategy "login" for user [${username}]`);
let rows: [
userUuid: string,
userName: string,
userPasswordHash: string,
userSalt: string,
userAlgorithm: string,
userHashCount: string,
][];
try {
rows = await query(
`SELECT
user_uuid,
user_name,
user_password_hash,
user_salt,
user_algorithm,
user_hash_count
FROM users
WHERE user_algorithm != 'DELETED'
AND user_name = '${username}'
LIMIT 1;`,
);
} catch (queryError) {
return done(queryError);
}
if (!rows.length) {
return done(null, false);
}
const {
0: [userUuid, , userPasswordHash, userSalt, userAlgorithm, userHashCount],
} = rows;
let encryptResult: {
user_password_hash: string;
user_salt: string;
user_hash_count: number;
user_algorithm: string;
};
try {
[encryptResult] = await sub('encrypt_password', {
params: [
{
algorithm: userAlgorithm,
hash_count: userHashCount,
password,
salt: userSalt,
},
],
pre: ['Account'],
});
} catch (subError) {
return done(subError);
}
const { user_password_hash: inputPasswordHash } = encryptResult;
if (inputPasswordHash !== userPasswordHash) {
return done(null, false);
}
const user: Express.User = {
name: username,
uuid: userUuid,
};
return done(null, user);
}),
);
passport.serializeUser((user, done) => {
const { name, uuid } = user;
stdout(`Serialize user [${name}]`);
return done(null, uuid);
});
passport.deserializeUser(async (id, done) => {
const uuid = sanitize(id, 'string', { modifierType: 'sql' });
stdout(`Deserialize user identified by ${uuid}`);
let rows: [userName: string][];
try {
rows = await query(
`SELECT user_name
FROM users
WHERE user_algorithm != '${DELETED}'
AND user_uuid = '${uuid}';`,
);
} catch (error) {
return done(error);
}
if (!rows.length) {
return done(null, false);
}
const {
0: [userName],
} = rows;
const user: Express.User = { name: userName, uuid };
return done(null, user);
});
export default passport;

@ -0,0 +1,13 @@
import express from 'express';
import { guardApi } from '../lib/assertAuthentication';
import { login, logout } from '../lib/request_handlers/auth';
import passport from '../passport';
const router = express.Router();
router
.post('/login', passport.authenticate('login'), login)
.put('/logout', guardApi, logout);
export default router;

@ -1,5 +1,7 @@
import express from 'express'; import express from 'express';
import { stdoutVar } from '../lib/shell';
const router = express.Router(); const router = express.Router();
router router
@ -7,9 +9,11 @@ router
response.status(200).send({ message: 'Empty echo.' }); response.status(200).send({ message: 'Empty echo.' });
}) })
.post('/', (request, response) => { .post('/', (request, response) => {
console.log('echo:post', JSON.stringify(request.body, undefined, 2)); const { body = {} } = request;
stdoutVar(body, 'echo:post\n');
const message = request.body.message ?? 'No message.'; const { message = 'No message.' } = body;
response.status(200).send({ message }); response.status(200).send({ message });
}); });

@ -1,202 +1,21 @@
import express from 'express'; import express from 'express';
import { import {
dbJobAnvilSyncShared, createFile,
dbQuery, deleteFile,
dbSubRefreshTimestamp, getFile,
dbWrite, getFileDetail,
} from '../lib/accessModule'; updateFile,
import getFile from '../lib/request_handlers/file/getFile'; } from '../lib/request_handlers/file';
import getFileDetail from '../lib/request_handlers/file/getFileDetail';
import uploadSharedFiles from '../middlewares/uploadSharedFiles'; import uploadSharedFiles from '../middlewares/uploadSharedFiles';
const router = express.Router(); const router = express.Router();
router router
.delete('/:fileUUID', (request, response) => { .delete('/:fileUUID', deleteFile)
const { fileUUID } = request.params;
const FILE_TYPE_DELETED = 'DELETED';
const [[oldFileType]] = dbQuery(
`SELECT file_type FROM files WHERE file_uuid = '${fileUUID}';`,
).stdout;
if (oldFileType !== FILE_TYPE_DELETED) {
dbWrite(
`UPDATE files
SET
file_type = '${FILE_TYPE_DELETED}',
modified_date = '${dbSubRefreshTimestamp()}'
WHERE file_uuid = '${fileUUID}';`,
).stdout;
dbJobAnvilSyncShared('purge', `file_uuid=${fileUUID}`, '0136', '0137', {
jobHostUUID: 'all',
});
}
response.status(204).send();
})
.get('/', getFile) .get('/', getFile)
.get('/:fileUUID', getFileDetail) .get('/:fileUUID', getFileDetail)
.post('/', uploadSharedFiles.single('file'), ({ file, body }, response) => { .post('/', uploadSharedFiles.single('file'), createFile)
console.log('Receiving shared file.'); .put('/:fileUUID', updateFile);
if (file) {
console.log(`file: ${JSON.stringify(file, null, 2)}`);
console.log(`body: ${JSON.stringify(body, null, 2)}`);
dbJobAnvilSyncShared(
'move_incoming',
`file=${file.path}`,
'0132',
'0133',
);
response.status(200).send();
}
})
.put('/:fileUUID', (request, response) => {
console.log('Begin edit single file.');
console.dir(request.body);
const { fileUUID } = request.params;
const { fileName, fileLocations, fileType } = request.body;
const anvilSyncSharedFunctions = [];
let query = '';
if (fileName) {
const [[oldFileName]] = dbQuery(
`SELECT file_name FROM files WHERE file_uuid = '${fileUUID}';`,
).stdout;
console.log(`oldFileName=[${oldFileName}],newFileName=[${fileName}]`);
if (fileName !== oldFileName) {
query += `
UPDATE files
SET
file_name = '${fileName}',
modified_date = '${dbSubRefreshTimestamp()}'
WHERE file_uuid = '${fileUUID}';`;
anvilSyncSharedFunctions.push(() =>
dbJobAnvilSyncShared(
'rename',
`file_uuid=${fileUUID}\nold_name=${oldFileName}\nnew_name=${fileName}`,
'0138',
'0139',
{ jobHostUUID: 'all' },
),
);
}
}
if (fileType) {
query += `
UPDATE files
SET
file_type = '${fileType}',
modified_date = '${dbSubRefreshTimestamp()}'
WHERE file_uuid = '${fileUUID}';`;
anvilSyncSharedFunctions.push(() =>
dbJobAnvilSyncShared(
'check_mode',
`file_uuid=${fileUUID}`,
'0143',
'0144',
{ jobHostUUID: 'all' },
),
);
}
if (fileLocations) {
fileLocations.forEach(
({
fileLocationUUID,
isFileLocationActive,
}: {
fileLocationUUID: string;
isFileLocationActive: boolean;
}) => {
let fileLocationActive = 0;
let jobName = 'purge';
let jobTitle = '0136';
let jobDescription = '0137';
if (isFileLocationActive) {
fileLocationActive = 1;
jobName = 'pull_file';
jobTitle = '0132';
jobDescription = '0133';
}
query += `
UPDATE file_locations
SET
file_location_active = '${fileLocationActive}',
modified_date = '${dbSubRefreshTimestamp()}'
WHERE file_location_uuid = '${fileLocationUUID}';`;
const targetHosts = dbQuery(
`SELECT
anv.anvil_node1_host_uuid,
anv.anvil_node2_host_uuid,
anv.anvil_dr1_host_uuid
FROM anvils AS anv
JOIN file_locations AS fil_loc
ON anv.anvil_uuid = fil_loc.file_location_anvil_uuid
WHERE fil_loc.file_location_uuid = '${fileLocationUUID}';`,
).stdout;
targetHosts.flat().forEach((hostUUID: string) => {
if (hostUUID) {
anvilSyncSharedFunctions.push(() =>
dbJobAnvilSyncShared(
jobName,
`file_uuid=${fileUUID}`,
jobTitle,
jobDescription,
{ jobHostUUID: hostUUID },
),
);
}
});
},
);
}
console.log(`Query (type=[${typeof query}]): [${query}]`);
let queryStdout;
try {
({ stdout: queryStdout } = dbWrite(query));
} catch (queryError) {
console.log(`Failed to execute query; CAUSE: ${queryError}`);
response.status(500).send();
}
console.log(
`Query stdout (type=[${typeof queryStdout}]): ${JSON.stringify(
queryStdout,
null,
2,
)}`,
);
anvilSyncSharedFunctions.forEach((fn, index) => {
console.log(
`Anvil sync shared [${index}] output: [${JSON.stringify(
fn(),
null,
2,
)}]`,
);
});
response.status(200).send(queryStdout);
});
export default router; export default router;

@ -1,6 +1,5 @@
import { Router } from 'express';
import anvilRouter from './anvil'; import anvilRouter from './anvil';
import authRouter from './auth';
import commandRouter from './command'; import commandRouter from './command';
import echoRouter from './echo'; import echoRouter from './echo';
import fenceRouter from './fence'; import fenceRouter from './fence';
@ -11,23 +10,30 @@ import manifestRouter from './manifest';
import networkInterfaceRouter from './network-interface'; import networkInterfaceRouter from './network-interface';
import serverRouter from './server'; import serverRouter from './server';
import sshKeyRouter from './ssh-key'; import sshKeyRouter from './ssh-key';
import staticRouter from './static';
import upsRouter from './ups'; import upsRouter from './ups';
import userRouter from './user'; import userRouter from './user';
const routes: Readonly<Record<string, Router>> = { const routes = {
anvil: anvilRouter, private: {
command: commandRouter, anvil: anvilRouter,
echo: echoRouter, command: commandRouter,
fence: fenceRouter, fence: fenceRouter,
file: fileRouter, file: fileRouter,
host: hostRouter, host: hostRouter,
job: jobRouter, job: jobRouter,
manifest: manifestRouter, manifest: manifestRouter,
'network-interface': networkInterfaceRouter, 'network-interface': networkInterfaceRouter,
server: serverRouter, server: serverRouter,
'ssh-key': sshKeyRouter, 'ssh-key': sshKeyRouter,
ups: upsRouter, ups: upsRouter,
user: userRouter, user: userRouter,
},
public: {
auth: authRouter,
echo: echoRouter,
},
static: staticRouter,
}; };
export default routes; export default routes;

@ -13,7 +13,7 @@ const router = express.Router();
router router
.delete('/', deleteManifest) .delete('/', deleteManifest)
.delete('/manifestUuid', deleteManifest) .delete('/:manifestUuid', deleteManifest)
.get('/', getManifest) .get('/', getManifest)
.get('/template', getManifestTemplate) .get('/template', getManifestTemplate)
.get('/:manifestUUID', getManifestDetail) .get('/:manifestUUID', getManifestDetail)

@ -0,0 +1,51 @@
import express from 'express';
import { existsSync } from 'fs';
import path from 'path';
import { SERVER_PATHS } from '../lib/consts';
import { assertAuthentication } from '../lib/assertAuthentication';
import { stdout } from '../lib/shell';
const router = express.Router();
const htmlDir = SERVER_PATHS.var.www.html.self;
router.use(
(...args) => {
const { 0: request, 2: next } = args;
const { originalUrl } = request;
if (/^[/]login/.test(originalUrl)) {
stdout(`Static:login requested`);
return assertAuthentication({ fail: (rq, rs, nx) => nx(), succeed: '/' })(
...args,
);
}
const parts = originalUrl.replace(/[/]$/, '').split('/');
const tail = parts.pop() || 'index';
const extended = /[.]html$/.test(tail) ? tail : `${tail}.html`;
parts.push(extended);
const htmlPath = path.posix.join(htmlDir, ...parts);
const isHtmlExists = existsSync(htmlPath);
if (isHtmlExists) {
stdout(`Static:[${htmlPath}] requested`);
return assertAuthentication({ fail: '/login', failReturnTo: true })(
...args,
);
}
return next();
},
express.static(htmlDir, {
extensions: ['htm', 'html'],
}),
);
export default router;

@ -1,9 +1,12 @@
import express from 'express'; import express from 'express';
import { getUser } from '../lib/request_handlers/user'; import { deleteUser, getUser } from '../lib/request_handlers/user';
const router = express.Router(); const router = express.Router();
router.get('/', getUser); router
.get('/', getUser)
.delete('/', deleteUser)
.delete('/:userUuid', deleteUser);
export default router; export default router;

@ -0,0 +1,201 @@
import assert from 'assert';
import expressSession, {
SessionData,
Store as BaseSessionStore,
} from 'express-session';
import { DELETED } from './lib/consts';
import { getLocalHostUUID, query, timestamp, write } from './lib/accessModule';
import { getSessionSecret } from './lib/getSessionSecret';
import { stderr, stdout, stdoutVar, uuid } from './lib/shell';
const DEFAULT_COOKIE_ORIGINAL_MAX_AGE = 28800000; // 8 hours
export class SessionStore extends BaseSessionStore {
constructor(options = {}) {
super(options);
}
public async destroy(
sid: string,
done?: ((err?: unknown) => void) | undefined,
): Promise<void> {
stdout(`Destroy session ${sid}`);
try {
const wcode = await write(
`UPDATE sessions
SET session_salt = '${DELETED}', modified_date = '${timestamp()}'
WHERE session_uuid = '${sid}';`,
);
assert(wcode === 0, `Write exited with code ${wcode}`);
} catch (error) {
stderr(
`Failed to complete DB write in destroy session ${sid}; CAUSE: ${error}`,
);
return done?.call(null, error);
}
return done?.call(null);
}
public async get(
sid: string,
done: (err: unknown, session?: SessionData | null | undefined) => void,
): Promise<void> {
stdout(`Get session ${sid}`);
let rows: [
sessionUuid: string,
userUuid: string,
sessionModifiedDate: string,
][];
try {
rows = await query(
`SELECT
s.session_uuid,
u.user_uuid,
s.modified_date
FROM sessions AS s
JOIN users AS u
ON s.session_user_uuid = u.user_uuid
WHERE s.session_salt != '${DELETED}'
AND s.session_uuid = '${sid}';`,
);
} catch (queryError) {
return done(queryError);
}
if (!rows.length) {
return done(null);
}
const {
0: [, userUuid, sessionModifiedDate],
} = rows;
const cookieMaxAge =
SessionStore.calculateCookieMaxAge(sessionModifiedDate);
const data: SessionData = {
cookie: {
maxAge: cookieMaxAge,
originalMaxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE,
},
passport: { user: userUuid },
};
return done(null, data);
}
public async set(
sid: string,
session: SessionData,
done?: ((err?: unknown) => void) | undefined,
): Promise<void> {
stdoutVar({ session }, `Set session ${sid}`);
const { passport: { user: userUuid } = {} } = session;
try {
assert.ok(userUuid, 'Missing user identifier');
const localHostUuid = getLocalHostUUID();
const modifiedDate = timestamp();
const wcode = await write(
`INSERT INTO
sessions (
session_uuid,
session_host_uuid,
session_user_uuid,
session_salt,
modified_date
)
VALUES
(
'${sid}',
'${localHostUuid}',
'${userUuid}',
'',
'${modifiedDate}'
)
ON CONFLICT (session_uuid)
DO UPDATE SET modified_date = '${modifiedDate}';`,
);
assert(wcode === 0, `Write exited with code ${wcode}`);
} catch (error) {
stderr(
`Failed to complete DB write in set session ${sid}; CAUSE: ${error}`,
);
return done?.call(null, error);
}
return done?.call(null);
}
public async touch(
sid: string,
session: SessionData,
done?: ((err?: unknown) => void) | undefined,
): Promise<void> {
stdoutVar({ session }, `Touch session ${sid}`);
try {
const wcode = await write(
`UPDATE sessions
SET modified_date = '${timestamp()}'
WHERE session_uuid = '${sid}';`,
);
assert(wcode === 0, `Write exited with code ${wcode}`);
} catch (error) {
stderr(
`Failed to complete DB write in touch session ${sid}; CAUSE: ${error}`,
);
return done?.call(null, error);
}
return done?.call(null);
}
public static calculateCookieMaxAge(
sessionModifiedDate: string,
cookieOriginalMaxAge: number = DEFAULT_COOKIE_ORIGINAL_MAX_AGE,
) {
const sessionModifiedEpoch = Date.parse(sessionModifiedDate);
const sessionDeadlineEpoch = sessionModifiedEpoch + cookieOriginalMaxAge;
const cookieMaxAge = sessionDeadlineEpoch - Date.now();
stdoutVar({ sessionModifiedDate, sessionDeadlineEpoch, cookieMaxAge });
return cookieMaxAge;
}
}
export default (async () =>
expressSession({
cookie: {
httpOnly: true,
maxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE,
secure: false,
},
genid: ({ path }) => {
const sid = uuid();
stdout(`Generated session identifier ${sid}; request.path=${path}`);
return sid;
},
resave: false,
saveUninitialized: false,
secret: await getSessionSecret(),
store: new SessionStore(),
}))();

@ -0,0 +1,12 @@
type AccessStartOptions = {
args?: readonly string[];
} & import('child_process').SpawnOptions;
type SubroutineCommonParams = {
debug?: number;
};
type InsertOrUpdateFunctionCommonParams = SubroutineCommonParams & {
file: string;
line?: number;
};

@ -1,8 +0,0 @@
type AnvilOverview = {
anvilName: string;
anvilUUID: string;
hosts: Array<{
hostName: string;
hostUUID: string;
}>;
};

@ -0,0 +1,3 @@
type JobAnvilSyncSharedOptions = {
jobHostUUID?: string;
};

@ -31,3 +31,12 @@ type AnvilDetailForProvisionServer = {
fileName: string; fileName: string;
}>; }>;
}; };
type AnvilOverview = {
anvilName: string;
anvilUUID: string;
hosts: Array<{
hostName: string;
hostUUID: string;
}>;
};

@ -0,0 +1,4 @@
type AuthLoginRequestBody = {
username: string;
password: string;
};

@ -0,0 +1,15 @@
type GetHostSshRequestBody = {
password: string;
port?: number;
ipAddress: string;
};
type GetHostSshResponseBody = {
badSSHKeys?: DeleteSshKeyConflictRequestBody;
hostName: string;
hostOS: string;
hostUUID: string;
isConnected: boolean;
isInetConnected: boolean;
isOSRegistered: boolean;
};

@ -0,0 +1,89 @@
type CreateHostConnectionRequestBody = {
dbName?: string;
ipAddress: string;
isPing?: boolean;
/** Host password; same as database password */
password: string;
port?: number;
sshPort?: number;
/** Database user */
user?: string;
};
type DeleteHostConnectionRequestBody = {
[hostUUID: string]: string[];
};
type HostConnectionOverview = {
inbound: {
ipAddress: {
[ipAddress: string]: {
hostUUID: string;
ipAddress: string;
ipAddressUUID: string;
networkLinkNumber: number;
networkNumber: number;
networkType: string;
};
};
port: number;
user: string;
};
peer: {
[ipAddress: string]: {
hostUUID: string;
ipAddress: string;
isPing: boolean;
port: number;
user: string;
};
};
};
type HostOverview = {
hostName: string;
hostType: string;
hostUUID: string;
shortHostName: string;
};
type InitializeStrikerNetworkForm = {
interfaces: Array<NetworkInterfaceOverview | null | undefined>;
ipAddress: string;
name: string;
subnetMask: string;
type: string;
};
type InitializeStrikerForm = {
adminPassword: string;
domainName: string;
hostName: string;
hostNumber: number;
networkDNS: string;
networkGateway: string;
networks: InitializeStrikerNetworkForm[];
organizationName: string;
organizationPrefix: string;
};
type PrepareHostRequestBody = {
enterpriseUUID?: string;
hostIPAddress: string;
hostName: string;
hostPassword: string;
hostSSHPort?: number;
hostType: string;
hostUser?: string;
hostUUID?: string;
redhatPassword: string;
redhatUser: string;
};
type SetHostInstallTargetRequestBody = {
isEnableInstallTarget: boolean;
};
type UpdateHostParams = {
hostUUID: string;
};

@ -0,0 +1,12 @@
type SshKeyConflict = {
[stateUUID: string]: {
badFile: string;
badLine: number;
hostName: string;
hostUUID: string;
ipAddress: string;
stateUUID: string;
};
};
type DeleteSshKeyConflictRequestBody = { [hostUUID: string]: string[] };

@ -1,11 +1,11 @@
type UPSOverview = { type UpsOverview = {
upsAgent: string; upsAgent: string;
upsIPAddress: string; upsIPAddress: string;
upsName: string; upsName: string;
upsUUID: string; upsUUID: string;
}; };
type UPSTemplate = { type UpsTemplate = {
[upsName: string]: AnvilDataUPSHash[string] & { [upsName: string]: AnvilDataUPSHash[string] & {
links: { links: {
[linkId: string]: { [linkId: string]: {

@ -0,0 +1,7 @@
type DeleteUserParamsDictionary = {
userUuid: string;
};
type DeleteUserRequestBody = {
uuids?: string[];
};

@ -7,4 +7,4 @@ type BuildQueryOptions = {
type BuildQueryFunction = ( type BuildQueryFunction = (
request: import('express').Request, request: import('express').Request,
options?: BuildQueryOptions, options?: BuildQueryOptions,
) => string; ) => string | Promise<string>;

@ -1,11 +0,0 @@
type CreateHostConnectionRequestBody = {
dbName?: string;
ipAddress: string;
isPing?: boolean;
// Host password; same as database password.
password: string;
port?: number;
sshPort?: number;
// database user.
user?: string;
};

@ -1,9 +0,0 @@
type DBInsertOrUpdateFunctionCommonParams = ModuleSubroutineCommonParams & {
file: string;
line?: number;
};
type DBInsertOrUpdateFunctionCommonOptions = Omit<
ExecModuleSubroutineOptions,
'subParams' | 'subModuleName'
>;

@ -1,3 +0,0 @@
type DBJobAnvilSyncSharedOptions = {
jobHostUUID?: string;
};

@ -1,3 +0,0 @@
type DeleteHostConnectionRequestBody = {
[hostUUID: string]: string[];
};

@ -1 +0,0 @@
type DeleteSSHKeyConflictRequestBody = { [hostUUID: string]: string[] };

@ -1,5 +0,0 @@
type ExecModuleSubroutineOptions = {
spawnSyncOptions?: import('child_process').SpawnSyncOptions;
subModuleName?: string;
subParams?: Record<string, unknown>;
};

@ -111,6 +111,6 @@ type AnvilDataUPSHash = {
}; };
}; };
type GetAnvilDataOptions = import('child_process').SpawnSyncOptions & { type GetAnvilDataOptions = {
predata?: Array<[string, ...unknown[]]>; predata?: Array<[string, ...unknown[]]>;
}; };

@ -6,23 +6,19 @@ type PeerDataHash = {
os_registered: string; os_registered: string;
}; };
type GetPeerDataOptions = Omit< type GetPeerDataOptions = SubroutineCommonParams & {
ExecModuleSubroutineOptions, password?: string;
'subModuleName' | 'subParams' port?: number;
> & };
ModuleSubroutineCommonParams & {
password?: string;
port?: number;
};
type GetPeerDataFunction = ( type GetPeerDataFunction = (
target: string, target: string,
options?: GetPeerDataOptions, options?: GetPeerDataOptions,
) => { ) => Promise<{
hostName: string; hostName: string;
hostOS: string; hostOS: string;
hostUUID: string; hostUUID: string;
isConnected: boolean; isConnected: boolean;
isInetConnected: boolean; isInetConnected: boolean;
isOSRegistered: boolean; isOSRegistered: boolean;
}; }>;

@ -1,25 +0,0 @@
type HostConnectionOverview = {
inbound: {
ipAddress: {
[ipAddress: string]: {
hostUUID: string;
ipAddress: string;
ipAddressUUID: string;
networkLinkNumber: number;
networkNumber: number;
networkType: string;
};
};
port: number;
user: string;
};
peer: {
[ipAddress: string]: {
hostUUID: string;
ipAddress: string;
isPing: boolean;
port: number;
user: string;
};
};
};

@ -1,6 +0,0 @@
type HostOverview = {
hostName: string;
hostType: string;
hostUUID: string;
shortHostName: string;
};

@ -1,19 +0,0 @@
type InitializeStrikerNetworkForm = {
interfaces: Array<NetworkInterfaceOverview | null | undefined>;
ipAddress: string;
name: string;
subnetMask: string;
type: string;
};
type InitializeStrikerForm = {
adminPassword: string;
domainName: string;
hostName: string;
hostNumber: number;
networkDNS: string;
networkGateway: string;
networks: InitializeStrikerNetworkForm[];
organizationName: string;
organizationPrefix: string;
};

@ -1,4 +1,4 @@
type DBJobParams = DBInsertOrUpdateFunctionCommonParams & { type JobParams = InsertOrUpdateFunctionCommonParams & {
job_command: string; job_command: string;
job_data?: string; job_data?: string;
job_name: string; job_name: string;
@ -7,5 +7,3 @@ type DBJobParams = DBInsertOrUpdateFunctionCommonParams & {
job_host_uuid?: string; job_host_uuid?: string;
job_progress?: number; job_progress?: number;
}; };
type DBInsertOrUpdateJobOptions = DBInsertOrUpdateFunctionCommonOptions;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save