{"id":13762439,"url":"https://github.com/myplaceonline/posixcube","last_synced_at":"2026-01-17T19:37:35.741Z","repository":{"id":55926310,"uuid":"76755603","full_name":"myplaceonline/posixcube","owner":"myplaceonline","description":"posixcube.sh is a POSIX compliant, shell script-based server automation framework.","archived":false,"fork":false,"pushed_at":"2023-02-16T15:55:14.000Z","size":296,"stargazers_count":169,"open_issues_count":8,"forks_count":6,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-08-03T14:05:29.891Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/myplaceonline.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-12-18T00:53:49.000Z","updated_at":"2024-04-13T22:05:53.000Z","dependencies_parsed_at":"2024-01-15T03:59:13.667Z","dependency_job_id":"619bb5f4-b298-4d5b-b707-0b12a04188cc","html_url":"https://github.com/myplaceonline/posixcube","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myplaceonline%2Fposixcube","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myplaceonline%2Fposixcube/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myplaceonline%2Fposixcube/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myplaceonline%2Fposixcube/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/myplaceonline","download_url":"https://codeload.github.com/myplaceonline/posixcube/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224974208,"owners_count":17401100,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T14:00:43.363Z","updated_at":"2026-01-17T19:37:35.706Z","avatar_url":"https://github.com/myplaceonline.png","language":"Shell","funding_links":[],"categories":["Shell","Uncategorized"],"sub_categories":["Uncategorized"],"readme":"# posixcube\n\n## Usage\n\n    usage: posixcube.sh -h HOST... [-l] [-c CUBE_DIR...] [OPTION]... COMMAND...\n\n      A POSIX compliant, shell script-based server automation framework.\n\n      -?        Help.\n      -a        If using Bash, asynchronously execute remote CUBEs/COMMANDs.\n      -b        If using Bash, install programmable tab completion for SSH hosts.\n      -c CUBE   Execute a cube. Option may be specified multiple times. If COMMANDS\n                are also specified, cubes are run first.\n      -d        Print debugging information.\n      -D CMD    Use `CMD` as the superuser command instead of `sudo`.\n      -e ENVAR  Shell script with environment variable assignments which is\n                uploaded and sourced on each HOST. Option may be specified\n                multiple times. Files ending with .enc will be decrypted\n                temporarily. If not specified, defaults to envars*sh envars*sh.enc\n      -F FILE   SSH `-F` option.\n      -h HOST   Target host. Option may be specified multiple times. The HOST may\n                be preceded with USER@ to specify the remote user. If a host has\n                a wildcard ('*'), then HOST is interpeted as a regular expression,\n                with '*' replaced with '.*' and any matching hosts in the following\n                files are added to the HOST list: /etc/ssh_config,\n                /etc/ssh/ssh_config, ~/.ssh/config, /etc/ssh_known_hosts,\n                /etc/ssh/ssh_known_hosts, ~/.ssh/known_hosts, and /etc/hosts.\n      -i FILE   SSH `-i` option for identity file.\n      -k        Keep the cube_exec.sh generated script.\n      -l        Local execution. Instead of (or in addition to) `-h HOST`s or a\n                sub-COMMAND, run the `-c` cubes or `COMMAND`s locally.\n      -o K=V    SSH `-o` option. Option may be specified multiple times. Defaults\n                to `-o ConnectTimeout=5`.\n      -O P=V    Set the specified variable P with the value V. Option may be\n                specified multiple times. Do not put double quotes around V. If\n                V contains *, replace with matching hosts per the -h algorithm.\n      -p PORT   SSH `-p` option.\n      -P PWD    Password for decrypting .enc ENVAR files.\n      -q        Quiet; minimize output.\n      -r ROLE   Role name. Option may be specified multiple times.\n      -R        Use `rsync` instead of scp.\n      -s        Skip remote host initialization (making ~/.posixcube, uploading\n                posixcube.sh, etc.). Assumes at least one run completed without -s.\n                Does not support encrypted ENVAR files.\n      -S        Run cube_package and cube_service APIs as superuser.\n      -t        SSH `-t` option.\n      -u USER   SSH user. Defaults to ${USER}. This may also be specified in HOST.\n      -U CUBE   Upload a CUBE but do not execute it. This is needed when one CUBE\n                includes this CUBE using cube_include.\n      -v        Show version information.\n      -w PWDF   File that contains the password for decrypting .enc ENVAR files.\n                Defaults to ~/.posixcube.pwd\n      -z SPEC   Use the SPEC set of options from the ./cubespecs.ini file\n      -y        If a HOST returns a non-zero code, continue processing other HOSTs.\n      COMMAND   Remote command to run on each HOST. Option may be specified\n                multiple times. If no HOSTs are specified, available sub-commands:\n                  edit: Decrypt, edit, and re-encrypt ENVAR file with $EDITOR.\n                  show: Decrypt and print ENVAR file.\n                  source: Source all ENVAR files. Must be run with\n                          POSIXCUBE_SOURCED (see Public Variables section below).\n\n    Description:\n\n      posixcube.sh is used to execute CUBEs and/or COMMANDs on one or more HOSTs.\n      \n      A CUBE is a shell script or directory containing shell scripts. The CUBE\n      is transferred to each HOST. If CUBE is a shell script, it's executed. If\n      CUBE is a directory, a shell script of the same name in that directory\n      is executed. In both cases, the directory is changed to the directory\n      containing the script before execution so that you may reference files\n      such as templates using relative paths.\n      \n      An ENVAR script is encouraged to use environment variable names of the form\n      cubevar_${uniquecontext}_envar=\"value\". If a CUBE directory contains the\n      file `envars.sh`, it's sourced before anything else (including `-e ENVARs`).\n      \n      Both CUBEs and COMMANDs may execute any of the functions defined in the\n      \"Public APIs\" in the posixcube.sh script. Short descriptions of the\n      functions are in the APIs section below. See the source comments above each\n      function for details.\n      \n    Examples (assuming posixcube.sh is on ${PATH}, or executed absolutely):\n\n      posixcube.sh -h socrates uptime\n      \n        Run the `uptime` command on host `socrates`. This is not very different\n        from ssh ${USER}@socrates uptime, except that COMMANDs (`uptime`) have\n        access to the cube_* public functions.\n      \n      posixcube.sh -h socrates -c test.sh\n      \n        Run the `test.sh` script (CUBE) on host `socrates`. The script has\n        access to the cube_* public functions.\n      \n      posixcube.sh -h socrates -c test\n      \n        Upload the entire `test` directory (CUBE) to the host `socrates` and\n        then execute the `test.sh` script within that directory (the name\n        of the script is expected to be the same as the name of the CUBE). This\n        allows for easily packaging other scripts and resources needed by\n        `test.sh`.\n      \n      posixcube.sh -S -h plato@socrates cube_package install atop\n      \n        As the remote user `plato` on the host `socrates`, install the package\n        `atop`. The `-S` option is required to run the commands within\n        cube_package as the superuser (see the Philosophy section, #3).\n      \n      posixcube.sh -h root@socrates -h seneca uptime\n      \n        Run the `uptime` command on hosts `socrates` and `seneca`\n        as the user `root`.\n      \n      posixcube.sh -h web*.test.com uptime\n      \n        Run the `uptime` command on all hosts matching the regular expression\n        web.*.test.com in the SSH configuration files.\n      \n      sudo posixcube.sh -b \u0026\u0026 . /etc/bash_completion.d/posixcube_completion.sh\n      \n        For Bash users, install a programmable completion script to support tab\n        auto-completion of hosts from SSH configuration files.\n\n      posixcube.sh -e production.sh.enc show\n      \n        Decrypt and show the contents of production.sh\n      \n      posixcube.sh -e production.sh.enc edit\n      \n        Decrypt, edit, and re-encrypt the contents of production.sh with $EDITOR\n\n      posixcube.sh -i ~/.ssh/plato -o LogLevel=VERBOSE -o ConnectTimeout=30 \\\n                  -h plato@socrates uptime\n      \n        Run the `uptime` command on host `socrates` as the remote user `plato`\n        using the SSH identity file ~/.ssh/plato and specifying the SSH options\n        LogLevel=VERBOSE and ConnectTimeout=30\n      \n    Philosophy:\n\n      1. Fail hard and fast. In principle, a well written script would check ${?}\n      after each command and either gracefully handle it, or report an error.\n      Few people write scripts this well, so we enforce this check (using\n      `cube_check_return` within all APIs) and we encourage you to do the same;\n      for example, `touch /etc/fstab || cube_check_return`. All cube_*\n      APIs are guaranteed to do their own checks, so you don't have to do this\n      for those calls; however, note that if you're executing a cube_* API in a\n      sub-shell, although any failures will be reported by cube_check_return,\n      the script will continue unless you also check the return of the sub-shell.\n      For example: $(cube_readlink /etc/localtime) || cube_check_return\n      \n      With this strategy, unfortunately, piping becomes more difficult. There are\n      non-standard mechanisms like pipefail and PIPESTATUS, but the standardized\n      approach is to run each command separately and check the status. For example:\n      \n      cubevar_app_x=\"$(cmd1)\" || cube_check_return\n      cubevar_app_y=\"$(printf '%s' \"${cubevar_app_x}\" | cmd2)\" || cube_check_return\n      \n      2. We don't use `set -e` because some functions may handle all errors\n      internally (with `cube_check_return`) and use a positive return code as a\n      \"benign\" result (e.g. `cube_set_file_contents`). We don't use `set -u`\n      because we source in the user's scripts and they may not want this behavior.\n      \n      3. Recent versions of many distributions encourage running most commands\n      as a non-superuser, and then using `sudo` if needed, with some distributions\n      disallowing remote SSH using the `root` account by default. First, the `sudo`\n      command is not standardized (see https://unix.stackexchange.com/a/48553).\n      Moreoever, it is not enough to prefix posixcube APIs with `sudo` because\n      `sudo` doesn't pass along functions (even if they're exported and `sudo` is\n      executed with --preserve-env). `su -c` may be used but it requires\n      password input. For the most common use case of requiring `sudo` for\n      cube_package and cube_service, if `-S` is specified, the commands within\n      those APIs are executed using `sudo`. If you need to run something else as a\n      superuser and you need access to the posixcube APIs, see the `cube_sudo` API.\n\n    Frequently Asked Questions:\n\n      * Why is there a long delay between \"Preparing hosts\" and the first remote\n        execution?\n      \n        You can see details of what's happening with the `-d` flag. By default,\n        the script first loops through every host and ensures that ~/.posixcube/\n        exists, then it transfers itself to the remote host. These two actions\n        may be skipped with the `-s` parameter if you've already run the script\n        at least once and your version of this script hasn't been updated. Next,\n        the script loops through every host and transfers any CUBEs and a script\n        containing the CUBEs and COMMANDs to run (`cube_exec.sh`). If the shell\n        is detected to be `bash`, then the above occurs asynchronously across the\n        HOSTs. Finally, you'll see the \"Executing on HOST...\" line and the real\n        execution starts.\n\n    Cube Development:\n\n      Shell scripts don't have scoping, so to reduce the chances of function name\n      conflicts, name functions cube_${cubename}_${function} and name variables\n      cubevar_${cubename}_${var}.\n\n    Public APIs:\n      \n      * cube_echo\n          Print ${@} to stdout prefixed with ([$(date)] [$(hostname)]) and\n          suffixed with a newline.\n          Example: cube_echo \"Hello World\"\n\n      * cube_printf\n          Print $1 to stdout prefixed with ([$(date)] [$(hostname)]) and\n          suffixed with a newline (with optional printf arguments in $@).\n          Example: cube_printf \"Hello World from PID %5s\" $$\n\n      * cube_error_echo\n          Same as cube_echo except output to stderr and include a red \"Error: \"\n          message prefix.\n          Example: cube_error_echo \"Goodbye World\"\n\n      * cube_error_printf\n          Same as cube_printf except output to stderr and include a red \"Error: \"\n          message prefix.\n          Example: cube_error_printf \"Goodbye World from PID %5s\" $$\n\n      * cube_warning_echo\n          Same as cube_echo except output to stderr and include a yellow\n          \"Warning: \" message prefix.\n          Example: cube_warning_echo \"Watch out, World\"\n\n      * cube_warning_printf\n          Same as cube_printf except output to stderr and include a yellow\n          \"Warning: \" message prefix.\n          Example: cube_warning_printf \"Watch out, World from PID %5s\" $$\n\n      * cube_throw\n          Same as cube_error_echo but also print a stack of functions and processes\n          (if available) and then call `exit 1`.\n          Example: cube_throw \"Expected some_file.\"\n\n      * cube_check_return\n          Check if $? is non-zero and call cube_throw if so.\n          Example: some_command || cube_check_return\n\n      * cube_include\n          Include the ${1} cube\n          Example: cube_include core_cube\n\n      * cube_check_numargs\n          Call cube_throw if there are less than $1 arguments in $@\n          Example: cube_check_numargs 2 \"${@}\"\n\n      * cube_service\n          Run the $1 action on the $2 service.\n          Example: cube_service start crond\n\n      * cube_package\n          Pass $@ to the package manager. Implicitly passes the parameter\n          to say yes to questions. On Debian-based systems, uses --force-confold\n          Example: cube_package install python\n\n      * cube_package_uninstall\n          Same as cube_package, except that you only specify the packages\n          to uninstall, and this will figure out the right operation to\n          pass to the package manager (e.g. dnf=remove, apt=purge).\n          Example: cube_package_uninstall python\n\n      * cube_append_str\n          Print $1 to stdout with $2 appended after a space if $1 was not blank.\n          Example: cubevar_app_str=$(cube_append_str \"${cubevar_app_str}\" \"Test\")\n\n      * cube_element_exists\n          Check if $1 contains the $2 element with the $3 delimiter (default space).\n          Example: cube_element_exists \"${cubevar_str}\" \"X\"\n\n      * cube_elements_count\n          Print to stdout the number of elements in the string $1 as separated by\n          the delimeter $2 (default space).\n          Example: cubevar_app_count=$(cube_elements_count \"${cubevar_str}\")\n\n      * cube_command_exists\n          Check if $1 command or function exists in the current context.\n          Example: cube_command_exists systemctl\n\n      * cube_dir_exists\n          Check if $1 exists as a directory.\n          Example: cube_dir_exists /etc/cron.d/\n\n      * cube_file_exists\n          Check if $1 exists as a file with read access.\n          Example: cube_file_exists /etc/cron.d/0hourly\n\n      * cube_operating_system\n          Detect operating system and return one of the POSIXCUBE_OS_* values.\n          Example: [ $(cube_operating_system) -eq ${POSIXCUBE_OS_LINUX} ] \u0026\u0026 ...\n\n      * cube_operating_system_version_major\n          Returns the major version of the operating system distribution.\n          Example: [ $(cube_operating_system_version_major) -gt 10 ] \u0026\u0026 ...\n\n      * cube_operating_system_version_minor\n          Returns the minor version of the operating system distribution.\n          Example: [ $(cube_operating_system_version_minor) -gt 10 ] \u0026\u0026 ...\n\n      * cube_operating_system_has_flavor\n          Check if the operating system flavor includes the flavor specified in $1\n          by one of the POSIXCUBE_OS_FLAVOR_* values.\n          Example: cube_operating_system_has_flavor ${POSIXCUBE_OS_FLAVOR_FEDORA} \\\n                    \u0026\u0026 ...\n\n      * cube_shell\n          Detect running shell and return one of the CUBE_SHELL_* values.\n          Example: [ $(cube_shell) -eq ${POSIXCUBE_SHELL_BASH} ] \u0026\u0026 ...\n\n      * cube_current_script_name\n          Print to stdout the basename of the currently executing script.\n          Example: script_name=$(cube_current_script_name)\n\n      * cube_current_script_abs_path\n          Print to stdout the absolute path the currently executing script.\n          Example: script_name=$(cube_current_script_abs_path)\n\n      * cube_file_size\n          Print to stdout the size of a file $1 in bytes\n          Example: cube_file_size some_file\n\n      * cube_set_file_contents\n          Copy the contents of $2 on top of $1 if $1 doesn't exist or the contents\n          are different than $2. If $2 ends with \".template\", first evaluate all\n          ${VARIABLE} expressions (except for \\${VARIABLE}).\n          Example: cube_set_file_contents \"/etc/npt.conf\" \"templates/ntp.conf\"\n\n      * cube_set_file_contents_string\n          Set the contents of $1 to the string $@. Create file if it doesn't exist.\n          Example: cube_set_file_contents_string ~/.info \"Hello World\"\n\n      * cube_expand_parameters\n          Print stdin to stdout with all ${VAR}'s evaluated (except for \\${VAR})\n          Example: cube_expand_parameters \u003c template \u003e output\n\n      * cube_readlink\n          Print to stdout the absolute path of $1 without any symbolic links.\n          Example: cube_readlink /etc/localtime\n\n      * cube_random_number\n          Print to stdout a random number between 1 and $1\n          Example: cube_random_number 10\n\n      * cube_tmpdir\n          Print to stdout a temporary directory\n          Example: cube_tmpdir\n\n      * cube_total_memory\n          Print to stdout total system memory in bytes\n          Example: cube_total_memory\n\n      * cube_ensure_directory\n          Ensure directory $1, and parent directories, exist. Return true if the\n          directory is created; otherwise, false.\n          Example: cube_ensure_directory ~/.ssh/\n\n      * cube_ensure_file\n          Ensure file $1, and parent directories, exist. Return true if the file is\n          created; otherwise, false.\n          Example: cube_ensure_file ~/.ssh/authorized_keys\n\n      * cube_pushd\n          Add the current directory to a stack of directories and change directory\n          to ${1}\n          Example: cube_pushd ~/.ssh/\n\n      * cube_popd\n          Pop the top of the stack of directories from `cube_pushd` and change\n          directory to that directory.\n          Example: cube_popd\n\n      * cube_has_role\n          Return true if the role $1 is set.\n          Example: cube_has_role \"database_backup\"\n\n      * cube_file_contains\n          Check if the file $1 contains $2. Uses grep with default arguments\n          (e.g. regex).\n          Example: cube_file_contains /etc/fstab nfsmount\n\n      * cube_stdin_contains\n          Check if stdin contains $1\n          Example: echo \"Hello World\" | cube_stdin_contains \"Hello\"\n\n      * cube_interface_ipv4_address\n          Print to stdout the IPv4 address of interface $1\n          Example: cube_interface_ipv4_address eth0\n\n      * cube_interface_ipv6_address\n          Print to stdout the IPv6 address of interface $1\n          Example: cube_interface_ipv6_address eth0\n\n      * cube_prompt\n          Prompt the question $1 followed by \" (y/N)\" and prompt for an answer.\n          A blank string answer is equivalent to No. Return true if yes, false\n          otherwise.\n          Example: cube_prompt \"Are you sure?\"\n\n      * cube_hostname\n          Print to stdout the full hostname.\n          Example: cube_hostname\n\n      * cube_string_contains\n          Return true if $1 contains $2; otherwise, false.\n          Example: cube_string_contains \"${cubevar_app_str}\" \"@\" \u0026\u0026 ...\n      \n      * cube_string_substring_before\n          Print to stdout a substring of $1 strictly before the first match of the\n          regular expression $2.\n          Example: cubevar_app_x=\"$(cube_string_substring_before \"${somevar}\" \"@\")\"\n\n      * cube_string_substring_after\n          Print to stdout a substring of $1 strictly after the first match of the\n          regular expression $2.\n          Example: cubevar_app_x=\"$(cube_string_substring_after \"${somevar}\" \"@\")\"\n      \n      * cube_sudo\n          Execute $* as superuser with all posixcube APIs available\n          (see Philosophy #3).\n          Example: cube_sudo cube_ensure_file /etc/app.txt\n      \n      * cube_read_stdin\n          Read stdin (e.g. HEREDOC) into the variable named by the first argument.\n\n      * cube_ensure_user\n          Create the user $1 if it doesn't already exist (with group name $1)\n          Example: cube_ensure_user nginx\n\n      * cube_user_exists\n          Check if the $1 user exists\n          Example: cube_user_exists nginx\n\n      * cube_create_user\n          Create the user $1\n          Example: cube_create_user nginx\n\n      * cube_group_exists\n          Check if the $1 group exists\n          Example: cube_group_exists nginx\n\n      * cube_create_group\n          Create the group $1\n          Example: cube_create_group nginx\n\n      * cube_group_contains_user\n          Check if the $1 group contains the user $2\n          Example: cube_group_contains_user nginx nginx\n\n      * cube_add_group_user\n          Add the user $2 to group $1\n          Example: cube_add_group_user nginx nginx\n      \n      * cube_current_user\n          Print to stdout the current user\n          Example: cube_current_user\n      \n      * cube_user_home_dir\n          Print to stdout the home directory of user $1 (defaults to\n          `cube_current_user`)\n          Example: cube_user_home_dir root\n      \n      * cube_user_ensure_private_key\n          Ensure the SSH private key contents $1 are placed in a file named $2\n          (defaults to `id_rsa`) for the user $3 (defaults to `cube_current_user`)\n          Example: cube_user_ensure_private_key \"${cubevar_app_key1_private}\"\n      \n      * cube_user_ensure_authorized_public_key\n          Ensure the SSH public key contents $1 exist in the `.ssh/authorized_keys`\n          file in the home directory of user $2 (defaults to `cube_current_user`)\n          Example: cube_user_ensure_authorized_public_key \"${cubevar_app_key1_pub}\"\n      \n      * cube_user_authorize_known_host\n          Ensure the public key(s) of the host $1 are in the `.ssh/known_hosts` file\n          in the home directory of user $2 (defaults to `cube_current_user`). Note:\n          the fingerprints are currently not checked, but you may review STDOUT.\n          To reduce the risks of MITM attacks, you can call this only after a\n          successful call to `cube_user_ensure_private_key` so that the scan is\n          only done once on private key setup.\n          Example: cube_user_ensure_private_key \"${cubevar_app_host_privkey}\" \u0026\u0026 \\\n                    cube_user_authorize_known_host some_host\n      \n    Public Variables:\n\n      * POSIXCUBE_APIS_ONLY\n          Set this to any value to only source the public APIs in posixcube.sh.\n          Example: POSIXCUBE_APIS_ONLY=true . posixcube.sh \u0026\u0026 cube_echo \\\n                    $(cube_random_number 10)\n      \n      * POSIXCUBE_SOURCED\n          Set this to any value to only run a sub-COMMAND, most commonly `source`,\n          to source in all ENVAR files, but skip actual execution of posixcube.\n          Example: POSIXCUBE_SOURCED=true . posixcube.sh source; \\\n                    POSIXCUBE_SOURCED= ; cube_echo Test\n\n    Source: https://github.com/myplaceonline/posixcube\n\n## Examples\n\n* [https://github.com/myplaceonline/myplaceonline_posixcubes](https://github.com/myplaceonline/myplaceonline_posixcubes):\n  Cubes that build a full Ruby on Rails stack with haproxy load balancer (frontend), nginx+passenger Rails\n  servers (web), postgresql database (database) and more (elasticsearch, database backup, rsyslog server, etc.).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyplaceonline%2Fposixcube","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmyplaceonline%2Fposixcube","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyplaceonline%2Fposixcube/lists"}