Skip to content

Commit 753ba79

Browse files
committed
feat: add demo apps publisher repo
Seed the first concrete sw-ourbox-apps repo with real landing and todo-bloom source. Add lightweight validation plus GHCR publish workflows. Downstream catalog repos can now consume published demo app images.
0 parents  commit 753ba79

File tree

13 files changed

+1160
-0
lines changed

13 files changed

+1160
-0
lines changed

.commitlintrc.cjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** @type {import('@commitlint/types').UserConfig} */
2+
module.exports = {
3+
extends: ['@commitlint/config-conventional'],
4+
ignores: [(msg) => /^Merge\b/i.test(msg)],
5+
rules: {
6+
'header-max-length': [2, 'always', 100],
7+
'body-empty': [2, 'never'],
8+
'body-leading-blank': [2, 'always'],
9+
'subject-empty': [2, 'never'],
10+
'type-empty': [2, 'never'],
11+
'type-case': [2, 'always', 'lower-case'],
12+
},
13+
};

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
validate:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Validate manifest and source layout
15+
run: |
16+
python3 - <<'PY'
17+
import json
18+
from pathlib import Path
19+
20+
manifest = json.loads(Path("apps-manifest.json").read_text(encoding="utf-8"))
21+
assert manifest["schema"] == 1
22+
assert manifest["kind"] == "ourbox-apps-collection"
23+
apps = manifest["apps"]
24+
assert len(apps) >= 2
25+
for app in apps:
26+
src = Path(app["source_path"])
27+
assert src.is_dir(), f"missing app source path: {src}"
28+
assert (src / "Dockerfile").is_file(), f"missing Dockerfile: {src / 'Dockerfile'}"
29+
assert app["image_repo"].startswith("ghcr.io/techofourown/sw-ourbox-apps-demo/"), app["image_repo"]
30+
print("apps-manifest.json and source layout look valid")
31+
PY
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Publish Application Images
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
9+
jobs:
10+
matrix:
11+
runs-on: ubuntu-latest
12+
outputs:
13+
matrix: ${{ steps.out.outputs.matrix }}
14+
steps:
15+
- uses: actions/checkout@v4
16+
- id: out
17+
name: Build publish matrix from apps-manifest.json
18+
run: |
19+
python3 - <<'PY' >>"${GITHUB_OUTPUT}"
20+
import json
21+
from pathlib import Path
22+
23+
manifest = json.loads(Path("apps-manifest.json").read_text(encoding="utf-8"))
24+
include = []
25+
for app in manifest["apps"]:
26+
include.append(
27+
{
28+
"app_id": app["app_id"],
29+
"display_name": app["display_name"],
30+
"source_path": app["source_path"],
31+
"image_repo": app["image_repo"],
32+
"default_tag": app["default_tag"],
33+
}
34+
)
35+
print(f"matrix={json.dumps({'include': include})}")
36+
PY
37+
38+
publish:
39+
needs: matrix
40+
runs-on: ubuntu-latest
41+
permissions:
42+
contents: read
43+
packages: write
44+
strategy:
45+
fail-fast: false
46+
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
47+
steps:
48+
- uses: actions/checkout@v4
49+
- uses: docker/setup-buildx-action@v3
50+
- uses: docker/login-action@v3
51+
with:
52+
registry: ghcr.io
53+
username: ${{ github.actor }}
54+
password: ${{ secrets.GITHUB_TOKEN }}
55+
- id: build
56+
name: Build and publish ${{ matrix.display_name }}
57+
uses: docker/build-push-action@v6
58+
with:
59+
context: ${{ matrix.source_path }}
60+
push: true
61+
tags: |
62+
${{ matrix.image_repo }}:${{ matrix.default_tag }}
63+
${{ matrix.image_repo }}:sha-${{ github.sha }}
64+
- name: Write publish record
65+
run: |
66+
mkdir -p dist
67+
python3 - <<'PY'
68+
import json
69+
import os
70+
from pathlib import Path
71+
72+
record = {
73+
"schema": 1,
74+
"kind": "ourbox-application-image-publish-record",
75+
"app_id": os.environ["APP_ID"],
76+
"display_name": os.environ["DISPLAY_NAME"],
77+
"image_repo": os.environ["IMAGE_REPO"],
78+
"default_tag": os.environ["DEFAULT_TAG"],
79+
"pinned_ref": f"{os.environ['IMAGE_REPO']}@{os.environ['DIGEST']}",
80+
"digest": os.environ["DIGEST"],
81+
"source_path": os.environ["SOURCE_PATH"],
82+
"github_sha": os.environ["GITHUB_SHA"],
83+
"github_run_id": os.environ["GITHUB_RUN_ID"],
84+
"github_run_attempt": os.environ["GITHUB_RUN_ATTEMPT"],
85+
}
86+
out = Path("dist") / f"{os.environ['APP_ID']}.publish-record.json"
87+
out.write_text(json.dumps(record, indent=2) + "\n", encoding="utf-8")
88+
PY
89+
env:
90+
APP_ID: ${{ matrix.app_id }}
91+
DISPLAY_NAME: ${{ matrix.display_name }}
92+
SOURCE_PATH: ${{ matrix.source_path }}
93+
IMAGE_REPO: ${{ matrix.image_repo }}
94+
DEFAULT_TAG: ${{ matrix.default_tag }}
95+
DIGEST: ${{ steps.build.outputs.digest }}
96+
- uses: actions/upload-artifact@v4
97+
with:
98+
name: ${{ matrix.app_id }}-publish-record
99+
path: dist/${{ matrix.app_id }}.publish-record.json

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.DS_Store

