mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
Compare commits
17 Commits
v0.17.0-be
...
da489fab24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da489fab24 | ||
|
|
e4b8076351 | ||
|
|
70c72f0f9a | ||
|
|
c45b760abc | ||
|
|
9ba26b7599 | ||
|
|
01127ee9d6 | ||
|
|
77f5886110 | ||
|
|
6b6338291b | ||
|
|
2c11b7c7de | ||
|
|
a0fa043207 | ||
|
|
143701820a | ||
|
|
aff875c62f | ||
|
|
e52c25d87b | ||
|
|
9fec6883f6 | ||
|
|
f4df9e935d | ||
|
|
f326f41599 | ||
|
|
f6b8e7e5a2 |
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -62,8 +62,6 @@ jobs:
|
|||||||
type=semver,pattern={{major}}.{{minor}}.{{patch}},prefix=v,enable=${{ needs.determine-release-type.outputs.release_type == 'release' }}
|
type=semver,pattern={{major}}.{{minor}}.{{patch}},prefix=v,enable=${{ needs.determine-release-type.outputs.release_type == 'release' }}
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ needs.determine-release-type.outputs.release_type == 'release' }}
|
latest=${{ needs.determine-release-type.outputs.release_type == 'release' }}
|
||||||
cache-from: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache
|
|
||||||
cache-to: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache,mode=max
|
|
||||||
|
|
||||||
- name: Build and push images
|
- name: Build and push images
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -76,6 +74,8 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
build-args: |
|
build-args: |
|
||||||
APP_VERSION=${{ needs.determine-release-type.outputs.tagname }}
|
APP_VERSION=${{ needs.determine-release-type.outputs.tagname }}
|
||||||
|
cache-from: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache
|
||||||
|
cache-to: type=registry,ref=ghcr.io/nicotsx/zerobyte:buildcache,mode=max
|
||||||
|
|
||||||
publish-release:
|
publish-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from "node:fs/promises";
|
import * as fs from "node:fs/promises";
|
||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
||||||
|
import { cryptoUtils } from "../../../utils/crypto";
|
||||||
import { toMessage } from "../../../utils/errors";
|
import { toMessage } from "../../../utils/errors";
|
||||||
import { logger } from "../../../utils/logger";
|
import { logger } from "../../../utils/logger";
|
||||||
import { getMountForPath } from "../../../utils/mountinfo";
|
import { getMountForPath } from "../../../utils/mountinfo";
|
||||||
@@ -33,10 +34,12 @@ const mount = async (config: BackendConfig, path: string) => {
|
|||||||
const run = async () => {
|
const run = async () => {
|
||||||
await fs.mkdir(path, { recursive: true });
|
await fs.mkdir(path, { recursive: true });
|
||||||
|
|
||||||
|
const password = await cryptoUtils.decrypt(config.password);
|
||||||
|
|
||||||
const source = `//${config.server}/${config.share}`;
|
const source = `//${config.server}/${config.share}`;
|
||||||
const options = [
|
const options = [
|
||||||
`user=${config.username}`,
|
`user=${config.username}`,
|
||||||
`pass=${config.password}`,
|
`pass=${password}`,
|
||||||
`vers=${config.vers}`,
|
`vers=${config.vers}`,
|
||||||
`port=${config.port}`,
|
`port=${config.port}`,
|
||||||
"uid=1000",
|
"uid=1000",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
|
|||||||
import * as os from "node:os";
|
import * as os from "node:os";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
||||||
|
import { cryptoUtils } from "../../../utils/crypto";
|
||||||
import { toMessage } from "../../../utils/errors";
|
import { toMessage } from "../../../utils/errors";
|
||||||
import { logger } from "../../../utils/logger";
|
import { logger } from "../../../utils/logger";
|
||||||
import { getMountForPath } from "../../../utils/mountinfo";
|
import { getMountForPath } from "../../../utils/mountinfo";
|
||||||
@@ -49,8 +50,9 @@ const mount = async (config: BackendConfig, path: string) => {
|
|||||||
: ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"];
|
: ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"];
|
||||||
|
|
||||||
if (config.username && config.password) {
|
if (config.username && config.password) {
|
||||||
|
const password = await cryptoUtils.decrypt(config.password);
|
||||||
const secretsFile = "/etc/davfs2/secrets";
|
const secretsFile = "/etc/davfs2/secrets";
|
||||||
const secretsContent = `${source} ${config.username} ${config.password}\n`;
|
const secretsContent = `${source} ${config.username} ${password}\n`;
|
||||||
await fs.appendFile(secretsFile, secretsContent, { mode: 0o600 });
|
await fs.appendFile(secretsFile, secretsContent, { mode: 0o600 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import slugify from "slugify";
|
|||||||
import { getCapabilities, parseDockerHost } from "../../core/capabilities";
|
import { getCapabilities, parseDockerHost } from "../../core/capabilities";
|
||||||
import { db } from "../../db/db";
|
import { db } from "../../db/db";
|
||||||
import { volumesTable } from "../../db/schema";
|
import { volumesTable } from "../../db/schema";
|
||||||
|
import { cryptoUtils } from "../../utils/crypto";
|
||||||
import { toMessage } from "../../utils/errors";
|
import { toMessage } from "../../utils/errors";
|
||||||
import { generateShortId } from "../../utils/id";
|
import { generateShortId } from "../../utils/id";
|
||||||
import { getStatFs, type StatFs } from "../../utils/mountinfo";
|
import { getStatFs, type StatFs } from "../../utils/mountinfo";
|
||||||
@@ -19,6 +20,23 @@ import { logger } from "../../utils/logger";
|
|||||||
import { serverEvents } from "../../core/events";
|
import { serverEvents } from "../../core/events";
|
||||||
import type { BackendConfig } from "~/schemas/volumes";
|
import type { BackendConfig } from "~/schemas/volumes";
|
||||||
|
|
||||||
|
async function encryptSensitiveFields(config: BackendConfig): Promise<BackendConfig> {
|
||||||
|
switch (config.backend) {
|
||||||
|
case "smb":
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
password: await cryptoUtils.encrypt(config.password),
|
||||||
|
};
|
||||||
|
case "webdav":
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
password: config.password ? await cryptoUtils.encrypt(config.password) : undefined,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const listVolumes = async () => {
|
const listVolumes = async () => {
|
||||||
const volumes = await db.query.volumesTable.findMany({});
|
const volumes = await db.query.volumesTable.findMany({});
|
||||||
|
|
||||||
@@ -37,13 +55,14 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shortId = generateShortId();
|
const shortId = generateShortId();
|
||||||
|
const encryptedConfig = await encryptSensitiveFields(backendConfig);
|
||||||
|
|
||||||
const [created] = await db
|
const [created] = await db
|
||||||
.insert(volumesTable)
|
.insert(volumesTable)
|
||||||
.values({
|
.values({
|
||||||
shortId,
|
shortId,
|
||||||
name: slug,
|
name: slug,
|
||||||
config: backendConfig,
|
config: encryptedConfig,
|
||||||
type: backendConfig.backend,
|
type: backendConfig.backend,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
@@ -175,11 +194,13 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
|
|||||||
await backend.unmount();
|
await backend.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const encryptedConfig = volumeData.config ? await encryptSensitiveFields(volumeData.config) : undefined;
|
||||||
|
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.update(volumesTable)
|
.update(volumesTable)
|
||||||
.set({
|
.set({
|
||||||
name: newName,
|
name: newName,
|
||||||
config: volumeData.config,
|
config: encryptedConfig,
|
||||||
type: volumeData.config?.backend,
|
type: volumeData.config?.backend,
|
||||||
autoRemount: volumeData.autoRemount,
|
autoRemount: volumeData.autoRemount,
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
|
|||||||
@@ -6,18 +6,26 @@ const keyLength = 32;
|
|||||||
const encryptionPrefix = "encv1";
|
const encryptionPrefix = "encv1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a string, encrypts it using a randomly generated salt
|
* Checks if a given string is encrypted by looking for the encryption prefix.
|
||||||
|
*/
|
||||||
|
const isEncrypted = (val?: string): boolean => {
|
||||||
|
return typeof val === "string" && val.startsWith(encryptionPrefix);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a string, encrypts it using a randomly generated salt.
|
||||||
|
* Returns the input unchanged if it's empty or already encrypted.
|
||||||
*/
|
*/
|
||||||
const encrypt = async (data: string) => {
|
const encrypt = async (data: string) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.startsWith(encryptionPrefix)) {
|
if (isEncrypted(data)) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const secret = (await Bun.file(RESTIC_PASS_FILE).text()).trim();
|
const secret = await Bun.file(RESTIC_PASS_FILE).text();
|
||||||
|
|
||||||
const salt = crypto.randomBytes(16);
|
const salt = crypto.randomBytes(16);
|
||||||
const key = crypto.pbkdf2Sync(secret, salt, 100000, keyLength, "sha256");
|
const key = crypto.pbkdf2Sync(secret, salt, 100000, keyLength, "sha256");
|
||||||
@@ -31,10 +39,15 @@ const encrypt = async (data: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an encrypted string, decrypts it using the salt stored in the string
|
* Given an encrypted string, decrypts it using the salt stored in the string.
|
||||||
|
* Returns the input unchanged if it's not encrypted (for backward compatibility).
|
||||||
*/
|
*/
|
||||||
const decrypt = async (encryptedData: string) => {
|
const decrypt = async (encryptedData: string) => {
|
||||||
const secret = await Bun.file(RESTIC_PASS_FILE).text();
|
if (!isEncrypted(encryptedData)) {
|
||||||
|
return encryptedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = (await Bun.file(RESTIC_PASS_FILE).text()).trim();
|
||||||
|
|
||||||
const parts = encryptedData.split(":").slice(1); // Remove prefix
|
const parts = encryptedData.split(":").slice(1); // Remove prefix
|
||||||
const saltHex = parts.shift() as string;
|
const saltHex = parts.shift() as string;
|
||||||
@@ -58,4 +71,5 @@ const decrypt = async (encryptedData: string) => {
|
|||||||
export const cryptoUtils = {
|
export const cryptoUtils = {
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt,
|
decrypt,
|
||||||
|
isEncrypted,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user