Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
5a966c0
feat(wireguard): embed userspace WireGuard via defguard/boringtun
dviejokfs Mar 12, 2026
b402a9b
docs(changelog): add embedded WireGuard entry
dviejokfs Mar 12, 2026
b823576
feat(config,web): add public settings endpoint and hide demo button w…
dviejokfs Mar 12, 2026
9a4eaf2
feat(proxy): add cookie-based password protection for environments
dviejokfs Mar 12, 2026
8839641
feat(agent,providers): add remote managed service support on worker n…
dviejokfs Mar 12, 2026
5bec865
fix(proxy): show error on invalid password and preserve password conf…
dviejokfs Mar 12, 2026
f31395c
fix(ai-gateway): sanitize OpenAI requests for model compatibility
dviejokfs Mar 12, 2026
6f11424
fix(ai-gateway): improve provider key test UX and error messages
dviejokfs Mar 12, 2026
a62392f
feat(on-demand): improve wake/sleep lifecycle with container orchestr…
dviejokfs Mar 13, 2026
dd81c5e
fix(web): improve mobile responsiveness for environment settings
dviejokfs Mar 13, 2026
ac4273e
Merge remote-tracking branch 'origin/main' into feat/embedded-wireguard
dviejokfs Mar 14, 2026
3acef75
feat(providers): add service clusters, remote services, and email val…
dviejokfs Mar 14, 2026
e594803
feat(cli): enhance project create with repo, branch, preset, and conn…
dviejokfs Mar 14, 2026
acf3fc8
fix(tests): fix 3 failing tests in presets and providers
dviejokfs Mar 14, 2026
2cd72cc
fix(deps): patch 3 Dependabot vulnerabilities and finalize v0.0.6 cha…
dviejokfs Mar 14, 2026
bc3dca2
test(providers): add cluster validation tests for initialize and retry
dviejokfs Mar 14, 2026
2b59855
fix(proxy): reload route table when deployment_config changes
dviejokfs Mar 14, 2026
d055c6e
fix(routes): remove deployment state filter that caused route table race
dviejokfs Mar 14, 2026
cdb4349
fix(status-page): skip health checks for on-demand environments
dviejokfs Mar 14, 2026
5bbac03
fix(web): stabilize FunnelCard date range to prevent infinite re-fetc…
dviejokfs Mar 14, 2026
8b344e9
feat(web): add date range picker to funnel list page
dviejokfs Mar 14, 2026
55edff9
fix(web): remove CalendarDays import not exported by lucide-react
dviejokfs Mar 14, 2026
ba6ea01
fix(proxy): wire on-demand configs from route table to idle sweep
dviejokfs Mar 14, 2026
ef5641d
fix(proxy): reload routes after on-demand callback registration
dviejokfs Mar 14, 2026
deda44b
fix(security): harden auth, encryption, rate limiting, and input sani…
dviejokfs Mar 15, 2026
0c21556
fix(ux): improve error messages, setup guidance, and proxy error page
dviejokfs Mar 15, 2026
c361030
fix(web): improve analytics UI responsive design for mobile and desktop
dviejokfs Mar 15, 2026
154a25d
fix(web): improve analytics responsive design and extract shared date…
dviejokfs Mar 15, 2026
62ae9d3
feat(error-tracking): improve alert rules engine with regression dete…
dviejokfs Mar 15, 2026
1f01ea1
feat(status-page): add per-project health monitoring and fix SQL inje…
dviejokfs Mar 15, 2026
88c4eb2
feat(web): refresh deployments on window focus and add refresh button
dviejokfs Mar 15, 2026
73f6acb
fix(query-postgres): remove RwLock deadlock and add read-only explore…
dviejokfs Mar 16, 2026
78bbd73
feat(web): add source selection step to create project page
dviejokfs Mar 16, 2026
02c342e
fix(routes): fix route readiness check and payload parsing
dviejokfs Mar 16, 2026
c2c94af
feat(web): add analytics sub-pages to observability sidebar
dviejokfs Mar 16, 2026
5805f3d
perf(console): add proper cache-control headers for static assets
dviejokfs Mar 16, 2026
6ec16d8
fix(routes): add periodic self-healing sync, manual refresh endpoint,…
dviejokfs Mar 16, 2026
78387f7
fix(web): fix sidebar active state and show on-demand status in monitors
dviejokfs Mar 16, 2026
aa3d640
feat(cli): add --auto flag to temps setup for zero-prompt installation
dviejokfs Mar 18, 2026
5263052
fix(migrations): fix 3 failing migration tests
dviejokfs Mar 18, 2026
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
117 changes: 52 additions & 65 deletions CHANGELOG.md

