Compare commits
3 commits
master
...
simple-syn
Author | SHA1 | Date | |
---|---|---|---|
|
4fd86c9e8f | ||
|
06aa89bb9f | ||
|
7489e35a2e |
7 changed files with 79 additions and 77 deletions
58
.github/workflows/docker.yml
vendored
58
.github/workflows/docker.yml
vendored
|
@ -1,31 +1,10 @@
|
||||||
name: Build Docker Image
|
name: Build Docker Image
|
||||||
|
|
||||||
# Docker Images are built for every push to master or when a new tag is created
|
# Docker Images are only built when a new tag is created
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- 'v*.*.*'
|
||||||
paths-ignore:
|
|
||||||
- README.md
|
|
||||||
- LICENSE.txt
|
|
||||||
|
|
||||||
env:
|
|
||||||
IMAGES: |
|
|
||||||
jlongster/actual-server
|
|
||||||
ghcr.io/actualbudget/actual-server
|
|
||||||
|
|
||||||
# Creates the following tags:
|
|
||||||
# - actual-server:latest (see docker/metadata-action flavor inputs, below)
|
|
||||||
# - actual-server:edge (for master)
|
|
||||||
# - actual-server:1.3
|
|
||||||
# - actual-server:1.3.7
|
|
||||||
# - actual-server:sha-90dd603
|
|
||||||
TAGS: |
|
|
||||||
type=edge,value=edge
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=sha
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
@ -45,22 +24,35 @@ jobs:
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
# Push to both Docker Hub and Github Container Registry
|
# Push to both Docker Hub and Github Container Registry
|
||||||
images: ${{ env.IMAGES }}
|
images: |
|
||||||
# Automatically update :latest for our semver tags
|
jlongster/actual-server
|
||||||
flavor: |
|
ghcr.io/actualbudget/actual-server
|
||||||
latest=auto
|
# Creates the following tags:
|
||||||
tags: ${{ env.TAGS }}
|
# - actual-server:latest
|
||||||
|
# - actual-server:1.3
|
||||||
|
# - actual-server:1.3.7
|
||||||
|
# - actual-server:sha-90dd603
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
- name: Docker meta for Alpine image
|
- name: Docker meta for Alpine image
|
||||||
id: alpine-meta
|
id: alpine-meta
|
||||||
uses: docker/metadata-action@v4
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: ${{ env.IMAGES }}
|
images: |
|
||||||
# Automatically update :latest for our semver tags and suffix all tags
|
jlongster/actual-server
|
||||||
flavor: |
|
ghcr.io/actualbudget/actual-server
|
||||||
latest=auto
|
tags: |
|
||||||
suffix=-alpine,onlatest=true
|
type=ref,event=branch
|
||||||
tags: $${{ env.TAGS }}
|
type=ref,event=pr
|
||||||
|
type=raw,value=latest,suffix=-alpine
|
||||||
|
type=semver,pattern={{version}},suffix=-alpine
|
||||||
|
type=semver,pattern={{major}}.{{minor}},suffix=-alpine
|
||||||
|
type=sha,suffix=-alpine
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
|
|
50
app-sync.js
50
app-sync.js
|
@ -1,11 +1,12 @@
|
||||||
let fs = require('fs/promises');
|
|
||||||
let { Buffer } = require('buffer');
|
let { Buffer } = require('buffer');
|
||||||
|
let { join } = require('path');
|
||||||
let express = require('express');
|
let express = require('express');
|
||||||
let uuid = require('uuid');
|
let uuid = require('uuid');
|
||||||
|
let AdmZip = require('adm-zip');
|
||||||
let { validateUser } = require('./util/validate-user');
|
let { validateUser } = require('./util/validate-user');
|
||||||
let errorMiddleware = require('./util/error-middleware');
|
let errorMiddleware = require('./util/error-middleware');
|
||||||
|
let config = require('./load-config');
|
||||||
let { getAccountDb } = require('./account-db');
|
let { getAccountDb } = require('./account-db');
|
||||||
let { getPathForUserFile, getPathForGroupFile } = require('./util/paths');
|
|
||||||
|
|
||||||
let simpleSync = require('./sync-simple');
|
let simpleSync = require('./sync-simple');
|
||||||
|
|
||||||
|
@ -15,8 +16,17 @@ let SyncPb = actual.internal.SyncProtoBuf;
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(errorMiddleware);
|
app.use(errorMiddleware);
|
||||||
|
|
||||||
// eslint-disable-next-line
|
async function init() {
|
||||||
async function init() {}
|
let fileDir = join(process.env.ACTUAL_USER_FILES || config.userFiles);
|
||||||
|
|
||||||
|
console.log('Initializing Actual with user file dir:', fileDir);
|
||||||
|
|
||||||
|
await actual.init({
|
||||||
|
config: {
|
||||||
|
dataDir: fileDir
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// This is a version representing the internal format of sync
|
// This is a version representing the internal format of sync
|
||||||
// messages. When this changes, all sync files need to be reset. We
|
// messages. When this changes, all sync files need to be reset. We
|
||||||
|
@ -166,7 +176,7 @@ app.post('/user-create-key', (req, res) => {
|
||||||
res.send(JSON.stringify({ status: 'ok' }));
|
res.send(JSON.stringify({ status: 'ok' }));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/reset-user-file', async (req, res) => {
|
app.post('/reset-user-file', (req, res) => {
|
||||||
let user = validateUser(req, res);
|
let user = validateUser(req, res);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return;
|
return;
|
||||||
|
@ -186,11 +196,10 @@ app.post('/reset-user-file', async (req, res) => {
|
||||||
accountDb.mutate('UPDATE files SET group_id = NULL WHERE id = ?', [fileId]);
|
accountDb.mutate('UPDATE files SET group_id = NULL WHERE id = ?', [fileId]);
|
||||||
|
|
||||||
if (group_id) {
|
if (group_id) {
|
||||||
try {
|
// TODO: Instead of doing this, just delete the db file named
|
||||||
await fs.unlink(getPathForGroupFile(group_id));
|
// after the group
|
||||||
} catch (e) {
|
// db.mutate('DELETE FROM messages_binary WHERE group_id = ?', [group_id]);
|
||||||
console.log(`Unable to delete sync data for group "${group_id}"`);
|
// db.mutate('DELETE FROM messages_merkles WHERE group_id = ?', [group_id]);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send(JSON.stringify({ status: 'ok' }));
|
res.send(JSON.stringify({ status: 'ok' }));
|
||||||
|
@ -247,11 +256,21 @@ app.post('/upload-user-file', async (req, res) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: If we want to support end-to-end encryption, we'd write the
|
||||||
|
// raw file down because it's an encrypted blob. This isn't
|
||||||
|
// supported yet in the self-hosted version because it's unclear if
|
||||||
|
// it's still needed, given that you own your server
|
||||||
|
//
|
||||||
|
// await fs.writeFile(join(config.userFiles, `${fileId}.blob`), req.body);
|
||||||
|
|
||||||
|
let zip = new AdmZip(req.body);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fs.writeFile(getPathForUserFile(fileId), req.body);
|
zip.extractAllTo(join(config.userFiles, fileId), true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error writing file', err);
|
console.log('Error writing file', err);
|
||||||
res.send(JSON.stringify({ status: 'error' }));
|
res.send(JSON.stringify({ status: 'error' }));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rows = accountDb.all('SELECT id FROM files WHERE id = ?', [fileId]);
|
let rows = accountDb.all('SELECT id FROM files WHERE id = ?', [fileId]);
|
||||||
|
@ -301,14 +320,15 @@ app.get('/download-user-file', async (req, res) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let buffer;
|
let zip = new AdmZip();
|
||||||
try {
|
try {
|
||||||
buffer = await fs.readFile(getPathForUserFile(fileId));
|
zip.addLocalFile(join(config.userFiles, fileId, 'db.sqlite'), '');
|
||||||
|
zip.addLocalFile(join(config.userFiles, fileId, 'metadata.json'), '');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`Error: file does not exist: ${getPathForUserFile(fileId)}`);
|
res.status(500).send('Error reading files');
|
||||||
res.status(500).send('File does not exist on server');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let buffer = zip.toBuffer();
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
|
res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
|
||||||
res.send(buffer);
|
res.send(buffer);
|
||||||
|
|
|
@ -16,7 +16,4 @@ try {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// The env variable always takes precedence
|
|
||||||
config.userFiles = process.env.ACTUAL_USER_FILES || config.userFiles;
|
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "actual-sync",
|
"name": "actual-sync",
|
||||||
"version": "22.12.09",
|
"version": "1.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "actual syncing server",
|
"description": "actual syncing server",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
@ -12,8 +12,9 @@
|
||||||
"verify": "yarn -s lint && yarn types"
|
"verify": "yarn -s lint && yarn types"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actual-app/api": "4.1.5",
|
"@actual-app/api": "4.1.0",
|
||||||
"@actual-app/web": "22.12.3",
|
"@actual-app/web": "4.1.0",
|
||||||
|
"adm-zip": "^0.5.9",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
"better-sqlite3": "^7.5.0",
|
"better-sqlite3": "^7.5.0",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
let { existsSync, readFileSync } = require('fs');
|
let { existsSync, readFileSync } = require('fs');
|
||||||
let { join } = require('path');
|
let { join } = require('path');
|
||||||
let { openDatabase } = require('./db');
|
let { openDatabase } = require('./db');
|
||||||
let { getPathForGroupFile } = require('./util/paths');
|
|
||||||
|
|
||||||
let actual = require('@actual-app/api');
|
let actual = require('@actual-app/api');
|
||||||
let merkle = actual.internal.merkle;
|
let merkle = actual.internal.merkle;
|
||||||
let SyncPb = actual.internal.SyncProtoBuf;
|
let SyncPb = actual.internal.SyncProtoBuf;
|
||||||
let Timestamp = actual.internal.timestamp.Timestamp;
|
let Timestamp = actual.internal.timestamp.default;
|
||||||
|
|
||||||
function getGroupDb(groupId) {
|
function getGroupDb(groupId) {
|
||||||
let path = getPathForGroupFile(groupId);
|
let path = join(__dirname, `user-files/${groupId}.sqlite`);
|
||||||
let needsInit = !existsSync(path);
|
let needsInit = !existsSync(path);
|
||||||
|
|
||||||
let db = openDatabase(path);
|
let db = openDatabase(path);
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
let { join } = require('path');
|
|
||||||
let config = require('../load-config');
|
|
||||||
|
|
||||||
function getPathForUserFile(fileId) {
|
|
||||||
return join(config.userFiles, `file-${fileId}.blob`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPathForGroupFile(groupId) {
|
|
||||||
return join(config.userFiles, `group-${groupId}.sqlite`);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { getPathForUserFile, getPathForGroupFile };
|
|
21
yarn.lock
21
yarn.lock
|
@ -2,19 +2,19 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
"@actual-app/api@4.1.5":
|
"@actual-app/api@4.1.0":
|
||||||
version "4.1.5"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@actual-app/api/-/api-4.1.5.tgz#727f7e2233dd29204d2b7e244a671b3b88d0683a"
|
resolved "https://registry.yarnpkg.com/@actual-app/api/-/api-4.1.0.tgz#43580a56a370a12dae5668b8d56c540c1849c4c9"
|
||||||
integrity sha512-rI4xqaHt9UNxovSPo8tukjluuESlxscMXUkImyYV4gYzAETsSFET3k0708Zw0EZsXCVanqAtfv+v84SNEBqn0A==
|
integrity sha512-8tLuA6zDnN0NjD9wui5fB/cSe0hr/iYWOHGyW/ikJoGPrCu8N8zIITz2FhfplD2QDszgAsTRt48H2YtPmR9plg==
|
||||||
dependencies:
|
dependencies:
|
||||||
better-sqlite3 "^7.5.0"
|
better-sqlite3 "^7.5.0"
|
||||||
node-fetch "^1.6.3"
|
node-fetch "^1.6.3"
|
||||||
uuid "3.3.2"
|
uuid "3.3.2"
|
||||||
|
|
||||||
"@actual-app/web@22.12.3":
|
"@actual-app/web@4.1.0":
|
||||||
version "22.12.3"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@actual-app/web/-/web-22.12.3.tgz#e171fcd2bc3cdeea5cd87d879cc28986e3685a0f"
|
resolved "https://registry.yarnpkg.com/@actual-app/web/-/web-4.1.0.tgz#9a5c5d5fd3c6e5a1a34dcaf087fb1920a27f13eb"
|
||||||
integrity sha512-Ii6xbISEfDlLP8X+ZUYwyINuiJF1r8PHGy83OZl9lx6IbBj+d81Z8l2Qv5fhizmqHin8JytfZt0dR7rmpduVRg==
|
integrity sha512-QgxrHBUoDXsUCRzQTD4PmDkTt27UUcPfFJMqXPHtk2lP0ey7iVsZIW7AUeYkEs1+ghmqVVqk+jw3OAD4XQzbRA==
|
||||||
|
|
||||||
"@eslint/eslintrc@^1.2.3":
|
"@eslint/eslintrc@^1.2.3":
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
|
@ -201,6 +201,11 @@ acorn@^8.7.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
||||||
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
||||||
|
|
||||||
|
adm-zip@^0.5.9:
|
||||||
|
version "0.5.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.9.tgz#b33691028333821c0cf95c31374c5462f2905a83"
|
||||||
|
integrity sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==
|
||||||
|
|
||||||
agent-base@6:
|
agent-base@6:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||||
|
|
Loading…
Reference in a new issue