.gitlint

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[general]
2+
ignore=merge-commits
3+
contrib=contrib-title-conventional-commits
4+
5+
[title-max-length]
6+
line-length=100
7+
8+
[body-max-line-length]
9+
line-length=100
10+
11+
[contrib-title-conventional-commits]
12+
types=feat,fix,perf,refactor,docs,chore,test,build,ci,style,revert

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# sw-ourbox-apps-demo
2+
3+
`sw-ourbox-apps-demo` is the first concrete OurBox `apps` repository.
4+
5+
It owns first-party demo application source and the CI that publishes those
6+
applications as OCI images for downstream catalog repos to consume.
7+
8+
## Published applications
9+
10+
- `landing`
11+
- static landing page served from nginx
12+
- image: `ghcr.io/techofourown/sw-ourbox-apps-demo/landing`
13+
- `todo-bloom`
14+
- browser-only to-do demo app served from nginx
15+
- image: `ghcr.io/techofourown/sw-ourbox-apps-demo/todo-bloom`
16+
17+
This repo does not claim ownership of third-party applications such as `dufs`
18+
or `flatnotes`. Catalog repos may still include those external images alongside
19+
the images published here.
20+
21+
## Repository layout
22+
23+
- [apps-manifest.json](/techofourown/sw-ourbox-apps-demo/apps-manifest.json)
24+
- machine-readable description of the published applications
25+
- [apps/landing](/techofourown/sw-ourbox-apps-demo/apps/landing)
26+
- source and image build inputs for the landing page
27+
- [apps/todo-bloom](/techofourown/sw-ourbox-apps-demo/apps/todo-bloom)
28+
- source and image build inputs for Todo Bloom
29+
- [.github/workflows/ci.yml](/techofourown/sw-ourbox-apps-demo/.github/workflows/ci.yml)
30+
- lightweight validation for manifest and source layout
31+
- [.github/workflows/publish-images.yml](/techofourown/sw-ourbox-apps-demo/.github/workflows/publish-images.yml)
32+
- builds and publishes the application images to GHCR
33+
34+
## Local notes
35+
36+
These applications are intentionally simple. The goal of this repo is to prove
37+
the `sw-ourbox-apps-*` publisher shape with more than one app in the same repo,
38+
using real published images that downstream catalog repos can pin by digest.

