Skip to content

Commit

Permalink
Split data dir into public and private data
Browse files Browse the repository at this point in the history
  • Loading branch information
MaddyGuthridge committed Oct 1, 2024
1 parent d0ef981 commit f2bab9b
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 37 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

# Data directory
/data/
# Private data directory
/private_data/

# Server logs
*.log
Expand Down
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"cSpell.words": [
"Asciinema",
"firstrun",
"Minifolio",
"superstruct"
]
}
31 changes: 31 additions & 0 deletions docs/Files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# File locations in Minifolio

## Data directory

Determined using environment variable `DATA_REPO_PATH`.

Main portfolio data. Should be backed up using a `git` repo.

### `config.json`

Main site configuration.

## Private data directory

Determined using environment variable `PRIVATE_DATA_PATH`.

Contains private data, including credentials and authentication secrets.

### `config.local.json`

Contains the local configuration of the server, including credentials and token
info.

### `id_ed25519`, `id_ed25519.pub`

SSH key used by the server. These are used to perform git operations over SSH.

### `auth.secret`

Contains the authentication secret used by the server. This is used to validate
JWTs.
37 changes: 26 additions & 11 deletions src/lib/server/data/dataDir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@ export function getDataDir(): string {
return repoPath;
}

/**
* Returns the path to the private data directory, which contains data such as
* `config.local.json` and the server's SSH keys.
*/
export function getPrivateDataDir(): string {
const privateDataPath = process.env.PRIVATE_DATA_PATH;
if (!privateDataPath) {
throw new Error('PRIVATE_DATA_PATH environment variable is not set');
}
return privateDataPath;
}

/**
* Returns whether the data directory contains data that can conceivably
* be used by the portfolio website.
* be used by Minifolio.
*
* This should be used when setting up a git repo to ensure that its data is
* valid enough to be used by us.
*
* Currently, this checks for the existence of a config.json, but in the future
* I may force it to check more thoroughly for data validity.
Expand All @@ -23,19 +38,19 @@ export async function dataDirContainsData(): Promise<boolean> {
return await fileExists(path.join(repoPath, 'config.json'));
}

/** Returns whether the data directory is a git repo */
export async function dataDirUsesGit(): Promise<boolean> {
const repoPath = getDataDir();
const repo = simpleGit(repoPath);
return repo.checkIsRepo(CheckRepoActions.IS_REPO_ROOT);
}

