{"id":20975235,"url":"https://github.com/functionland/go-fula","last_synced_at":"2026-02-22T01:35:38.411Z","repository":{"id":39649394,"uuid":"486056102","full_name":"functionland/go-fula","owner":"functionland","description":"Client-server stack for Web3! Turn your Raspberry Pi to a BAS server in minutes and enjoy the freedom of decentralized Web with a superior user experience! ","archived":false,"fork":false,"pushed_at":"2026-02-20T07:13:59.000Z","size":192020,"stargazers_count":14,"open_issues_count":21,"forks_count":6,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-02-20T08:39:58.241Z","etag":null,"topics":["android","fula","go","golang","gomobile","gomobile-bind","graphsync","ios","libp2p","web3"],"latest_commit_sha":null,"homepage":"","language":"Go","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/functionland.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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-04-27T05:21:29.000Z","updated_at":"2026-02-20T07:14:03.000Z","dependencies_parsed_at":"2023-02-10T12:16:52.439Z","dependency_job_id":"2b3a083b-49f8-4e25-88dc-48e1dcb3338a","html_url":"https://github.com/functionland/go-fula","commit_stats":null,"previous_names":[],"tags_count":101,"template":false,"template_full_name":null,"purl":"pkg:github/functionland/go-fula","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionland%2Fgo-fula","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionland%2Fgo-fula/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionland%2Fgo-fula/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionland%2Fgo-fula/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/functionland","download_url":"https://codeload.github.com/functionland/go-fula/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/functionland%2Fgo-fula/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29703227,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T23:35:04.139Z","status":"ssl_error","status_checked_at":"2026-02-21T23:35:03.832Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["android","fula","go","golang","gomobile","gomobile-bind","graphsync","ios","libp2p","web3"],"created_at":"2024-11-19T04:41:16.463Z","updated_at":"2026-02-22T01:35:38.404Z","avatar_url":"https://github.com/functionland.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# box\n\n[![Go Test](https://github.com/functionland/go-fula/actions/workflows/go-test.yml/badge.svg)](https://github.com/functionland/go-fula/actions/workflows/go-test.yml) [![](https://jitpack.io/v/functionland/fula-build-aar.svg)](https://jitpack.io/#functionland/fula-build-aar)\n\n\nClient-server stack for Web3\n\n[Intro blog](https://dev.to/fx/google-photos-open-source-alternative-with-react-native-80c#ending-big-techs-reign-by-building-opensource-p2p-apps)\n\nYou can see it in action in our Flagship App: [Fx Fotos](https://github.com/functionland/fx-fotos)\n\n[![Blox server demo](https://gateway.pinata.cloud/ipfs/QmVd3eioLfp19hG1GnfgceBsbK16i39vsQ9Lxq35aAjxnY)](https://gateway.pinata.cloud/ipfs/QmWUjQczA5jHC3ibLq4y7CizVrebr1DTFRaTdJgFyxR5Nh)\n\n## Motivation\n\nThere are currently two ways to interact with Web3 storage solutions:\n\n1. Through a pinning service and a gateway: the advantage is that files are served through URLs, an app can then access the files with conventional methods, e.g. simply putting a picture in `\u003cimg src=\"gateway.example.com/Qm...\"\u003e`. The disadvantage is that there is a subscription payment associated with pinning services. Also this is not really decentralized!\n2. Turn the device to a full IPFS node: this model works beautifully in Brave desktop browser as an example, and makes sense for laptop and PC since they normally have large HDDs. It's much harder on mobile devices, however, biggest hurdle is to have Apple on board with the idea of relaxing file system access in iOS! Even if all goes well, a mobile device is NOT a good candidate for hosting the future Web! They get lost easily and are resource constrained (battery, memory).\n\n[**blox**](https://github.com/functionland/BLOX) aims to address these issues by creating a third alternative: **Personal Server**\n\nA personal server is a commodity hardware device (such as a PC or Raspberry Pi) that is kept *at home* rather than carried with the user. It can help with actual decentralization and can save money in the long run, as there is a one-time cost for the hard drive and no monthly charges. From a privacy perspective, it also guarantees that data does not leave the premises unless the user specifically wants to share it.\n\nTo achieve this, we are developing protocols to accommodate client-server programming with minimal effort on developer's side.\n\n## Architecture\n\n`go-fula` is an implementation of the Fula protocol in Go (Golang). It is designed to facilitate smooth communication between clients and a mesh of backend devices (servers) and provide APIs for developers to build mobile-native decentralized applications (dApps).\n\n![box architecture](https://gateway.pinata.cloud/ipfs/QmNkoQfCKAzQetJKWfNtLioJf6FCxzqjoDT2KshDZfsJd3)\n\nA React Native app can communicate with servers using the `@functionland/react-native-fula` library, which abstracts the underlying protocols and `libp2p` connection and exposes APIs similar to those of MongoDB for data persistence and S3 for file storage.\n\nData is encrypted on the client side using [WebNative Filesystem (WNFS)](https://github.com/wnfs-wg/rs-wnfs) ( with bridges for [Android](https://github.com/functionland/wnfs-android) and [iOS](https://github.com/functionland/wnfs-ios) ). The encrypted Merkle DAG is then transferred to the blox server using Graphsync.\n\nThe **blox** stack can provide backup guarantees by having the data pinned on multiple servers owned by the user. In cases where absolute assurance of data longevity is required (e.g. password records in a password manager app or scans of sensitive documents), the cids of encrypted data can be sent to the [Fula blockchain](https://github.com/functionland/sugarfunge-node) and backed up by other blox owners, who are rewarded for their efforts.\n\nBy default, Libp2p connections are established through Functionland's libp2p relay over the internet or directly over LAN without the use of a relay.\n\n## Packages\n\n| Name | Description |\n| --- | --- |\n| [blox](blox) | Blox provides the backend to receive the DAG created by fulamobile and store it |\n| [mobile](mobile) | Initiates a libp2p instance and interacts with WNFS (as its datastore) to encrypt the data and Send and receive files in a browser or an Android or iOS app. Available for [React-Native here](https://github.com/functionland/react-native-fula) and for [Android here](https://github.com/functionland/fula-build-aar) |\n| [exchange](exchange) | Fula exchange protocol is responsible for the actual transfer of data |\n| [blockchain](blockchain) | On-chain interactions for pool management, manifests, and account operations |\n| [wap](wap) | Wireless Access Point server — provides HTTP endpoints (`/properties`, `/readiness`, `/wifi/*`, `/peer/*`) on port 3500 and mDNS service discovery |\n\n## Other related libraries\n\n| Name | Description |\n| --- | --- |\n| [WNFS for Android](https://github.com/functionland/wnfs-android) | Android build for WNFS rust version |\n| [WNFS for iOS](https://github.com/functionland/wnfs-ios) | iOS build for WNFS rust version |\n\n## PeerID Architecture\n\nBlox devices maintain two separate libp2p identities:\n\n- **Kubo peerID** — Derived via HMAC-SHA256 (domain `\"fula-kubo-identity-v1\"`) from the main identity key. Used by the embedded IPFS (kubo) node. Always available.\n- **ipfs-cluster peerID** — The original identity from `config.yaml`. Used by ipfs-cluster when installed.\n\nKubo is always running on the device; ipfs-cluster may not be installed. The `wifi.GetKuboPeerID()` utility function provides reliable access to the kubo peerID (via kubo API, falling back to config file) without depending on ipfs-cluster.\n\n### Getting ipfs-cluster and kubo PeerIDs\n\nThere are several ways to retrieve peerIDs depending on which part of the system you are working with.\n\n#### 1. WAP HTTP Endpoints (port 3500)\n\nThese endpoints are served by the WAP server on the device. Mobile apps connect to them over the local network.\n\n**GET `/properties`**\n\nReturns device properties including the kubo peerID.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/properties\n```\n\nResponse (relevant fields):\n```json\n{\n  \"kubo_peer_id\": \"12D3KooWAbCdEf...\",\n  \"ipfs_cluster_peer_id\": \"12D3KooWXyZaBc...\",\n  \"hardwareID\": \"a1b2c3...\",\n  \"bloxFreeSpace\": { \"device_count\": 1, \"size\": 500000000000, \"used\": 120000000000, \"avail\": 380000000000, \"used_percentage\": 24 },\n  \"ota_version\": \"1.2.3\",\n  \"...\": \"...\"\n}\n```\n\n- `kubo_peer_id` — The kubo (IPFS) peerID. Always present when kubo is running.\n- `ipfs_cluster_peer_id` — The ipfs-cluster peerID. Present when ipfs-cluster identity exists.\n\n**GET `/readiness`**\n\nReturns readiness status and properties, also including the kubo peerID.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/readiness\n```\n\nResponse (relevant fields):\n```json\n{\n  \"name\": \"fula_go\",\n  \"kubo_peer_id\": \"12D3KooWAbCdEf...\",\n  \"...\": \"...\"\n}\n```\n\n#### 2. Mobile SDK (`GetClusterInfo`)\n\nFrom the mobile app, call `GetClusterInfo()` on the Fula client. This uses libp2p to call the blox node directly (no HTTP needed).\n\n```go\nresult, err := client.GetClusterInfo()\n```\n\nResponse JSON:\n```json\n{\n  \"cluster_peer_id\": \"12D3KooWXyZaBc...\",\n  \"cluster_peer_name\": \"12D3KooWAbCdEf...\"\n}\n```\n\n| Field | Description | When empty |\n|-------|-------------|------------|\n| `cluster_peer_id` | ipfs-cluster peerID (from `/uniondrive/ipfs-cluster/identity.json`) | ipfs-cluster is not installed |\n| `cluster_peer_name` | kubo peerID (from kubo config or API) | Only if kubo is also unreachable |\n\nWhen ipfs-cluster is not installed, the response will have an empty `cluster_peer_id` but `cluster_peer_name` (kubo peerID) will still be populated:\n```json\n{\n  \"cluster_peer_id\": \"\",\n  \"cluster_peer_name\": \"12D3KooWAbCdEf...\"\n}\n```\n\n#### 3. Kubo API Directly (port 5001)\n\nIf you have direct access to the device, you can query kubo's identity endpoint:\n\n```bash\ncurl -X POST http://127.0.0.1:5001/api/v0/id\n```\n\nResponse:\n```json\n{\n  \"ID\": \"12D3KooWAbCdEf...\",\n  \"PublicKey\": \"base64encodedkey...\",\n  \"Addresses\": [],\n  \"AgentVersion\": \"0.0.1\",\n  \"ProtocolVersion\": \"fx_exchange/0.0.1\",\n  \"Protocols\": [\"fx_exchange\"]\n}\n```\n\n- `ID` — The kubo peerID.\n\n#### 4. mDNS Service Discovery\n\nBlox devices advertise themselves on the local network via mDNS (multicast DNS), allowing mobile apps and other clients to discover devices without knowing their IP addresses.\n\n**Service type:** `_fulatower._tcp`\n\n**Instance naming:** Each device registers with a unique instance name derived from its kubo peerID: `fulatower_\u003clast 5 chars of peerID\u003e`. For example, a device with kubo peerID `12D3KooWAbCdEfGh12345` registers as `fulatower_12345`. If the peerID is not yet available (e.g. first boot before configuration), the device registers as `fulatower_NEW`. This ensures multiple blox devices on the same LAN are all discoverable without overwriting each other.\n\n**TXT records:**\n\n| TXT Key | Value | Description |\n|---------|-------|-------------|\n| `bloxPeerIdString` | `12D3KooWAbCdEf...` | Kubo peerID |\n| `ipfsClusterID` | `12D3KooWXyZaBc...` | ipfs-cluster peerID |\n| `poolName` | `my-pool` | Pool name from config |\n| `authorizer` | `...` | Authorizer address |\n| `hardwareID` | `a1b2c3...` | Device hardware ID |\n\nIf the config file is missing or identity derivation fails, `bloxPeerIdString` falls back to reading from kubo's config file via `GetKuboPeerID()`. Fields default to `\"NA\"` when unavailable.\n\n**Discovering devices from the command line:**\n\nUsing `dns-sd` (macOS):\n```bash\ndns-sd -B _fulatower._tcp local.\n```\n\nExample output:\n```\nBrowsing for _fulatower._tcp.local.\nDATE: ---Mon 17 Feb 2026---\nTimestamp     A/R  Flags  if  Domain       Service Type         Instance Name\n10:23:45.123  Add      2   4  local.       _fulatower._tcp.     fulatower_12345\n10:23:45.456  Add      2   4  local.       _fulatower._tcp.     fulatower_67890\n```\n\nTo get full details (IP, port, TXT records) for a specific device:\n```bash\ndns-sd -L \"fulatower_12345\" _fulatower._tcp local.\n```\n\nExample output:\n```\nLookup fulatower_12345._fulatower._tcp.local.\nDATE: ---Mon 17 Feb 2026---\nfulatower_12345._fulatower._tcp.local. can be reached at blox-device.local.:40001\n bloxPeerIdString=12D3KooWAbCdEfGh12345\n ipfsClusterID=12D3KooWXyZaBcDe67890\n poolName=my-pool\n authorizer=...\n hardwareID=a1b2c3d4e5\n```\n\nUsing `avahi-browse` (Linux):\n```bash\navahi-browse -r _fulatower._tcp\n```\n\nExample output:\n```\n+ eth0 IPv4 fulatower_12345              _fulatower._tcp      local\n= eth0 IPv4 fulatower_12345              _fulatower._tcp      local\n   hostname = [blox-device.local]\n   address = [192.168.1.100]\n   port = [40001]\n   txt = [\"bloxPeerIdString=12D3KooWAbCdEfGh12345\" \"ipfsClusterID=12D3KooWXyZaBcDe67890\" \"poolName=my-pool\" \"authorizer=...\" \"hardwareID=a1b2c3d4e5\"]\n+ eth0 IPv4 fulatower_67890              _fulatower._tcp      local\n= eth0 IPv4 fulatower_67890              _fulatower._tcp      local\n   hostname = [blox-device-2.local]\n   address = [192.168.1.101]\n   port = [40001]\n   txt = [\"bloxPeerIdString=12D3KooWZzYyXxWw67890\" \"ipfsClusterID=12D3KooWQqRrSsTt11111\" \"poolName=my-pool\" \"authorizer=...\" \"hardwareID=f6g7h8i9j0\"]\n```\n\n**Programmatic discovery (Go):**\n\n```go\nimport \"github.com/grandcat/zeroconf\"\n\nresolver, _ := zeroconf.NewResolver(nil)\nentries := make(chan *zeroconf.ServiceEntry)\n\ngo func() {\n    for entry := range entries {\n        fmt.Printf(\"Found: %s at %s:%d\\n\", entry.Instance, entry.AddrIPv4, entry.Port)\n        for _, txt := range entry.Text {\n            fmt.Printf(\"  %s\\n\", txt)\n        }\n    }\n}()\n\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\nresolver.Browse(ctx, \"_fulatower._tcp\", \"local.\", entries)\n\u003c-ctx.Done()\n```\n\n**Multi-device coexistence:** When multiple blox devices are on the same network, each registers with its own unique instance name. A browsing client will see all devices and can identify them by their TXT records (peerID, hardwareID, pool name). The service type `_fulatower._tcp` remains the same across all devices for compatibility — only the instance name varies.\n\n#### 5. Go Code (`wifi.GetKuboPeerID()`)\n\nFor internal Go code that needs the kubo peerID, use the standalone utility function:\n\n```go\nimport wifi \"github.com/functionland/go-fula/wap/pkg/wifi\"\n\nkuboPeerID, err := wifi.GetKuboPeerID()\nif err != nil {\n    // both kubo API and config file are unavailable\n}\n```\n\nThis function tries two sources in order:\n1. Call kubo API at `POST http://127.0.0.1:5001/api/v0/id` and parse the `ID` field\n2. Read `/internal/ipfs_data/config` and parse `Identity.PeerID`\n\nIt does not depend on ipfs-cluster being installed.\n\n## WAP HTTP Server Reference\n\nThe WAP (Wireless Access Point) server runs on port `3500` (default bind: `10.42.0.1:3500`, fallback `127.0.0.1:3500`). It exposes HTTP endpoints used by BLE commands, mobile apps, and local services.\n\n### GET /properties\n\nReturns device properties, storage info, container status, and peer IDs.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/properties\n```\n\nResponse:\n```json\n{\n  \"name\": \"fula_go\",\n  \"kubo_peer_id\": \"12D3KooWAbCdEf...\",\n  \"ipfs_cluster_peer_id\": \"12D3KooWXyZaBc...\",\n  \"hardwareID\": \"a1b2c3...\",\n  \"bloxFreeSpace\": {\n    \"device_count\": 1,\n    \"size\": 500000000000,\n    \"used\": 120000000000,\n    \"avail\": 380000000000,\n    \"used_percentage\": 24\n  },\n  \"containerInfo_fula\": {\n    \"image\": \"functionland/go-fula:release\",\n    \"version\": \"abc123\",\n    \"id\": \"d1c53fff7db3...\",\n    \"labels\": {},\n    \"created\": \"2026-02-17T10:00:00Z\",\n    \"repo_digests\": [\"functionland/go-fula@sha256:...\"]\n  },\n  \"containerInfo_fxsupport\": { \"image\": \"...\", \"...\" : \"...\" },\n  \"containerInfo_node\": { \"image\": \"...\", \"...\" : \"...\" },\n  \"ota_version\": \"1.2.3\",\n  \"restartNeeded\": \"false\"\n}\n```\n\n### GET /readiness\n\nReturns system readiness status and the kubo peer ID.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/readiness\n```\n\nResponse:\n```json\n{\n  \"name\": \"fula_go\",\n  \"kubo_peer_id\": \"12D3KooWAbCdEf...\"\n}\n```\n\n### GET /wifi/list\n\nScans and returns available WiFi networks (up to 6, strongest first, duplicates removed).\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/wifi/list\n```\n\nResponse:\n```json\n[\n  { \"essid\": \"MyNetwork\", \"ssid\": \"MyNetwork\", \"rssi\": -45 },\n  { \"essid\": \"Neighbor\", \"ssid\": \"Neighbor\", \"rssi\": -72 }\n]\n```\n\n### GET /wifi/status\n\nChecks whether WiFi is currently connected.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/wifi/status\n```\n\nResponse:\n```json\n{ \"status\": true }\n```\n\n### POST /wifi/connect\n\nConnects to a WiFi network. Deletes any existing connection with the same SSID, creates a new one with autoconnect and priority 20, and removes the FxBlox hotspot on success.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/wifi/connect \\\n  -d \"ssid=MyNetwork\u0026password=secret123\u0026countryCode=US\"\n```\n\nParameters (form-encoded):\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `ssid` | yes | Network SSID |\n| `password` | yes | Network password |\n| `countryCode` | no | ISO country code (default from config) |\n\nResponse: `\"Wifi connected!\"`\n\n### GET /ap/enable\n\nEnables the WiFi hotspot/access point.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/ap/enable\n```\n\nResponse:\n```json\n{ \"status\": \"enabled\" }\n```\n\n### GET /ap/disable\n\nDisables the WiFi hotspot/access point.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/ap/disable\n```\n\nResponse:\n```json\n{ \"status\": \"disable\" }\n```\n\n### POST /partition\n\nCreates a partition flag file to trigger disk partitioning.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/partition\n```\n\nResponse:\n```json\n{ \"status\": true, \"message\": \"File created\" }\n```\n\n### POST /delete-fula-config\n\nDeletes the Fula configuration file (`/internal/config.yaml`).\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/delete-fula-config\n```\n\nResponse:\n```json\n{ \"status\": true, \"message\": \"Config file deleted successfully.\" }\n```\n\n### POST /peer/exchange\n\nInitializes the blox identity from the mobile client's seed, then returns the **kubo peerID** that the mobile app should use to connect. The mobile app communicates through kubo's libp2p host, which internally forwards requests to go-fula.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/peer/exchange \\\n  -d \"peer_id=12D3KooWClient...\u0026seed=myseed123\"\n```\n\nParameters (form-encoded):\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `peer_id` | yes | Client's libp2p peer ID |\n| `seed` | yes | Seed value for key generation |\n\nResponse:\n```json\n{ \"peer_id\": \"12D3KooWKuboPeerID...\" }\n```\n\nThe `peer_id` in the response is the kubo peerID (obtained from kubo API, falling back to kubo config file). This is the peerID the mobile app uses for all subsequent libp2p connections to the device.\n\n### POST /peer/generate-identity\n\nGenerates a deterministic libp2p identity (Ed25519 key pair) from a seed.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/peer/generate-identity \\\n  -d \"seed=myseed123\"\n```\n\nParameters (form-encoded):\n\n| Field | Required | Description |\n|-------|----------|-------------|\n| `seed` | yes | Seed value for identity generation |\n\nResponse:\n```json\n{\n  \"peer_id\": \"12D3KooWAbCdEf...\",\n  \"seed\": \"base64-encoded-private-key\"\n}\n```\n\n### POST /pools/join\n\nJoins a storage/compute pool by writing the pool ID to `config.yaml`.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/pools/join \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"poolID\": \"my-pool-1\"}'\n```\n\nResponse:\n```json\n{ \"status\": \"joined\", \"poolID\": \"my-pool-1\" }\n```\n\n### POST /pools/leave\n\nLeaves a storage/compute pool.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/pools/leave \\\n  -d \"poolID=my-pool-1\"\n```\n\nResponse:\n```json\n{ \"status\": \"left\", \"poolID\": \"my-pool-1\" }\n```\n\n### POST /pools/cancel\n\nCancels a pending pool join.\n\n```bash\ncurl -X POST http://\u003cdevice-ip\u003e:3500/pools/cancel \\\n  -d \"poolID=my-pool-1\"\n```\n\nResponse:\n```json\n{ \"status\": \"cancelled\", \"poolID\": \"my-pool-1\" }\n```\n\n### GET /chain/status\n\nReturns blockchain sync status (stub implementation).\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/chain/status\n```\n\nResponse:\n```json\n{ \"isSynced\": true, \"syncProgress\": 100 }\n```\n\n### GET /account/id\n\nReturns the account ID from the device secrets.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/account/id\n```\n\nResponse:\n```json\n{ \"accountId\": \"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY\" }\n```\n\n### GET /account/seed\n\nReturns the account seed from the device secrets.\n\n```bash\ncurl http://\u003cdevice-ip\u003e:3500/account/seed\n```\n\nResponse:\n```json\n{ \"accountSeed\": \"bottom drive obey lake...\" }\n```\n\n## BLE (Bluetooth Low Energy) Server Reference\n\nThe BLE server runs on the fxsupport container and provides a GATT service for mobile app communication during device setup and management.\n\n### Service Details\n\n| Property | Value |\n|----------|-------|\n| Service UUID | `00000001-710e-4a5b-8d75-3e5b444bc3cf` |\n| Command Characteristic UUID | `00000003-710e-4a5b-8d75-3e5b444bc3cf` |\n| Broadcast Characteristic UUID | `00000002-710e-4a5b-8d75-3e5b444bc3cf` |\n| Advertised Name | `fulatower_\u003clast 5 chars of kubo peerID\u003e` or `fulatower_NEW` |\n\nThe advertised name uses the kubo peerID to match the mDNS instance name. The kubo peerID is obtained from the kubo API (`POST http://127.0.0.1:5001/api/v0/id`), falling back to the kubo config file (`/home/pi/.internal/ipfs_data/config` → `Identity.PeerID`). If neither source is available (e.g. first boot before setup), the name defaults to `fulatower_NEW`.\n\n### Response Chunking\n\nResponses larger than 512 bytes are split into chunks:\n\n**Header** (sent first):\n```json\n{ \"type\": \"ble_header\", \"total_length\": 2048, \"chunks\": 5 }\n```\n\n**Data chunks** (sent sequentially with 100ms delay):\n```json\n{ \"type\": \"ble_chunk\", \"index\": 1, \"data\": \"...\" }\n```\n\nThe client reassembles by concatenating all `data` fields in order.\n\n### BLE Commands\n\nCommands are sent as UTF-8 strings via BLE write to the Command Characteristic. Responses are sent back via notifications or indications. Long-running commands send periodic `\"Processing \u003ccommand\u003e\"` updates every 2 seconds.\n\n#### properties\n\nReturns device properties (proxies to `GET /properties`).\n\nWrite: `properties`\n\nResponse:\n```json\n{\n  \"bloxFreeSpace\": { \"device_count\": 1, \"size\": 500000000000, \"used\": 120000000000, \"avail\": 380000000000, \"used_percentage\": 24 },\n  \"containerInfo_fula\": {},\n  \"containerInfo_fxsupport\": {},\n  \"containerInfo_node\": {},\n  \"hardwareID\": \"a1b2c3...\",\n  \"ota_version\": \"1.2.3\",\n  \"restartNeeded\": \"false\",\n  \"kubo_peer_id\": \"12D3KooWAbCdEf...\",\n  \"ipfs_cluster_peer_id\": \"12D3KooWXyZaBc...\"\n}\n```\n\n#### readiness\n\nReturns system readiness (proxies to `GET /readiness`).\n\nWrite: `readiness`\n\n#### wifi/list\n\nScans available WiFi networks (proxies to `GET /wifi/list`).\n\nWrite: `wifi/list`\n\nResponse:\n```json\n[\n  { \"essid\": \"MyNetwork\", \"ssid\": \"MyNetwork\", \"rssi\": -45 }\n]\n```\n\n#### wifi/status\n\nChecks WiFi connection (proxies to `GET /wifi/status`).\n\nWrite: `wifi/status`\n\nResponse:\n```json\n{ \"status\": true }\n```\n\n#### wifi/connect\n\nConnects to WiFi (proxies to `POST /wifi/connect`).\n\nWrite: `wifi/connect \u003cssid\u003e \u003cpassword\u003e [country_code]`\n\nExamples:\n- `wifi/connect MyNetwork secret123`\n- `wifi/connect MyNetwork secret123 US`\n\n#### peer/exchange\n\nInitializes blox identity and returns the kubo peerID for mobile connection (proxies to `POST /peer/exchange`).\n\nWrite: `peer/exchange \u003cpeer_id\u003e \u003cseed\u003e`\n\nExample: `peer/exchange 12D3KooWClient... myseed123`\n\nResponse:\n```json\n{ \"peer_id\": \"12D3KooWKuboPeerID...\" }\n```\n\n#### peer/generate-identity\n\nGenerates identity from seed (proxies to `POST /peer/generate-identity`).\n\nWrite: `peer/generate-identity \u003cseed\u003e`\n\nExample: `peer/generate-identity myseed123`\n\nResponse:\n```json\n{ \"peer_id\": \"12D3KooWAbCdEf...\", \"seed\": \"base64-private-key\" }\n```\n\n#### ap/enable\n\nEnables WiFi hotspot (proxies to `GET /ap/enable`).\n\nWrite: `ap/enable`\n\n#### partition\n\nTriggers disk partitioning (proxies to `POST /partition`).\n\nWrite: `partition`\n\n#### reset\n\nFactory reset with 20-second cancellation window. During the window, the LED turns red. After 20 seconds: deletes all WiFi connections, removes `config.yaml`, and reboots.\n\nWrite: `reset`\n\n#### cancel\n\nCancels an in-progress reset (must be sent within 20 seconds of `reset`).\n\nWrite: `cancel`\n\n#### stopleds\n\nStops all LED activity.\n\nWrite: `stopleds`\n\n#### removedockercpblock\n\nRemoves the Docker copy block flag (`stop_docker_copy.txt`), allowing OTA file extraction to resume.\n\nWrite: `removedockercpblock`\n\n#### logs\n\nRetrieves container or system logs.\n\nWrite: `logs {\"container_name\":\"fula_go\",\"tail_count\":\"50\"}`\n\nResponse: Log text from the specified container.\n\n#### wireguard/start\n\nStarts the WireGuard support tunnel. Registers with the support server if not already registered, then brings up the tunnel. This is a long-running command (threaded).\n\nWrite: `wireguard/start`\n\nResponse:\n```json\n{ \"installed\": true, \"registered\": true, \"active\": true, \"endpoint\": \"support.fx.land:51820\", \"assigned_ip\": \"10.0.0.5/32\", \"peer_id_registered\": \"12D3KooW...\" }\n```\n\n#### wireguard/stop\n\nStops the WireGuard support tunnel.\n\nWrite: `wireguard/stop`\n\nResponse:\n```json\n{ \"status\": \"stopped\" }\n```\n\n#### wireguard/status\n\nReturns the current WireGuard support tunnel status without changing state.\n\nWrite: `wireguard/status`\n\nResponse:\n```json\n{ \"installed\": true, \"registered\": true, \"active\": false, \"endpoint\": \"support.fx.land:51820\", \"assigned_ip\": \"10.0.0.5/32\", \"peer_id_registered\": \"12D3KooW...\" }\n```\n\n#### forceupdate\n\nPulls the latest Docker images from Docker Hub (runs `fula.sh update`). This is a long-running command (threaded, up to 10-minute timeout). The LED turns purple during the update and yellow for 10 seconds after completion. Does not restart services — send `restart_fula` via `logs {\"exec\":[\"restart_fula\"]}` separately if needed.\n\nWrite: `forceupdate`\n\nResponse (success):\n```json\n{ \"status\": \"updated\", \"msg\": \"Docker images pulled successfully\" }\n```\n\nResponse (error):\n```json\n{ \"status\": \"error\", \"msg\": \"stderr output (last 500 chars)\" }\n```\n\nResponse (timeout):\n```json\n{ \"status\": \"timeout\", \"msg\": \"Update timed out after 10 minutes\" }\n```\n\nAlso available via the logs exec path:\n\nWrite: `logs {\"exec\":[\"force_update\"]}`\n\n### Error Responses\n\nOn any error, BLE commands return:\n```json\n{ \"error\": \"error message\", \"status\": \"error\" }\n```\n\n## Run\n\n```go\ngit clone https://github.com/functionland/go-fula.git\n\ncd go-fula\n\ngo run ./cmd/blox --authorizer [PeerID of client allowed to write to the backend] --logLevel=[info/debug] --ipniPublisherDisabled=[true/false]\n```\n\nexample on `Windows` is:\n```\ngo run ./cmd/blox --authorizer=12D3KooWMMt4C3FKui14ai4r1VWwznRw6DoP5DcgTfzx2D5VZoWx --config=C:\\Users\\me\\.fula\\blox2\\config.yaml --storeDir=C:\\Users\\me\\.fula\\blox2 --secretsPath=C:\\Users\\me\\.fula\\blox2\\secret_seed.txt --logLevel=debug\n```\n\nThe default generated config goes to a YAML file in home directory, under `/.fula/blox/config.yaml`\n\n## Build\n\n```go\ngoreleaser --rm-dist --snapshot\n```\n\n## Build for the iOS platform (using gomobile)\nThis process includes some iOS specific preparation.\n```sh\nmake fula-xcframework\n```\n\n\n## License\n\n[MIT](LICENSE)\n\n## Related Publications and News\n\n- https://www.crunchbase.com/organization/functionland/signals_and_news\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionland%2Fgo-fula","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffunctionland%2Fgo-fula","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffunctionland%2Fgo-fula/lists"}