apps-manifest.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"schema": 1,
3+
"kind": "ourbox-apps-collection",
4+
"collection_id": "demo",
5+
"display_name": "Demo Apps Publisher",
6+
"apps": [
7+
{
8+
"app_id": "landing",
9+
"display_name": "Landing",
10+
"source_path": "apps/landing",
11+
"image_repo": "ghcr.io/techofourown/sw-ourbox-apps-demo/landing",
12+
"default_tag": "latest"
13+
},
14+
{
15+
"app_id": "todo-bloom",
16+
"display_name": "Todo Bloom",
17+
"source_path": "apps/todo-bloom",
18+
"image_repo": "ghcr.io/techofourown/sw-ourbox-apps-demo/todo-bloom",
19+
"default_tag": "latest"
20+
}
21+
],
22+
"notes": "Published images from this repo are intended to be consumed by sw-ourbox-catalog-* repositories."
23+
}

apps/landing/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM nginx:1.27-alpine
2+
3+
COPY index.html /usr/share/nginx/html/index.html

apps/landing/index.html

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>OurBox</title>
7+
<style>
8+
* { margin: 0; padding: 0; box-sizing: border-box; }
9+
body {
10+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
11+
background: #0f172a;
12+
color: #e2e8f0;
13+
min-height: 100vh;
14+
display: flex;
15+
flex-direction: column;
16+
align-items: center;
17+
justify-content: center;
18+
padding: 2rem;
19+
}
20+
h1 { font-size: 2rem; margin-bottom: 0.5rem; color: #f8fafc; }
21+
.subtitle { color: #94a3b8; margin-bottom: 2.5rem; text-align: center; }
22+
.apps {
23+
display: grid;
24+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
25+
gap: 1.25rem;
26+
max-width: 760px;
27+
width: 100%;
28+
}
29+
.app {
30+
background: #1e293b;
31+
border: 1px solid #334155;
32+
border-radius: 12px;
33+
padding: 1.5rem;
34+
text-decoration: none;
35+
color: #e2e8f0;
36+
transition: border-color 0.2s, transform 0.2s;
37+
}
38+
.app:hover { border-color: #60a5fa; transform: translateY(-2px); }
39+
.app h2 { font-size: 1.15rem; margin-bottom: 0.4rem; color: #f8fafc; }
40+
.app p { font-size: 0.875rem; color: #94a3b8; line-height: 1.4; }
41+
footer {
42+
margin-top: 3rem;
43+
font-size: 0.75rem;
44+
color: #475569;
45+
}
46+
</style>
47+
</head>
48+
<body>
49+
<h1>OurBox</h1>
50+
<p class="subtitle">Your apps, served by your machine, to your phone.</p>
51+
<div class="apps" id="apps"></div>
52+
<footer id="footer"></footer>
53+
<script>
54+
var host = location.hostname;
55+
var apps = [
56+
{ name: "Files", desc: "Upload, download, and share files.", sub: "files" },
57+
{ name: "Notes", desc: "Write and organize markdown notes.", sub: "notes" },
58+
{ name: "Todo", desc: "Plan your day with clarity.", sub: "todo" }
59+
];
60+
var container = document.getElementById("apps");
61+
apps.forEach(function(app) {
62+
var a = document.createElement("a");
63+
a.className = "app";
64+
a.href = location.protocol + "//" + app.sub + "." + host;
65+
a.innerHTML = "<h2>" + app.name + "</h2><p>" + app.desc + "</p>";
66+
container.appendChild(a);
67+
});
68+
document.getElementById("footer").textContent = host;
69+
</script>
70+
</body>
71+
</html>

apps/todo-bloom/Dockerfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM nginx:1.27-alpine
2+
3+
COPY index.html /usr/share/nginx/html/index.html
4+
COPY app.js /usr/share/nginx/html/app.js
5+
COPY styles.css /usr/share/nginx/html/styles.css

0 commit comments

Comments
 (0)