Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,20 @@
"graphql": "^16.6.0",
"image-size": "^0.8.1",
"inquirer": "^6.2.1",
"ipfs-http-client": "^60.0.1",
"is-ipfs": "^8.0.4",
"js-yaml": "^4.1.0",
"listr": "^0.14.3",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mime-types": "^2.1.24",
"moment": "^2.27.0",
"multiformats": "^13.3.2",
"prettier": "^2.1.2",
"request": "^2.88.2",
"rimraf": "^3.0.2",
"semver": "^7.6.0",
"sinon": "^20.0.0",
"source-map-support": "^0.5.19",
"tar-fs": "^2.0.0",
"timestring": "^5.0.1",
Expand Down Expand Up @@ -87,7 +92,7 @@
"@typescript-eslint/parser": "^3.5.0",
"chai": "^5.1.0",
"eslint": "^7.3.1",
"mocha": "^10.2.0",
"mocha": "^11.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.6.3"
},
Expand Down
44 changes: 44 additions & 0 deletions scripts/run-ipfs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail

NAME="ipfs-node"
IMAGE="ipfs/kubo:latest"
DATA_DIR="${HOME}/ipfs-data"
API_PORT=5001 # HTTP API
GATEWAY_PORT=8080 # Gateway
SWARM_PORT=4001 # Libp2p swarm (TCP)

echo "▶︎ Starting IPFS daemon in Docker…"

# Create data directory if it doesn't exist
mkdir -p "${DATA_DIR}"

# Stop & remove any existing container with the same name
if docker ps -a --format '{{.Names}}' | grep -q "^${NAME}$"; then
echo "⏹ Removing existing container '${NAME}'…"
docker rm -f "${NAME}" >/dev/null
fi

# Launch the container detached
docker run -d --name "${NAME}" \
-v "${DATA_DIR}:/data/ipfs" \
-p "${SWARM_PORT}:4001" \
-p "${API_PORT}:5001" \
-p "${GATEWAY_PORT}:8080" \
"${IMAGE}" daemon >/dev/null

echo "⏳ Waiting for API to respond on :${API_PORT}…"
for i in {1..20}; do
if curl -fsS "http://127.0.0.1:${API_PORT}/api/v0/version" >/dev/null 2>&1; then
echo "✅ IPFS is up!"
echo "API: http://127.0.0.1:${API_PORT}"
echo "Gateway: http://127.0.0.1:${GATEWAY_PORT}"
exit 0
fi
sleep 1
done

echo "❌ IPFS API did not respond in time." >&2
docker logs --tail=100 "${NAME}" >&2
exit 1

