Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,20 @@ jobs:
type=ref,event=tag
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}

- name: Docker Meta (Cloud)
id: meta-cloud
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}/cloud
bake-target: cloud
flavor: latest=false
tags: |
type=sha
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}

- name: Docker Meta (Docs)
id: meta-docs
uses: docker/metadata-action@v5
Expand Down Expand Up @@ -169,6 +183,7 @@ jobs:
${{ steps.meta-nodejs.outputs.bake-file }}
${{ steps.meta-docker.outputs.bake-file }}
${{ steps.meta-docker-nodejs.outputs.bake-file }}
${{ steps.meta-cloud.outputs.bake-file }}
${{ steps.meta-docs.outputs.bake-file }}
${{ steps.meta-agent.outputs.bake-file }}
${{ steps.meta-manager.outputs.bake-file }}
Expand All @@ -182,6 +197,8 @@ jobs:
docker.cache-to=type=gha,mode=max,scope=docker-${{ github.ref_name }}
docker-nodejs.cache-from=type=gha,scope=docker-nodejs-${{ github.ref_name }}
docker-nodejs.cache-to=type=gha,mode=max,scope=docker-nodejs-${{ github.ref_name }}
cloud.cache-from=type=gha,scope=cloud-${{ github.ref_name }}
cloud.cache-to=type=gha,mode=max,scope=cloud-${{ github.ref_name }}
docs.cache-from=type=gha,scope=docs-${{ github.ref_name }}
docs.cache-to=type=gha,mode=max,scope=docs-${{ github.ref_name }}
agent.cache-from=type=gha,scope=agent-${{ github.ref_name }}
Expand Down
69 changes: 69 additions & 0 deletions .github/workflows/os-cloud-provider-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: os-cloud-provider preview package

# On every push to a same-repo pull request that touches the provider, publish
# @mieweb/os-cloud-provider to the GitHub Packages npm registry as
# `<version>-pr<PR>.<run>` under dist-tag `pr<PR>`, so cross-repo PRs (e.g.
# @mieweb/cli in mieweb/cloud) can depend on it with a plain semver reference
# plus `@mieweb:registry=https://npm.pkg.github.com` in .npmrc. Once merged and
# released to npmjs, consumers switch to the real version and drop that line.

on:
pull_request:
paths:
- 'packages/os-cloud-provider/**'
- '.github/workflows/os-cloud-provider-preview.yml'

concurrency:
group: os-cloud-provider-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
packages: write

jobs:
publish:
# GITHUB_TOKEN is read-only on fork PRs.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/os-cloud-provider
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
package_json_file: packages/os-cloud-provider/package.json
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: packages/os-cloud-provider/pnpm-lock.yaml
registry-url: https://npm.pkg.github.com
scope: '@mieweb'
- run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Stamp preview version
id: stamp
env:
SUFFIX: pr${{ github.event.pull_request.number }}.${{ github.run_number }}
run: |
v=$(node -p 'require("./package.json").version.replace(/-.*$/, "")')-$SUFFIX
npm pkg set version="$v"
echo "version=$v" >> "$GITHUB_OUTPUT"
# Preview builds default to this PR's cloud image (build-images.yml pushes
# cloud:pr-<N> on every PR push); `latest` only exists after a release.
- name: Default to this PR's cloud image
env:
TAG: pr-${{ github.event.pull_request.number }}
run: |
sed -i "s#\(DEFAULT_IMAGE = 'ghcr.io/mieweb/opensource-server/cloud\):latest'#\1:$TAG'#" src/config.ts
grep -q "cloud:$TAG'" src/config.ts
# prepack builds dist/.
- run: pnpm publish --no-git-checks --tag pr${{ github.event.pull_request.number }}
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: echo "Published \`@mieweb/os-cloud-provider@${{ steps.stamp.outputs.version }}\` to npm.pkg.github.com" >> "$GITHUB_STEP_SUMMARY"
55 changes: 55 additions & 0 deletions .github/workflows/os-cloud-provider.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: os-cloud-provider

# Typecheck + test @mieweb/os-cloud-provider (packages/os-cloud-provider).
# The regular suite runs against an in-process fake Manager; the live suite
# (pnpm test:live) needs a running Manager and is not run here.

on:
push:
branches: [main]
paths:
- 'packages/os-cloud-provider/**'
- 'create-a-container/openapi.v1.yaml'
- '.github/workflows/os-cloud-provider.yml'
pull_request:
paths:
- 'packages/os-cloud-provider/**'
- 'create-a-container/openapi.v1.yaml'
- '.github/workflows/os-cloud-provider.yml'

