Update Loops to security release beta.15
Validate, publish and promote loops chart / publish (push) Failing after 4m8s
Validate, publish and promote loops chart / validate (push) Successful in 1s

This commit is contained in:
feedc0de committed 2026-10-26 06:53:10 +01:00
1 parent 3795736e5e
commit a6ffa46d42
9 files changed
+154 -25

No files matched your search

+30 -2
View File
@@ -19,8 +19,36 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Derive immutable chart version
run: echo "CHART_VERSION=$(./scripts/chart-version.sh)" >> "$GITHUB_ENV"
- name: Derive release versions
run: |
set -euo pipefail
echo "CHART_VERSION=$(./scripts/chart-version.sh)" >> "$GITHUB_ENV"
echo "APP_VERSION=$(sed -n 's/^appVersion: \"\(.*\)\"$/\1/p' chart/Chart.yaml)" >> "$GITHUB_ENV"
- name: Build, test and publish Loops image
env:
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
QUAY_TOKEN: ${{ secrets.QUAY_TOKEN }}
COMMIT_SHA: ${{ gitea.sha }}
run: |
set -euo pipefail
test -n "$QUAY_USERNAME" && test -n "$QUAY_TOKEN"
source_dir="$RUNNER_TEMP/loops-source"
git clone --depth 1 --branch "$APP_VERSION" https://github.com/joinloops/loops-server.git "$source_dir"
git -C "$source_dir" apply "$GITHUB_WORKSPACE/patches/loops-frontend.patch"
docker build --pull \
--label "org.opencontainers.image.revision=$COMMIT_SHA" \
--label "org.opencontainers.image.version=$APP_VERSION" \
--label "org.opencontainers.image.source=https://github.com/joinloops/loops-server" \
--tag "registry.brunner.ninja/feedc0de/loops:$APP_VERSION" \
"$source_dir"
docker run --rm --entrypoint php "registry.brunner.ninja/feedc0de/loops:$APP_VERSION" -r '
if (PHP_VERSION_ID < 80512) { fwrite(STDERR, "PHP security floor 8.5.12 not met\\n"); exit(1); }
echo PHP_VERSION, "\n";
'
docker run --rm --entrypoint ffmpeg "registry.brunner.ninja/feedc0de/loops:$APP_VERSION" -version | head -n 1 | grep -F 'ffmpeg version 9.0.2'
docker run --rm --entrypoint sh "registry.brunner.ninja/feedc0de/loops:$APP_VERSION" -ec 'grep -R -l "recommended-feed" /var/www/html/public/build/assets >/dev/null; grep -R -l "fyfeed" /var/www/html/public/build/assets >/dev/null'
printf '%s' "$QUAY_TOKEN" | docker login registry.brunner.ninja --username "$QUAY_USERNAME" --password-stdin
docker push "registry.brunner.ninja/feedc0de/loops:$APP_VERSION"
- name: Package and upload
env:
PACKAGE_USERNAME: ${{ secrets.PACKAGE_USERNAME }}
+5 -1
View File
@@ -17,4 +17,8 @@ helm repo update brunner.ninja
helm upgrade --install loops brunner.ninja/loops --namespace loops --create-namespace --wait --timeout 20m
```
The deployed image is built from Loops source commit f614b34f. The chart's frontend, backend and Following-feed overlays are tied to that exact image; review and rebase them before changing `appVersion` or `image.tag`. The upstream project does not issue SemVer releases for this commit, so Git tag `f614b34f` maps reversibly to chart version `0.0.0-f614b34f`; chart-only commits append their Git-describe count and hash.
The chart and image are released together. The Git tag and image tag match the upstream Loops release (for example `v1.0.0-beta.15`), `Chart.yaml.appVersion` is that exact tag, and chart versions come from `git describe --tags --long`. A chart-only commit after a release tag therefore gets a unique version such as `1.0.0-beta.15-1-g<hash>`.
CI checks out the matching upstream release, applies `patches/loops-frontend.patch`, builds the upstream Dockerfile, verifies PHP 8.5.12+ and FFmpeg 9.0.2, publishes the image to `registry.brunner.ninja`, publishes the chart to the public `brunner.ninja/charts` repository, and promotes the chart version to `homelab-gitops`. The frontend fix is now built into the immutable image; it is not a hash-specific ConfigMap overlay. The backend and Following-feed patches remain chart-managed and must be reviewed against the next upstream release before updating `appVersion`.
Argo CD runs `php artisan migrate --force` as a sync hook after the database and configuration resources are ready, and before the application Deployments roll out. Application pods keep automatic migrations disabled to avoid concurrent migration runs.
+2 -2
View File
@@ -2,5 +2,5 @@ apiVersion: v2
name: loops
description: Kubernetes deployment contract for loops
type: application
version: 0.0.0-f614b34f
appVersion: "f614b34f"
version: 1.0.0-beta.15
appVersion: "v1.0.0-beta.15"
+8 -7
View File
@@ -109,6 +109,8 @@ kind: Deployment
metadata:
name: loops-web
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: loops-web
app.kubernetes.io/part-of: loops
@@ -203,10 +205,6 @@ spec:
- name: filesystem-config
mountPath: /var/www/html/config/filesystems.php
subPath: filesystems.php
- name: frontend-hotfix
mountPath: /var/www/html/public/build/assets/index-Cng6wq5r.js
subPath: index-Cng6wq5r.js
readOnly: true
- name: backend-hotfix
mountPath: /var/www/html/app/Services/ForYouFeedService.php
subPath: ForYouFeedService.php
@@ -238,9 +236,6 @@ spec:
- name: filesystem-config
configMap:
name: loops-filesystem-config
- name: frontend-hotfix
configMap:
name: loops-frontend-hotfix
- name: backend-hotfix
configMap:
name: loops-backend-hotfix
@@ -270,6 +265,8 @@ kind: Deployment
metadata:
name: loops-horizon
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: loops-horizon
app.kubernetes.io/part-of: loops
@@ -343,6 +340,8 @@ kind: Deployment
metadata:
name: loops-transcoder
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: loops-transcoder
app.kubernetes.io/component: transcoder
@@ -466,6 +465,8 @@ kind: Deployment
metadata:
name: loops-scheduler
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "2"
labels:
app.kubernetes.io/name: loops-scheduler
app.kubernetes.io/part-of: loops
-10
View File
@@ -1,10 +0,0 @@
apiVersion: v1
data:
index-Cng6wq5r.js: |
import{_ as h}from"./HideCommentConfirmModal-QPWnMLxt.js";import{u as k,b as y,a as g,S as v}from"./LoopLink.vue_vue_type_script_setup_true_lang-D1dGRmzo.js";import{u as x}from"./usePublicFeed-BqqecCdM.js";import{c as P}from"./app--YWhAMOJ.js";import{V}from"./VideoPlayer-DP4y4yM4.js";import{y as b,d as F,w as I,a as S,c as C,e as r,u as w,q,ay as B}from"./vendor-core-C7C5leYv.js";import"./Comments-ByHWOGrH.js";import"./KlipyPicker-CbkaMsji.js";import"./comments-CRjqfgs4.js";import"./useHashids-DT1SW4G7.js";import"./AutolinkedText-BAfi_gt-.js";import"./CommentMediaAttachment-DfTLtvaG.js";import"./__vite-browser-external-ke9ZaMpT.js";/* empty css *//* empty css */import"./ShareModal-Bb3hVgNV.js";import"./DmQuickShare-C1LTf34j.js";import"./DmShareModal-DB3TK55A.js";const H=()=>k({queryKey:["fyfeed"],queryFn:({pageParam:a})=>y({cursor:a}),getNextPageParam:a=>a.meta?.next_cursor??void 0,initialPageParam:null,enabled:!0,staleTime:0,retry:2}),X={__name:"index",setup(a){const s=S("authStore"),n=H(),j=x(),{hasInteracted:o,handleFirstInteraction:m,globalMuted:c}=g(),u=(e,t)=>({"video-id":e.id,"video-url":e.media.src_url,"share-url":e.url,"profile-id":e.account.id,username:e.account.username,"profile-image":e.account.avatar,caption:e.caption,hashtags:e.tags,mentions:e.mentions,likes:e.likes,hasLiked:e.has_liked,hasBookmarked:e.has_bookmarked,bookmarks:e.bookmarks,shares:e.shares,comments:[],canComment:e.permissions?.can_comment,"comment-count":e.comments,index:t,isSensitive:e?.is_sensitive,altText:e?.media.alt_text,autoPlay:o.value,muted:c.value}),l=e=>e.id,_=e=>{},f=e=>{},p=()=>{m()};return(e,t)=>{const i=h;return C(),F(P,null,{default:I(()=>[r(v,{key:s.authenticated?"recommended-feed":"public-feed","feed-data":s.authenticated?n:j,"item-component":V,"get-item-props":u,"get-item-key":l,"auto-play":w(o),"scroll-threshold":1.5,"snap-sensitivity":50,onItemVisible:_,onItemHidden:f,onInteraction:p},null,8,["feed-data","auto-play"]),r(i)]),_:1})}}};export{X as default};
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/part-of: loops
name: loops-frontend-hotfix
namespace: {{ .Release.Namespace }}
+8
View File
@@ -3,6 +3,8 @@ kind: Service
metadata:
name: mysql
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "-1"
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/part-of: loops
@@ -21,6 +23,8 @@ kind: StatefulSet
metadata:
name: mysql
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "-1"
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/part-of: loops
@@ -111,6 +115,8 @@ kind: Service
metadata:
name: valkey
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "-1"
labels:
app.kubernetes.io/name: valkey
app.kubernetes.io/part-of: loops
@@ -129,6 +135,8 @@ kind: StatefulSet
metadata:
name: valkey
namespace: {{ .Release.Namespace }}
annotations:
argocd.argoproj.io/sync-wave: "-1"
labels:
app.kubernetes.io/name: valkey
app.kubernetes.io/part-of: loops
+36
View File
@@ -0,0 +1,36 @@
apiVersion: batch/v1
kind: Job
metadata:
name: loops-migrate
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: loops-migrate
app.kubernetes.io/part-of: loops
annotations:
argocd.argoproj.io/hook: Sync
argocd.argoproj.io/sync-wave: "1"
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
spec:
backoffLimit: 2
activeDeadlineSeconds: 900
template:
metadata:
labels:
app.kubernetes.io/name: loops-migrate
app.kubernetes.io/part-of: loops
spec:
restartPolicy: Never
containers:
- name: migrate
image: {{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}
imagePullPolicy: IfNotPresent
command:
- php
- artisan
- migrate
- --force
envFrom:
- configMapRef:
name: loops-env
- secretRef:
name: {{ .Values.runtimeSecret }}
+62
View File
@@ -0,0 +1,62 @@
diff --git a/resources/js/composables/useFeed.js b/resources/js/composables/useFeed.js
--- a/resources/js/composables/useFeed.js
+++ b/resources/js/composables/useFeed.js
@@ -4,9 +4,9 @@
export const useFeed = () => {
return useInfiniteQuery({
- queryKey: ['feed'],
+ queryKey: ['fyfeed'],
queryFn: ({ pageParam }) => fetchFeedPage({ cursor: pageParam }),
getNextPageParam: (lastPage) => lastPage.meta?.next_cursor ?? undefined,
initialPageParam: null,
enabled: true,
- staleTime: 1000 * 60 * 5, // 5 minutes
+ staleTime: 0,
retry: 2
})
}
diff --git a/resources/js/pages/index.vue b/resources/js/pages/index.vue
--- a/resources/js/pages/index.vue
+++ b/resources/js/pages/index.vue
@@ -1,7 +1,7 @@
<template>
<FeedLayout>
<SnapScrollFeed
- key="local-feed"
+ :key="authStore.authenticated ? 'recommended-feed' : 'public-feed'"
:feed-data="feedData"
:item-component="VideoPlayer"
:get-item-props="getVideoProps"
@@ -18,7 +18,7 @@
</template>
<script setup>
-import { inject, computed, shallowRef, watch } from 'vue'
+import { inject, computed } from 'vue'
import { useFeed } from '~/composables/useFeed'
import { usePublicFeed } from '~/composables/usePublicFeed'
import { useFeedInteraction } from '~/composables/useFeedInteraction'
@@ -28,20 +28,10 @@
const authStore = inject('authStore')
-const activeFeed = shallowRef(null)
-
const { hasInteracted, handleFirstInteraction, globalMuted } = useFeedInteraction()
-watch(
- () => authStore.authenticated,
- (isAuthenticated, _, onCleanup) => {
- activeFeed.value = isAuthenticated ? useFeed() : usePublicFeed()
- },
- { immediate: true }
-)
-
-const feedData = computed(() => {
- return activeFeed.value
-})
+const authenticatedFeed = useFeed()
+const publicFeed = usePublicFeed()
+const feedData = computed(() => authStore.authenticated ? authenticatedFeed : publicFeed)
const getVideoProps = (post, index) => ({
+3 -3
View File
@@ -1,8 +1,8 @@
#!/bin/sh
set -eu
# Loops has no upstream SemVer release. Its source commit is the Git tag.
description=$(git describe --tags --long --match 'f[0-9a-f]*' --dirty)
# Chart releases follow the upstream Loops application release tag.
description=$(git describe --tags --long --match 'v[0-9]*' --dirty)
case "$description" in *-dirty) echo 'Refusing dirty tree' >&2; exit 1 ;; esac
version=0.0.0-$description
version=${description#v}
case "$version" in *-0-g*) version=${version%%-0-g*} ;; esac
printf '%s\n' "$version"