2 changes: 2 additions & 0 deletions src/commands/build/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export async function buildHandler({
upload_to: uploadTo,
skip_save: skipSave,
skip_upload,
sign_release: signReleaseFlag,
require_git_data: requireGitData,
delete_old_pins: deleteOldPins,
all_variants: allVariants,
Expand All @@ -39,6 +40,7 @@ export async function buildHandler({
userTimeout,
skipSave,
skipUpload,
signReleaseFlag: signReleaseFlag ?? false,
composeFileName,
requireGitData,
deleteOldPins,
Expand Down
7 changes: 6 additions & 1 deletion src/commands/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ export const build: CommandModule<CliGlobalOptions, BuildCommandOptions> = {
alias: "variant",
description: `Specify the package variants to build (only for packages that support it). Defined by comma-separated list of variant names. Example: "variant1,variant2"`,
type: "string"
}
},
sign_release: {
alias: "sign-release",
description: `Adds a signature.json file to the release directory. Requires to set ENV SIGNING_KEY to a hex string of the private key of the signing ECDSA_256 key`,
type: "boolean"
},
},

handler: async (args): Promise<void> => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/build/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface BuildCommandOptions extends CliGlobalOptions {
timeout?: string;
skip_save?: boolean;
skip_upload?: boolean;
sign_release?: boolean;
require_git_data?: boolean;
delete_old_pins?: boolean;
variants_dir_name?: string;
Expand Down
2 changes: 2 additions & 0 deletions src/commands/publish/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function publishHandler({
developer_address: developerAddress = process.env.DEVELOPER_ADDRESS,
timeout: userTimeout,
upload_to: uploadTo,
sign_release: signReleaseFlag,
github_release: githubRelease,
dappnode_team_preset: dappnode_team_preset,
require_git_data: requireGitData,
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function publishHandler({
composeFileName,
contentProvider,
uploadTo,
signReleaseFlag,
userTimeout,
requireGitData,
deleteOldPins,
Expand Down
7 changes: 6 additions & 1 deletion src/commands/publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ export const publish: CommandModule<CliGlobalOptions, PublishCommandOptions> = {
alias: "variant",
description: `Specify the package variants to build (only for packages that support it). Defined by comma-separated list of variant names. If not specified, all variants will be built. Example: "variant1,variant2"`,
type: "string"
}
},
sign_release: {
alias: "sign-release",
description: `Adds a signature.json file to the release directory. Requires to set ENV SIGNING_KEY to a hex string of the private key of the signing ECDSA_256 key`,
type: "boolean"
},
},

handler: async args => {
Expand Down
1 change: 1 addition & 0 deletions src/commands/publish/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface PublishCommandOptions extends CliGlobalOptions {
eth_provider: string;
content_provider: string;
upload_to: UploadTo;
sign_release: boolean;
developer_address?: string;
timeout?: string;
github_release?: boolean;
Expand Down
10 changes: 5 additions & 5 deletions src/releaseUploader/errors.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
export class ReleaseUploaderConnectionError extends Error {
ipfsProvider: string;
url: string;
reason: string;
help?: string;
constructor({
ipfsProvider,
url,
reason,
help
}: {
ipfsProvider: string;
url: string;
reason: string;
help?: string;
}) {
super(`Can't connect to ${ipfsProvider}: ${reason}`);
this.ipfsProvider = ipfsProvider;
super(`Can't connect to ${url}: ${reason}`);
this.url = url;
this.reason = reason;
this.help = help;
}
Expand Down
5 changes: 5 additions & 0 deletions src/releaseUploader/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ export interface IReleaseUploader {
* Resolves if connection is okay. Rejects otherwise
*/
testConnection(): Promise<void>;

/**
* Returns the IPFS API URL or returns an error if not available
*/
ipfsApiUrl(): string;
}
5 changes: 2 additions & 3 deletions src/releaseUploader/ipfsNode/addFromFs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import got from "got";
import { normalizeIpfsProvider } from "./ipfsProvider.js";

Check warning on line 2 in src/releaseUploader/ipfsNode/addFromFs.ts

View workflow job for this annotation

GitHub Actions / test (20)

'normalizeIpfsProvider' is defined but never used
import { getFormDataFileUpload } from "../utils/formDataFileUpload.js";

/**
Expand All @@ -11,17 +11,16 @@
*/
export async function ipfsAddFromFs(
dirOrFilePath: string,
ipfsProvider: string,
ipfsApiUrl: string,
onProgress?: (percent: number) => void
): Promise<string> {
// Create form and append all files recursively
const form = getFormDataFileUpload(dirOrFilePath);

// Parse the ipfsProvider the a full base apiUrl
let lastPercent = -1;
const apiUrl = normalizeIpfsProvider(ipfsProvider);
const res = await got({
prefixUrl: apiUrl,
prefixUrl: ipfsApiUrl,
url: "api/v0/add",
method: "POST",
headers: form.getHeaders(),
Expand Down
13 changes: 9 additions & 4 deletions src/releaseUploader/ipfsNode/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { IReleaseUploader } from "../interface.js";
import { ipfsAddFromFs } from "./addFromFs.js";
import { normalizeIpfsProvider } from "./ipfsProvider.js";
import { verifyIpfsConnection } from "./verifyConnection.js";

export class ReleaseUploaderIpfsNode implements IReleaseUploader {
networkName = "IPFS node";
ipfsProvider: string;
apiUrl: string;

constructor({ ipfsProvider }: { ipfsProvider: string }) {
this.ipfsProvider = ipfsProvider;
this.apiUrl = normalizeIpfsProvider(ipfsProvider);
}

async addFromFs({
Expand All @@ -17,10 +18,14 @@ export class ReleaseUploaderIpfsNode implements IReleaseUploader {
dirPath: string;
onProgress?: (percent: number) => void;
}): Promise<string> {
return await ipfsAddFromFs(dirPath, this.ipfsProvider, onProgress);
return await ipfsAddFromFs(dirPath, this.apiUrl, onProgress);
}

async testConnection(): Promise<void> {
await verifyIpfsConnection(this.ipfsProvider);
await verifyIpfsConnection(this.apiUrl);
}

ipfsApiUrl(): string {
return this.apiUrl;
}
}
6 changes: 2 additions & 4 deletions src/releaseUploader/ipfsNode/ipfsVersion.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import got from "got";
import { normalizeIpfsProvider } from "./ipfsProvider.js";

interface IpfsApiVersionResponse {
Version: string; // "0.4.21",
Expand All @@ -14,12 +13,11 @@ interface IpfsApiVersionResponse {
* @param ipfsProvider "dappnode" | "http://localhost:5001"
*/
export async function ipfsVersion(
ipfsProvider: string
ipfsApiUrl: string
): Promise<IpfsApiVersionResponse> {
// Parse the ipfsProvider the a full base apiUrl
const apiUrl = normalizeIpfsProvider(ipfsProvider);
const res = await got<IpfsApiVersionResponse>({
prefixUrl: apiUrl,
prefixUrl: ipfsApiUrl,
url: "/api/v0/version",
method: "POST",
responseType: "json"
Expand Down
11 changes: 6 additions & 5 deletions src/releaseUploader/ipfsNode/verifyConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ import { ReleaseUploaderConnectionError } from "../errors.js";
* @param ipfsProvider
*/
export async function verifyIpfsConnection(
ipfsProvider: string
ipfsApiUrl: string
): Promise<void> {
try {
await ipfsVersion(ipfsProvider);
await ipfsVersion(ipfsApiUrl);
} catch (e) {
// On IPFS version 0.5 only POST methods are allowed
// Tolerate errors 405 to be more backwards compatible
if (e.message.includes("Method Not Allowed")) return;

if (e.code === "ENOTFOUND") {
throw new ReleaseUploaderConnectionError({
ipfsProvider,
url: ipfsApiUrl,
reason: "ENOTFOUND",
// .dappnode URLs are internal to DAppNode use
help:
ipfsProvider === "dappnode" ? "Check your VPN connection" : undefined
ipfsApiUrl.endsWith(".dappnode") ? "Check your VPN connection" : undefined
});
} else {
throw new ReleaseUploaderConnectionError({
ipfsProvider,
url: ipfsApiUrl,
reason: e.message
});
}
Expand Down
4 changes: 4 additions & 0 deletions src/releaseUploader/pinata/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@ export class ReleaseUploaderIpfsPinata implements IReleaseUploader {
throw e;
}
}

ipfsApiUrl(): string {
throw Error("Pinata does not expose IPFS API url");
}
}
4 changes: 4 additions & 0 deletions src/releaseUploader/swarmNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ export class ReleaseUploaderSwarmNode implements IReleaseUploader {
async testConnection(): Promise<void> {
// ### TODO
}

ipfsApiUrl(): string {
throw Error("Swarm does not support IPFS API")
}
}
2 changes: 1 addition & 1 deletion src/tasks/buildAndUpload/getVerifyConnectionTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function verifyConnection(
function handleConnectionError(e: Error): never {
if (e instanceof ReleaseUploaderConnectionError) {
throw new CliError(
`Can't connect to ${e.ipfsProvider}: ${e.reason}. ${e.help || ""}`
`Can't connect to ${e.url}: ${e.reason}. ${e.help || ""}`
);
} else {
throw e;
Expand Down
8 changes: 8 additions & 0 deletions src/tasks/buildAndUpload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { getBuildTasks } from "./getBuildTasks.js";
import { getUploadTasks } from "./getUploadTasks.js";
import { getDeleteOldPinsTask } from "./getDeleteOldPinsTask.js";
import { getSaveUploadResultsTask } from "./getSaveUploadResultsTask.js";
import { signReleaseTasks } from "./signRelease.js";

export function buildAndUpload({
contentProvider,
uploadTo,
userTimeout,
skipSave,
skipUpload,
signReleaseFlag,
requireGitData,
deleteOldPins,
composeFileName,
Expand Down Expand Up @@ -57,6 +59,12 @@ export function buildAndUpload({
composeFileName,
skipUpload
}),
...signReleaseTasks({
packagesToBuildProps,
releaseUploader,
skipUpload,
signReleaseFlag
}),
getDeleteOldPinsTask({
packagesToBuildProps,
deleteOldPins: !!deleteOldPins,
Expand Down
Loading
Loading