first pass

This commit is contained in:
2025-11-29 14:50:33 -05:00
parent cf3015638a
commit 0bfab0bdc8
10 changed files with 631 additions and 0 deletions

29
src/config.ts Normal file
View File

@@ -0,0 +1,29 @@
import userConfig from "../config";
import hash from "object-hash";
export type DeployerConfig = {
services: {
[host: string]: {
user: string;
repo: string;
branch: string;
};
};
giteaUrl: string;
directory: string;
basePort: number;
token: string;
systemServicesDir: string;
};
export const config = userConfig as DeployerConfig;
export const indexedConfig = Object.fromEntries(
Object.entries(config.services).map(([host, service], i) => [
hash(service),
{
...service,
host,
port: config.basePort + i,
},
])
);

68
src/deploy.ts Normal file
View File

@@ -0,0 +1,68 @@
import { $ } from "bun";
import path from "path";
import { config } from "./config";
type DeployInstance = {
host: string;
user: string;
repo: string;
branch: string;
commitHash: string;
port: number;
};
export const deploy = async ({
host,
user,
repo,
branch,
port,
commitHash,
}: DeployInstance) => {
const deploymentId = new Date().toISOString();
const serviceDir = path.join(config.directory, host);
// Fetch
await $`
cd ${serviceDir}
mkdir -p src
mkdir -p logs
git clone \
-b ${branch} \
http://deployer:${config.token}@${config.giteaUrl}/${user}/${repo} \
${serviceDir}/src
cd src
git fetch origin ${branch}
git reset --hard origin/${branch}
git checkout ${commitHash}
`;
// Build
await $`
cd ${serviceDir}/src
make build
`;
// Register service
const systemdServiceName = `deployer-${host}.service`;
await $`
cat template.service | envsubst > ${serviceDir}/${systemdServiceName}
ln -sf ${serviceDir}/${systemdServiceName} ${config.systemServicesDir}/${systemdServiceName}
`.env({
host,
port: port.toString(),
repo,
branch,
commitHash,
deploymentId,
logsDir: path.join(serviceDir, "logs"),
serviceDir,
});
// Start!
await $`
systemctl daemon-reload
systemctl restart ${systemdServiceName}
`;
};

216
src/giteaTypes.ts Normal file
View File

@@ -0,0 +1,216 @@
export interface PushEvent {
ref: string;
before: string;
after: string;
compare_url: string;
commits: Commit[];
total_commits: number;
head_commit: HeadCommit;
repository: Repository;
pusher: Pusher;
sender: Sender;
}
export interface Commit {
id: string;
message: string;
url: string;
author: Author;
committer: Committer;
verification: any;
timestamp: string;
added: any;
removed: any;
modified: any;
}
export interface Author {
name: string;
email: string;
username: string;
}
export interface Committer {
name: string;
email: string;
username: string;
}
export interface HeadCommit {
id: string;
message: string;
url: string;
author: Author2;
committer: Committer2;
verification: any;
timestamp: string;
added: any;
removed: any;
modified: any;
}
export interface Author2 {
name: string;
email: string;
username: string;
}
export interface Committer2 {
name: string;
email: string;
username: string;
}
export interface Repository {
id: number;
owner: Owner;
name: string;
full_name: string;
description: string;
empty: boolean;
private: boolean;
fork: boolean;
template: boolean;
mirror: boolean;
size: number;
language: string;
languages_url: string;
html_url: string;
url: string;
link: string;
ssh_url: string;
clone_url: string;
original_url: string;
website: string;
stars_count: number;
forks_count: number;
watchers_count: number;
open_issues_count: number;
open_pr_counter: number;
release_counter: number;
default_branch: string;
archived: boolean;
created_at: string;
updated_at: string;
archived_at: string;
permissions: Permissions;
has_code: boolean;
has_issues: boolean;
internal_tracker: InternalTracker;
has_wiki: boolean;
has_pull_requests: boolean;
has_projects: boolean;
projects_mode: string;
has_releases: boolean;
has_packages: boolean;
has_actions: boolean;
ignore_whitespace_conflicts: boolean;
allow_merge_commits: boolean;
allow_rebase: boolean;
allow_rebase_explicit: boolean;
allow_squash_merge: boolean;
allow_fast_forward_only_merge: boolean;
allow_rebase_update: boolean;
allow_manual_merge: boolean;
autodetect_manual_merge: boolean;
default_delete_branch_after_merge: boolean;
default_merge_style: string;
default_allow_maintainer_edit: boolean;
avatar_url: string;
internal: boolean;
mirror_interval: string;
object_format_name: string;
mirror_updated: string;
topics: any[];
licenses: any[];
}
export interface Owner {
id: number;
login: string;
login_name: string;
source_id: number;
full_name: string;
email: string;
avatar_url: string;
html_url: string;
language: string;
is_admin: boolean;
last_login: string;
created: string;
restricted: boolean;
active: boolean;
prohibit_login: boolean;
location: string;
website: string;
description: string;
visibility: string;
followers_count: number;
following_count: number;
starred_repos_count: number;
username: string;
}
export interface Permissions {
admin: boolean;
push: boolean;
pull: boolean;
}
export interface InternalTracker {
enable_time_tracker: boolean;
allow_only_contributors_to_track_time: boolean;
enable_issue_dependencies: boolean;
}
export interface Pusher {
id: number;
login: string;
login_name: string;
source_id: number;
full_name: string;
email: string;
avatar_url: string;
html_url: string;
language: string;
is_admin: boolean;
last_login: string;
created: string;
restricted: boolean;
active: boolean;
prohibit_login: boolean;
location: string;
website: string;
description: string;
visibility: string;
followers_count: number;
following_count: number;
starred_repos_count: number;
username: string;
}
export interface Sender {
id: number;
login: string;
login_name: string;
source_id: number;
full_name: string;
email: string;
avatar_url: string;
html_url: string;
language: string;
is_admin: boolean;
last_login: string;
created: string;
restricted: boolean;
active: boolean;
prohibit_login: boolean;
location: string;
website: string;
description: string;
visibility: string;
followers_count: number;
following_count: number;
starred_repos_count: number;
username: string;
}

42
src/index.ts Normal file
View File

@@ -0,0 +1,42 @@
import { Elysia } from "elysia";
import hash from "object-hash";
import { indexedConfig } from "./config";
import { deploy } from "./deploy";
import { PushEvent } from "./giteaTypes";
const port = 6001;
new Elysia()
.onError(({ error }) => console.error(error))
.onRequest(({ request }) => {
console.log(request.method, new URL(request.url).pathname);
})
.get("/ping", () => "pong")
.post("/gitea", ({ body }) => {
const event = body as PushEvent;
const user = event.repository.owner.username;
const repo = event.repository.name;
const branch = event.ref.replace(/^(refs\/heads\/)/, "");
const commitHash = event.before;
const service =
indexedConfig[
hash({
user,
repo,
branch,
})
];
if (service == null) {
console.error(`No service defined for ${user}/${repo}:${branch}`);
return;
}
void deploy({ ...service, commitHash });
return "deploying";
})
.listen(port);
console.log(`started deployer2 on :${port}`);