/**
* Returns whether the data directory has been initialized.
* Returns whether the private data has been initialized.
*
* This checks for the existence of a config.local.json.
*/
export async function dataDirIsInit(): Promise<boolean> {
const repoPath = getDataDir();
export async function serverIsSetUp(): Promise<boolean> {
const repoPath = getPrivateDataDir();
return await fileExists(path.join(repoPath, 'config.local.json'));
}

/** Returns whether the data directory is backed by git */
export async function dataDirUsesGit(): Promise<boolean> {
const repoPath = getDataDir();
const repo = simpleGit(repoPath);
return repo.checkIsRepo(CheckRepoActions.IS_REPO_ROOT);
}
15 changes: 7 additions & 8 deletions src/lib/server/data/localConfig.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import { readFile, writeFile } from 'fs/promises';
import { nullable, number, object, record, string, validate, type Infer } from 'superstruct';
import { getDataDir } from './dataDir';
import { number, object, record, string, validate, type Infer } from 'superstruct';
import { getPrivateDataDir } from './dataDir';

/** Path to config.local.json */
const CONFIG_LOCAL_JSON = () => `${getDataDir()}/config.local.json`;
const CONFIG_LOCAL_JSON = () => `${getPrivateDataDir()}/config.local.json`;

/**
* Validator for config.local.json file
*/
export const ConfigLocalJsonStruct = object({
/**
* Authentication data.
*
* If null, then authentication is disabled, and logging in is not allowed.
* Authentication data, as a record between user IDs and their
* authentication info.
*/
auth: nullable(object({
/** Username of account */
auth: record(string(), object({
/** The user's username, used for logging in */
username: string(),
/** Information about the user's password */
password: object({
Expand Down
32 changes: 16 additions & 16 deletions src/lib/server/data/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,11 @@ import { version } from '$app/environment';
import { getConfig, setConfig } from '../config';
import { getDataDir } from '../dataDir';
import { getLocalConfig, setLocalConfig } from '../localConfig';
import migrateV010 from './v0.1.0';
import migrateV020 from './v0.2.0';
import migrateV030 from './v0.3.0';
import migrateFromV010 from './v0.1.0';
import migrateFromV020 from './v0.2.0';
import migrateFromV030 from './v0.3.0';
import semver from 'semver';

export type MigrationFunction = (dataDir: string) => Promise<void>;

/** Lookup table of migrations */
const migrations: Record<string, MigrationFunction> = {
'~0.1.0': migrateV010,
'~0.2.0': migrateV020,
'~0.3.0': migrateV030,
// No major changes to data format this release
'~0.4.0': updateConfigVersions,
// Pre-empt future releases
'~0.5.0': updateConfigVersions,
};

/** Update config versions (only for minor, non-breaking changes to config.json) */
export async function updateConfigVersions(_dataDir: string) {
const config = await getConfig();
Expand All @@ -30,6 +17,19 @@ export async function updateConfigVersions(_dataDir: string) {
await setLocalConfig(configLocal);
}

export type MigrationFunction = (dataDir: string) => Promise<void>;

/** Lookup table of migrations */
const migrations: Record<string, MigrationFunction> = {
'~0.1.0': migrateFromV010,
'~0.2.0': migrateFromV020,
'~0.3.0': migrateFromV030,
// No major changes to data format this release
'~0.4.0': updateConfigVersions,
// Pre-empt future releases
'~0.5.0': updateConfigVersions,
};

/** Perform a migration from the given version */
export default async function migrate(oldVersion: string) {
console.log(`Data directory uses version ${oldVersion}. Migration needed`);
Expand Down
24 changes: 24 additions & 0 deletions src/lib/server/data/migrations/shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Shared helper functions for common data migration actions.
*/
import { dev } from '$app/environment';
import fs from 'fs/promises';

/** Move config.local.json to the private data directory */
export async function moveLocalConfig(dataDir: string, privateDataDir: string) {
const originalPath = `${dataDir}/config.local.json`;
const newPath = `${privateDataDir}/config.local.json`;
await fs.rename(originalPath, newPath);
}

/** Write auth secret from environment variable */
export async function writeAuthSecret(privateDataDir: string) {
const secret = process.env.AUTH_SECRET;
if (!secret) {
throw new Error('AUTH_SECRET environment variable must be set to a value');
}
if (!dev && secret === 'CHANGE ME') {
throw new Error('AUTH_SECRET must be changed when running in production');
}
await fs.writeFile(`${privateDataDir}/auth.secret`, secret, { encoding: 'utf-8' });
}
7 changes: 7 additions & 0 deletions src/lib/server/data/migrations/v0.6.0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Migrate to v0.6.0
*
* Primary changes:
*
* * Move `config.local.json` to the new private data directory.
*/
4 changes: 2 additions & 2 deletions src/lib/server/git.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { error } from '@sveltejs/kit';
import { dataDirContainsData, dataDirIsInit, getDataDir } from './data/dataDir';
import { dataDirContainsData, serverIsSetUp, getDataDir } from './data/dataDir';
import simpleGit, { type FileStatusResult } from 'simple-git';
import fs from 'fs/promises';
import { rimraf } from 'rimraf';
Expand Down Expand Up @@ -102,7 +102,7 @@ export async function getRepoStatus(): Promise<RepoStatus> {
/** Set up the data dir given a git repo URL and branch name */
export async function setupGitRepo(repo: string, branch: string | null) {
// Check whether the data repo is set up
if (await dataDirIsInit()) {
if (await serverIsSetUp()) {
throw error(403, 'Data repo is already set up');
}

Expand Down
12 changes: 12 additions & 0 deletions src/lib/server/keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** Code for managing the server's SSH keys */
import fs from 'fs/promises';

/** Generate an SSH key */
export async function sshKeygen() {

}

export async function getPublicKey(): Promise<string> {
const data
return fs.readFile()
}

0 comments on commit f2bab9b

Please sign in to comment.