offspot-config


Nameoffspot-config JSON
Version 2.3.1 PyPI version JSON
download
home_pageNone
SummaryOffspot Config helpers
upload_time2024-10-17 15:03:19
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseGPL-3.0-or-later
keywords offspot
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # offspot-config

A library to read/write an Offspot runtime config and a collection of scripts to use it within [offspot/base-image](https://github.com/offspot/base-image).

[![CodeFactor](https://www.codefactor.io/repository/github/offspot/offspot-config/badge)](https://www.codefactor.io/repository/github/offspot/offspot-config)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![codecov](https://codecov.io/gh/offspot/offspot-config/branch/main/graph/badge.svg)](https://codecov.io/gh/offspot/offspot-config)
[![PyPI version shields.io](https://img.shields.io/pypi/v/offspot-config.svg)](https://pypi.org/project/offspot-config/)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/offspot-config.svg)](https://pypi.org/project/offspot-config)

## Scripts Usage

Launched via `offspot-runtime-config-fromfile`, it:

- reads a YAML config file and changes the offspot configuration accordingly.
- starts associated services

Its primary goal is to allow one to change some key offspot configuration upon next boot by changing one file (stored in FAT32 `/boot/firmware` –so writable anywhere), following a descriptive format.

**Notes**:

- It is **not a configuration reference**. It only lists the changes requested (whatever the status of those settings) at next boot. In an already configured system the file should be an empty YAML document (`---`).
- While `-fromfile` reads a YAML file, it mostly runs individual, feature-specific scripts that takes parameters on the command-line.
- it's a configuration tool that must be ran as root.


```sh
offspot-runtime-config-fromfile --debug /boot/firmware/offspot.yaml
```

- `--debug` will show you what exact parameters were passed to individual scripts so you can manually launch them should there be an issue.
- this script is meant to be run automaticaly on boot (via systemd) **before `docker-compose.service`**.
- It returns `0` on success, `1` on general failures and `2` on misconfiguration (invalid parameter). Same goes for individual scripts.
- it starts `hostapd`, `dnsmasq` and `iptables-restore` automatically. If you start it on boot, disable them (`systemctl disable hostapd dnsmasq`)

## Installation

**⚠️ Warning**: only tested on offspot base-image (raspiOS bookworm)

```sh
apt install hostapd dnsmasq dhcpcd5 python3-yaml python3-pip
systemctl unmask hostapd
systemctl disable hostapd dnsmasq
pip3 install offspot-config
```

## Library usage

```sh
pip3 install offspot-config
```

```py
from offspot_runtime.checks import is_valid_ipv4

# CheckResponse can be treated as a boolean
if is_valid_ipv4("10.0.0.1"):
   …

# CheckResponse exposes `.passed` (`bool`) and `.help_text` (`str`)
check = is_valid_ipv4("10.0.0.a")
if not check.passed:
    raise SystemExit(check.help_text)

# Directly raise a `ValueError` exception
is_valid_ipv4("10.0.0.a").raise_for_status()
```

---

## `offspot.yaml` format

`offspot.yaml` is composed of a single `object` with predefined candidate members.

- No member is required.
- Unknown members are simply ignored.
- No relation between first-level members.

### Valid first-level members

| Member       | Kind      | Function                                               |
|--------------|-----------|--------------------------------------------------------|
| `firmware`   | `string`  | Set WiFi firmware to use                               |
| `timezone`   | `string`  | Set Host timezone                                      |
| `hostname`   | `string`  | Set machine's hostname (not domain, see `ap`).         |
| `ethernet`   | `object`  | Set network configuration for ethernet interface       |
| `ap`         | `object`  | Set WiFi AP configuration for wireless interface       |
| `containers` | `object`  | Builds the docker-compose file                         |

### `firmware`

`firmware` is itself a single `object`.

| Member       | Kind      | Required | Function                                             |
|--------------|-----------|----------|------------------------------------------------------|
| `brcm43455`  | `string`  | no       | Firmware to use for `brcm43455` chipset (Pi 3B+/4/5) |
| `brcm43430`  | `string`  | no       | Firmware to use for `brcm43430 ` chipset (Pi 0W/3    |

Chipsets included in RaspiOS have limitations on the number of WiFi clients that can connect to it.
Using this, you can change the version of the firmware to use for your chipset.

Each chipset supports a different set of firmwares.

#### `brcm43455` chipset firmwares

| Firmware                            | Comment                                                                        |
|-------------------------------------|--------------------------------------------------------------------------------|
| `raspios`                           | Supports 4/5 clients. Came with RaspiOS                                        |
| `supports-19_2021-11-30`            | Supports 19 clients. Can be used in AP+STA mode (not supported in Hotspot yet) |
| `supports-24_2021-10-05_noap+sta`   | Supports 24 clients. Can **not** be used in `AP+STA` mode                      |
| `supports-32_2015-03-01_unreliable` | Supports 32 clients. **Unreliable**. Available for tests only                  |

#### `brcm43430` chipset firmwares

| Firmware                            | Comment                                                                        |
|-------------------------------------|--------------------------------------------------------------------------------|
| `raspios`                           | Supports 4/5 clients. Came with RaspiOS                                        |
| `supports-30_2018-09-28`            | Supports 30 clients.                                                           |

**⚠️ Warning**: WiFi firmware update **requires a reboot** to be effective.

Example:

```yaml
---
firmware:
  brcm43455: raspios
  brcm43430: supports-30_2018-09-28
```

### `timezone`

Must be a valid timezone. Get a complete list with:

```sh
timedatectl list-timezones
```

**⚠️ Warning**: timezones are **case-sentitive**. `Africa/Bamako` works but `Africa/bamako` or `utc` doesn't.

Example:

```yaml
---
timezone: Europe/Berlin
```


### `hostname`

Must be alphanumeric string up to 63 characters. Can be composed of multiple (max 64) of those, separated by a single dot. Total length must be under 256 characters.

Example:

```yaml
---
hostname: library-lab-pi23
```

**Note**: this is not the domain name on the network. See `ap` for this. `hostname` is mostly useless.

### `ethernet`

`network` is itself a single `object`.

| Member       | Kind      | Required | Function   |
|--------------|-----------|----------|------------|
| `type    `   | `string`  | **yes**  | Either `dhcp` or `static`                   |
| `address`    | `string`  | **`static`** | Static IPv4 adress to set                |
| `routers`    | `string`  | **`static`** | Space-separated IPv4 addresses to use as gateways. Use any different address inside subnet if not using it. |
| `dns`        | `string`  | **`static`** | Space-separated IPv4 addresses to use as domain name servers. Use any different address inside subnet if not using it. |

Examples:

```yaml
---
ethernet:
  type: dhcp
```

```yaml
---
ethernet:
  type: static
  address: 192.168.5.1
  routers: 192.168.5.200
  dns: 192.168.5.200
```

#### Notes

If you need to mix this simple configuration tool with a more complex `dhcpcd.conf` file, use the following *armor* in your file:

```git
### config-network: start ###
### config-network: end ###
```

The script will set its properties in-between those lines, keeping the rest of your configuration.

Without an *armor*, configuration is appended at end of file, specifying `eth0` interface if missing (untouched `dhcpcd.conf`)

### `ap`

`ap` is itself an `object`.

| Member              | Kind       | Required | Function                                                                                                         |
|---------------------|----------- |----------|------------------------------------------------------------------------------------------------------------------|
| `ssid    `          | `string`   | **yes**  | SSID (Network Name)                                                                                              |
| `passphrase`        | `string`   | no       | Passphrase/password to connect to the network. Defaults to Open Network                                          |
| `address`           | `string`   | no       | IP address to set on the wireless interface. Defaults to 192.168.2.1                                             |
| `channel`           | `integer`  | no       | WiFi channel to use for the network (1-14). Defaults to `11`.                                                    |
| `country`           | `string`   | no       | Country-code to apply frequencies limitations for. Defaults to `FR`                                              |
| `hide`              | `boolean`  | no       | Hide SSID (Clients must know and enter its name to connect)                                                      |
| `interface`         | `string`   | no       | Interface to configure AP for. Defaults to `wlan0`                                                               |
| `dhcp-range`        | `string`   | no       | IP range for AP clients. `start,end,subnet,ttl` format. Default: `.100-.240` from address                        |
| `network`           | `[string]` | no       | Network to advertise DHCP on. Defaults to `.0/24` from address                                                   |
| `nodhcp-interfaces` | `[string]` | no       | Interfaces where the DHCP server will not run                                                                    |
| `dns`               | `[string]` | no       | DNS to set via DHCP when working as Internet gateway. Defaults to `8.8.8.8`, `1.1.1.1`                           |
| `captured-address`  | `string`   | no       | IP address to set DNS fallback to when offline (all domains but locals are sent to it). Default:  `198.51.100.1` |
| `as-gateway`        | `boolean`  | no       | Make this device act as a gateway to Internet (wired) for AP (wireless) clients (when/if `eth0` has connectivity)|
| `tld`               | `string`   | no       | Search (top-level) *domain* to set via DHCP. Defaults to `offspot`                                               |
| `domain`            | `string`   | no       | Domain name to direct to the offspot. Defaults to `generic` (resolved as `generic.{tld}`                         |
| `welcome`           | `string`   | no       | Additional domain to direct to offspot. Defaults to `goto.kiwix` (resolved as `goto.generic.{tld}`               |
| `spoof`             | `boolean`* | no       | Whether to direct all DNS requests to the offspot. Useful for captive-portal without Internet bridge^1.          |

- ^1: Special value `auto` triggers it when the hotspot is offline and disables it when it is connected to Internet

#### notes

- `iptables` is not persistent. `ap` will write rules to `/etc/iptables/*.rules`. If you don't use `offspot-runtime-config-fromfile` on start, manually reload them via a script or service:

```sh
/usr/bin/find /etc/iptables/ -name '*.rules' -exec /sbin/iptables-restore {} \;
```

### `containers`

`containers` is the full docker-compose.yaml you want to use. It will be written to `/etc/docker/compose.yml`.

```yaml
---
containers:
  services:
    kiwix:
      container_name: kiwix
      image: ghcr.io/offspot/kiwix-serve:dev
      command: /bin/sh -c "kiwix-serve /data/*.zim"
      volumes:
        - "/data/zims:/data:ro"
      expose:
        - "80"
      restart: always
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "offspot-config",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "offspot",
    "author": null,
    "author_email": "Kiwix <dev@kiwix.org>",
    "download_url": "https://files.pythonhosted.org/packages/7c/9e/f2b570cda5dd6d14ee7de9fb499752a969212e3687ba1fcb8f5a6736d8a8/offspot_config-2.3.1.tar.gz",
    "platform": null,
    "description": "# offspot-config\n\nA library to read/write an Offspot runtime config and a collection of scripts to use it within [offspot/base-image](https://github.com/offspot/base-image).\n\n[![CodeFactor](https://www.codefactor.io/repository/github/offspot/offspot-config/badge)](https://www.codefactor.io/repository/github/offspot/offspot-config)\n[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![codecov](https://codecov.io/gh/offspot/offspot-config/branch/main/graph/badge.svg)](https://codecov.io/gh/offspot/offspot-config)\n[![PyPI version shields.io](https://img.shields.io/pypi/v/offspot-config.svg)](https://pypi.org/project/offspot-config/)\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/offspot-config.svg)](https://pypi.org/project/offspot-config)\n\n## Scripts Usage\n\nLaunched via `offspot-runtime-config-fromfile`, it:\n\n- reads a YAML config file and changes the offspot configuration accordingly.\n- starts associated services\n\nIts primary goal is to allow one to change some key offspot configuration upon next boot by changing one file (stored in FAT32 `/boot/firmware` \u2013so writable anywhere), following a descriptive format.\n\n**Notes**:\n\n- It is **not a configuration reference**. It only lists the changes requested (whatever the status of those settings) at next boot. In an already configured system the file should be an empty YAML document (`---`).\n- While `-fromfile` reads a YAML file, it mostly runs individual, feature-specific scripts that takes parameters on the command-line.\n- it's a configuration tool that must be ran as root.\n\n\n```sh\noffspot-runtime-config-fromfile --debug /boot/firmware/offspot.yaml\n```\n\n- `--debug` will show you what exact parameters were passed to individual scripts so you can manually launch them should there be an issue.\n- this script is meant to be run automaticaly on boot (via systemd) **before `docker-compose.service`**.\n- It returns `0` on success, `1` on general failures and `2` on misconfiguration (invalid parameter). Same goes for individual scripts.\n- it starts `hostapd`, `dnsmasq` and `iptables-restore` automatically. If you start it on boot, disable them (`systemctl disable hostapd dnsmasq`)\n\n## Installation\n\n**\u26a0\ufe0f Warning**: only tested on offspot base-image (raspiOS bookworm)\n\n```sh\napt install hostapd dnsmasq dhcpcd5 python3-yaml python3-pip\nsystemctl unmask hostapd\nsystemctl disable hostapd dnsmasq\npip3 install offspot-config\n```\n\n## Library usage\n\n```sh\npip3 install offspot-config\n```\n\n```py\nfrom offspot_runtime.checks import is_valid_ipv4\n\n# CheckResponse can be treated as a boolean\nif is_valid_ipv4(\"10.0.0.1\"):\n   \u2026\n\n# CheckResponse exposes `.passed` (`bool`) and `.help_text` (`str`)\ncheck = is_valid_ipv4(\"10.0.0.a\")\nif not check.passed:\n    raise SystemExit(check.help_text)\n\n# Directly raise a `ValueError` exception\nis_valid_ipv4(\"10.0.0.a\").raise_for_status()\n```\n\n---\n\n## `offspot.yaml` format\n\n`offspot.yaml` is composed of a single `object` with predefined candidate members.\n\n- No member is required.\n- Unknown members are simply ignored.\n- No relation between first-level members.\n\n### Valid first-level members\n\n| Member       | Kind      | Function                                               |\n|--------------|-----------|--------------------------------------------------------|\n| `firmware`   | `string`  | Set WiFi firmware to use                               |\n| `timezone`   | `string`  | Set Host timezone                                      |\n| `hostname`   | `string`  | Set machine's hostname (not domain, see `ap`).         |\n| `ethernet`   | `object`  | Set network configuration for ethernet interface       |\n| `ap`         | `object`  | Set WiFi AP configuration for wireless interface       |\n| `containers` | `object`  | Builds the docker-compose file                         |\n\n### `firmware`\n\n`firmware` is itself a single `object`.\n\n| Member       | Kind      | Required | Function                                             |\n|--------------|-----------|----------|------------------------------------------------------|\n| `brcm43455`  | `string`  | no       | Firmware to use for `brcm43455` chipset (Pi 3B+/4/5) |\n| `brcm43430`  | `string`  | no       | Firmware to use for `brcm43430 ` chipset (Pi 0W/3    |\n\nChipsets included in RaspiOS have limitations on the number of WiFi clients that can connect to it.\nUsing this, you can change the version of the firmware to use for your chipset.\n\nEach chipset supports a different set of firmwares.\n\n#### `brcm43455` chipset firmwares\n\n| Firmware                            | Comment                                                                        |\n|-------------------------------------|--------------------------------------------------------------------------------|\n| `raspios`                           | Supports 4/5 clients. Came with RaspiOS                                        |\n| `supports-19_2021-11-30`            | Supports 19 clients. Can be used in AP+STA mode (not supported in Hotspot yet) |\n| `supports-24_2021-10-05_noap+sta`   | Supports 24 clients. Can **not** be used in `AP+STA` mode                      |\n| `supports-32_2015-03-01_unreliable` | Supports 32 clients. **Unreliable**. Available for tests only                  |\n\n#### `brcm43430` chipset firmwares\n\n| Firmware                            | Comment                                                                        |\n|-------------------------------------|--------------------------------------------------------------------------------|\n| `raspios`                           | Supports 4/5 clients. Came with RaspiOS                                        |\n| `supports-30_2018-09-28`            | Supports 30 clients.                                                           |\n\n**\u26a0\ufe0f Warning**: WiFi firmware update **requires a reboot** to be effective.\n\nExample:\n\n```yaml\n---\nfirmware:\n  brcm43455: raspios\n  brcm43430: supports-30_2018-09-28\n```\n\n### `timezone`\n\nMust be a valid timezone. Get a complete list with:\n\n```sh\ntimedatectl list-timezones\n```\n\n**\u26a0\ufe0f Warning**: timezones are **case-sentitive**. `Africa/Bamako` works but `Africa/bamako` or `utc` doesn't.\n\nExample:\n\n```yaml\n---\ntimezone: Europe/Berlin\n```\n\n\n### `hostname`\n\nMust be alphanumeric string up to 63 characters. Can be composed of multiple (max 64) of those, separated by a single dot. Total length must be under 256 characters.\n\nExample:\n\n```yaml\n---\nhostname: library-lab-pi23\n```\n\n**Note**: this is not the domain name on the network. See `ap` for this. `hostname` is mostly useless.\n\n### `ethernet`\n\n`network` is itself a single `object`.\n\n| Member       | Kind      | Required | Function   |\n|--------------|-----------|----------|------------|\n| `type    `   | `string`  | **yes**  | Either `dhcp` or `static`                   |\n| `address`    | `string`  | **`static`** | Static IPv4 adress to set                |\n| `routers`    | `string`  | **`static`** | Space-separated IPv4 addresses to use as gateways. Use any different address inside subnet if not using it. |\n| `dns`        | `string`  | **`static`** | Space-separated IPv4 addresses to use as domain name servers. Use any different address inside subnet if not using it. |\n\nExamples:\n\n```yaml\n---\nethernet:\n  type: dhcp\n```\n\n```yaml\n---\nethernet:\n  type: static\n  address: 192.168.5.1\n  routers: 192.168.5.200\n  dns: 192.168.5.200\n```\n\n#### Notes\n\nIf you need to mix this simple configuration tool with a more complex `dhcpcd.conf` file, use the following *armor* in your file:\n\n```git\n### config-network: start ###\n### config-network: end ###\n```\n\nThe script will set its properties in-between those lines, keeping the rest of your configuration.\n\nWithout an *armor*, configuration is appended at end of file, specifying `eth0` interface if missing (untouched `dhcpcd.conf`)\n\n### `ap`\n\n`ap` is itself an `object`.\n\n| Member              | Kind       | Required | Function                                                                                                         |\n|---------------------|----------- |----------|------------------------------------------------------------------------------------------------------------------|\n| `ssid    `          | `string`   | **yes**  | SSID (Network Name)                                                                                              |\n| `passphrase`        | `string`   | no       | Passphrase/password to connect to the network. Defaults to Open Network                                          |\n| `address`           | `string`   | no       | IP address to set on the wireless interface. Defaults to 192.168.2.1                                             |\n| `channel`           | `integer`  | no       | WiFi channel to use for the network (1-14). Defaults to `11`.                                                    |\n| `country`           | `string`   | no       | Country-code to apply frequencies limitations for. Defaults to `FR`                                              |\n| `hide`              | `boolean`  | no       | Hide SSID (Clients must know and enter its name to connect)                                                      |\n| `interface`         | `string`   | no       | Interface to configure AP for. Defaults to `wlan0`                                                               |\n| `dhcp-range`        | `string`   | no       | IP range for AP clients. `start,end,subnet,ttl` format. Default: `.100-.240` from address                        |\n| `network`           | `[string]` | no       | Network to advertise DHCP on. Defaults to `.0/24` from address                                                   |\n| `nodhcp-interfaces` | `[string]` | no       | Interfaces where the DHCP server will not run                                                                    |\n| `dns`               | `[string]` | no       | DNS to set via DHCP when working as Internet gateway. Defaults to `8.8.8.8`, `1.1.1.1`                           |\n| `captured-address`  | `string`   | no       | IP address to set DNS fallback to when offline (all domains but locals are sent to it). Default:  `198.51.100.1` |\n| `as-gateway`        | `boolean`  | no       | Make this device act as a gateway to Internet (wired) for AP (wireless) clients (when/if `eth0` has connectivity)|\n| `tld`               | `string`   | no       | Search (top-level) *domain* to set via DHCP. Defaults to `offspot`                                               |\n| `domain`            | `string`   | no       | Domain name to direct to the offspot. Defaults to `generic` (resolved as `generic.{tld}`                         |\n| `welcome`           | `string`   | no       | Additional domain to direct to offspot. Defaults to `goto.kiwix` (resolved as `goto.generic.{tld}`               |\n| `spoof`             | `boolean`* | no       | Whether to direct all DNS requests to the offspot. Useful for captive-portal without Internet bridge^1.          |\n\n- ^1: Special value `auto` triggers it when the hotspot is offline and disables it when it is connected to Internet\n\n#### notes\n\n- `iptables` is not persistent. `ap` will write rules to `/etc/iptables/*.rules`. If you don't use `offspot-runtime-config-fromfile` on start, manually reload them via a script or service:\n\n```sh\n/usr/bin/find /etc/iptables/ -name '*.rules' -exec /sbin/iptables-restore {} \\;\n```\n\n### `containers`\n\n`containers` is the full docker-compose.yaml you want to use. It will be written to `/etc/docker/compose.yml`.\n\n```yaml\n---\ncontainers:\n  services:\n    kiwix:\n      container_name: kiwix\n      image: ghcr.io/offspot/kiwix-serve:dev\n      command: /bin/sh -c \"kiwix-serve /data/*.zim\"\n      volumes:\n        - \"/data/zims:/data:ro\"\n      expose:\n        - \"80\"\n      restart: always\n```\n",
    "bugtrack_url": null,
    "license": "GPL-3.0-or-later",
    "summary": "Offspot Config helpers",
    "version": "2.3.1",
    "project_urls": {
        "Donate": "https://www.kiwix.org/en/support-us/",
        "Homepage": "https://github.com/offspot/offspot-config"
    },
    "split_keywords": [
        "offspot"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "e5b76c83c1cfdc29dc43f01bf97f657aa740bcc5fecdf64aa71853b423cf6b60",
                "md5": "ca05f0af569441ecad19d8984c71f655",
                "sha256": "b2c70a32c57e492715741f5d0ae3b5080fb89a565d9b52ca25f3b16427d5a00e"
            },
            "downloads": -1,
            "filename": "offspot_config-2.3.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ca05f0af569441ecad19d8984c71f655",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 137264,
            "upload_time": "2024-10-17T15:03:17",
            "upload_time_iso_8601": "2024-10-17T15:03:17.136444Z",
            "url": "https://files.pythonhosted.org/packages/e5/b7/6c83c1cfdc29dc43f01bf97f657aa740bcc5fecdf64aa71853b423cf6b60/offspot_config-2.3.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "7c9ef2b570cda5dd6d14ee7de9fb499752a969212e3687ba1fcb8f5a6736d8a8",
                "md5": "e1cf437921620993dd6fba4b0cae7de1",
                "sha256": "f7b3ba32eb4d3435b9cd15b3d82860351538cedd4bd2ef91facccdff1e7789ae"
            },
            "downloads": -1,
            "filename": "offspot_config-2.3.1.tar.gz",
            "has_sig": false,
            "md5_digest": "e1cf437921620993dd6fba4b0cae7de1",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 135716,
            "upload_time": "2024-10-17T15:03:19",
            "upload_time_iso_8601": "2024-10-17T15:03:19.276836Z",
            "url": "https://files.pythonhosted.org/packages/7c/9e/f2b570cda5dd6d14ee7de9fb499752a969212e3687ba1fcb8f5a6736d8a8/offspot_config-2.3.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-10-17 15:03:19",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "offspot",
    "github_project": "offspot-config",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "offspot-config"
}
        
Elapsed time: 1.41057s