Compare commits
5 Commits
f2c2a821cd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8dc5db63cd | |||
| 233c8fefbf | |||
| 3ca5f1b29f | |||
| 1e62f2a4d8 | |||
| 4b8d400923 |
@@ -5,6 +5,11 @@
|
|||||||
RADICALE_USERNAME=admin
|
RADICALE_USERNAME=admin
|
||||||
RADICALE_PASSWORD=tu_contraseña_segura_aqui
|
RADICALE_PASSWORD=tu_contraseña_segura_aqui
|
||||||
|
|
||||||
|
# Configuración de dominios públicos (Coolify)
|
||||||
|
RADICALE_DOMAIN=radicale.tu-dominio.com
|
||||||
|
INFCLOUD_DOMAIN=infcloud.tu-dominio.com
|
||||||
|
COOLIFY_ENTRYPOINTS=https
|
||||||
|
|
||||||
# Configuración de Red
|
# Configuración de Red
|
||||||
RADICALE_PORT=5232
|
RADICALE_PORT=5232
|
||||||
INFCLOUD_PORT=8090
|
INFCLOUD_PORT=8090
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ INFCLOUD_PORT=8090
|
|||||||
# Configuración de Zona Horaria
|
# Configuración de Zona Horaria
|
||||||
TZ=Europe/Madrid
|
TZ=Europe/Madrid
|
||||||
|
|
||||||
|
# Dominios públicos (Coolify)
|
||||||
|
RADICALE_DOMAIN=radicale.tu-dominio.com
|
||||||
|
INFCLOUD_DOMAIN=infcloud.tu-dominio.com
|
||||||
|
COOLIFY_ENTRYPOINTS=https
|
||||||
|
|
||||||
# URLs base
|
# URLs base
|
||||||
RADICALE_BASE_URL=http://localhost:5232
|
RADICALE_BASE_URL=http://localhost:5232
|
||||||
INFCLOUD_BASE_URL=http://localhost:8090
|
INFCLOUD_BASE_URL=http://localhost:8090
|
||||||
@@ -130,6 +135,16 @@ docker compose restart radicale
|
|||||||
- **Ruta principal:** /usuario/
|
- **Ruta principal:** /usuario/
|
||||||
- **SSL:** No (para desarrollo local)
|
- **SSL:** No (para desarrollo local)
|
||||||
|
|
||||||
|
## 🌐 Integración con coolify-proxy
|
||||||
|
|
||||||
|
1. **Define los dominios públicos:** Rellena las variables `RADICALE_DOMAIN`, `INFCLOUD_DOMAIN` y `COOLIFY_ENTRYPOINTS` (por defecto `https`) en `.env`. Mantén `RADICALE_BASE_URL` e `INFCLOUD_BASE_URL` apuntando a las URLs HTTPS finales para que InfCloud genere enlaces correctos.
|
||||||
|
2. **Genera la configuración de InfCloud:** `make config`.
|
||||||
|
3. **Asegura la red externa:** Coolify crea una red llamada `coolify-proxy`. Si no existe ejecútalo una vez como root: `docker network create coolify-proxy`.
|
||||||
|
4. **Ajusta y despliega los servicios:** `docker compose up -d` (o `make start`). Cada servicio se conectará automáticamente tanto a `caldav_net` como a `coolify-proxy` y expondrá las etiquetas Traefik necesarias.
|
||||||
|
5. **Verifica la conexión a la red de Coolify:** `docker network inspect coolify-proxy | grep -E 'radicale|infcloud'`.
|
||||||
|
6. **Crea los routers desde Coolify:** Dentro del panel de Coolify > Proxy, declara cada subdominio apuntando al contenedor correspondiente y al puerto interno (`5232` para Radicale, `80` para InfCloud). Traefik detectará los labels y emitirá los certificados usando el resolver indicado.
|
||||||
|
7. **Pruebas rápidas:** `docker compose ps` para comprobar que ambos contenedores están arriba y `curl -u usuario:contraseña -X PROPFIND https://tu-subdominio-radicale/usuario/ -H "Depth: 0"` para validar la publicación detrás de Coolify.
|
||||||
|
|
||||||
## 🐛 Resolución de Problemas
|
## 🐛 Resolución de Problemas
|
||||||
|
|
||||||
### Error 405 PROPFIND
|
### Error 405 PROPFIND
|
||||||
|
|||||||
@@ -14,6 +14,12 @@ services:
|
|||||||
- "${RADICALE_PORT}:5232"
|
- "${RADICALE_PORT}:5232"
|
||||||
networks:
|
networks:
|
||||||
- caldav_net
|
- caldav_net
|
||||||
|
- coolify-proxy
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.radicale.rule=Host(`${RADICALE_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.radicale.entrypoints=${COOLIFY_ENTRYPOINTS}"
|
||||||
|
- "traefik.http.services.radicale.loadbalancer.server.port=5232"
|
||||||
|
|
||||||
infcloud:
|
infcloud:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
@@ -30,7 +36,15 @@ services:
|
|||||||
- ./infcloud_config/nginx-simple.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./infcloud_config/nginx-simple.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
networks:
|
networks:
|
||||||
- caldav_net
|
- caldav_net
|
||||||
|
- coolify-proxy
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.infcloud.rule=Host(`${INFCLOUD_DOMAIN}`)"
|
||||||
|
- "traefik.http.routers.infcloud.entrypoints=${COOLIFY_ENTRYPOINTS}"
|
||||||
|
- "traefik.http.services.infcloud.loadbalancer.server.port=80"
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
caldav_net:
|
caldav_net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
coolify-proxy:
|
||||||
|
external: true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// Configuración según documentación oficial de Radicale
|
// Configuración según documentación oficial de Radicale
|
||||||
// https://github.com/Kozea/Radicale/wiki/Client-InfCloud
|
// https://github.com/Kozea/Radicale/wiki/Client-InfCloud
|
||||||
var globalNetworkCheckSettings = {
|
var globalNetworkCheckSettings = {
|
||||||
href: 'http://localhost:8090/radicale/',
|
href: 'https://infcloud-h5j23io4jn45.organic-machine.com/radicale/',
|
||||||
timeOut: 90000,
|
timeOut: 90000,
|
||||||
lockTimeOut: 10000,
|
lockTimeOut: 10000,
|
||||||
checkContentType: false,
|
checkContentType: false,
|
||||||
@@ -28,7 +28,8 @@ var globalSearchTransformAlphabet = 'AÁÀÂàáâBCÇcçDEÉÈÊeéèêFGHIÍÌ
|
|||||||
// Configuración de calendario
|
// Configuración de calendario
|
||||||
var globalCalendarSelected = '';
|
var globalCalendarSelected = '';
|
||||||
var globalTodoCalendarSelected = '';
|
var globalTodoCalendarSelected = '';
|
||||||
var globalActiveView = 'multiWeek'; // Vista por defecto: semana múltiple
|
var globalActiveView = 'agendaWeek'; // Vista por defecto: semana
|
||||||
|
var globalDefaultEventDuration = 60; // Eventos nuevos duran 1 hora
|
||||||
var globalOpenFormMode = 'double';
|
var globalOpenFormMode = 'double';
|
||||||
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
||||||
var globalActiveApp = null;
|
var globalActiveApp = null;
|
||||||
@@ -40,7 +41,7 @@ var globalDisplayHiddenEvents = false;
|
|||||||
|
|
||||||
// Configuraciones adicionales de usuario que se recordarán
|
// Configuraciones adicionales de usuario que se recordarán
|
||||||
var globalUserPreferences = {
|
var globalUserPreferences = {
|
||||||
defaultView: 'multiWeek', // Vista preferida del usuario
|
defaultView: 'agendaWeek', // Vista preferida del usuario
|
||||||
showWeekends: true, // Mostrar fines de semana
|
showWeekends: true, // Mostrar fines de semana
|
||||||
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
||||||
workingHours: {start: 8, end: 18}, // Horario laboral
|
workingHours: {start: 8, end: 18}, // Horario laboral
|
||||||
@@ -95,7 +96,7 @@ var globalUseCrossOrigin = false;
|
|||||||
|
|
||||||
// Variables adicionales requeridas
|
// Variables adicionales requeridas
|
||||||
var globalContactDataMinVisiblePercentage = 0.2;
|
var globalContactDataMinVisiblePercentage = 0.2;
|
||||||
var globalEditorFadeAnimation = 666;
|
var globalEditorFadeAnimation = 0;
|
||||||
var globalEventStartPastLimit = 3;
|
var globalEventStartPastLimit = 3;
|
||||||
var globalEventStartFutureLimit = 3;
|
var globalEventStartFutureLimit = 3;
|
||||||
var globalTodoStartPastLimit = 3;
|
var globalTodoStartPastLimit = 3;
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ var globalSearchTransformAlphabet = 'AÁÀÂàáâBCÇcçDEÉÈÊeéèêFGHIÍÌ
|
|||||||
// Configuración de calendario
|
// Configuración de calendario
|
||||||
var globalCalendarSelected = '';
|
var globalCalendarSelected = '';
|
||||||
var globalTodoCalendarSelected = '';
|
var globalTodoCalendarSelected = '';
|
||||||
var globalActiveView = 'multiWeek'; // Vista por defecto: semana múltiple
|
var globalActiveView = 'agendaWeek'; // Vista por defecto: semana
|
||||||
|
var globalDefaultEventDuration = 60; // Eventos nuevos duran 1 hora
|
||||||
var globalOpenFormMode = 'double';
|
var globalOpenFormMode = 'double';
|
||||||
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
var globalTodoListFilterSelected = ['filterAction', 'filterProgress', 'filterCompleted', 'filterCanceled'];
|
||||||
var globalActiveApp = null;
|
var globalActiveApp = null;
|
||||||
@@ -40,7 +41,7 @@ var globalDisplayHiddenEvents = false;
|
|||||||
|
|
||||||
// Configuraciones adicionales de usuario que se recordarán
|
// Configuraciones adicionales de usuario que se recordarán
|
||||||
var globalUserPreferences = {
|
var globalUserPreferences = {
|
||||||
defaultView: 'multiWeek', // Vista preferida del usuario
|
defaultView: 'agendaWeek', // Vista preferida del usuario
|
||||||
showWeekends: true, // Mostrar fines de semana
|
showWeekends: true, // Mostrar fines de semana
|
||||||
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
firstDayOfWeek: 1, // Lunes = 1, Domingo = 0
|
||||||
workingHours: {start: 8, end: 18}, // Horario laboral
|
workingHours: {start: 8, end: 18}, // Horario laboral
|
||||||
@@ -95,7 +96,7 @@ var globalUseCrossOrigin = false;
|
|||||||
|
|
||||||
// Variables adicionales requeridas
|
// Variables adicionales requeridas
|
||||||
var globalContactDataMinVisiblePercentage = 0.2;
|
var globalContactDataMinVisiblePercentage = 0.2;
|
||||||
var globalEditorFadeAnimation = 666;
|
var globalEditorFadeAnimation = 0;
|
||||||
var globalEventStartPastLimit = 3;
|
var globalEventStartPastLimit = 3;
|
||||||
var globalEventStartFutureLimit = 3;
|
var globalEventStartFutureLimit = 3;
|
||||||
var globalTodoStartPastLimit = 3;
|
var globalTodoStartPastLimit = 3;
|
||||||
|
|||||||
@@ -298,10 +298,11 @@ body, input, select, textarea
|
|||||||
right: 583px;
|
right: 583px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #ffffff;
|
background: rgba(0, 0, 0, 0);
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -1202,10 +1203,11 @@ input.non_editable
|
|||||||
border-right: 1px solid;
|
border-right: 1px solid;
|
||||||
border-right-color: #c0c0c0;
|
border-right-color: #c0c0c0;
|
||||||
width: 224px;
|
width: 224px;
|
||||||
background: #ffffff;
|
background: rgba(0, 0, 0, 0);
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -1220,11 +1222,12 @@ input.non_editable
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #ffffff;
|
background: rgba(0, 0, 0, 0);
|
||||||
opacity: 0.8;
|
opacity: 1;
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 22;
|
z-index: 22;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -2203,15 +2206,16 @@ body
|
|||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: #ffffff;
|
background: rgba(0, 0, 0, 0);
|
||||||
display: none;
|
display: none;
|
||||||
opacity: 0.8;
|
opacity: 1;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
cursor:default;
|
cursor:default;
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select:none;
|
-webkit-user-select:none;
|
||||||
-moz-user-select: -moz-none;
|
-moz-user-select: -moz-none;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#CAEvent .saveLoader
|
#CAEvent .saveLoader
|
||||||
@@ -2613,9 +2617,8 @@ h3
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #f9f9f9;
|
background-color: rgba(249, 249, 249, 0.15);
|
||||||
opacity: 0.7;
|
opacity: 1;
|
||||||
filter: alpha(opacity=70);
|
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
|||||||
+70
-1
@@ -17,7 +17,76 @@ GNU Affero General Public License for more details.
|
|||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Disable jQuery fade animations to make UI snappier
|
||||||
|
(function (win) {
|
||||||
|
if (!win || !win.jQuery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var $ = win.jQuery;
|
||||||
|
if ($.fx && $.fx.off !== true) {
|
||||||
|
$.fx.off = true;
|
||||||
|
}
|
||||||
|
if ($.fn._noAnimationPatch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function getCallback(args) {
|
||||||
|
for (var i = args.length - 1; i >= 0; i--) {
|
||||||
|
if (typeof args[i] === 'function') {
|
||||||
|
return args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function immediate(action) {
|
||||||
|
return function () {
|
||||||
|
var callback = getCallback(arguments);
|
||||||
|
return this.each(function () {
|
||||||
|
var el = $(this);
|
||||||
|
if (action === 'show') {
|
||||||
|
el.show().css('opacity', 1);
|
||||||
|
} else if (action === 'hide') {
|
||||||
|
el.hide().css('opacity', 0);
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback.call(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
$.fn.fadeIn = immediate('show');
|
||||||
|
$.fn.fadeOut = immediate('hide');
|
||||||
|
$.fn.fadeTo = function () {
|
||||||
|
var callback = getCallback(arguments);
|
||||||
|
var target = typeof arguments[1] === 'number' ? arguments[1] : 1;
|
||||||
|
return this.each(function () {
|
||||||
|
var el = $(this);
|
||||||
|
el.css('opacity', target);
|
||||||
|
if (target === 0) {
|
||||||
|
el.hide();
|
||||||
|
} else {
|
||||||
|
el.show();
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback.call(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$.fn.fadeToggle = function () {
|
||||||
|
var callback = getCallback(arguments);
|
||||||
|
return this.each(function () {
|
||||||
|
var el = $(this);
|
||||||
|
var hidden = !el.is(':visible') || el.css('opacity') === '0';
|
||||||
|
if (hidden) {
|
||||||
|
el.fadeIn(callback);
|
||||||
|
} else {
|
||||||
|
el.fadeOut(callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
$.fn._noAnimationPatch = true;
|
||||||
|
})(window);
|
||||||
|
|
||||||
var globalSettings={
|
var globalSettings={
|
||||||
version: {value: (typeof globalSettingsVersion!='undefined' && globalSettingsVersion!=null) ? globalSettingsVersion : 1, locked:false},
|
version: {value: (typeof globalSettingsVersion!='undefined' && globalSettingsVersion!=null) ? globalSettingsVersion : 1, locked:false},
|
||||||
|
|||||||
@@ -76,6 +76,26 @@ prompt_password() {
|
|||||||
htpasswd_update() {
|
htpasswd_update() {
|
||||||
local user="$1" password="$2"
|
local user="$1" password="$2"
|
||||||
htpasswd -B -C "$DEFAULT_COST" -b "$USERS_FILE" "$user" "$password" >/dev/null
|
htpasswd -B -C "$DEFAULT_COST" -b "$USERS_FILE" "$user" "$password" >/dev/null
|
||||||
|
normalize_bcrypt_prefix "$user"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_bcrypt_prefix() {
|
||||||
|
local user="$1"
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "⚠️ python3 no disponible: no se pudo normalizar el hash bcrypt de '$user' (prefijo \$2y\$)." >&2
|
||||||
|
echo " Radicale solo acepta hashes \$2b\$, por lo que este usuario podría fallar." >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
python3 - "$USERS_FILE" "$user" <<'PY'
|
||||||
|
import pathlib, re, sys
|
||||||
|
path = pathlib.Path(sys.argv[1])
|
||||||
|
user = sys.argv[2]
|
||||||
|
data = path.read_text()
|
||||||
|
pattern = re.compile(rf'^({re.escape(user)}:\$)2y', re.M)
|
||||||
|
updated, count = pattern.subn(r'\g<1>2b', data)
|
||||||
|
if count:
|
||||||
|
path.write_text(updated)
|
||||||
|
PY
|
||||||
}
|
}
|
||||||
|
|
||||||
list_users() {
|
list_users() {
|
||||||
|
|||||||
Reference in New Issue
Block a user