permissions:
contents: read
# @mieweb/deploy-contract preview versions come from GitHub Packages.
packages: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node: ['22', '24']
defaults:
run:
working-directory: packages/os-cloud-provider
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: pnpm/action-setup@v4
with:
package_json_file: packages/os-cloud-provider/package.json
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: pnpm
cache-dependency-path: packages/os-cloud-provider/pnpm-lock.yaml
registry-url: https://npm.pkg.github.com
scope: '@mieweb'
- run: pnpm install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Generated Manager types are up to date
run: pnpm gen:types && git diff --exit-code -- src/generated
- run: pnpm typecheck
- run: pnpm test
- run: pnpm build
3 changes: 3 additions & 0 deletions create-a-container/client/src/pages/auth/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ function asExternalUrl(target: string): string | null {
return null;
}
if (url.protocol !== 'http:' && url.protocol !== 'https:') return null;
// Same-origin server routes (e.g. the CLI login handoff at
// /api/v1/auth/cli/callback) aren't SPA pages — they need a real navigation.
if (url.origin === window.location.origin && url.pathname.startsWith('/api/')) return url.href;
// Same-origin targets stay in-app (let react-router handle them as paths).
if (url.origin === window.location.origin) return null;
return url.href;
Expand Down
3 changes: 2 additions & 1 deletion create-a-container/models/dns-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ module.exports = (sequelize, DataTypes) => {
references: {
model: 'Services',
key: 'id'
}
},
onDelete: 'CASCADE'
},
recordType: {
type: DataTypes.ENUM('SRV'),
Expand Down
3 changes: 2 additions & 1 deletion create-a-container/models/http-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module.exports = (sequelize, DataTypes) => {
references: {
model: 'Services',
key: 'id'
}
},
onDelete: 'CASCADE'
},
externalHostname: {
type: DataTypes.STRING(255),
Expand Down
3 changes: 2 additions & 1 deletion create-a-container/models/transport-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ module.exports = (sequelize, DataTypes) => {
references: {
model: 'Services',
key: 'id'
}
},
onDelete: 'CASCADE'
},
protocol: {
type: DataTypes.ENUM('tcp', 'udp'),
Expand Down
45 changes: 45 additions & 0 deletions create-a-container/openapi.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,51 @@ paths:
responses:
'302': { description: 'Redirect to the post-login destination on success, or /login?oidc_error=<code> on failure' }
'404': { description: 'OIDC not configured (code: oidc_disabled)' }
/auth/cli/callback:
get:
operationId: cli_login_authorize
tags: [Auth]
summary: CLI loopback login — browser confirmation page
description: >-
Browser-facing (HTML), used by `mieweb login`. Without a session, it
redirects through the normal sign-in (OIDC or /login) and comes back
here. With a session, it renders a confirmation form that POSTs back to
this path. A GET never mints a key.
security: []
parameters:
- { in: query, name: port, required: true, schema: { type: integer, minimum: 1024, maximum: 65535 }, description: Loopback port the CLI listens on (host is always 127.0.0.1) }
- { in: query, name: state, required: true, schema: { type: string, pattern: '^[A-Za-z0-9_-]{16,128}$' }, description: CLI-generated one-time nonce, echoed back }
- { in: query, name: client, schema: { type: string, pattern: '^[A-Za-z0-9._@-]{1,64}$' }, description: Label recorded in the minted key's description }
responses:
'200': { description: HTML confirmation page }
'302': { description: Redirect to sign-in when there is no session }
'400': { description: HTML error page for invalid parameters }
post:
operationId: cli_login_mint
tags: [Auth]
summary: CLI loopback login — mint an API key and hand it to the loopback listener
description: >-
Requires a browser session and a CSRF token (`_csrf`). Bearer-only
requests are rejected. Mints an API key for the session user and
303-redirects to `http://127.0.0.1:<port>/callback#key=…&id=…&user=…&state=…`.
The key is in the URL fragment, so it never appears in a request line.
security: []
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [_csrf, port, state]
properties:
_csrf: { type: string }
port: { type: integer }
state: { type: string }
client: { type: string }
responses:
'303': { description: Redirect to the loopback listener with the key in the fragment }
'400': { description: HTML error page for invalid parameters }
'401': { description: No browser session }
'403': { description: Missing/invalid CSRF token }
/auth/logout:
post:
operationId: logout
Expand Down
Loading
Loading