Large diffs are not rendered by default.

711 changes: 696 additions & 15 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/temps-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@temps-sdk/cli",
"version": "0.1.14",
"version": "0.1.16",
"description": "CLI for Temps deployment platform",
"type": "module",
"bin": {
Expand Down
113 changes: 97 additions & 16 deletions apps/temps-cli/src/commands/projects/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
selectRepository,
selectBranch,
detectAndSelectPreset,
findRepositoryByName,
fetchGitConnections,
} from '../../lib/git-connection.js'
import { selectStorageServices } from '../../lib/service-setup.js'

Expand All @@ -33,46 +35,120 @@ interface CreateOptions {
preset?: string
connection?: string
repo?: string
yes?: boolean
}

export async function create(options: CreateOptions): Promise<void> {
await requireAuth()
await setupClient()

const skipPrompts = options.yes ?? false

newline()
console.log(colors.bold(`${icons.sparkles} Create New Project`))
console.log(colors.muted('─'.repeat(40)))
newline()

try {
// Step 1: Select Git Connection
const connection = await selectGitConnection()
let connection
if (options.connection) {
// Resolve connection by ID
const connections = await fetchGitConnections()
const connId = parseInt(options.connection, 10)
connection = connections.find((c) => c.id === connId)
if (!connection) {
error(`Git connection with ID ${options.connection} not found.`)
return
}
info(`Using git connection: ${connection.account_name}`)
} else if (options.repo && skipPrompts) {
// Auto-find the connection that has this repo
const parts = options.repo.split('/')
if (parts.length !== 2 || !parts[0] || !parts[1]) {
error('Repository must be in owner/name format (e.g., myorg/myrepo)')
return
}
const connections = await fetchGitConnections()
for (const conn of connections) {
const repo = await findRepositoryByName(conn.id, parts[0], parts[1])
if (repo) {
connection = conn
info(`Auto-selected git connection: ${conn.account_name}`)
break
}
}
if (!connection) {
error(`Repository "${options.repo}" not found in any git connection.`)
return
}
} else {
connection = await selectGitConnection()
}
if (!connection) {
error('No git connection selected. Please set up a git provider first.')
return
}

// Step 2: Select Repository
const repository = await selectRepository(connection.id)
let repository
if (options.repo) {
// Parse owner/name format
const parts = options.repo.split('/')
if (parts.length !== 2 || !parts[0] || !parts[1]) {
error('Repository must be in owner/name format (e.g., myorg/myrepo)')
return
}
repository = await findRepositoryByName(connection.id, parts[0], parts[1])
if (!repository) {
error(`Repository "${options.repo}" not found in connection "${connection.account_name}".`)
return
}
info(`Using repository: ${repository.owner}/${repository.name}`)
} else {
repository = await selectRepository(connection.id)
}
if (!repository) {
error('No repository selected.')
return
}

// Step 3: Select Branch
const branch = await selectBranch(connection.id, repository)
let branch: string
if (options.branch) {
branch = options.branch
info(`Using branch: ${branch}`)
} else {
branch = await selectBranch(connection.id, repository)
}

// Step 4: Detect and Select Preset
const { preset, directory } = await detectAndSelectPreset(repository.id, branch)
let preset: string
let directory: string
if (options.preset) {
preset = options.preset
directory = options.directory || './'
info(`Using preset: ${preset}, directory: ${directory}`)
} else {
const detected = await detectAndSelectPreset(repository.id, branch)
preset = detected.preset
directory = detected.directory
}

// Step 5: Configure Project Name
const projectName = await configureProjectName(repository, directory)
let projectName: string
if (options.name) {
projectName = options.name
info(`Using project name: ${projectName}`)
} else {
projectName = await configureProjectName(repository, directory)
}

// Step 6: Select Storage Services
const serviceIds = await selectStorageServices()
// Step 6: Select Storage Services (skip with --yes)
const serviceIds = skipPrompts ? [] : await selectStorageServices()

// Step 7: Configure Environment Variables
const envVars = await configureEnvironmentVariables()
// Step 7: Configure Environment Variables (skip with --yes)
const envVars = skipPrompts ? [] : await configureEnvironmentVariables()

// Step 8: Create the Project
const project = await withSpinner('Creating project...', async () => {
Expand Down Expand Up @@ -122,15 +198,20 @@ export async function create(options: CreateOptions): Promise<void> {

newline()

// Ask if user wants to set as default
const setDefault = await promptConfirm({
message: 'Set as default project?',
default: true,
})

if (setDefault) {
// Ask if user wants to set as default (auto-set with --yes)
if (skipPrompts) {
config.set('defaultProject', project.slug)
success(`Default project set to "${project.slug}"`)
} else {
const setDefault = await promptConfirm({
message: 'Set as default project?',
default: true,
})

if (setDefault) {
config.set('defaultProject', project.slug)
success(`Default project set to "${project.slug}"`)
}
}

newline()
Expand Down
7 changes: 6 additions & 1 deletion apps/temps-cli/src/commands/projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ export function registerProjectsCommands(program: Command): void {
.description('Create a new project')
.option('-n, --name <name>', 'Project name')
.option('-d, --description <description>', 'Project description')
.option('--repo <repository>', 'Git repository URL')
.option('--repo <repository>', 'Repository in owner/name format')
.option('--branch <branch>', 'Git branch')
.option('--directory <directory>', 'Root directory (relative to repo)')
.option('--preset <preset>', 'Build preset (e.g., nextjs, nodejs, static, docker)')
.option('--connection <id>', 'Git connection ID')
.option('-y, --yes', 'Skip optional prompts (services, env vars, set-default)')
.action(create)

projects
Expand Down
1 change: 1 addition & 0 deletions crates/temps-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ utoipa-swagger-ui = { workspace = true }
sysinfo = { workspace = true }
uuid = { workspace = true }
http-body-util = "0.1"
bollard = { workspace = true }
temps-core = { path = "../temps-core" }
temps-deployer = { path = "../temps-deployer" }
37 changes: 32 additions & 5 deletions crates/temps-agent/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,23 @@ use crate::NodeHealthReport;
pub struct AgentState {
pub container_deployer: Arc<dyn ContainerDeployer>,
pub image_builder: Arc<dyn ImageBuilder>,
/// Direct Docker client for service operations (create/exec/backup).
/// None if Docker is not available (shouldn't happen on a real agent).
pub docker: Option<bollard::Docker>,
}

/// Response wrapper for consistent agent API responses.
#[derive(Serialize, ToSchema)]
pub struct AgentResponse<T: Serialize> {
success: bool,
pub(crate) success: bool,
#[schema(nullable = true)]
data: Option<T>,
pub(crate) data: Option<T>,
#[schema(nullable = true)]
error: Option<String>,
pub(crate) error: Option<String>,
}

impl<T: Serialize> AgentResponse<T> {
fn ok(data: T) -> Json<Self> {
pub(crate) fn ok(data: T) -> Json<Self> {
Json(Self {
success: true,
data: Some(data),
Expand Down Expand Up @@ -65,13 +68,27 @@ fn error_response(status: StatusCode, message: String) -> impl IntoResponse {
image_exists,
import_image,
health_check,
crate::service_handlers::create_service,
crate::service_handlers::stop_service,
crate::service_handlers::start_service,
crate::service_handlers::remove_service,
crate::service_handlers::service_status,
crate::service_handlers::service_exec,
crate::service_handlers::list_services,
crate::service_handlers::backup_service,
crate::service_handlers::restore_service,
),
components(schemas(
AgentResponse<temps_deployer::DeployResult>,
AgentResponse<String>,
AgentResponse<bool>,
AgentResponse<temps_deployer::ContainerInfo>,
AgentResponse<NodeHealthReport>,
AgentResponse<crate::ServiceCreateResponse>,
AgentResponse<crate::ServiceExecResponse>,
AgentResponse<crate::ServiceStatus>,
AgentResponse<Vec<crate::ServiceStatus>>,
AgentResponse<crate::ServiceBackupResponse>,
NodeHealthReport,
temps_deployer::DeployRequest,
temps_deployer::DeployResult,
Expand All @@ -82,10 +99,20 @@ fn error_response(status: StatusCode, message: String) -> impl IntoResponse {
temps_deployer::ResourceLimits,
temps_deployer::RestartPolicy,
temps_deployer::ContainerLogConfig,
crate::ServiceCreateRequest,
crate::ServiceCreateResponse,
crate::ServicePortMapping,
crate::ServiceExecRequest,
crate::ServiceExecResponse,
crate::ServiceBackupRequest,
crate::ServiceBackupResponse,
crate::ServiceRestoreRequest,
crate::S3CredentialsPayload,
crate::ServiceStatus,
)),
info(
title = "Temps Agent API",
description = "Worker node agent API for container management. All endpoints require Bearer token authentication.",
description = "Worker node agent API for container and service management. All endpoints require Bearer token authentication.",
version = "1.0.0"
),
security(
Expand Down
Loading
Loading